mirror of
https://github.com/doctrine/orm.git
synced 2026-03-30 02:42:18 +02:00
Compare commits
1915 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9fc5388f1 | ||
|
|
d2e51eacff | ||
|
|
5a6ae4686f | ||
|
|
8b8a1cbe81 | ||
|
|
27a5284899 | ||
|
|
0086d17afe | ||
|
|
ab62167c8a | ||
|
|
6d43195669 | ||
|
|
7065ff0ac9 | ||
|
|
aa61328e90 | ||
|
|
62719f2a97 | ||
|
|
66770c5bfe | ||
|
|
42691c21b4 | ||
|
|
596e895763 | ||
|
|
d5c82094df | ||
|
|
4148220f9c | ||
|
|
e173c930ec | ||
|
|
7071984559 | ||
|
|
216c466233 | ||
|
|
65f5777e60 | ||
|
|
6e3ce26429 | ||
|
|
752d4f9eac | ||
|
|
2983081a60 | ||
|
|
464b5fdbfb | ||
|
|
04254a8e34 | ||
|
|
1d213e6733 | ||
|
|
bc82e94afc | ||
|
|
b9af1c8fa5 | ||
|
|
d606efd4eb | ||
|
|
581e1638a2 | ||
|
|
17ae8d1b2d | ||
|
|
3a058f8522 | ||
|
|
567220ef71 | ||
|
|
39098ce415 | ||
|
|
1eb9c8a7f6 | ||
|
|
f2f53ba9dc | ||
|
|
ebbc443ec3 | ||
|
|
16802d2614 | ||
|
|
ef73249bc7 | ||
|
|
ed637e51b9 | ||
|
|
a3ece3b419 | ||
|
|
2d1bc78749 | ||
|
|
eaf8b1c7ca | ||
|
|
8070b50150 | ||
|
|
e6a83bedbe | ||
|
|
b6e5464b98 | ||
|
|
6366d190d7 | ||
|
|
89eed31e79 | ||
|
|
4ca00f7a9d | ||
|
|
6bc405455e | ||
|
|
173729e560 | ||
|
|
86abbb0e78 | ||
|
|
69ef75ff2d | ||
|
|
c68edec0c2 | ||
|
|
f9bbd953a7 | ||
|
|
9097014c3d | ||
|
|
12d178777a | ||
|
|
ed1c4de2b6 | ||
|
|
fff56c7f3f | ||
|
|
97e90ddefc | ||
|
|
d5adda954d | ||
|
|
c507b52f20 | ||
|
|
08be905fc3 | ||
|
|
d29cc3660f | ||
|
|
768c291cd1 | ||
|
|
10ed690d99 | ||
|
|
e4e59d8064 | ||
|
|
0e208f7538 | ||
|
|
3e6c6af845 | ||
|
|
584345397b | ||
|
|
786791b8fe | ||
|
|
724dfa0de3 | ||
|
|
39592ba59c | ||
|
|
58a6013d15 | ||
|
|
4580429616 | ||
|
|
6e563a313e | ||
|
|
e8c9cb2f23 | ||
|
|
4792b4f974 | ||
|
|
c2f6b09ee0 | ||
|
|
cb3179865b | ||
|
|
a1602bd91f | ||
|
|
34696126e0 | ||
|
|
aa4b2e59ce | ||
|
|
2cd86deeb4 | ||
|
|
293c6f6313 | ||
|
|
52288abf48 | ||
|
|
f495dfcc9c | ||
|
|
3b6616fe65 | ||
|
|
f20de54281 | ||
|
|
aa80c7d2c5 | ||
|
|
f8270d7bb2 | ||
|
|
7ecadb4c61 | ||
|
|
014d554c28 | ||
|
|
8d186528e6 | ||
|
|
5058312d6d | ||
|
|
b7cf41ca45 | ||
|
|
ac70c18d50 | ||
|
|
61bcfda167 | ||
|
|
afd0f28c5f | ||
|
|
13ef9fd179 | ||
|
|
9a2afd1a9a | ||
|
|
f6a1ea4859 | ||
|
|
82230cc427 | ||
|
|
ba00fc1e90 | ||
|
|
6c5dbd8d4c | ||
|
|
5c93e61686 | ||
|
|
9d7aa9ba39 | ||
|
|
608dfa2f57 | ||
|
|
b76107e20f | ||
|
|
edcc0fc024 | ||
|
|
09d28819b5 | ||
|
|
af3f5c5c5a | ||
|
|
a4aa18f796 | ||
|
|
010d52378c | ||
|
|
a8342a4be2 | ||
|
|
f92307d06d | ||
|
|
ff75a3ad49 | ||
|
|
54d7efd92c | ||
|
|
b923c937e2 | ||
|
|
5cd8334897 | ||
|
|
60bacda685 | ||
|
|
df902c9c6a | ||
|
|
cf1ecffb14 | ||
|
|
9eb458caa6 | ||
|
|
dc99ed286a | ||
|
|
d97d3ec0e5 | ||
|
|
45d39dd50d | ||
|
|
6dfff304f3 | ||
|
|
147bdd8ede | ||
|
|
81ccd93b74 | ||
|
|
06998d015f | ||
|
|
70c8591bbe | ||
|
|
22d699008c | ||
|
|
4c119c44a1 | ||
|
|
cbdf23ca65 | ||
|
|
34168d7085 | ||
|
|
525c77d4a1 | ||
|
|
e57be9da5e | ||
|
|
6070c9395d | ||
|
|
d1a695b42b | ||
|
|
4b10db3a43 | ||
|
|
e6887954ec | ||
|
|
b057b64c1c | ||
|
|
4c84f54493 | ||
|
|
591fd00d73 | ||
|
|
24425436b0 | ||
|
|
483d48cc54 | ||
|
|
05db65d034 | ||
|
|
be18915ed6 | ||
|
|
b3a6fb7fde | ||
|
|
df8be6f31e | ||
|
|
4daf383e4f | ||
|
|
3b0fb6b4b8 | ||
|
|
1add0a04fa | ||
|
|
517cb7e2a2 | ||
|
|
0738571b7d | ||
|
|
648fde8914 | ||
|
|
4095bbaa92 | ||
|
|
0c0d3a1a7c | ||
|
|
bfd628e153 | ||
|
|
ab740abe96 | ||
|
|
f244db80fb | ||
|
|
8f0b3398d3 | ||
|
|
ac9a5e69ec | ||
|
|
5c4b6a2140 | ||
|
|
ce41e35c1f | ||
|
|
0a2d9fa130 | ||
|
|
d710555265 | ||
|
|
504552e779 | ||
|
|
b721841c73 | ||
|
|
b492d61398 | ||
|
|
df0875c596 | ||
|
|
da72f3e62c | ||
|
|
5c89bb8c6b | ||
|
|
712b6a7a64 | ||
|
|
11da060668 | ||
|
|
40c41857e8 | ||
|
|
d6687e070e | ||
|
|
5d6a39d14c | ||
|
|
904bcfa4f9 | ||
|
|
c7ad932b5f | ||
|
|
1b850fbb23 | ||
|
|
2bdc1142fe | ||
|
|
41e873bd72 | ||
|
|
32137c72e4 | ||
|
|
376e74fb7e | ||
|
|
8ac66bb608 | ||
|
|
eebce88146 | ||
|
|
778ed04eac | ||
|
|
2fb11cdf05 | ||
|
|
8d7ba1aebb | ||
|
|
89ab4b3f48 | ||
|
|
a409e7591d | ||
|
|
6cf76158a0 | ||
|
|
d87118437e | ||
|
|
cdc739b7f8 | ||
|
|
6a75bf078b | ||
|
|
f2d8b6d762 | ||
|
|
228dc895ca | ||
|
|
a6931dedaf | ||
|
|
5ae888b853 | ||
|
|
ea70e080c2 | ||
|
|
cfc7236599 | ||
|
|
a3f9f153dd | ||
|
|
73d4d88ba1 | ||
|
|
71821bb7a3 | ||
|
|
0824c6136d | ||
|
|
34b7f07344 | ||
|
|
2aef87c9b2 | ||
|
|
69e5562de4 | ||
|
|
6afcac84d0 | ||
|
|
0e14136fad | ||
|
|
f2f47930e6 | ||
|
|
dce76bba60 | ||
|
|
b1d918b124 | ||
|
|
588c92875d | ||
|
|
19f18fa069 | ||
|
|
6e40361fe7 | ||
|
|
f09b9895b6 | ||
|
|
c059de9e60 | ||
|
|
a41e774bb1 | ||
|
|
94223621dc | ||
|
|
1afaec9cbf | ||
|
|
7fc1dc9209 | ||
|
|
57bcd7cd3d | ||
|
|
5ab4c3d50c | ||
|
|
1369cdda2b | ||
|
|
37f5c82d15 | ||
|
|
d33ee5e7ff | ||
|
|
4680a7b861 | ||
|
|
4d43f5ca52 | ||
|
|
d72ad9cc50 | ||
|
|
38c819ae04 | ||
|
|
1661d96b92 | ||
|
|
a6448f5c77 | ||
|
|
24dd0c6a55 | ||
|
|
b9577bf2f3 | ||
|
|
60164931b8 | ||
|
|
a2461d6d5f | ||
|
|
58e20c70c6 | ||
|
|
ed6fa0deb4 | ||
|
|
a13143b1ac | ||
|
|
1f67218dc5 | ||
|
|
baeab5d4f4 | ||
|
|
51881fed94 | ||
|
|
cd728344e9 | ||
|
|
b5238966d1 | ||
|
|
4a6afb46e2 | ||
|
|
f90897465c | ||
|
|
2a99d5a19b | ||
|
|
8eea7c86f7 | ||
|
|
cb52782e5e | ||
|
|
c9e66e464d | ||
|
|
1630ec1ebd | ||
|
|
4d531d8855 | ||
|
|
4323d9ce4c | ||
|
|
7948b0c160 | ||
|
|
5838115582 | ||
|
|
2e890362c5 | ||
|
|
51b34919ba | ||
|
|
7222991b13 | ||
|
|
5e29bbd41f | ||
|
|
2eb7dedf4f | ||
|
|
081ad0efbe | ||
|
|
eb90cf0c3b | ||
|
|
4c68a38bd6 | ||
|
|
0e08633790 | ||
|
|
75bb3a0de3 | ||
|
|
a0f3b75ec0 | ||
|
|
aa0f18def3 | ||
|
|
f996f6583a | ||
|
|
cd7f91910b | ||
|
|
78d3f51140 | ||
|
|
f9cf2b6a95 | ||
|
|
64ef89ce4c | ||
|
|
b30d1dd39c | ||
|
|
fb5cd928ee | ||
|
|
17a23ea825 | ||
|
|
3ed73b4a60 | ||
|
|
e76b20b109 | ||
|
|
ffaffa0b8a | ||
|
|
c6158ae3f4 | ||
|
|
969788d928 | ||
|
|
3b61d2d0fe | ||
|
|
75e41eefb5 | ||
|
|
5795d53ca7 | ||
|
|
2fbc483fe3 | ||
|
|
c7a6352b08 | ||
|
|
a9671fdc2e | ||
|
|
c4019d96b9 | ||
|
|
f32766c00d | ||
|
|
3f28adf9b6 | ||
|
|
a0eb6005f3 | ||
|
|
9eaac13615 | ||
|
|
539c364ca6 | ||
|
|
9839c400b8 | ||
|
|
7e8d27d41a | ||
|
|
58e2510f41 | ||
|
|
146354d835 | ||
|
|
d024193cc0 | ||
|
|
d62cb6237f | ||
|
|
5bf18298b1 | ||
|
|
3570f4a49a | ||
|
|
670acfc693 | ||
|
|
d4b278c809 | ||
|
|
cb780e8bb6 | ||
|
|
7e85c94f48 | ||
|
|
4a05e19f37 | ||
|
|
97ea6a7d85 | ||
|
|
dff365318d | ||
|
|
186c593058 | ||
|
|
39a8941d1b | ||
|
|
04a271a04e | ||
|
|
36bc448880 | ||
|
|
16f447d1ac | ||
|
|
6e3ad496e2 | ||
|
|
157bf203bc | ||
|
|
1672448993 | ||
|
|
a37fa97be3 | ||
|
|
67f60f2286 | ||
|
|
23a0d9a1fb | ||
|
|
8b9171c8ad | ||
|
|
ebdfab8f2c | ||
|
|
2a7f149029 | ||
|
|
b9f698c98c | ||
|
|
55930a3402 | ||
|
|
4c62d3bfda | ||
|
|
981cebbf4c | ||
|
|
e36c7b0c2a | ||
|
|
28e0da4321 | ||
|
|
d952077d04 | ||
|
|
05a8e1c77d | ||
|
|
1aa453d493 | ||
|
|
5ec300452a | ||
|
|
1b0a5e38d9 | ||
|
|
8910c2c482 | ||
|
|
91f4ed8b92 | ||
|
|
21995a8b10 | ||
|
|
30dcece125 | ||
|
|
3df9b4d122 | ||
|
|
885700d38c | ||
|
|
bd667b82d9 | ||
|
|
a4982a8dc2 | ||
|
|
1e6c071bb8 | ||
|
|
4e08c99b86 | ||
|
|
0a3d6966d6 | ||
|
|
5ae980e0f9 | ||
|
|
302e6218bb | ||
|
|
942004226c | ||
|
|
6a1755972d | ||
|
|
c8160fcd0e | ||
|
|
01a9dadee7 | ||
|
|
b7b716a6bb | ||
|
|
d443d4f3b6 | ||
|
|
6a2b7c2a8e | ||
|
|
99c5650ba4 | ||
|
|
a8796fa489 | ||
|
|
ff986a9bf5 | ||
|
|
ed0331d25a | ||
|
|
43f301fdad | ||
|
|
15397bbe40 | ||
|
|
aed01ea571 | ||
|
|
f184956d3a | ||
|
|
c2b3348f99 | ||
|
|
db3697433b | ||
|
|
3f360d7fbc | ||
|
|
52fb4e53bc | ||
|
|
efa375ac96 | ||
|
|
22533042a5 | ||
|
|
72785eb27e | ||
|
|
b889e18a9a | ||
|
|
a67332fb51 | ||
|
|
8305649a45 | ||
|
|
8987c9ab37 | ||
|
|
f189c1aaf0 | ||
|
|
fc8191f557 | ||
|
|
4058ad3958 | ||
|
|
dfa4bbd67a | ||
|
|
6547d5462e | ||
|
|
c1cc768aee | ||
|
|
cd3ffa8f14 | ||
|
|
e969a6be85 | ||
|
|
635d2c141f | ||
|
|
fbe294927f | ||
|
|
8a6b1b48e3 | ||
|
|
b7c28924b1 | ||
|
|
a36bea2951 | ||
|
|
8579baf28c | ||
|
|
786c34faa5 | ||
|
|
c589b5d013 | ||
|
|
5a1a32297b | ||
|
|
93b6eeca54 | ||
|
|
fa45c0834d | ||
|
|
2a6c844953 | ||
|
|
7a73d27600 | ||
|
|
39c36f6037 | ||
|
|
3caaf26069 | ||
|
|
3930fd50a7 | ||
|
|
ea213ef23c | ||
|
|
5e6ec8ebf3 | ||
|
|
335492aed7 | ||
|
|
f019bf4379 | ||
|
|
542d9b664b | ||
|
|
daf2763894 | ||
|
|
6ac0ae3c87 | ||
|
|
2528d0b3fc | ||
|
|
2418f8f5e6 | ||
|
|
cd616ff40e | ||
|
|
d8d4ec6eb2 | ||
|
|
544add9e8b | ||
|
|
9abbdb9e7f | ||
|
|
185f22668c | ||
|
|
bffc99a116 | ||
|
|
d48093886d | ||
|
|
79ba8c6be8 | ||
|
|
61e07e5907 | ||
|
|
7cba76082e | ||
|
|
39766e645c | ||
|
|
c7f5ee8e9e | ||
|
|
b1d7a057fd | ||
|
|
1cd03625a5 | ||
|
|
66479334d4 | ||
|
|
9c1275bb1f | ||
|
|
d0c0f43c79 | ||
|
|
71a6a88de8 | ||
|
|
5e76f12000 | ||
|
|
83de071c00 | ||
|
|
d1a2655090 | ||
|
|
b5dd999f84 | ||
|
|
059c33e69d | ||
|
|
1ae153d315 | ||
|
|
643ae78691 | ||
|
|
04e4940607 | ||
|
|
88e071d22d | ||
|
|
b120dafb70 | ||
|
|
00a2c8e09c | ||
|
|
84562bca82 | ||
|
|
025565005d | ||
|
|
369a9934a5 | ||
|
|
29007e7d79 | ||
|
|
60523d1b62 | ||
|
|
04d8f66b72 | ||
|
|
be9d92bb26 | ||
|
|
82163a3301 | ||
|
|
4cde35dc18 | ||
|
|
d5f6b4440a | ||
|
|
b1474768fe | ||
|
|
624b98544a | ||
|
|
95fe03b182 | ||
|
|
564624814b | ||
|
|
0e4a7caf0b | ||
|
|
95c6cca336 | ||
|
|
8ddcc4b270 | ||
|
|
3c5a794691 | ||
|
|
3f64f3252b | ||
|
|
e73bd9e9bb | ||
|
|
1b4eee6d0d | ||
|
|
5ec2014051 | ||
|
|
1bfa68d94f | ||
|
|
779af8ce8e | ||
|
|
77c2e24215 | ||
|
|
b0792330e4 | ||
|
|
74e93e4cad | ||
|
|
8f097ab304 | ||
|
|
3fd3da3d46 | ||
|
|
39aeb9935b | ||
|
|
8350de781f | ||
|
|
dfc0910756 | ||
|
|
7031539314 | ||
|
|
ed800e4b86 | ||
|
|
42bea80a6a | ||
|
|
a4ebc08c4f | ||
|
|
b23a8dd429 | ||
|
|
fea0425a4f | ||
|
|
f06d652393 | ||
|
|
1c6fd512a5 | ||
|
|
3c2ed7d8a8 | ||
|
|
fe4d46014d | ||
|
|
0c5ea34fd4 | ||
|
|
dc98da585d | ||
|
|
349966832f | ||
|
|
918d2910d9 | ||
|
|
6ba5211310 | ||
|
|
e9fd5678a5 | ||
|
|
f8072dd8e0 | ||
|
|
b5ba84f667 | ||
|
|
6164f17229 | ||
|
|
f13b085582 | ||
|
|
f1170c84a4 | ||
|
|
40849f8ae1 | ||
|
|
a0165858c4 | ||
|
|
033b8e51e9 | ||
|
|
556c562258 | ||
|
|
dad3a6bae1 | ||
|
|
67f59dc256 | ||
|
|
2702540930 | ||
|
|
131a39bad4 | ||
|
|
584839d38a | ||
|
|
edf054cd0c | ||
|
|
b32c779c37 | ||
|
|
d47f724f16 | ||
|
|
f88f5ce454 | ||
|
|
a39b6869ca | ||
|
|
ce446a6f03 | ||
|
|
096bd90aed | ||
|
|
e5c4e65764 | ||
|
|
0743238b43 | ||
|
|
4eaa7ea821 | ||
|
|
6e2179aa8e | ||
|
|
5e49aeef6f | ||
|
|
e39f08f6bd | ||
|
|
c82794bff7 | ||
|
|
1e606d1ed3 | ||
|
|
41900dca76 | ||
|
|
91bceca7ee | ||
|
|
aaa6443954 | ||
|
|
a80532a538 | ||
|
|
445798ed46 | ||
|
|
5c1d1931b7 | ||
|
|
fe21f18991 | ||
|
|
a0b43d0a7f | ||
|
|
7b9c6a69bd | ||
|
|
ba3df8577b | ||
|
|
f7b14085f2 | ||
|
|
4f9f7eedf5 | ||
|
|
f60f2a567a | ||
|
|
f6445d5a3e | ||
|
|
074ec358ab | ||
|
|
57ce6ccfcf | ||
|
|
45e733eb60 | ||
|
|
42c9ff026e | ||
|
|
937113e234 | ||
|
|
cd3fc6e6ea | ||
|
|
94724b7a56 | ||
|
|
3769cd1119 | ||
|
|
70840131ff | ||
|
|
438feccd55 | ||
|
|
de4e980389 | ||
|
|
b7566dc65b | ||
|
|
d1e7960f99 | ||
|
|
4ed0a6ce53 | ||
|
|
3df119f4fe | ||
|
|
a18f258b4d | ||
|
|
0329ac5074 | ||
|
|
dde09872df | ||
|
|
318b23097b | ||
|
|
ec35d4886c | ||
|
|
f28654de12 | ||
|
|
935a79e15f | ||
|
|
7e4dab17ec | ||
|
|
705a7d2cc2 | ||
|
|
655394d433 | ||
|
|
6acfdc1439 | ||
|
|
f799986be1 | ||
|
|
de589799e8 | ||
|
|
efe9e3ab28 | ||
|
|
339bd23ac8 | ||
|
|
dd8db1acd2 | ||
|
|
6fc0d84b7a | ||
|
|
0e7646cf4b | ||
|
|
f90475772d | ||
|
|
f97bb00dff | ||
|
|
0f2c117f3e | ||
|
|
6564f95260 | ||
|
|
a82eecfc07 | ||
|
|
514fd008b9 | ||
|
|
f38d4551f1 | ||
|
|
0b2fb7fd78 | ||
|
|
8a0989aa50 | ||
|
|
5bd921139e | ||
|
|
a1d77bdc65 | ||
|
|
5be57d0f4a | ||
|
|
243210e896 | ||
|
|
41dd584f62 | ||
|
|
55a75bfb1b | ||
|
|
d343617f13 | ||
|
|
934e4fdde4 | ||
|
|
a88726d84e | ||
|
|
8b223c5c83 | ||
|
|
e2acd74cb4 | ||
|
|
aca719be41 | ||
|
|
58cd520e32 | ||
|
|
57cad1f232 | ||
|
|
a1e40fc3f1 | ||
|
|
3ee47bef41 | ||
|
|
89520b1f38 | ||
|
|
9024c04c72 | ||
|
|
541e7bdf72 | ||
|
|
8e28cb9119 | ||
|
|
effe33096d | ||
|
|
e05858b09c | ||
|
|
e030dac3d0 | ||
|
|
1d4b96eed0 | ||
|
|
0106cba602 | ||
|
|
7164e28138 | ||
|
|
7b168de236 | ||
|
|
962f479318 | ||
|
|
80ce601eae | ||
|
|
f874189456 | ||
|
|
9d6890e6ca | ||
|
|
67788d89b5 | ||
|
|
9ba7c9a74d | ||
|
|
0a91b83e27 | ||
|
|
6f85ca3356 | ||
|
|
3ba9689448 | ||
|
|
f0102a69f9 | ||
|
|
eefa3b2e53 | ||
|
|
04467218a3 | ||
|
|
7911beaf1c | ||
|
|
51bf82b7e7 | ||
|
|
0fd51cf852 | ||
|
|
d96cd1b690 | ||
|
|
53845b79e1 | ||
|
|
3820fa57d3 | ||
|
|
941bfca68c | ||
|
|
617fd6cfdc | ||
|
|
cf641cd0a3 | ||
|
|
8b2b54c033 | ||
|
|
c6d839869b | ||
|
|
0dbc6a12ee | ||
|
|
54b3c0548d | ||
|
|
c571f6f6a9 | ||
|
|
00a8265fb9 | ||
|
|
ba5378fecc | ||
|
|
ae8ab0812e | ||
|
|
d712ad97a5 | ||
|
|
662b49608c | ||
|
|
6debf2c909 | ||
|
|
762e798d22 | ||
|
|
6021e3a1b8 | ||
|
|
06f256b410 | ||
|
|
f14063def5 | ||
|
|
94a2036d50 | ||
|
|
eaa9187dd8 | ||
|
|
8ea394e778 | ||
|
|
a710706c81 | ||
|
|
ce4df761df | ||
|
|
e4cbdb57ad | ||
|
|
d91b0b4938 | ||
|
|
f5eb20b63d | ||
|
|
efd4500e6b | ||
|
|
d66356542d | ||
|
|
3be43a1def | ||
|
|
437f812718 | ||
|
|
19c52e4ae1 | ||
|
|
51528fbdea | ||
|
|
25b7c64dc6 | ||
|
|
9c3cb57931 | ||
|
|
35dd7f8e2b | ||
|
|
278b8bfa08 | ||
|
|
7f71cbc8c7 | ||
|
|
97d1d5343e | ||
|
|
c4366124c7 | ||
|
|
17a865ec7f | ||
|
|
b99f4461be | ||
|
|
5942b6c302 | ||
|
|
77234d6aec | ||
|
|
a8dcc2acf3 | ||
|
|
c49b0795db | ||
|
|
de4723883f | ||
|
|
bc268da8c2 | ||
|
|
678f47f494 | ||
|
|
965cdbdbbb | ||
|
|
96955d6e79 | ||
|
|
5630c37b35 | ||
|
|
1bfa7ea754 | ||
|
|
fc72b41953 | ||
|
|
66c556fbfd | ||
|
|
8e4092750d | ||
|
|
1ac688644a | ||
|
|
00b6f62287 | ||
|
|
36f9822466 | ||
|
|
ef113e52ab | ||
|
|
ef5a3f1bb7 | ||
|
|
759836113f | ||
|
|
79bf84e1ad | ||
|
|
9cb17d2915 | ||
|
|
14c3adbec0 | ||
|
|
b81209c278 | ||
|
|
f571a9ef88 | ||
|
|
0ffc752f6f | ||
|
|
5cd73f0d12 | ||
|
|
0a19fbb376 | ||
|
|
a884452ffc | ||
|
|
b1144e74ea | ||
|
|
aa4796cd0d | ||
|
|
a906295c65 | ||
|
|
d09280a1dc | ||
|
|
45221b1951 | ||
|
|
c9ccd91421 | ||
|
|
730c2a81f7 | ||
|
|
8c54a65aa5 | ||
|
|
29d4d342bd | ||
|
|
948d6c2b9f | ||
|
|
833058fd2e | ||
|
|
7b81cfb6ea | ||
|
|
89530f88f7 | ||
|
|
516d04c391 | ||
|
|
fccd08afa5 | ||
|
|
af93539576 | ||
|
|
22e12e0043 | ||
|
|
dd883f2136 | ||
|
|
0c8a31bf2b | ||
|
|
96184b9541 | ||
|
|
367a79206d | ||
|
|
35ea399d33 | ||
|
|
f3b31c2807 | ||
|
|
b9c0868f08 | ||
|
|
0f362b0650 | ||
|
|
ebf5811761 | ||
|
|
25d40caf1e | ||
|
|
ec08286173 | ||
|
|
7a0cb1c370 | ||
|
|
8d287b17d7 | ||
|
|
932a56f26f | ||
|
|
c5f1b99721 | ||
|
|
73afcec22a | ||
|
|
573153669c | ||
|
|
fbcf18cf33 | ||
|
|
4dbfdfb9d6 | ||
|
|
9201d09ab7 | ||
|
|
be91cc9bb3 | ||
|
|
664b6bf4c5 | ||
|
|
d1e5034659 | ||
|
|
0cbab230bf | ||
|
|
6fdb100793 | ||
|
|
43db4a4182 | ||
|
|
a701c9d046 | ||
|
|
74a2cfc83b | ||
|
|
08e8af9372 | ||
|
|
b90943c56b | ||
|
|
bacadbf366 | ||
|
|
4935da138d | ||
|
|
c964dd0cd2 | ||
|
|
8bbc492978 | ||
|
|
82cda1ecb0 | ||
|
|
ef65270387 | ||
|
|
6448627bc9 | ||
|
|
eb22db0dcf | ||
|
|
5237692979 | ||
|
|
d13e2fbb83 | ||
|
|
0f165bce50 | ||
|
|
a93e5fa1c2 | ||
|
|
74f00327bd | ||
|
|
e2b34ff5d2 | ||
|
|
27fe4d78b7 | ||
|
|
8f15c5e905 | ||
|
|
8d91f7de60 | ||
|
|
ccb09ad0ac | ||
|
|
12b5a0cdd7 | ||
|
|
50ef1369c3 | ||
|
|
cdb62a70cd | ||
|
|
61c72e4aa7 | ||
|
|
d9cbe1a8b7 | ||
|
|
2f76571d3a | ||
|
|
193e31f22a | ||
|
|
92efd887e2 | ||
|
|
82847e1851 | ||
|
|
8fd28fcd8f | ||
|
|
5a6bbbd624 | ||
|
|
b6bbf6ac22 | ||
|
|
68ed56ce27 | ||
|
|
1cc42d6a63 | ||
|
|
78ba7f1073 | ||
|
|
c1000d23db | ||
|
|
2fac6272a1 | ||
|
|
9509bec79a | ||
|
|
cf1b16a505 | ||
|
|
357292de44 | ||
|
|
55bcc193ae | ||
|
|
af0be57538 | ||
|
|
d75d6ffb11 | ||
|
|
d259ba91b3 | ||
|
|
d48546d2dd | ||
|
|
9900d35367 | ||
|
|
c1eff7045a | ||
|
|
0059e01936 | ||
|
|
590d971f83 | ||
|
|
ac67a10d4c | ||
|
|
6169175a89 | ||
|
|
6e8057b6ea | ||
|
|
0c9a898ccd | ||
|
|
a6cf714f41 | ||
|
|
e53e8bfe8e | ||
|
|
c689ff081d | ||
|
|
2bc8811e62 | ||
|
|
42e0e6b0b8 | ||
|
|
529a268bbc | ||
|
|
38650b748d | ||
|
|
e65417f1f6 | ||
|
|
d20e018081 | ||
|
|
dcf824688a | ||
|
|
b4a23e97a9 | ||
|
|
112fdf46d0 | ||
|
|
a8b0ac82b4 | ||
|
|
ecf028490f | ||
|
|
7f84bc76a6 | ||
|
|
56cb47c585 | ||
|
|
fc3f233923 | ||
|
|
1d5a0a0a1b | ||
|
|
b9506ac64a | ||
|
|
8d4b46d014 | ||
|
|
c35f1310bc | ||
|
|
ce308dc476 | ||
|
|
aa1454562c | ||
|
|
798a8b6d91 | ||
|
|
351707f1f1 | ||
|
|
9d2c0481ae | ||
|
|
d3b1bf571b | ||
|
|
30bf192cf4 | ||
|
|
8e26705693 | ||
|
|
c2ad338707 | ||
|
|
18fd892c85 | ||
|
|
27e671378b | ||
|
|
13c03b573c | ||
|
|
ee13405a13 | ||
|
|
9eae0d5ce8 | ||
|
|
b3fe0904eb | ||
|
|
71135972c6 | ||
|
|
291b68634e | ||
|
|
f9b4df70ff | ||
|
|
34d2af8a77 | ||
|
|
084ce7ecc0 | ||
|
|
b761d84d4f | ||
|
|
7648a3c590 | ||
|
|
17ec1aab77 | ||
|
|
ec6781954a | ||
|
|
f536daa1e4 | ||
|
|
ac0f1b2ce2 | ||
|
|
64752532dd | ||
|
|
42547234d2 | ||
|
|
ecb09b5627 | ||
|
|
69064ac13d | ||
|
|
a80117a4ee | ||
|
|
af39fce6e5 | ||
|
|
18244161f7 | ||
|
|
de1843ad30 | ||
|
|
fa014ee55d | ||
|
|
d02ea224df | ||
|
|
42aff75108 | ||
|
|
82c5547931 | ||
|
|
bf5003f25e | ||
|
|
7e64d606d2 | ||
|
|
88ce68e733 | ||
|
|
89684b4ce9 | ||
|
|
e7be0c4dc9 | ||
|
|
a1660997ff | ||
|
|
9280a4621d | ||
|
|
44304c30b3 | ||
|
|
ab62914f87 | ||
|
|
f987cf77b5 | ||
|
|
2888791e5c | ||
|
|
511893e182 | ||
|
|
2ead9e23ab | ||
|
|
9caef62489 | ||
|
|
25849a3412 | ||
|
|
3287ce12a4 | ||
|
|
c973d8df1a | ||
|
|
dd012dfd90 | ||
|
|
e2f1ea7f23 | ||
|
|
c47a072815 | ||
|
|
2ffda34f23 | ||
|
|
0990d64756 | ||
|
|
76e1a469ef | ||
|
|
da7e4ed8de | ||
|
|
85eac7200d | ||
|
|
20c6bfd360 | ||
|
|
b7d104d2a2 | ||
|
|
3a0d7d1d6b | ||
|
|
3924ede3ad | ||
|
|
6fc6d18882 | ||
|
|
ea6f2130b4 | ||
|
|
95bc0fb456 | ||
|
|
a0e107249f | ||
|
|
9ccb56a7ab | ||
|
|
9bf8f6ed4c | ||
|
|
29e99b2b89 | ||
|
|
d5d64b756d | ||
|
|
1208cc836a | ||
|
|
806068b0df | ||
|
|
39a8a31de9 | ||
|
|
574f3ea909 | ||
|
|
254a46e79c | ||
|
|
250b38710c | ||
|
|
db74efbe34 | ||
|
|
0f289a1270 | ||
|
|
97fdd0adb7 | ||
|
|
100766e360 | ||
|
|
dcdffbffc7 | ||
|
|
a762158e99 | ||
|
|
7cf427cfbf | ||
|
|
ed79648d7d | ||
|
|
cf078d8da8 | ||
|
|
e9d7c23261 | ||
|
|
37de75b2ae | ||
|
|
c71b489e9e | ||
|
|
b5552a82e3 | ||
|
|
51cd8f7ce8 | ||
|
|
45ce6a41a3 | ||
|
|
a9bd51c0e4 | ||
|
|
b989175754 | ||
|
|
264cd8ee11 | ||
|
|
69494dc624 | ||
|
|
10da2eb85b | ||
|
|
17c9388473 | ||
|
|
435befd25c | ||
|
|
d557a0e01b | ||
|
|
a4e9c235f8 | ||
|
|
109b0444f5 | ||
|
|
fb5f76f025 | ||
|
|
ebc996c820 | ||
|
|
06b5c84728 | ||
|
|
0f26c62e73 | ||
|
|
d361ed904e | ||
|
|
697640f561 | ||
|
|
b33c9befb7 | ||
|
|
f5ecabbc21 | ||
|
|
143f87960e | ||
|
|
35d4405727 | ||
|
|
abe97bf0df | ||
|
|
f28fa2d3a1 | ||
|
|
b61496a36b | ||
|
|
8093c6ddb0 | ||
|
|
a9847533e5 | ||
|
|
56378b9bf0 | ||
|
|
7142c9029c | ||
|
|
9ef3285ebb | ||
|
|
1e467fd23c | ||
|
|
5adce9a1e3 | ||
|
|
3ca0dae606 | ||
|
|
1e6ca40ffa | ||
|
|
1d8ef5ed18 | ||
|
|
82887d0361 | ||
|
|
83cd44697d | ||
|
|
f2bdfe102e | ||
|
|
10b801dec0 | ||
|
|
94f5f53d2a | ||
|
|
0bff6aadbc | ||
|
|
dd398ce577 | ||
|
|
05045d9544 | ||
|
|
547a902bd3 | ||
|
|
d93f648230 | ||
|
|
3f8865c6fb | ||
|
|
15a00ea807 | ||
|
|
f12c311a79 | ||
|
|
b291d8d589 | ||
|
|
b249aa9f65 | ||
|
|
4974edc70a | ||
|
|
dd9a6bea0a | ||
|
|
ffe38e5088 | ||
|
|
b9090ef73e | ||
|
|
e2fea427a4 | ||
|
|
6bc003e47d | ||
|
|
e295a6d05e | ||
|
|
409f6b4bc1 | ||
|
|
cd547fecea | ||
|
|
859a5f88cb | ||
|
|
dc3bc45d1d | ||
|
|
857fed0310 | ||
|
|
ba50f978a5 | ||
|
|
589d26fc5e | ||
|
|
3bd916f763 | ||
|
|
d9b43dc649 | ||
|
|
6e79515a75 | ||
|
|
6e34985b51 | ||
|
|
f731a66e1c | ||
|
|
1378626937 | ||
|
|
2a80e34179 | ||
|
|
2120d41029 | ||
|
|
398688ab38 | ||
|
|
d48be34696 | ||
|
|
4b504c9436 | ||
|
|
f0c02bb6d9 | ||
|
|
00eb0d3b67 | ||
|
|
3d4113bd1b | ||
|
|
48a86511cb | ||
|
|
45d74e7220 | ||
|
|
a2e0133a94 | ||
|
|
097840dc93 | ||
|
|
02ba144c8d | ||
|
|
91fa4c9be3 | ||
|
|
c8565c2772 | ||
|
|
8d3fba5540 | ||
|
|
65f8357b46 | ||
|
|
e7739d9411 | ||
|
|
ad1f228ef6 | ||
|
|
400acad533 | ||
|
|
e47b31845c | ||
|
|
613119599f | ||
|
|
ff80187930 | ||
|
|
c20b3a7cf2 | ||
|
|
dfbaac0401 | ||
|
|
aab7fce2d4 | ||
|
|
bf03694e28 | ||
|
|
19d3552f2a | ||
|
|
36bbd28b75 | ||
|
|
ae16afa428 | ||
|
|
01f22988b1 | ||
|
|
3fed769b40 | ||
|
|
7865de92ab | ||
|
|
a665cb0229 | ||
|
|
5ff67c92ee | ||
|
|
edc2ed9512 | ||
|
|
ebc70d1baf | ||
|
|
9b7318ab4c | ||
|
|
7b145f8269 | ||
|
|
361ec2a474 | ||
|
|
f8a8437c95 | ||
|
|
c2993bcdeb | ||
|
|
d52dd39592 | ||
|
|
2c3126353c | ||
|
|
6ac19b04bf | ||
|
|
133bd288bf | ||
|
|
0768916a06 | ||
|
|
bca9d31531 | ||
|
|
cd4bc93483 | ||
|
|
20b72ef344 | ||
|
|
1003466a5f | ||
|
|
94ba6e2dfc | ||
|
|
723529ffff | ||
|
|
4e805bb59a | ||
|
|
e0ae7634d5 | ||
|
|
2ae8538f96 | ||
|
|
7c2ab7fff8 | ||
|
|
bf91d03adf | ||
|
|
54ac6a0535 | ||
|
|
458b953522 | ||
|
|
164352562b | ||
|
|
0650bb954f | ||
|
|
2d23c95c3f | ||
|
|
66d67445c9 | ||
|
|
1b9f42ae67 | ||
|
|
a37f99f242 | ||
|
|
114bd2435f | ||
|
|
df80d82aab | ||
|
|
5d608b6206 | ||
|
|
a4c7a89507 | ||
|
|
c04b01cbd7 | ||
|
|
3dc2a68583 | ||
|
|
90efaeec42 | ||
|
|
c0ee57ae55 | ||
|
|
089cca636e | ||
|
|
a6a9c72a75 | ||
|
|
a76506c3fd | ||
|
|
1d16e5322f | ||
|
|
2b24478f05 | ||
|
|
70fe21b30a | ||
|
|
bb5345b330 | ||
|
|
27adf8d6e9 | ||
|
|
eeb7ff4a6d | ||
|
|
499f09fc99 | ||
|
|
0ab2672872 | ||
|
|
205a5de4e5 | ||
|
|
040c445297 | ||
|
|
c019047d6c | ||
|
|
ad10a18071 | ||
|
|
85fbf68436 | ||
|
|
0ade3aa62a | ||
|
|
b8ef3af982 | ||
|
|
5361676bba | ||
|
|
90dee7036d | ||
|
|
b80149344d | ||
|
|
38fcc66c16 | ||
|
|
ae0ee72425 | ||
|
|
ae2235fd3c | ||
|
|
b31ba98076 | ||
|
|
45358bf5d0 | ||
|
|
bf1e7e150e | ||
|
|
c9901c9017 | ||
|
|
a8035f25a2 | ||
|
|
6a4867512e | ||
|
|
65e7cc9143 | ||
|
|
8c0166d3bf | ||
|
|
60cb01be1f | ||
|
|
c370426792 | ||
|
|
4e61ceb0df | ||
|
|
0dde8585c3 | ||
|
|
9e36a95a97 | ||
|
|
20f96cc9d3 | ||
|
|
22d71de2c3 | ||
|
|
ee5f465a2f | ||
|
|
eef32d4372 | ||
|
|
d98b4a5124 | ||
|
|
d5dd21dd79 | ||
|
|
247803715b | ||
|
|
38187a31d6 | ||
|
|
381cf8022f | ||
|
|
84a0fddaf4 | ||
|
|
19b5e87cab | ||
|
|
e940ce1df6 | ||
|
|
59720370f9 | ||
|
|
7dfca09ff6 | ||
|
|
85c02e57b1 | ||
|
|
391847d627 | ||
|
|
fdca5d7584 | ||
|
|
f7c5a0684a | ||
|
|
a7aa634247 | ||
|
|
7aa1c0a907 | ||
|
|
ef43b223fd | ||
|
|
9a5fc49690 | ||
|
|
ca37f87c27 | ||
|
|
3ff92f587f | ||
|
|
a5cf6417b3 | ||
|
|
5c447cfb71 | ||
|
|
50d7975fd6 | ||
|
|
32b24ba155 | ||
|
|
3ade0cf6a5 | ||
|
|
d71159c6c5 | ||
|
|
dcf8d6a86e | ||
|
|
daa90bf32d | ||
|
|
6f622ab1f7 | ||
|
|
46ebb57b45 | ||
|
|
b3d01b6036 | ||
|
|
7dac285de6 | ||
|
|
8b1f5d71e1 | ||
|
|
e577e77867 | ||
|
|
2101a8fdc6 | ||
|
|
72750b05e6 | ||
|
|
a851dd68fb | ||
|
|
93c276d059 | ||
|
|
5d1275e938 | ||
|
|
526c757901 | ||
|
|
495fe6002d | ||
|
|
5ee286e7e0 | ||
|
|
f941264b5e | ||
|
|
3d32bc47a7 | ||
|
|
a7d072f525 | ||
|
|
466808bf48 | ||
|
|
edaeaf48a8 | ||
|
|
b400ad52cc | ||
|
|
20e47ae52d | ||
|
|
9c24e0b510 | ||
|
|
b421d03f69 | ||
|
|
49bca5171b | ||
|
|
7debf736a6 | ||
|
|
43c4c80388 | ||
|
|
140dc92e5f | ||
|
|
04aea73a85 | ||
|
|
a259138180 | ||
|
|
c68ed4c204 | ||
|
|
d2174a893a | ||
|
|
546bb53ef9 | ||
|
|
c46b63f6b4 | ||
|
|
81fbb049a5 | ||
|
|
7cb40ed915 | ||
|
|
9d7d731090 | ||
|
|
8a8c6ea3a6 | ||
|
|
ddfc951a0e | ||
|
|
239b862665 | ||
|
|
f52a512c59 | ||
|
|
a04113f410 | ||
|
|
d6c727dcc1 | ||
|
|
8babb77d37 | ||
|
|
5a4c558865 | ||
|
|
b16423b26b | ||
|
|
47ca10076b | ||
|
|
d30e3ab43c | ||
|
|
92a2b01c77 | ||
|
|
172df22281 | ||
|
|
0a6c2027f5 | ||
|
|
45e9d6ed5a | ||
|
|
11de4c2e72 | ||
|
|
50832fd3bc | ||
|
|
876643e83e | ||
|
|
6cb5097ea8 | ||
|
|
8514ec7320 | ||
|
|
3c522f4984 | ||
|
|
28afb52734 | ||
|
|
ac15b184b6 | ||
|
|
632382b069 | ||
|
|
7551b1ad00 | ||
|
|
9813c2d5f1 | ||
|
|
0fa7b45a0e | ||
|
|
9b7dce1940 | ||
|
|
b556bcb16c | ||
|
|
f1a793f2ee | ||
|
|
91df8f5649 | ||
|
|
65e55a0c49 | ||
|
|
c7eb42b04f | ||
|
|
5c828fc6c8 | ||
|
|
4874070b3f | ||
|
|
c16de21172 | ||
|
|
ccc789eadd | ||
|
|
9cdcba3fbc | ||
|
|
08347cf4f7 | ||
|
|
94837a0105 | ||
|
|
5cf906d76b | ||
|
|
7fffba80c3 | ||
|
|
204b7fe854 | ||
|
|
8a3def097f | ||
|
|
7a305475e3 | ||
|
|
27845088e3 | ||
|
|
677cea4748 | ||
|
|
f065a5c8b9 | ||
|
|
cf84183c18 | ||
|
|
48172f3a53 | ||
|
|
9cd16ec56a | ||
|
|
be94eb9d1f | ||
|
|
35c8cd7f23 | ||
|
|
21437bb276 | ||
|
|
e8e86205f5 | ||
|
|
9ba31a394a | ||
|
|
f7de00b401 | ||
|
|
9692fc8c5b | ||
|
|
a3febd79de | ||
|
|
a4dac7a292 | ||
|
|
9ec54b8fed | ||
|
|
d2c2067aaf | ||
|
|
9ebce31a46 | ||
|
|
29de3e00ff | ||
|
|
63d21ca4b2 | ||
|
|
4185a9ce4b | ||
|
|
1e90fd1164 | ||
|
|
9b847f1a04 | ||
|
|
6a8ee87268 | ||
|
|
a3f95d919b | ||
|
|
38b6838386 | ||
|
|
df806977c6 | ||
|
|
9441e063aa | ||
|
|
10a0daf620 | ||
|
|
54d9f05e39 | ||
|
|
e1259098f5 | ||
|
|
9ab6ef723a | ||
|
|
b4933d76c7 | ||
|
|
abb4671bfc | ||
|
|
e263426cdf | ||
|
|
6af3236ba6 | ||
|
|
318f5032db | ||
|
|
be1cc14a9c | ||
|
|
1dc3396ad4 | ||
|
|
fa1cc9269c | ||
|
|
d57d4b71f9 | ||
|
|
48e227167e | ||
|
|
df6a411365 | ||
|
|
4b388b2ce8 | ||
|
|
a790639167 | ||
|
|
6931cd08c4 | ||
|
|
d7f87cdd36 | ||
|
|
9b2ee88683 | ||
|
|
87a907f9dd | ||
|
|
cd0f94dd6c | ||
|
|
68f489ecbb | ||
|
|
e5f79d1f73 | ||
|
|
1b5eb55ed9 | ||
|
|
04b52149ab | ||
|
|
841bdd5ca5 | ||
|
|
ceada41b83 | ||
|
|
54898eca60 | ||
|
|
13afde0140 | ||
|
|
f5e6044cf5 | ||
|
|
1488a509b2 | ||
|
|
6e9b15a48f | ||
|
|
b28fa9a05a | ||
|
|
4029dc2ea8 | ||
|
|
1cd0b26a40 | ||
|
|
5ce6dabe9b | ||
|
|
68d477a4c6 | ||
|
|
38911076ad | ||
|
|
2da74e5147 | ||
|
|
3a1e24e680 | ||
|
|
a87d3e080e | ||
|
|
eccd8f85bc | ||
|
|
72bb16173a | ||
|
|
32ed32cf56 | ||
|
|
b3a2988d2c | ||
|
|
cc2fb1a070 | ||
|
|
da24133306 | ||
|
|
8b6b1c68a0 | ||
|
|
1b4eafc873 | ||
|
|
ac956f2b8c | ||
|
|
dc10bb69f6 | ||
|
|
8740d54210 | ||
|
|
df020e08a0 | ||
|
|
a054b59550 | ||
|
|
d1c722c1d0 | ||
|
|
f7b95c1aa5 | ||
|
|
73e5bbecbe | ||
|
|
6cb3fa8fb7 | ||
|
|
4d950a9e10 | ||
|
|
b3ec4df8ce | ||
|
|
bfb66f1d85 | ||
|
|
f80fceda0e | ||
|
|
e6bf096583 | ||
|
|
11b3065fd1 | ||
|
|
3680bb7ccc | ||
|
|
733102b4a1 | ||
|
|
a8b96803a4 | ||
|
|
eb6498544f | ||
|
|
f54d6157f4 | ||
|
|
f2f571e4ab | ||
|
|
26307a0cee | ||
|
|
8d0a5958eb | ||
|
|
59842e9bc6 | ||
|
|
3044009550 | ||
|
|
da96f4938a | ||
|
|
7d9630786b | ||
|
|
8f688509c8 | ||
|
|
b3ec87ab09 | ||
|
|
ca19db34d2 | ||
|
|
46b16a5e10 | ||
|
|
b145ebf955 | ||
|
|
798075931a | ||
|
|
048c56bdb0 | ||
|
|
263d9f30f2 | ||
|
|
927d69b61a | ||
|
|
8addb5ffa8 | ||
|
|
5335b2a2ad | ||
|
|
71fc425902 | ||
|
|
a03c8da683 | ||
|
|
dd8075ea95 | ||
|
|
2aea3036b6 | ||
|
|
0caeb4edbf | ||
|
|
24d4fd17f3 | ||
|
|
d473824279 | ||
|
|
e415da7f47 | ||
|
|
ecc2857e2d | ||
|
|
396337bd0d | ||
|
|
fb2d2bce45 | ||
|
|
0621b5a161 | ||
|
|
c8836a008d | ||
|
|
6e651b13c9 | ||
|
|
fdb9f90848 | ||
|
|
ef4e73f987 | ||
|
|
262e465e39 | ||
|
|
56e879b7aa | ||
|
|
400f420925 | ||
|
|
6bbc07ddbf | ||
|
|
7bf2bcb017 | ||
|
|
263822fd19 | ||
|
|
9b4ae6d556 | ||
|
|
383604d4b8 | ||
|
|
44c1dae1b9 | ||
|
|
49587776fa | ||
|
|
e403bf207c | ||
|
|
553883bdd1 | ||
|
|
83ef47c8d0 | ||
|
|
871dc5d186 | ||
|
|
cd2043915c | ||
|
|
fa79de6ea4 | ||
|
|
b30b1a5999 | ||
|
|
d888d7d1c0 | ||
|
|
02daf0049a | ||
|
|
fea200043e | ||
|
|
87505c8716 | ||
|
|
832127a0d6 | ||
|
|
f197a9c9f1 | ||
|
|
e9ec0a24da | ||
|
|
ecf242f6d4 | ||
|
|
8d4821b4dc | ||
|
|
7843eed8bc | ||
|
|
a395921fdc | ||
|
|
cbdd8bc041 | ||
|
|
5f429a5418 | ||
|
|
15432fc55f | ||
|
|
6e64cc101a | ||
|
|
63d849b6f0 | ||
|
|
a91e05db9e | ||
|
|
a522aa0a81 | ||
|
|
f0a6eb8723 | ||
|
|
8c2aef3f14 | ||
|
|
f4c63f8238 | ||
|
|
1921c2f74a | ||
|
|
8a168bb2ce | ||
|
|
1fe6dbc1f2 | ||
|
|
f5897d4b0b | ||
|
|
51e5db86b7 | ||
|
|
c06201303d | ||
|
|
dfe7a68053 | ||
|
|
5a2497d482 | ||
|
|
0797feeb0f | ||
|
|
b550d44cb9 | ||
|
|
a45ecb5733 | ||
|
|
6cbd643d4b | ||
|
|
f03f991a25 | ||
|
|
0d5de64c0f | ||
|
|
a55f2c48ca | ||
|
|
e5ba28676d | ||
|
|
fbbe922cb6 | ||
|
|
c69b7562ab | ||
|
|
bb1f71f1f9 | ||
|
|
be39afa2f4 | ||
|
|
9385a600cf | ||
|
|
738cc250f8 | ||
|
|
07654ddd3f | ||
|
|
160b07d1e3 | ||
|
|
0dbd742588 | ||
|
|
0677423d14 | ||
|
|
4382304e77 | ||
|
|
e27d963784 | ||
|
|
1f0d4197a9 | ||
|
|
a3b4104612 | ||
|
|
94896ce552 | ||
|
|
8cdbcf1263 | ||
|
|
3543ccea7f | ||
|
|
7ef1063007 | ||
|
|
4b061a0e4c | ||
|
|
9b0d7dde91 | ||
|
|
57ac3bd4be | ||
|
|
4a6d6e34f8 | ||
|
|
a5fbb20fbb | ||
|
|
1c94c16234 | ||
|
|
e8f51acdb2 | ||
|
|
324f200f1b | ||
|
|
08f6291350 | ||
|
|
c4b1d4fa28 | ||
|
|
412e4ab9da | ||
|
|
6cd0861fa3 | ||
|
|
2a77a739dc | ||
|
|
4c4e4f6d3d | ||
|
|
288953aa6d | ||
|
|
d7b917aa49 | ||
|
|
53a5a48aed | ||
|
|
3c2b626102 | ||
|
|
43e37d4f2f | ||
|
|
496fa85641 | ||
|
|
24feae84cd | ||
|
|
d31f7ebf57 | ||
|
|
1a08b52ab4 | ||
|
|
35511db4da | ||
|
|
50ba19d91b | ||
|
|
8a0901c92b | ||
|
|
8e3f4561c0 | ||
|
|
058e024f0e | ||
|
|
31a2870c3f | ||
|
|
960fbfc110 | ||
|
|
b76e95cbb9 | ||
|
|
5fc7e653fe | ||
|
|
105d9e998b | ||
|
|
f07c576e5f | ||
|
|
496f9a0176 | ||
|
|
0c2c5006f8 | ||
|
|
5475d616e3 | ||
|
|
b167a64544 | ||
|
|
ffe5bf1fe3 | ||
|
|
12985b7811 | ||
|
|
7ceb9b0b50 | ||
|
|
128e83909b | ||
|
|
9c19d1e5c4 | ||
|
|
1d40c85c3c | ||
|
|
6fe40b055f | ||
|
|
a6b43b93ac | ||
|
|
2eb428df79 | ||
|
|
724e69146c | ||
|
|
557686aa0a | ||
|
|
1f3290faae | ||
|
|
543ab500da | ||
|
|
7107a85041 | ||
|
|
a6c8ab8a5f | ||
|
|
5c44dd3823 | ||
|
|
352f38a77e | ||
|
|
e8d47fa9a3 | ||
|
|
626efdafd4 | ||
|
|
9653213914 | ||
|
|
25d71462e3 | ||
|
|
e322ab4deb | ||
|
|
7a32eca039 | ||
|
|
bf9fbd896b | ||
|
|
8c92e0f19f | ||
|
|
43d94e7b5e | ||
|
|
310afdf5d7 | ||
|
|
7e5a1c6b0d | ||
|
|
9b7e949025 | ||
|
|
b815a4b2d3 | ||
|
|
596d2070ed | ||
|
|
9eafb11a02 | ||
|
|
22bcfef523 | ||
|
|
fabef48ca2 | ||
|
|
30c3eb0465 | ||
|
|
330c66559c | ||
|
|
04e6061584 | ||
|
|
bba5ec27fb | ||
|
|
a83f5df47c | ||
|
|
0e5146b06b | ||
|
|
41ec5fd56d | ||
|
|
b69481b639 | ||
|
|
65f002d62c | ||
|
|
996499e6fc | ||
|
|
b9ff877f14 | ||
|
|
cf43edd6a1 | ||
|
|
a485e791bb | ||
|
|
b0736b5b6c | ||
|
|
3eced21a01 | ||
|
|
b863b9b957 | ||
|
|
771da768ac | ||
|
|
12556e2dfe | ||
|
|
5989c0cb54 | ||
|
|
e9739f8591 | ||
|
|
4772cbfae6 | ||
|
|
db31c58102 | ||
|
|
93c1106735 | ||
|
|
f4c30dcd1c | ||
|
|
4e824ac6e1 | ||
|
|
5f93c83059 | ||
|
|
b23d0bec33 | ||
|
|
b65d9e6c83 | ||
|
|
7f30c31e98 | ||
|
|
fc2b9980ae | ||
|
|
797fb9c34a | ||
|
|
85a16f9f28 | ||
|
|
7020f4135f | ||
|
|
87b4155665 | ||
|
|
e879626d73 | ||
|
|
946419459c | ||
|
|
4f585a3d63 | ||
|
|
69cad4079d | ||
|
|
8de37de4f7 | ||
|
|
2a9351b8dc | ||
|
|
1edf6b65b1 | ||
|
|
c8c7cf0528 | ||
|
|
5828e4c67c | ||
|
|
eb45690e10 | ||
|
|
352f5394e5 | ||
|
|
9a3cf77919 | ||
|
|
93c018668d | ||
|
|
0d8f572661 | ||
|
|
2326033e79 | ||
|
|
57e7559c1b | ||
|
|
6963bf6028 | ||
|
|
8118dddc6a | ||
|
|
0a2b5b8efd | ||
|
|
22d4d2e812 | ||
|
|
53ed898684 | ||
|
|
2feffe164a | ||
|
|
b1e031a1b4 | ||
|
|
96ec3c1613 | ||
|
|
41581847b2 | ||
|
|
e91ed74b25 | ||
|
|
17809abd29 | ||
|
|
05576d3e1c | ||
|
|
ea84cfbdd1 | ||
|
|
10576286b8 | ||
|
|
7a4a46a95c | ||
|
|
27c9074b71 | ||
|
|
3a8aaea14a | ||
|
|
a2cdc7a1c7 | ||
|
|
c9e06a6854 | ||
|
|
bee74f898d | ||
|
|
a7b9140d2f | ||
|
|
58c57c50bf | ||
|
|
07f67c5d1a | ||
|
|
337857dc8a | ||
|
|
7360231b4b | ||
|
|
2f0b4a5d81 | ||
|
|
c9791fe97f | ||
|
|
c6810861ca | ||
|
|
5439a613d6 | ||
|
|
cf4c805427 | ||
|
|
8554b04053 | ||
|
|
22e3a76327 | ||
|
|
f0546455d5 | ||
|
|
423ea00539 | ||
|
|
1032a16db2 | ||
|
|
ad9366a1fc | ||
|
|
48ea45fad0 | ||
|
|
b081e5681d | ||
|
|
4e0e1b8061 | ||
|
|
0a66a2bc09 | ||
|
|
71903c28a8 | ||
|
|
fb98277783 | ||
|
|
d135e402bb | ||
|
|
61bff7d5f6 | ||
|
|
1438a59c00 | ||
|
|
1bfa8f0fc3 | ||
|
|
3140593e9b | ||
|
|
86ae6f18ab | ||
|
|
a5b7069fd7 | ||
|
|
602c3be3fc | ||
|
|
aa5df1dbac | ||
|
|
6f1642b35d | ||
|
|
5fa1b10506 | ||
|
|
bd4d27eabf | ||
|
|
ce914bef3f | ||
|
|
3cc630798b | ||
|
|
d20b136270 | ||
|
|
72e6de9417 | ||
|
|
600af3e617 | ||
|
|
8a38534be4 | ||
|
|
940c1f3b1c | ||
|
|
4ed96e2ab6 | ||
|
|
d820c25eda | ||
|
|
40f9facfd1 | ||
|
|
b6a0c8b1ad | ||
|
|
94a3e6c42b | ||
|
|
b461c9cf23 | ||
|
|
3669321161 | ||
|
|
eded05d415 | ||
|
|
88a56ee8f8 | ||
|
|
53fbb0b2d1 | ||
|
|
2cccb3cc62 | ||
|
|
6d58824ac5 | ||
|
|
67135e5d6f | ||
|
|
877ba9bf17 | ||
|
|
f7f7c460f2 | ||
|
|
fbb7b5ad8e | ||
|
|
928c32d616 | ||
|
|
90a0d29b2b | ||
|
|
38c59ce5a4 | ||
|
|
3caf1fdfa6 | ||
|
|
2828d7b5cd | ||
|
|
e5cab1db2d | ||
|
|
606ae2e03d | ||
|
|
dfbf996a50 | ||
|
|
0e3c57dbd3 | ||
|
|
d63820755d | ||
|
|
45118a2811 | ||
|
|
c460344994 | ||
|
|
8ae48ad9db | ||
|
|
75d7ac2783 | ||
|
|
642d6e3033 | ||
|
|
1fe8ff756e | ||
|
|
a8ad9069c9 | ||
|
|
9e3ad91225 | ||
|
|
0fe6d75211 | ||
|
|
96fb0d7e14 | ||
|
|
393b3d37f5 | ||
|
|
e69cd37226 | ||
|
|
c2997b3961 | ||
|
|
3d12920cd4 | ||
|
|
88ae5883f3 | ||
|
|
ffd858b238 | ||
|
|
9807718100 | ||
|
|
3529cd4282 | ||
|
|
2925fea7bd | ||
|
|
0ee7b688c3 | ||
|
|
2a73a6f1f7 | ||
|
|
fb3a06b9e7 | ||
|
|
9ad376c006 | ||
|
|
6cdd65762f | ||
|
|
e6be52af3a | ||
|
|
9016a5a854 | ||
|
|
0edf2bc585 | ||
|
|
8b4b8e7268 | ||
|
|
141213915e | ||
|
|
271f952c4a | ||
|
|
8ad6853e84 | ||
|
|
dc476b62d1 | ||
|
|
c7d4077219 | ||
|
|
f3b8fe5255 | ||
|
|
1899bcf000 | ||
|
|
791ec3bc6e | ||
|
|
e018bb83f0 | ||
|
|
8bd54be4ec | ||
|
|
01dc0e8273 | ||
|
|
17e0a7b2f8 | ||
|
|
2b2f4894cb | ||
|
|
0cd6061fc2 | ||
|
|
5586ddd6b7 | ||
|
|
ece62d6ad7 | ||
|
|
d4e6618b28 | ||
|
|
97836ef8c6 | ||
|
|
f86abd81dd | ||
|
|
4f6c15099a | ||
|
|
20fb8270dc | ||
|
|
fd8b5bd045 | ||
|
|
41c937b983 | ||
|
|
30897c3115 | ||
|
|
c67ac8a11b | ||
|
|
38b041d909 | ||
|
|
35a62e9a05 | ||
|
|
202039e853 | ||
|
|
6eac308ca3 | ||
|
|
48d078a856 | ||
|
|
5c01e8e99e | ||
|
|
f3f9fe9daa | ||
|
|
b6f8d53ff1 | ||
|
|
514dd4e852 | ||
|
|
cedcac225f | ||
|
|
9a3298347c | ||
|
|
7249804e3f | ||
|
|
b7cb1e98ee | ||
|
|
195b1169ae | ||
|
|
0a31ecb283 | ||
|
|
84b980227f | ||
|
|
a96fe8770c | ||
|
|
d2e731174c | ||
|
|
a90311cb44 | ||
|
|
1e10fca66a | ||
|
|
553086ae3d | ||
|
|
db5274113a | ||
|
|
25342b706d | ||
|
|
95ad926c95 | ||
|
|
5506d7adce | ||
|
|
0d58e82b76 | ||
|
|
2f8b0801cc | ||
|
|
126cd0bac2 | ||
|
|
c5adfca0ed | ||
|
|
006dac13a9 | ||
|
|
bb63058410 | ||
|
|
be81b46d8e | ||
|
|
333177da00 | ||
|
|
aabb34f853 | ||
|
|
089006927e | ||
|
|
ded3d20630 | ||
|
|
33ddb9c0ca | ||
|
|
3f883d57a6 | ||
|
|
62ae8dc81e | ||
|
|
fc53bc8909 | ||
|
|
2021d12df8 | ||
|
|
dd4bdd776a | ||
|
|
4282fa4787 | ||
|
|
2c4c26c5d6 | ||
|
|
228a501014 | ||
|
|
4051d0da86 | ||
|
|
f64d6695ce | ||
|
|
0e8e5c9ba5 | ||
|
|
d8335eee7b | ||
|
|
f02d18f465 | ||
|
|
a4e3921090 | ||
|
|
72ae7f5497 | ||
|
|
bd0e0c3fcf | ||
|
|
1414cc315e | ||
|
|
1fda797c8f | ||
|
|
008187982d | ||
|
|
b7cffbde37 | ||
|
|
0326731348 | ||
|
|
76fda9562c | ||
|
|
bd7c7ebaf3 | ||
|
|
614eed7f86 | ||
|
|
a75d73b889 | ||
|
|
dca09148ca | ||
|
|
2089df36e3 | ||
|
|
a419c7c93b | ||
|
|
adf2b7cce7 | ||
|
|
619c6a03ce | ||
|
|
d4a08f7ab7 | ||
|
|
1ec5632a18 | ||
|
|
a18aba1bb6 | ||
|
|
b803f06c8f | ||
|
|
689da2f36b | ||
|
|
6f538c509c | ||
|
|
fe78977973 | ||
|
|
64a0161935 | ||
|
|
67dd32d9fb | ||
|
|
e49a673a01 | ||
|
|
a190dad0b1 | ||
|
|
c72698a997 | ||
|
|
1fcef3321e | ||
|
|
36d7d60c3b | ||
|
|
067ed7b1c6 | ||
|
|
b6e624b6bf | ||
|
|
237c1e24e6 | ||
|
|
ef722066e3 | ||
|
|
bb7f18ced7 | ||
|
|
bc37ceb58b | ||
|
|
9983fcbac3 | ||
|
|
a948cb49b8 | ||
|
|
546c817f64 | ||
|
|
caf6ba65e8 | ||
|
|
f7108b40c3 | ||
|
|
4d51a0290b | ||
|
|
0d246f7e9e | ||
|
|
8875462241 | ||
|
|
26e1ac6afd | ||
|
|
4a0a4094da | ||
|
|
87d2ff8665 | ||
|
|
85f227372d | ||
|
|
4a50493ab7 | ||
|
|
c6760e0375 | ||
|
|
3770019e0b | ||
|
|
5c2157219d | ||
|
|
f2fa47dedb | ||
|
|
275724fb46 | ||
|
|
6fcc091f88 | ||
|
|
3719a6f2f2 | ||
|
|
fab3f5e146 | ||
|
|
90c89b3881 | ||
|
|
6139a61ff0 | ||
|
|
490b501679 | ||
|
|
da7146c4d5 | ||
|
|
7ed22819b5 | ||
|
|
e808865e6f | ||
|
|
1d247fbeaa | ||
|
|
0a07207f59 | ||
|
|
982840ac3c | ||
|
|
0a9ff77303 | ||
|
|
528fe40839 | ||
|
|
340ae15ba7 | ||
|
|
c603fe7ab9 | ||
|
|
aa369b4212 | ||
|
|
cf2580d284 | ||
|
|
6f025f78ec | ||
|
|
f98dfc4758 | ||
|
|
fc7719d5fa | ||
|
|
b0513a7517 | ||
|
|
78c3016f95 | ||
|
|
5669aaf4a3 | ||
|
|
f634ba343c | ||
|
|
e2a67c2f1c | ||
|
|
c399c77dbe | ||
|
|
09d51f9df5 | ||
|
|
44f9952063 | ||
|
|
b041c22814 | ||
|
|
a765f2e3b6 | ||
|
|
cc83ac6ce8 | ||
|
|
43fc8bafa7 | ||
|
|
29366bb9c7 | ||
|
|
c89258a0ea | ||
|
|
3405659eba | ||
|
|
605c32dbb3 | ||
|
|
f674445486 | ||
|
|
b038e6b083 | ||
|
|
ad2ae4c4b4 | ||
|
|
eaeeee7740 | ||
|
|
6e265686ec | ||
|
|
c933ac1a4a | ||
|
|
494ab1fc2b | ||
|
|
6372e60086 | ||
|
|
036547e956 | ||
|
|
7f1c1d2b4a | ||
|
|
0ae8aa36d7 | ||
|
|
27bae51fa0 | ||
|
|
b6bf47a148 | ||
|
|
acb7dfb320 | ||
|
|
1c8fa12b64 | ||
|
|
5a7efa2895 | ||
|
|
e755fe7842 | ||
|
|
1bc6140394 | ||
|
|
7f1becf283 | ||
|
|
95af021ed9 | ||
|
|
610e18949b | ||
|
|
5f5c0ffc32 | ||
|
|
7853fb8529 | ||
|
|
b314476599 | ||
|
|
0cb89435b5 | ||
|
|
3ec267e8a6 | ||
|
|
0a42401a43 | ||
|
|
3c1fd9a3a9 | ||
|
|
34b855e253 | ||
|
|
14bc65bae7 | ||
|
|
d8fcdc0c54 | ||
|
|
dd975fe53d | ||
|
|
97762d21a4 | ||
|
|
97d9ba62bb | ||
|
|
094236d4e0 | ||
|
|
7ec59878a1 | ||
|
|
354d7050dc | ||
|
|
ea44ee1d55 | ||
|
|
e0fd377828 | ||
|
|
36e7e3ccde | ||
|
|
d9c1782a4f | ||
|
|
13c1efb240 | ||
|
|
15c2c4dd23 | ||
|
|
3c613b9c02 | ||
|
|
c715d91dc7 | ||
|
|
a19106b03d | ||
|
|
b070676797 | ||
|
|
b3d0ad7a87 | ||
|
|
08e38858ed | ||
|
|
157588f6dc | ||
|
|
d5bc48623a | ||
|
|
c7b4c9bf0f | ||
|
|
0e010994a7 | ||
|
|
d4814dec42 | ||
|
|
7055ccbf9b | ||
|
|
1fbe1ffc5a | ||
|
|
a53fe14fa2 | ||
|
|
8d13601e39 | ||
|
|
2dd73d4def | ||
|
|
7535e9664e | ||
|
|
c28b457221 | ||
|
|
eea8572238 | ||
|
|
69d4fdda1b | ||
|
|
bc7d06fe59 | ||
|
|
7bc18d7888 | ||
|
|
d7881a1ec2 | ||
|
|
9b574ad53b | ||
|
|
5fc1184a49 | ||
|
|
0083cb8ca6 | ||
|
|
6f39a426a7 | ||
|
|
23cc3ea4bc | ||
|
|
1c8ae50557 | ||
|
|
6bae2eac29 | ||
|
|
5fd844d73e | ||
|
|
4bc8f7be16 | ||
|
|
4882ff1ef5 | ||
|
|
8d2826c633 | ||
|
|
eba933bb47 | ||
|
|
b4e9dafd10 | ||
|
|
0c3581a1f8 | ||
|
|
fbbb161987 | ||
|
|
2a5652c807 | ||
|
|
0a151f2474 | ||
|
|
0aed9595c3 | ||
|
|
d249a22f74 | ||
|
|
3f112db725 | ||
|
|
78fc129614 | ||
|
|
641774630b | ||
|
|
b5394fc5a0 | ||
|
|
9f2994f462 | ||
|
|
6aa58d9939 | ||
|
|
91638aadcf | ||
|
|
981fcb2c21 | ||
|
|
90a41fba8b | ||
|
|
dc2be816a8 | ||
|
|
5e700db6d3 | ||
|
|
c57f2c39f6 | ||
|
|
73e2aa54ef | ||
|
|
997f22fbb9 | ||
|
|
bf92a40171 | ||
|
|
b7b107b08a | ||
|
|
3b7b457d35 | ||
|
|
b3414e3c1a | ||
|
|
937ba6385e | ||
|
|
9613f1d8cb | ||
|
|
879ab6e52b | ||
|
|
9797177193 | ||
|
|
011776f02f | ||
|
|
0204a8b69a | ||
|
|
32988b3cdf | ||
|
|
8898c91dfc | ||
|
|
8e3e2e770a | ||
|
|
bb5bdcf0f4 | ||
|
|
02d34bbba6 | ||
|
|
521276f1ed | ||
|
|
b4b9709090 |
@@ -1,4 +1,4 @@
|
||||
# for php-coveralls
|
||||
service_name: travis-ci
|
||||
src_dir: lib
|
||||
coverage_clover: build/logs/clover.xml
|
||||
coverage_clover: build/logs/clover*.xml
|
||||
|
||||
33
.travis.yml
33
.travis.yml
@@ -1,9 +1,12 @@
|
||||
language: php
|
||||
|
||||
php:
|
||||
- 5.3
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7.0
|
||||
- hhvm
|
||||
- hhvm-nightly
|
||||
|
||||
env:
|
||||
- DB=mysql
|
||||
@@ -11,14 +14,28 @@ env:
|
||||
- DB=sqlite
|
||||
|
||||
before_script:
|
||||
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'DROP DATABASE IF EXISTS doctrine_tests;' -U postgres; fi"
|
||||
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'DROP DATABASE IF EXISTS doctrine_tests_tmp;' -U postgres; fi"
|
||||
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'create database doctrine_tests;' -U postgres; fi"
|
||||
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'create database doctrine_tests_tmp;' -U postgres; fi"
|
||||
- sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS doctrine_tests_tmp;create database IF NOT EXISTS doctrine_tests;'; fi"
|
||||
- composer install --prefer-dist --dev
|
||||
- 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
|
||||
- composer self-update
|
||||
- composer install --prefer-source --dev
|
||||
|
||||
script: phpunit --configuration tests/travis/$DB.travis.xml
|
||||
script:
|
||||
- ENABLE_SECOND_LEVEL_CACHE=0 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml $PHPUNIT_FLAGS
|
||||
- ENABLE_SECOND_LEVEL_CACHE=1 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml --exclude-group performance,non-cacheable,locking_functional
|
||||
|
||||
after_script:
|
||||
- php vendor/bin/coveralls -v
|
||||
|
||||
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
|
||||
allow_failures:
|
||||
- php: 7.0
|
||||
- php: hhvm-nightly # hhvm-nightly currently chokes on composer installation
|
||||
|
||||
@@ -8,7 +8,7 @@ unified and future proof.
|
||||
|
||||
## We only accept PRs to "master"
|
||||
|
||||
Our branching strategy is summed up with "everything to master first", even
|
||||
Our branching strategy is "everything to master first", even
|
||||
bugfixes and we then merge them into the stable branches. You should only
|
||||
open pull requests against the master branch. Otherwise we cannot accept the PR.
|
||||
|
||||
@@ -33,7 +33,7 @@ with some exceptions/differences:
|
||||
|
||||
## Unit-Tests
|
||||
|
||||
Always add a test for your pull-request.
|
||||
Please try to add a test for your pull-request.
|
||||
|
||||
* If you want to fix a bug or provide a reproduce case, create a test file in
|
||||
``tests/Doctrine/Tests/ORM/Functional/Ticket`` with the name of the ticket,
|
||||
@@ -50,6 +50,12 @@ take a look at the ``tests/travis`` folder for some examples. Then run:
|
||||
|
||||
phpunit -c mysql.phpunit.xml
|
||||
|
||||
Tips for creating unittests:
|
||||
|
||||
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
|
||||
example.
|
||||
|
||||
## Travis
|
||||
|
||||
We automatically run your pull request through [Travis CI](http://www.travis-ci.org)
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
# Doctrine 2 ORM
|
||||
| [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] |
|
||||
|
||||
Master: [](http://travis-ci.org/doctrine/doctrine2)
|
||||
2.3: [](http://travis-ci.org/doctrine/doctrine2)
|
||||
2.2: [](http://travis-ci.org/doctrine/doctrine2)
|
||||
2.1: [](http://travis-ci.org/doctrine/doctrine2)
|
||||
|
||||
Master: [](https://coveralls.io/r/doctrine/doctrine2?branch=master)
|
||||
|
||||
[](https://packagist.org/packages/doctrine/orm) [](https://packagist.org/packages/doctrine/orm)
|
||||
|
||||
|
||||
Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.2+ that provides transparent persistence
|
||||
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 Hibernates HQL. This provides developers with a powerful alternative to SQL that maintains flexibility
|
||||
inspired by Hibernate's HQL. This provides developers with a powerful alternative to SQL that maintains flexibility
|
||||
without requiring unnecessary code duplication.
|
||||
|
||||
## More resources:
|
||||
@@ -23,3 +16,15 @@ without requiring unnecessary code duplication.
|
||||
* [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
|
||||
|
||||
18
SECURITY.md
Normal file
18
SECURITY.md
Normal file
@@ -0,0 +1,18 @@
|
||||
Security
|
||||
========
|
||||
|
||||
The Doctrine library is operating very close to your database and as such needs
|
||||
to handle and make assumptions about SQL injection vulnerabilities.
|
||||
|
||||
It is vital that you understand how Doctrine approaches security, because
|
||||
we cannot protect you from SQL injection.
|
||||
|
||||
Please read the documentation chapter on Security in Doctrine DBAL and ORM to
|
||||
understand the assumptions we make.
|
||||
|
||||
- [DBAL Security Page](https://github.com/doctrine/dbal/blob/master/docs/en/reference/security.rst)
|
||||
- [ORM Security Page](https://github.com/doctrine/doctrine2/blob/master/docs/en/reference/security.rst)
|
||||
|
||||
If you find a Security bug in Doctrine, please report it on Jira and change the
|
||||
Security Level to "Security Issues". It will be visible to Doctrine Core
|
||||
developers and you only.
|
||||
144
UPGRADE.md
144
UPGRADE.md
@@ -1,3 +1,147 @@
|
||||
# Upgrade to 2.5
|
||||
|
||||
## 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
|
||||
discriminator map.
|
||||
|
||||
When declaring an inheritance map, it was previously possible to skip the root
|
||||
of the inheritance in the discriminator map. This was actually a validation
|
||||
mistake by Doctrine2 and led to problems when trying to persist instances of
|
||||
that class.
|
||||
|
||||
If you don't plan to persist instances some classes in your inheritance, then
|
||||
either:
|
||||
|
||||
- make those classes `abstract`
|
||||
- map those classes as `MappedSuperclass`
|
||||
|
||||
## Minor BC BREAK: ``EntityManagerInterface`` instead of ``EntityManager`` in type-hints
|
||||
|
||||
As of 2.5, classes requiring the ``EntityManager`` in any method signature will now require
|
||||
an ``EntityManagerInterface`` instead.
|
||||
If you are extending any of the following classes, then you need to check following
|
||||
signatures:
|
||||
|
||||
- ``Doctrine\ORM\Tools\DebugUnitOfWorkListener#dumpIdentityMap(EntityManagerInterface $em)``
|
||||
- ``Doctrine\ORM\Mapping\ClassMetadataFactory#setEntityManager(EntityManagerInterface $em)``
|
||||
|
||||
## Minor BC BREAK: Custom Hydrators API change
|
||||
|
||||
As of 2.5, `AbstractHydrator` does not enforce the usage of cache as part of
|
||||
API, and now provides you a clean API for column information through the method
|
||||
`hydrateColumnInfo($column)`.
|
||||
Cache variable being passed around by reference is no longer needed since
|
||||
Hydrators are per query instantiated since Doctrine 2.4.
|
||||
|
||||
## Minor BC BREAK: Entity based ``EntityManager#clear()`` calls follow cascade detach
|
||||
|
||||
Whenever ``EntityManager#clear()`` method gets called with a given entity class
|
||||
name, until 2.4, it was only detaching the specific requested entity.
|
||||
As of 2.5, ``EntityManager`` will follow configured cascades, providing a better
|
||||
memory management since associations will be garbage collected, optimizing
|
||||
resources consumption on long running jobs.
|
||||
|
||||
## BC BREAK: NamingStrategy interface changes
|
||||
|
||||
1. A new method ``embeddedFieldToColumnName($propertyName, $embeddedColumnName)``
|
||||
|
||||
This method generates the column name for fields of embedded objects. If you implement your custom NamingStrategy, you
|
||||
now also need to implement this new method.
|
||||
|
||||
2. A change to method ``joinColumnName()`` to include the $className
|
||||
|
||||
## Updates on entities scheduled for deletion are no longer processed
|
||||
|
||||
In Doctrine 2.4, if you modified properties of an entity scheduled for deletion, UnitOfWork would
|
||||
produce an UPDATE statement to be executed right before the DELETE statement. The entity in question
|
||||
was therefore present in ``UnitOfWork#entityUpdates``, which means that ``preUpdate`` and ``postUpdate``
|
||||
listeners were (quite pointlessly) called. In ``preFlush`` listeners, it used to be possible to undo
|
||||
the scheduled deletion for updated entities (by calling ``persist()`` if the entity was found in both
|
||||
``entityUpdates`` and ``entityDeletions``). This does not work any longer, because the entire changeset
|
||||
calculation logic is optimized away.
|
||||
|
||||
## Minor BC BREAK: Default lock mode changed from LockMode::NONE to null in method signatures
|
||||
|
||||
A misconception concerning default lock mode values in method signatures lead to unexpected behaviour
|
||||
in SQL statements on SQL Server. With a default lock mode of ``LockMode::NONE`` throughout the
|
||||
method signatures in ORM, the table lock hint ``WITH (NOLOCK)`` was appended to all locking related
|
||||
queries by default. This could result in unpredictable results because an explicit ``WITH (NOLOCK)``
|
||||
table hint tells SQL Server to run a specific query in transaction isolation level READ UNCOMMITTED
|
||||
instead of the default READ COMMITTED transaction isolation level.
|
||||
Therefore there now is a distinction between ``LockMode::NONE`` and ``null`` to be able to tell
|
||||
Doctrine whether to add table lock hints to queries by intention or not. To achieve this, the following
|
||||
method signatures have been changed to declare ``$lockMode = null`` instead of ``$lockMode = LockMode::NONE``:
|
||||
|
||||
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#getSelectSQL()``
|
||||
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#load()``
|
||||
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#refresh()``
|
||||
- ``Doctrine\ORM\Decorator\EntityManagerDecorator#find()``
|
||||
- ``Doctrine\ORM\EntityManager#find()``
|
||||
- ``Doctrine\ORM\EntityRepository#find()``
|
||||
- ``Doctrine\ORM\Persisters\BasicEntityPersister#getSelectSQL()``
|
||||
- ``Doctrine\ORM\Persisters\BasicEntityPersister#load()``
|
||||
- ``Doctrine\ORM\Persisters\BasicEntityPersister#refresh()``
|
||||
- ``Doctrine\ORM\Persisters\EntityPersister#getSelectSQL()``
|
||||
- ``Doctrine\ORM\Persisters\EntityPersister#load()``
|
||||
- ``Doctrine\ORM\Persisters\EntityPersister#refresh()``
|
||||
- ``Doctrine\ORM\Persisters\JoinedSubclassPersister#getSelectSQL()``
|
||||
|
||||
You should update signatures for these methods if you have subclassed one of the above classes.
|
||||
Please also check the calling code of these methods in your application and update if necessary.
|
||||
|
||||
**Note:**
|
||||
This in fact is really a minor BC BREAK and should not have any affect on database vendors
|
||||
other than SQL Server because it is the only one that supports and therefore cares about
|
||||
``LockMode::NONE``. It's really just a FIX for SQL Server environments using ORM.
|
||||
|
||||
## Minor BC BREAK: `__clone` method not called anymore when entities are instantiated via metadata API
|
||||
|
||||
As of PHP 5.6, instantiation of new entities is deferred to the
|
||||
[`doctrine/instantiator`](https://github.com/doctrine/instantiator) library, which will avoid calling `__clone`
|
||||
or any public API on instantiated objects.
|
||||
|
||||
## BC BREAK: `Doctrine\ORM\Repository\DefaultRepositoryFactory` is now `final`
|
||||
|
||||
Please implement the `Doctrine\ORM\Repository\RepositoryFactory` interface instead of extending
|
||||
the `Doctrine\ORM\Repository\DefaultRepositoryFactory`.
|
||||
|
||||
## BC BREAK: New object expression DQL queries now respects user provided aliasing and not return consumed fields
|
||||
|
||||
When executing DQL queries with new object expressions, instead of returning DTOs numerically indexes, it will now respect user provided aliases. Consider the following query:
|
||||
|
||||
SELECT new UserDTO(u.id,u.name) as user,new AddressDTO(a.street,a.postalCode) as address, a.id as addressId FROM User u INNER JOIN u.addresses a WITH a.isPrimary = true
|
||||
|
||||
Previously, your result would be similar to this:
|
||||
|
||||
array(
|
||||
0=>array(
|
||||
0=>{UserDTO object},
|
||||
1=>{AddressDTO object},
|
||||
2=>{u.id scalar},
|
||||
3=>{u.name scalar},
|
||||
4=>{a.street scalar},
|
||||
5=>{a.postalCode scalar},
|
||||
'addressId'=>{a.id scalar},
|
||||
),
|
||||
...
|
||||
)
|
||||
|
||||
From now on, the resultset will look like this:
|
||||
|
||||
array(
|
||||
0=>array(
|
||||
'user'=>{UserDTO object},
|
||||
'address'=>{AddressDTO object},
|
||||
'addressId'=>{a.id scalar}
|
||||
),
|
||||
...
|
||||
)
|
||||
|
||||
## Minor BC BREAK: added second parameter $indexBy in EntityRepository#createQueryBuilder method signature
|
||||
|
||||
Added way to access the underlying QueryBuilder#from() method's 'indexBy' parameter when using EntityRepository#createQueryBuilder()
|
||||
|
||||
# Upgrade to 2.4
|
||||
|
||||
## BC BREAK: Compatibility Bugfix in PersistentCollection#matching()
|
||||
|
||||
@@ -20,7 +20,14 @@
|
||||
use Symfony\Component\Console\Helper\HelperSet;
|
||||
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||
|
||||
(@include_once __DIR__ . '/../vendor/autoload.php') || @include_once __DIR__ . '/../../../autoload.php';
|
||||
$autoloadFiles = array(__DIR__ . '/../vendor/autoload.php',
|
||||
__DIR__ . '/../../../autoload.php');
|
||||
|
||||
foreach ($autoloadFiles as $autoloadFile) {
|
||||
if (file_exists($autoloadFile)) {
|
||||
require_once $autoloadFile;
|
||||
}
|
||||
}
|
||||
|
||||
$directories = array(getcwd(), getcwd() . DIRECTORY_SEPARATOR . 'config');
|
||||
|
||||
|
||||
@@ -11,15 +11,20 @@
|
||||
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
|
||||
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"}
|
||||
],
|
||||
"minimum-stability": "dev",
|
||||
"require": {
|
||||
"php": ">=5.3.2",
|
||||
"php": ">=5.4",
|
||||
"ext-pdo": "*",
|
||||
"doctrine/collections": "~1.1",
|
||||
"doctrine/dbal": "~2.4",
|
||||
"symfony/console": "~2.0"
|
||||
"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"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/yaml": "~2.1",
|
||||
"symfony/yaml": "~2.3|~3.0",
|
||||
"phpunit/phpunit": "~4.0",
|
||||
"satooshi/php-coveralls": "dev-master"
|
||||
},
|
||||
"suggest": {
|
||||
@@ -28,10 +33,13 @@
|
||||
"autoload": {
|
||||
"psr-0": { "Doctrine\\ORM\\": "lib/" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-0": { "Doctrine\\Tests\\": "tests/" }
|
||||
},
|
||||
"bin": ["bin/doctrine", "bin/doctrine.php"],
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.4.x-dev"
|
||||
"dev-master": "2.6.x-dev"
|
||||
}
|
||||
},
|
||||
"archive": {
|
||||
|
||||
363
docs/LICENSE.md
Normal file
363
docs/LICENSE.md
Normal file
@@ -0,0 +1,363 @@
|
||||
The Doctrine2 documentation is licensed under [CC BY-NC-SA 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US)
|
||||
|
||||
Creative Commons Legal Code
|
||||
|
||||
Attribution-NonCommercial-ShareAlike 3.0 Unported
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR
|
||||
DAMAGES RESULTING FROM ITS USE.
|
||||
|
||||
License
|
||||
|
||||
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
|
||||
COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
|
||||
COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
|
||||
AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
|
||||
|
||||
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE
|
||||
TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY
|
||||
BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS
|
||||
CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
|
||||
CONDITIONS.
|
||||
|
||||
1. Definitions
|
||||
|
||||
a. "Adaptation" means a work based upon the Work, or upon the Work and
|
||||
other pre-existing works, such as a translation, adaptation,
|
||||
derivative work, arrangement of music or other alterations of a
|
||||
literary or artistic work, or phonogram or performance and includes
|
||||
cinematographic adaptations or any other form in which the Work may be
|
||||
recast, transformed, or adapted including in any form recognizably
|
||||
derived from the original, except that a work that constitutes a
|
||||
Collection will not be considered an Adaptation for the purpose of
|
||||
this License. For the avoidance of doubt, where the Work is a musical
|
||||
work, performance or phonogram, the synchronization of the Work in
|
||||
timed-relation with a moving image ("synching") will be considered an
|
||||
Adaptation for the purpose of this License.
|
||||
b. "Collection" means a collection of literary or artistic works, such as
|
||||
encyclopedias and anthologies, or performances, phonograms or
|
||||
broadcasts, or other works or subject matter other than works listed
|
||||
in Section 1(g) below, which, by reason of the selection and
|
||||
arrangement of their contents, constitute intellectual creations, in
|
||||
which the Work is included in its entirety in unmodified form along
|
||||
with one or more other contributions, each constituting separate and
|
||||
independent works in themselves, which together are assembled into a
|
||||
collective whole. A work that constitutes a Collection will not be
|
||||
considered an Adaptation (as defined above) for the purposes of this
|
||||
License.
|
||||
c. "Distribute" means to make available to the public the original and
|
||||
copies of the Work or Adaptation, as appropriate, through sale or
|
||||
other transfer of ownership.
|
||||
d. "License Elements" means the following high-level license attributes
|
||||
as selected by Licensor and indicated in the title of this License:
|
||||
Attribution, Noncommercial, ShareAlike.
|
||||
e. "Licensor" means the individual, individuals, entity or entities that
|
||||
offer(s) the Work under the terms of this License.
|
||||
f. "Original Author" means, in the case of a literary or artistic work,
|
||||
the individual, individuals, entity or entities who created the Work
|
||||
or if no individual or entity can be identified, the publisher; and in
|
||||
addition (i) in the case of a performance the actors, singers,
|
||||
musicians, dancers, and other persons who act, sing, deliver, declaim,
|
||||
play in, interpret or otherwise perform literary or artistic works or
|
||||
expressions of folklore; (ii) in the case of a phonogram the producer
|
||||
being the person or legal entity who first fixes the sounds of a
|
||||
performance or other sounds; and, (iii) in the case of broadcasts, the
|
||||
organization that transmits the broadcast.
|
||||
g. "Work" means the literary and/or artistic work offered under the terms
|
||||
of this License including without limitation any production in the
|
||||
literary, scientific and artistic domain, whatever may be the mode or
|
||||
form of its expression including digital form, such as a book,
|
||||
pamphlet and other writing; a lecture, address, sermon or other work
|
||||
of the same nature; a dramatic or dramatico-musical work; a
|
||||
choreographic work or entertainment in dumb show; a musical
|
||||
composition with or without words; a cinematographic work to which are
|
||||
assimilated works expressed by a process analogous to cinematography;
|
||||
a work of drawing, painting, architecture, sculpture, engraving or
|
||||
lithography; a photographic work to which are assimilated works
|
||||
expressed by a process analogous to photography; a work of applied
|
||||
art; an illustration, map, plan, sketch or three-dimensional work
|
||||
relative to geography, topography, architecture or science; a
|
||||
performance; a broadcast; a phonogram; a compilation of data to the
|
||||
extent it is protected as a copyrightable work; or a work performed by
|
||||
a variety or circus performer to the extent it is not otherwise
|
||||
considered a literary or artistic work.
|
||||
h. "You" means an individual or entity exercising rights under this
|
||||
License who has not previously violated the terms of this License with
|
||||
respect to the Work, or who has received express permission from the
|
||||
Licensor to exercise rights under this License despite a previous
|
||||
violation.
|
||||
i. "Publicly Perform" means to perform public recitations of the Work and
|
||||
to communicate to the public those public recitations, by any means or
|
||||
process, including by wire or wireless means or public digital
|
||||
performances; to make available to the public Works in such a way that
|
||||
members of the public may access these Works from a place and at a
|
||||
place individually chosen by them; to perform the Work to the public
|
||||
by any means or process and the communication to the public of the
|
||||
performances of the Work, including by public digital performance; to
|
||||
broadcast and rebroadcast the Work by any means including signs,
|
||||
sounds or images.
|
||||
j. "Reproduce" means to make copies of the Work by any means including
|
||||
without limitation by sound or visual recordings and the right of
|
||||
fixation and reproducing fixations of the Work, including storage of a
|
||||
protected performance or phonogram in digital form or other electronic
|
||||
medium.
|
||||
|
||||
2. Fair Dealing Rights. Nothing in this License is intended to reduce,
|
||||
limit, or restrict any uses free from copyright or rights arising from
|
||||
limitations or exceptions that are provided for in connection with the
|
||||
copyright protection under copyright law or other applicable laws.
|
||||
|
||||
3. License Grant. Subject to the terms and conditions of this License,
|
||||
Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
|
||||
perpetual (for the duration of the applicable copyright) license to
|
||||
exercise the rights in the Work as stated below:
|
||||
|
||||
a. to Reproduce the Work, to incorporate the Work into one or more
|
||||
Collections, and to Reproduce the Work as incorporated in the
|
||||
Collections;
|
||||
b. to create and Reproduce Adaptations provided that any such Adaptation,
|
||||
including any translation in any medium, takes reasonable steps to
|
||||
clearly label, demarcate or otherwise identify that changes were made
|
||||
to the original Work. For example, a translation could be marked "The
|
||||
original work was translated from English to Spanish," or a
|
||||
modification could indicate "The original work has been modified.";
|
||||
c. to Distribute and Publicly Perform the Work including as incorporated
|
||||
in Collections; and,
|
||||
d. to Distribute and Publicly Perform Adaptations.
|
||||
|
||||
The above rights may be exercised in all media and formats whether now
|
||||
known or hereafter devised. The above rights include the right to make
|
||||
such modifications as are technically necessary to exercise the rights in
|
||||
other media and formats. Subject to Section 8(f), all rights not expressly
|
||||
granted by Licensor are hereby reserved, including but not limited to the
|
||||
rights described in Section 4(e).
|
||||
|
||||
4. Restrictions. The license granted in Section 3 above is expressly made
|
||||
subject to and limited by the following restrictions:
|
||||
|
||||
a. You may Distribute or Publicly Perform the Work only under the terms
|
||||
of this License. You must include a copy of, or the Uniform Resource
|
||||
Identifier (URI) for, this License with every copy of the Work You
|
||||
Distribute or Publicly Perform. You may not offer or impose any terms
|
||||
on the Work that restrict the terms of this License or the ability of
|
||||
the recipient of the Work to exercise the rights granted to that
|
||||
recipient under the terms of the License. You may not sublicense the
|
||||
Work. You must keep intact all notices that refer to this License and
|
||||
to the disclaimer of warranties with every copy of the Work You
|
||||
Distribute or Publicly Perform. When You Distribute or Publicly
|
||||
Perform the Work, You may not impose any effective technological
|
||||
measures on the Work that restrict the ability of a recipient of the
|
||||
Work from You to exercise the rights granted to that recipient under
|
||||
the terms of the License. This Section 4(a) applies to the Work as
|
||||
incorporated in a Collection, but this does not require the Collection
|
||||
apart from the Work itself to be made subject to the terms of this
|
||||
License. If You create a Collection, upon notice from any Licensor You
|
||||
must, to the extent practicable, remove from the Collection any credit
|
||||
as required by Section 4(d), as requested. If You create an
|
||||
Adaptation, upon notice from any Licensor You must, to the extent
|
||||
practicable, remove from the Adaptation any credit as required by
|
||||
Section 4(d), as requested.
|
||||
b. You may Distribute or Publicly Perform an Adaptation only under: (i)
|
||||
the terms of this License; (ii) a later version of this License with
|
||||
the same License Elements as this License; (iii) a Creative Commons
|
||||
jurisdiction license (either this or a later license version) that
|
||||
contains the same License Elements as this License (e.g.,
|
||||
Attribution-NonCommercial-ShareAlike 3.0 US) ("Applicable License").
|
||||
You must include a copy of, or the URI, for Applicable License with
|
||||
every copy of each Adaptation You Distribute or Publicly Perform. You
|
||||
may not offer or impose any terms on the Adaptation that restrict the
|
||||
terms of the Applicable License or the ability of the recipient of the
|
||||
Adaptation to exercise the rights granted to that recipient under the
|
||||
terms of the Applicable License. You must keep intact all notices that
|
||||
refer to the Applicable License and to the disclaimer of warranties
|
||||
with every copy of the Work as included in the Adaptation You
|
||||
Distribute or Publicly Perform. When You Distribute or Publicly
|
||||
Perform the Adaptation, You may not impose any effective technological
|
||||
measures on the Adaptation that restrict the ability of a recipient of
|
||||
the Adaptation from You to exercise the rights granted to that
|
||||
recipient under the terms of the Applicable License. This Section 4(b)
|
||||
applies to the Adaptation as incorporated in a Collection, but this
|
||||
does not require the Collection apart from the Adaptation itself to be
|
||||
made subject to the terms of the Applicable License.
|
||||
c. You may not exercise any of the rights granted to You in Section 3
|
||||
above in any manner that is primarily intended for or directed toward
|
||||
commercial advantage or private monetary compensation. The exchange of
|
||||
the Work for other copyrighted works by means of digital file-sharing
|
||||
or otherwise shall not be considered to be intended for or directed
|
||||
toward commercial advantage or private monetary compensation, provided
|
||||
there is no payment of any monetary compensation in con-nection with
|
||||
the exchange of copyrighted works.
|
||||
d. If You Distribute, or Publicly Perform the Work or any Adaptations or
|
||||
Collections, You must, unless a request has been made pursuant to
|
||||
Section 4(a), keep intact all copyright notices for the Work and
|
||||
provide, reasonable to the medium or means You are utilizing: (i) the
|
||||
name of the Original Author (or pseudonym, if applicable) if supplied,
|
||||
and/or if the Original Author and/or Licensor designate another party
|
||||
or parties (e.g., a sponsor institute, publishing entity, journal) for
|
||||
attribution ("Attribution Parties") in Licensor's copyright notice,
|
||||
terms of service or by other reasonable means, the name of such party
|
||||
or parties; (ii) the title of the Work if supplied; (iii) to the
|
||||
extent reasonably practicable, the URI, if any, that Licensor
|
||||
specifies to be associated with the Work, unless such URI does not
|
||||
refer to the copyright notice or licensing information for the Work;
|
||||
and, (iv) consistent with Section 3(b), in the case of an Adaptation,
|
||||
a credit identifying the use of the Work in the Adaptation (e.g.,
|
||||
"French translation of the Work by Original Author," or "Screenplay
|
||||
based on original Work by Original Author"). The credit required by
|
||||
this Section 4(d) may be implemented in any reasonable manner;
|
||||
provided, however, that in the case of a Adaptation or Collection, at
|
||||
a minimum such credit will appear, if a credit for all contributing
|
||||
authors of the Adaptation or Collection appears, then as part of these
|
||||
credits and in a manner at least as prominent as the credits for the
|
||||
other contributing authors. For the avoidance of doubt, You may only
|
||||
use the credit required by this Section for the purpose of attribution
|
||||
in the manner set out above and, by exercising Your rights under this
|
||||
License, You may not implicitly or explicitly assert or imply any
|
||||
connection with, sponsorship or endorsement by the Original Author,
|
||||
Licensor and/or Attribution Parties, as appropriate, of You or Your
|
||||
use of the Work, without the separate, express prior written
|
||||
permission of the Original Author, Licensor and/or Attribution
|
||||
Parties.
|
||||
e. For the avoidance of doubt:
|
||||
|
||||
i. Non-waivable Compulsory License Schemes. In those jurisdictions in
|
||||
which the right to collect royalties through any statutory or
|
||||
compulsory licensing scheme cannot be waived, the Licensor
|
||||
reserves the exclusive right to collect such royalties for any
|
||||
exercise by You of the rights granted under this License;
|
||||
ii. Waivable Compulsory License Schemes. In those jurisdictions in
|
||||
which the right to collect royalties through any statutory or
|
||||
compulsory licensing scheme can be waived, the Licensor reserves
|
||||
the exclusive right to collect such royalties for any exercise by
|
||||
You of the rights granted under this License if Your exercise of
|
||||
such rights is for a purpose or use which is otherwise than
|
||||
noncommercial as permitted under Section 4(c) and otherwise waives
|
||||
the right to collect royalties through any statutory or compulsory
|
||||
licensing scheme; and,
|
||||
iii. Voluntary License Schemes. The Licensor reserves the right to
|
||||
collect royalties, whether individually or, in the event that the
|
||||
Licensor is a member of a collecting society that administers
|
||||
voluntary licensing schemes, via that society, from any exercise
|
||||
by You of the rights granted under this License that is for a
|
||||
purpose or use which is otherwise than noncommercial as permitted
|
||||
under Section 4(c).
|
||||
f. Except as otherwise agreed in writing by the Licensor or as may be
|
||||
otherwise permitted by applicable law, if You Reproduce, Distribute or
|
||||
Publicly Perform the Work either by itself or as part of any
|
||||
Adaptations or Collections, You must not distort, mutilate, modify or
|
||||
take other derogatory action in relation to the Work which would be
|
||||
prejudicial to the Original Author's honor or reputation. Licensor
|
||||
agrees that in those jurisdictions (e.g. Japan), in which any exercise
|
||||
of the right granted in Section 3(b) of this License (the right to
|
||||
make Adaptations) would be deemed to be a distortion, mutilation,
|
||||
modification or other derogatory action prejudicial to the Original
|
||||
Author's honor and reputation, the Licensor will waive or not assert,
|
||||
as appropriate, this Section, to the fullest extent permitted by the
|
||||
applicable national law, to enable You to reasonably exercise Your
|
||||
right under Section 3(b) of this License (right to make Adaptations)
|
||||
but not otherwise.
|
||||
|
||||
5. Representations, Warranties and Disclaimer
|
||||
|
||||
UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING AND TO THE
|
||||
FULLEST EXTENT PERMITTED BY APPLICABLE LAW, LICENSOR OFFERS THE WORK AS-IS
|
||||
AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE
|
||||
WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT
|
||||
LIMITATION, WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||
ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT
|
||||
DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED
|
||||
WARRANTIES, SO THIS EXCLUSION MAY NOT APPLY TO YOU.
|
||||
|
||||
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE
|
||||
LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR
|
||||
ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES
|
||||
ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS
|
||||
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
7. Termination
|
||||
|
||||
a. This License and the rights granted hereunder will terminate
|
||||
automatically upon any breach by You of the terms of this License.
|
||||
Individuals or entities who have received Adaptations or Collections
|
||||
from You under this License, however, will not have their licenses
|
||||
terminated provided such individuals or entities remain in full
|
||||
compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will
|
||||
survive any termination of this License.
|
||||
b. Subject to the above terms and conditions, the license granted here is
|
||||
perpetual (for the duration of the applicable copyright in the Work).
|
||||
Notwithstanding the above, Licensor reserves the right to release the
|
||||
Work under different license terms or to stop distributing the Work at
|
||||
any time; provided, however that any such election will not serve to
|
||||
withdraw this License (or any other license that has been, or is
|
||||
required to be, granted under the terms of this License), and this
|
||||
License will continue in full force and effect unless terminated as
|
||||
stated above.
|
||||
|
||||
8. Miscellaneous
|
||||
|
||||
a. Each time You Distribute or Publicly Perform the Work or a Collection,
|
||||
the Licensor offers to the recipient a license to the Work on the same
|
||||
terms and conditions as the license granted to You under this License.
|
||||
b. Each time You Distribute or Publicly Perform an Adaptation, Licensor
|
||||
offers to the recipient a license to the original Work on the same
|
||||
terms and conditions as the license granted to You under this License.
|
||||
c. If any provision of this License is invalid or unenforceable under
|
||||
applicable law, it shall not affect the validity or enforceability of
|
||||
the remainder of the terms of this License, and without further action
|
||||
by the parties to this agreement, such provision shall be reformed to
|
||||
the minimum extent necessary to make such provision valid and
|
||||
enforceable.
|
||||
d. No term or provision of this License shall be deemed waived and no
|
||||
breach consented to unless such waiver or consent shall be in writing
|
||||
and signed by the party to be charged with such waiver or consent.
|
||||
e. This License constitutes the entire agreement between the parties with
|
||||
respect to the Work licensed here. There are no understandings,
|
||||
agreements or representations with respect to the Work not specified
|
||||
here. Licensor shall not be bound by any additional provisions that
|
||||
may appear in any communication from You. This License may not be
|
||||
modified without the mutual written agreement of the Licensor and You.
|
||||
f. The rights granted under, and the subject matter referenced, in this
|
||||
License were drafted utilizing the terminology of the Berne Convention
|
||||
for the Protection of Literary and Artistic Works (as amended on
|
||||
September 28, 1979), the Rome Convention of 1961, the WIPO Copyright
|
||||
Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996
|
||||
and the Universal Copyright Convention (as revised on July 24, 1971).
|
||||
These rights and subject matter take effect in the relevant
|
||||
jurisdiction in which the License terms are sought to be enforced
|
||||
according to the corresponding provisions of the implementation of
|
||||
those treaty provisions in the applicable national law. If the
|
||||
standard suite of rights granted under applicable copyright law
|
||||
includes additional rights not granted under this License, such
|
||||
additional rights are deemed to be included in the License; this
|
||||
License is not intended to restrict the license of any rights under
|
||||
applicable law.
|
||||
|
||||
|
||||
Creative Commons Notice
|
||||
|
||||
Creative Commons is not a party to this License, and makes no warranty
|
||||
whatsoever in connection with the Work. Creative Commons will not be
|
||||
liable to You or any party on any legal theory for any damages
|
||||
whatsoever, including without limitation any general, special,
|
||||
incidental or consequential damages arising in connection to this
|
||||
license. Notwithstanding the foregoing two (2) sentences, if Creative
|
||||
Commons has expressly identified itself as the Licensor hereunder, it
|
||||
shall have all rights and obligations of Licensor.
|
||||
|
||||
Except for the limited purpose of indicating to the public that the
|
||||
Work is licensed under the CCPL, Creative Commons does not authorize
|
||||
the use by either party of the trademark "Creative Commons" or any
|
||||
related trademark or logo of Creative Commons without the prior
|
||||
written consent of Creative Commons. Any permitted use will be in
|
||||
compliance with Creative Commons' then-current trademark usage
|
||||
guidelines, as may be published on its website or otherwise made
|
||||
available upon request from time to time. For the avoidance of doubt,
|
||||
this trademark restriction does not form part of this License.
|
||||
|
||||
Creative Commons may be contacted at http://creativecommons.org/.
|
||||
|
||||
Submodule docs/en/_theme updated: 68795c5888...dc294be1db
710
docs/en/changelog/migration_2_5.rst
Normal file
710
docs/en/changelog/migration_2_5.rst
Normal file
@@ -0,0 +1,710 @@
|
||||
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.
|
||||
Please review this document carefully when updating to Doctrine 2.5.
|
||||
|
||||
First note, that with the ORM 2.5 release we are dropping support
|
||||
for PHP 5.3. We are enforcing this with Composer, servers without
|
||||
at least PHP 5.4 will not allow installing Doctrine 2.5.
|
||||
|
||||
New Features and Improvements
|
||||
-----------------------------
|
||||
|
||||
Events: PostLoad now triggered after associations are loaded
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Before Doctrine 2.5 if you had an entity with a ``@PostLoad`` event
|
||||
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>`_
|
||||
|
||||
Events: Add API to programatically add event listeners to Entity
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When developing third party libraries or decoupled applications
|
||||
it can be interesting to develop an entity listener without knowing
|
||||
the entities that require this listener.
|
||||
|
||||
You can now attach entity listeners to entities using the
|
||||
``AttachEntityListenersListener`` class, which is listening to the
|
||||
``loadMetadata`` event that is fired once for every entity during
|
||||
metadata generation:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Tools\AttachEntityListenersListener;
|
||||
use Doctrine\ORM\Events;
|
||||
|
||||
$listener = new AttachEntityListenersListener();
|
||||
$listener->addEntityListener(
|
||||
'MyProject\Entity\User', 'MyProject\Listener\TimestampableListener',
|
||||
Events::prePersist, 'onPrePersist'
|
||||
);
|
||||
|
||||
$evm->addEventListener(Events::loadClassMetadata, $listener);
|
||||
|
||||
class TimestampableListener
|
||||
{
|
||||
public function onPrePersist($event)
|
||||
{
|
||||
$entity = $event->getEntity();
|
||||
$entity->setCreated(new \DateTime('now'));
|
||||
}
|
||||
}
|
||||
|
||||
Embeddedable Objects
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Doctrine now supports creating multiple PHP objects from one database table
|
||||
implementing a feature called "Embeddedable 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
|
||||
"root-entity" or "aggregate"). Consequently embeddables don't have a primary
|
||||
key, they are identified only by their values.
|
||||
|
||||
Example of defining and using embeddables classes:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
/** @Entity */
|
||||
class Product
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private $id;
|
||||
|
||||
/** @Embedded(class = "Money") */
|
||||
private $price;
|
||||
}
|
||||
|
||||
/** @Embeddable */
|
||||
class Money
|
||||
{
|
||||
/** @Column(type = "decimal") */
|
||||
private $value;
|
||||
|
||||
/** @Column(type = "string") */
|
||||
private $currency = 'EUR';
|
||||
}
|
||||
|
||||
You can read more on the features of Embeddables objects `in the documentation
|
||||
<http://docs.doctrine-project.org/en/latest/tutorials/embeddables.html>`_.
|
||||
|
||||
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>`_
|
||||
|
||||
Second-Level-Cache
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Since version 2.0 of Doctrine, fetching the same object twice by primary key
|
||||
would result in just one query. This was achieved by the identity map pattern
|
||||
(first-level-cache) that kept entities in memory.
|
||||
|
||||
The newly introduced second-level-cache works a bit differently. Instead
|
||||
of saving objects in memory, it saves them in a fast in-memory cache such
|
||||
as Memcache, Redis, Riak or MongoDB. Additionally it allows saving the result
|
||||
of more complex queries than by primary key. Summarized this feature works
|
||||
like the existing Query result cache, but it is much more powerful.
|
||||
|
||||
As an example lets cache an entity Country that is a relation to the User
|
||||
entity. We always want to display the country, but avoid the additional
|
||||
query to this table.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
* @Cache(usage="READ_ONLY", region="country_region")
|
||||
*/
|
||||
class Country
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @GeneratedValue
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @Column(unique=true)
|
||||
*/
|
||||
protected $name;
|
||||
}
|
||||
|
||||
In this example we have specified a caching region name called
|
||||
``country_region``, which we have to configure now on the EntityManager:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$config->setSecondLevelCacheEnabled();
|
||||
|
||||
$cacheConfig = $config->getSecondLevelCacheConfiguration();
|
||||
$regionConfig = $cacheConfig->getRegionsConfiguration();
|
||||
$regionConfig->setLifetime('country_region', 3600);
|
||||
|
||||
Now Doctrine will first check for the data of any country in the cache
|
||||
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>`_
|
||||
|
||||
Criteria API: Support for ManyToMany assocations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
We introduced support for querying collections using the `Criteria API
|
||||
<http://docs.doctrine-project.org/en/latest/reference/working-with-associations.html#filtering-collections>`_
|
||||
in 2.4. This only worked efficently for One-To-Many assocations, not for
|
||||
Many-To-Many. With the start of 2.5 also Many-To-Many associations get queried
|
||||
instead of loading them into memory.
|
||||
|
||||
Criteria API: Add new contains() expression
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
It is now possible to use the Criteria API to check for string contains needle
|
||||
using ``contains()``. This translates to using a ``column LIKE '%needle%'`` SQL
|
||||
condition.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use \Doctrine\Common\Collections\Criteria;
|
||||
|
||||
$criteria = Criteria::create()
|
||||
->where(Criteria::expr()->contains('name', 'Benjamin'));
|
||||
|
||||
$users = $repository->matching($criteria);
|
||||
|
||||
Criteria API: Support for EXTRA_LAZY
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A collection that is marked as ``fetch="EXTRA_LAZY"`` will now return another
|
||||
lazy collection when using ``Collection::matching($criteria)``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
class Post
|
||||
{
|
||||
/** @OneToMany(targetEntity="Comment", fetch="EXTRA_LAZY") */
|
||||
private $comments;
|
||||
}
|
||||
|
||||
$criteria = Criteria::create()
|
||||
->where(Criteria->expr()->eq("published", 1));
|
||||
|
||||
$publishedComments = $post->getComments()->matching($criteria);
|
||||
|
||||
echo count($publishedComments);
|
||||
|
||||
The lazy criteria currently supports the ``count()`` and ``contains()``
|
||||
functionality lazily. All other operations of the ``Collection`` interface
|
||||
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>`_
|
||||
|
||||
Mapping: Allow configuring Index flags
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
It is now possible to control the index flags in the DBAL
|
||||
schema abstraction from the ORM using metadata. This was possible
|
||||
only with a schema event listener before.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @Table(name="product", indexes={@Index(columns={"description"},flags={"fulltext"})})
|
||||
*/
|
||||
class Product
|
||||
{
|
||||
private $description;
|
||||
}
|
||||
|
||||
This feature was contributed by `Adrian Olek <https://github.com/adrianolek>`_.
|
||||
|
||||
- `Pull Request <https://github.com/doctrine/doctrine2/pull/973>`_
|
||||
|
||||
SQLFilter API: Check if a parameter is set
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can now check in your SQLFilter if a parameter was set. This allows
|
||||
to more easily control which features of a filter to enable or disable.
|
||||
|
||||
Extending on the locale example of the documentation:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class MyLocaleFilter extends SQLFilter
|
||||
{
|
||||
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
|
||||
{
|
||||
if (!$targetEntity->reflClass->implementsInterface('LocaleAware')) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!$this->hasParameter('locale')) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return $targetTableAlias.'.locale = ' . $this->getParameter('locale');
|
||||
}
|
||||
}
|
||||
|
||||
This feature was contributed by `Miroslav Demovic <https://github.com/mdemo>`_
|
||||
|
||||
- `Pull Request <https://github.com/doctrine/doctrine2/pull/963>`_
|
||||
|
||||
|
||||
EXTRA_LAZY Improvements
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
1. Efficient query when using EXTRA_LAZY and containsKey
|
||||
|
||||
When calling ``Collection::containsKey($key)`` on one-to-many and many-to-many
|
||||
collections using ``indexBy`` and ``EXTRA_LAZY`` a query is now executed to check
|
||||
for the existance for the item. Prevoiusly this operation was performed in memory
|
||||
by loading all entities of the collection.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
class User
|
||||
{
|
||||
/** @OneToMany(targetEntity="Group", indexBy="id") */
|
||||
private $groups;
|
||||
}
|
||||
|
||||
if ($user->getGroups()->containsKey($groupId)) {
|
||||
echo "User is in group $groupId\n";
|
||||
}
|
||||
|
||||
This feature was contributed by `Asmir Mustafic <https://github.com/goetas>`_
|
||||
|
||||
- `Pull Request <https://github.com/doctrine/doctrine2/pull/937>`_
|
||||
|
||||
2. Add EXTRA_LAZY Support for get() for owning and inverse many-to-many
|
||||
|
||||
This was contributed by `Sander Marechal <https://github.com/sandermarechal>`_.
|
||||
|
||||
Improve efficiency of One-To-Many EAGER
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When marking a one-to-many association with ``fetch="EAGER"`` it will now
|
||||
execute one query less than before and work correctly in combination with
|
||||
``indexBy``.
|
||||
|
||||
Better support for EntityManagerInterface
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Many of the locations where previously only the ``Doctrine\ORM\EntityManager``
|
||||
was allowed are now changed to accept the ``EntityManagerInterface`` that was
|
||||
introduced in 2.4. This allows you to more easily use the decorator pattern
|
||||
to extend the EntityManager if you need. It's still not replaced everywhere,
|
||||
so you still have to be careful.
|
||||
|
||||
DQL Improvements
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
1. It is now possible to add functions to the ``ORDER BY`` clause in DQL statements:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$dql = "SELECT u FROM User u ORDER BY CONCAT(u.username, u.name)";
|
||||
|
||||
2. Support for functions in ``IS NULL`` expressions:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$dql = "SELECT u.name FROM User u WHERE MAX(u.name) IS NULL";
|
||||
|
||||
3. A ``LIKE`` expression is now suported in ``HAVING`` clause.
|
||||
|
||||
4. Subselects are now supported inside a ``NEW()`` expression:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$dql = "SELECT new UserDTO(u.name, SELECT count(g.id) FROM Group g WHERE g.id = u.id) FROM User u";
|
||||
|
||||
5. ``MEMBER OF`` expression now allows to filter for more than one result:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$dql = "SELECT u FROM User u WHERE :groups MEMBER OF u.groups";
|
||||
$query = $entityManager->createQuery($dql);
|
||||
$query->setParameter('groups', array(1, 2, 3));
|
||||
|
||||
$users = $query->getResult();
|
||||
|
||||
6. Expressions inside ``COUNT()`` now allowed
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$dql = "SELECT COUNT(DISTINCT CONCAT(u.name, u.lastname)) FROM User u";
|
||||
|
||||
7. Add support for ``HOUR`` in ``DATE_ADD()``/``DATE_SUB()`` functions
|
||||
|
||||
Custom DQL Functions: Add support for factories
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Previously custom DQL functions could only be provided with their
|
||||
full-qualified class-name, preventing runtime configuration through
|
||||
dependency injection.
|
||||
|
||||
A simplistic approach has been contributed by `Matthieu Napoli
|
||||
<https://github.com/mnapoli>`_ to pass a callback instead that resolves
|
||||
the function:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
|
||||
$config->addCustomNumericFunction(
|
||||
'IS_PUBLISHED', function($funcName) use ($currentSiteId) {
|
||||
return new IsPublishedFunction($currentSiteId);
|
||||
}
|
||||
);
|
||||
|
||||
Query API: WHERE IN Query using a Collection as parameter
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When performing a ``WHERE IN`` query for a collection of entities you can
|
||||
now pass the array collection of entities as a parameter value to the query
|
||||
object:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
$categories = $rootCategory->getChildren();
|
||||
|
||||
$queryBuilder
|
||||
->select('p')
|
||||
->from('Product', 'p')
|
||||
->where('p.category IN (:categories)')
|
||||
->setParameter('categories', $categories)
|
||||
;
|
||||
|
||||
This feature was contributed by `Michael Perrin
|
||||
<https://github.com/michaelperrin>`_.
|
||||
|
||||
- `Pull Request <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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To configure multiple different features such as custom AST Walker, fetch modes,
|
||||
locking and other features affecting DQL generation we have had a feature
|
||||
called "query hints" since version 2.0.
|
||||
|
||||
It is now possible to add query hints that are always enabled for every Query:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$config->setDefaultQueryHints(
|
||||
'doctrine.customOutputWalker' => 'MyProject\CustomOutputWalker'
|
||||
);
|
||||
|
||||
This feature was contributed by `Artur Eshenbrener
|
||||
<https://github.com/Strate>`_.
|
||||
|
||||
- `Pull Request <https://github.com/doctrine/doctrine2/pull/863>`_
|
||||
|
||||
ResultSetMappingBuilder: Add support for Single-Table Inheritance
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Before 2.5 the ResultSetMappingBuilder did not work with entities
|
||||
that are using Single-Table-Inheritance. This restriction was lifted
|
||||
by adding the missing support.
|
||||
|
||||
YAML Mapping: Many-To-Many doesnt require join column definition
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In Annotations and XML it was not necessary using conventions for naming
|
||||
the many-to-many join column names, in YAML it was not possible however.
|
||||
|
||||
A many-to-many definition in YAML is now possible using this minimal
|
||||
definition:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
manyToMany:
|
||||
groups:
|
||||
targetEntity: Group
|
||||
joinTable:
|
||||
name: users_groups
|
||||
|
||||
Schema Validator Command: Allow to skip sub-checks
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The Schema Validator command executes two independent checks
|
||||
for validity of the mappings and if the schema is synchronized
|
||||
correctly. It is now possible to skip any of the two steps
|
||||
when executing the command:
|
||||
|
||||
::
|
||||
|
||||
$ php vendor/bin/doctrine orm:validate-schema --skip-mapping
|
||||
$ php vendor/bin/doctrine orm:validate-schema --skip-sync
|
||||
|
||||
This allows you to write more specialized continuous integration and automation
|
||||
checks. When no changes are found the command returns the exit code 0
|
||||
and 1, 2 or 3 when failing because of mapping, sync or both.
|
||||
|
||||
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``
|
||||
flag:
|
||||
|
||||
::
|
||||
|
||||
$ php vendor/bin/doctrine orm:generate-entities src/ --no-backup
|
||||
|
||||
Support for Objects as Identifiers
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
It is now possible to use Objects as identifiers for Entities
|
||||
as long as they implement the magic method ``__toString()``.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
class UserId
|
||||
{
|
||||
private $value;
|
||||
|
||||
public function __construct($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return (string)$this->value;
|
||||
}
|
||||
}
|
||||
|
||||
class User
|
||||
{
|
||||
/** @Id @Column(type="userid") */
|
||||
private $id;
|
||||
|
||||
public function __construct(UserId $id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
}
|
||||
|
||||
class UserIdType extends \Doctrine\DBAL\Types\Type
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
Doctrine\DBAL\Types\Type::addType('userid', 'MyProject\UserIdType');
|
||||
|
||||
Behavioral Changes (BC Breaks)
|
||||
------------------------------
|
||||
|
||||
NamingStrategy interface changed
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``Doctrine\ORM\Mapping\NamingStrategyInterface`` changed slightly
|
||||
to pass the Class Name of the entity into the join column name generation:
|
||||
|
||||
::
|
||||
|
||||
- function joinColumnName($propertyName);
|
||||
+ function joinColumnName($propertyName, $className = null);
|
||||
|
||||
It also received a new method for supporting embeddables:
|
||||
|
||||
::
|
||||
|
||||
public function embeddedFieldToColumnName($propertyName, $embeddedColumnName);
|
||||
|
||||
Minor BC BREAK: EntityManagerInterface instead of EntityManager in type-hints
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As of 2.5, classes requiring the ``EntityManager`` in any method signature will now require
|
||||
an ``EntityManagerInterface`` instead.
|
||||
If you are extending any of the following classes, then you need to check following
|
||||
signatures:
|
||||
|
||||
- ``Doctrine\ORM\Tools\DebugUnitOfWorkListener#dumpIdentityMap(EntityManagerInterface $em)``
|
||||
- ``Doctrine\ORM\Mapping\ClassMetadataFactory#setEntityManager(EntityManagerInterface $em)``
|
||||
|
||||
Minor BC BREAK: Custom Hydrators API change
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As of 2.5, ``AbstractHydrator`` does not enforce the usage of cache as part of
|
||||
API, and now provides you a clean API for column information through the method
|
||||
``hydrateColumnInfo($column)``.
|
||||
Cache variable being passed around by reference is no longer needed since
|
||||
Hydrators are per query instantiated since Doctrine 2.4.
|
||||
|
||||
- `DDC-3060 <http://doctrine-project.org/jira/browse/DDC-3060>`_
|
||||
|
||||
Minor BC BREAK: All non-transient classes in an inheritance must be part of the inheritance map
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As of 2.5, classes, if you define an inheritance map for an inheritance tree, you are required
|
||||
to map all non-transient classes in that inheritance, including the root of the inheritance.
|
||||
|
||||
So far, the root of the inheritance was allowed to be skipped in the inheritance map: this is
|
||||
not possible anymore, and if you don't plan to persist instances of that class, then you should
|
||||
either:
|
||||
|
||||
- make that class as ``abstract``
|
||||
- add that class to your inheritance map
|
||||
|
||||
If you fail to do so, then a ``Doctrine\ORM\Mapping\MappingException`` will be thrown.
|
||||
|
||||
|
||||
- `DDC-3300 <http://doctrine-project.org/jira/browse/DDC-3300>`_
|
||||
- `DDC-3503 <http://doctrine-project.org/jira/browse/DDC-3503>`_
|
||||
|
||||
Minor BC BREAK: Entity based EntityManager#clear() calls follow cascade detach
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Whenever ``EntityManager#clear()`` method gets called with a given entity class
|
||||
name, until 2.4, it was only detaching the specific requested entity.
|
||||
As of 2.5, ``EntityManager`` will follow configured cascades, providing a better
|
||||
memory management since associations will be garbage collected, optimizing
|
||||
resources consumption on long running jobs.
|
||||
|
||||
Updates on entities scheduled for deletion are no longer processed
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In Doctrine 2.4, if you modified properties of an entity scheduled for deletion, UnitOfWork would
|
||||
produce an ``UPDATE`` statement to be executed right before the ``DELETE`` statement. The entity in question
|
||||
was therefore present in ``UnitOfWork#entityUpdates``, which means that ``preUpdate`` and ``postUpdate``
|
||||
listeners were (quite pointlessly) called. In ``preFlush`` listeners, it used to be possible to undo
|
||||
the scheduled deletion for updated entities (by calling ``persist()`` if the entity was found in both
|
||||
``entityUpdates`` and ``entityDeletions``). This does not work any longer, because the entire changeset
|
||||
calculation logic is optimized away.
|
||||
|
||||
Minor BC BREAK: Default lock mode changed from LockMode::NONE to null in method signatures
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A misconception concerning default lock mode values in method signatures lead to unexpected behaviour
|
||||
in SQL statements on SQL Server. With a default lock mode of ``LockMode::NONE`` throughout the
|
||||
method signatures in ORM, the table lock hint ``WITH (NOLOCK)`` was appended to all locking related
|
||||
queries by default. This could result in unpredictable results because an explicit ``WITH (NOLOCK)``
|
||||
table hint tells SQL Server to run a specific query in transaction isolation level READ UNCOMMITTED
|
||||
instead of the default READ COMMITTED transaction isolation level.
|
||||
Therefore there now is a distinction between ``LockMode::NONE`` and ``null`` to be able to tell
|
||||
Doctrine whether to add table lock hints to queries by intention or not. To achieve this, the following
|
||||
method signatures have been changed to declare ``$lockMode = null`` instead of ``$lockMode = LockMode::NONE``:
|
||||
|
||||
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#getSelectSQL()``
|
||||
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#load()``
|
||||
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#refresh()``
|
||||
- ``Doctrine\ORM\Decorator\EntityManagerDecorator#find()``
|
||||
- ``Doctrine\ORM\EntityManager#find()``
|
||||
- ``Doctrine\ORM\EntityRepository#find()``
|
||||
- ``Doctrine\ORM\Persisters\BasicEntityPersister#getSelectSQL()``
|
||||
- ``Doctrine\ORM\Persisters\BasicEntityPersister#load()``
|
||||
- ``Doctrine\ORM\Persisters\BasicEntityPersister#refresh()``
|
||||
- ``Doctrine\ORM\Persisters\EntityPersister#getSelectSQL()``
|
||||
- ``Doctrine\ORM\Persisters\EntityPersister#load()``
|
||||
- ``Doctrine\ORM\Persisters\EntityPersister#refresh()``
|
||||
- ``Doctrine\ORM\Persisters\JoinedSubclassPersister#getSelectSQL()``
|
||||
|
||||
You should update signatures for these methods if you have subclassed one of the above classes.
|
||||
Please also check the calling code of these methods in your application and update if necessary.
|
||||
|
||||
.. note::
|
||||
|
||||
This in fact is really a minor BC BREAK and should not have any affect on database vendors
|
||||
other than SQL Server because it is the only one that supports and therefore cares about
|
||||
``LockMode::NONE``. It's really just a FIX for SQL Server environments using ORM.
|
||||
|
||||
Minor BC BREAK: __clone method not called anymore when entities are instantiated via metadata API
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As of PHP 5.6, instantiation of new entities is deferred to the
|
||||
`doctrine/instantiator <https://github.com/doctrine/instantiator>`_ library, which will avoid calling ``__clone``
|
||||
or any public API on instantiated objects.
|
||||
|
||||
BC BREAK: DefaultRepositoryFactory is now final
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Please implement the ``Doctrine\ORM\Repository\RepositoryFactory`` interface instead of extending
|
||||
the ``Doctrine\ORM\Repository\DefaultRepositoryFactory``.
|
||||
|
||||
BC BREAK: New object expression DQL queries now respects user provided aliasing and not return consumed fields
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When executing DQL queries with new object expressions, instead of returning
|
||||
DTOs numerically indexes, it will now respect user provided aliases. Consider
|
||||
the following query:
|
||||
|
||||
::
|
||||
|
||||
SELECT new UserDTO(u.id,u.name) as user,new AddressDTO(a.street,a.postalCode) as address, a.id as addressId
|
||||
FROM User u INNER JOIN u.addresses a WITH a.isPrimary = true
|
||||
|
||||
Previously, your result would be similar to this:
|
||||
|
||||
::
|
||||
|
||||
array(
|
||||
0=>array(
|
||||
0=>{UserDTO object},
|
||||
1=>{AddressDTO object},
|
||||
2=>{u.id scalar},
|
||||
3=>{u.name scalar},
|
||||
4=>{a.street scalar},
|
||||
5=>{a.postalCode scalar},
|
||||
'addressId'=>{a.id scalar},
|
||||
),
|
||||
...
|
||||
)
|
||||
|
||||
From now on, the resultset will look like this:
|
||||
|
||||
::
|
||||
|
||||
array(
|
||||
0=>array(
|
||||
'user'=>{UserDTO object},
|
||||
'address'=>{AddressDTO object},
|
||||
'addressId'=>{a.id scalar}
|
||||
),
|
||||
...
|
||||
)
|
||||
@@ -150,7 +150,7 @@ Now we're going to create the ``point`` type and implement all required methods.
|
||||
return self::POINT;
|
||||
}
|
||||
|
||||
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||
{
|
||||
return 'POINT';
|
||||
}
|
||||
@@ -232,7 +232,7 @@ Example usage
|
||||
// Setup custom mapping type
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
|
||||
Type::addType('point', 'Geo\Types\Point');
|
||||
Type::addType('point', 'Geo\Types\PointType');
|
||||
$em->getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping('point', 'point');
|
||||
|
||||
// Store a Location object
|
||||
|
||||
@@ -149,7 +149,7 @@ collection, which means we can compute this value at runtime:
|
||||
public function getBalance()
|
||||
{
|
||||
$balance = 0;
|
||||
foreach ($this->entries AS $entry) {
|
||||
foreach ($this->entries as $entry) {
|
||||
$balance += $entry->getAmount();
|
||||
}
|
||||
return $balance;
|
||||
@@ -332,7 +332,7 @@ Optimistic locking is as easy as adding a version column:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class Amount
|
||||
class Account
|
||||
{
|
||||
/** @Column(type="integer") @Version */
|
||||
private $version;
|
||||
|
||||
103
docs/en/cookbook/custom-mapping-types.rst
Normal file
103
docs/en/cookbook/custom-mapping-types.rst
Normal file
@@ -0,0 +1,103 @@
|
||||
Custom Mapping Types
|
||||
====================
|
||||
|
||||
Doctrine allows you to create new mapping types. This can come in
|
||||
handy when you're missing a specific mapping type or when you want
|
||||
to replace the existing implementation of a mapping type.
|
||||
|
||||
In order to create a new mapping type you need to subclass
|
||||
``Doctrine\DBAL\Types\Type`` and implement/override the methods as
|
||||
you wish. Here is an example skeleton of such a custom type class:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace My\Project\Types;
|
||||
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
|
||||
/**
|
||||
* My custom datatype.
|
||||
*/
|
||||
class MyType extends Type
|
||||
{
|
||||
const MYTYPE = 'mytype'; // modify to match your type name
|
||||
|
||||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||
{
|
||||
// return the SQL used to create your column type. To create a portable column type, use the $platform.
|
||||
}
|
||||
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
// This is executed when the value is read from the database. Make your conversions here, optionally using the $platform.
|
||||
}
|
||||
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
// This is executed when the value is written to the database. Make your conversions here, optionally using the $platform.
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return self::MYTYPE; // modify to match your constant name
|
||||
}
|
||||
}
|
||||
|
||||
.. note::
|
||||
|
||||
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
|
||||
castable to string. Hence, when using custom types that map to PHP
|
||||
objects as IDs, such objects must implement the ``__toString()`` magic
|
||||
method.
|
||||
|
||||
When you have implemented the type you still need to let Doctrine
|
||||
know about it. This can be achieved through the
|
||||
``Doctrine\DBAL\Types\Type#addType($name, $className)``
|
||||
method. See the following example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// in bootstrapping code
|
||||
|
||||
// ...
|
||||
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
|
||||
// ...
|
||||
|
||||
// Register my type
|
||||
Type::addType('mytype', 'My\Project\Types\MyType');
|
||||
|
||||
To convert the underlying database type of your
|
||||
new "mytype" directly into an instance of ``MyType`` when performing
|
||||
schema operations, the type has to be registered with the database
|
||||
platform as well:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$conn = $em->getConnection();
|
||||
$conn->getDatabasePlatform()->registerDoctrineTypeMapping('db_mytype', 'mytype');
|
||||
|
||||
When registering the custom types in the configuration you specify a unique
|
||||
name for the mapping type and map that to the corresponding fully qualified
|
||||
class name. Now the new type can be used when mapping columns:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class MyPersistentClass
|
||||
{
|
||||
/** @Column(type="mytype") */
|
||||
private $field;
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ The ``Paginate::count(Query $query)`` looks like:
|
||||
}
|
||||
|
||||
It clones the query, resets the limit clause first and max results
|
||||
and registers the ``CountSqlWalker`` customer tree walker which
|
||||
and registers the ``CountSqlWalker`` custom tree walker which
|
||||
will modify the AST to execute a count query. The walkers
|
||||
implementation is:
|
||||
|
||||
@@ -130,7 +130,7 @@ implementation is:
|
||||
{
|
||||
$parent = null;
|
||||
$parentName = null;
|
||||
foreach ($this->_getQueryComponents() AS $dqlAlias => $qComp) {
|
||||
foreach ($this->_getQueryComponents() as $dqlAlias => $qComp) {
|
||||
if ($qComp['parent'] === null && $qComp['nestingLevel'] == 0) {
|
||||
$parent = $qComp;
|
||||
$parentName = $dqlAlias;
|
||||
|
||||
@@ -53,6 +53,17 @@ DQL query. ``$class`` is a string of a class-name which has to
|
||||
extend ``Doctrine\ORM\Query\Node\FunctionNode``. This is a class
|
||||
that offers all the necessary API and methods to implement a UDF.
|
||||
|
||||
Instead of providing the function class name, you can also provide
|
||||
a callable that returns the function object:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$config->addCustomStringFunction($name, function () {
|
||||
return new MyCustomFunction();
|
||||
});
|
||||
|
||||
In this post we will implement some MySql specific Date calculation
|
||||
methods, which are quite handy in my opinion:
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ For example for the previous enum type:
|
||||
const STATUS_VISIBLE = 'visible';
|
||||
const STATUS_INVISIBLE = 'invisible';
|
||||
|
||||
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||
{
|
||||
return "ENUM('visible', 'invisible') COMMENT '(DC2Type:enumvisibility)'";
|
||||
}
|
||||
@@ -148,7 +148,7 @@ You can generalize this approach easily to create a base class for enums:
|
||||
protected $name;
|
||||
protected $values = array();
|
||||
|
||||
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||
{
|
||||
$values = array_map(function($val) { return "'".$val."'"; }, $this->values);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ Keeping your Modules independent
|
||||
|
||||
.. versionadded:: 2.2
|
||||
|
||||
One of the goals of using modules is to create discreet units of functionality
|
||||
One of the goals of using modules is to create discrete units of functionality
|
||||
that do not have many (if any) dependencies, allowing you to use that
|
||||
functionality in other applications without including unnecessary items.
|
||||
|
||||
@@ -40,6 +40,7 @@ A Customer entity
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// src/Acme/AppModule/Entity/Customer.php
|
||||
|
||||
namespace Acme\AppModule\Entity;
|
||||
@@ -62,6 +63,7 @@ An Invoice entity
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// src/Acme/InvoiceModule/Entity/Invoice.php
|
||||
|
||||
namespace Acme\InvoiceModule\Entity;
|
||||
@@ -88,6 +90,7 @@ An InvoiceSubjectInterface
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// src/Acme/InvoiceModule/Model/InvoiceSubjectInterface.php
|
||||
|
||||
namespace Acme\InvoiceModule\Model;
|
||||
@@ -116,13 +119,15 @@ the targetEntity resolution will occur reliably:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$evm = new \Doctrine\Common\EventManager;
|
||||
|
||||
<?php
|
||||
$evm = new \Doctrine\Common\EventManager;
|
||||
$rtel = new \Doctrine\ORM\Tools\ResolveTargetEntityListener;
|
||||
|
||||
// Adds a target-entity class
|
||||
$rtel->addResolveTargetEntity('Acme\\InvoiceModule\\Model\\InvoiceSubjectInterface', 'Acme\\CustomerModule\\Entity\\Customer', array());
|
||||
|
||||
// Add the ResolveTargetEntityListener
|
||||
$evm->addEventSubscriber($rtel);
|
||||
$evm->addEventListener(Doctrine\ORM\Events::loadClassMetadata, $rtel);
|
||||
|
||||
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);
|
||||
|
||||
|
||||
@@ -39,10 +39,14 @@ appropriate autoloaders.
|
||||
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
|
||||
{
|
||||
$classMetadata = $eventArgs->getClassMetadata();
|
||||
$classMetadata->setTableName($this->prefix . $classMetadata->getTableName());
|
||||
|
||||
if (!$classMetadata->isInheritanceTypeSingleTable() || $classMetadata->getName() === $classMetadata->rootEntityName) {
|
||||
$classMetadata->setTableName($this->prefix . $classMetadata->getTableName());
|
||||
}
|
||||
|
||||
foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) {
|
||||
if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY) {
|
||||
$mappedTableName = $classMetadata->associationMappings[$fieldName]['joinTable']['name'];
|
||||
if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY && $mapping['isOwningSide']) {
|
||||
$mappedTableName = $mapping['joinTable']['name'];
|
||||
$classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix . $mappedTableName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ is allowed to:
|
||||
$orderLimit = $this->customer->getOrderLimit();
|
||||
|
||||
$amount = 0;
|
||||
foreach ($this->orderLines AS $line) {
|
||||
foreach ($this->orderLines as $line) {
|
||||
$amount += $line->getAmount();
|
||||
}
|
||||
|
||||
|
||||
@@ -65,29 +65,37 @@ Working with Objects
|
||||
Advanced Topics
|
||||
---------------
|
||||
|
||||
* :doc:`Architecture <reference/architecture>`
|
||||
* :doc:`Advanced Configuration <reference/advanced-configuration>`
|
||||
* :doc:`Limitations and knowns issues <reference/limitations-and-known-issues>`
|
||||
* :doc:`Commandline Tools <reference/tools>`
|
||||
* :doc:`Transactions and Concurrency <reference/transactions-and-concurrency>`
|
||||
* :doc:`Filters <reference/filters>`
|
||||
* :doc:`NamingStrategy <reference/namingstrategy>`
|
||||
* :doc:`Improving Performance <reference/improving-performance>`
|
||||
* :doc:`Caching <reference/caching>`
|
||||
* :doc:`Partial Objects <reference/partial-objects>`
|
||||
* :doc:`Change Tracking Policies <reference/change-tracking-policies>`
|
||||
* :doc:`Best Practices <reference/best-practices>`
|
||||
* :doc:`Metadata Drivers <reference/metadata-drivers>`
|
||||
* :doc:`Architecture <reference/architecture>`
|
||||
* :doc:`Advanced Configuration <reference/advanced-configuration>`
|
||||
* :doc:`Limitations and known issues <reference/limitations-and-known-issues>`
|
||||
* :doc:`Commandline Tools <reference/tools>`
|
||||
* :doc:`Transactions and Concurrency <reference/transactions-and-concurrency>`
|
||||
* :doc:`Filters <reference/filters>`
|
||||
* :doc:`NamingStrategy <reference/namingstrategy>`
|
||||
* :doc:`Improving Performance <reference/improving-performance>`
|
||||
* :doc:`Caching <reference/caching>`
|
||||
* :doc:`Partial Objects <reference/partial-objects>`
|
||||
* :doc:`Change Tracking Policies <reference/change-tracking-policies>`
|
||||
* :doc:`Best Practices <reference/best-practices>`
|
||||
* :doc:`Metadata Drivers <reference/metadata-drivers>`
|
||||
* :doc:`Batch Processing <reference/batch-processing>`
|
||||
* :doc:`Second Level Cache <reference/second-level-cache>`
|
||||
|
||||
Tutorials
|
||||
---------
|
||||
|
||||
* :doc:`Indexed associations <tutorials/working-with-indexed-associations>`
|
||||
* :doc:`Extra Lazy Associations <tutorials/extra-lazy-associations>`
|
||||
* :doc:`Composite Primary Keys <tutorials/composite-primary-keys>`
|
||||
* :doc:`Ordered associations <tutorials/ordered-associations>`
|
||||
* :doc:`Pagination <tutorials/pagination>`
|
||||
* :doc:`Override Field/Association Mappings In Subclasses <tutorials/override-field-association-mappings-in-subclasses>`
|
||||
* :doc:`Indexed associations <tutorials/working-with-indexed-associations>`
|
||||
* :doc:`Extra Lazy Associations <tutorials/extra-lazy-associations>`
|
||||
* :doc:`Composite Primary Keys <tutorials/composite-primary-keys>`
|
||||
* :doc:`Ordered associations <tutorials/ordered-associations>`
|
||||
* :doc:`Pagination <tutorials/pagination>`
|
||||
* :doc:`Override Field/Association Mappings In Subclasses <tutorials/override-field-association-mappings-in-subclasses>`
|
||||
* :doc:`Embeddables <tutorials/embeddables>`
|
||||
|
||||
Changelogs
|
||||
----------
|
||||
|
||||
* :doc:`Migration to 2.5 <changelog/migration_2_5>`
|
||||
|
||||
Cookbook
|
||||
--------
|
||||
|
||||
@@ -210,17 +210,56 @@ implementation that logs to the standard output using ``echo`` and
|
||||
Auto-generating Proxy Classes (***OPTIONAL***)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Proxy classes can either be generated manually through the Doctrine
|
||||
Console or automatically at runtime by Doctrine. The configuration
|
||||
option that controls this behavior is:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config->setAutoGenerateProxyClasses($bool);
|
||||
$config->getAutoGenerateProxyClasses();
|
||||
$config->setAutoGenerateProxyClasses($mode);
|
||||
|
||||
Gets or sets whether proxy classes should be generated
|
||||
automatically at runtime by Doctrine. If set to ``FALSE``, proxy
|
||||
classes must be generated manually through the doctrine command
|
||||
line task ``generate-proxies``. The strongly recommended value for
|
||||
a production environment is ``FALSE``.
|
||||
Possible values for ``$mode`` are:
|
||||
|
||||
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_NEVER``
|
||||
|
||||
Never autogenerate a proxy. You will need to generate the proxies
|
||||
manually, for this use the Doctrine Console like so:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ ./doctrine orm:generate-proxies
|
||||
|
||||
When you do this in a development environment,
|
||||
be aware that you may get class/file not found errors if certain proxies
|
||||
are not yet generated. You may also get failing lazy-loads if new
|
||||
methods were added to the entity class that are not yet in the proxy class.
|
||||
In such a case, simply use the Doctrine Console to (re)generate the
|
||||
proxy classes.
|
||||
|
||||
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_ALWAYS``
|
||||
|
||||
Always generates a new proxy in every request and writes it to disk.
|
||||
|
||||
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``
|
||||
|
||||
Generate the proxy class when the proxy file does not exist.
|
||||
This strategy causes a file exists call whenever any proxy is
|
||||
used the first time in a request.
|
||||
|
||||
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_EVAL``
|
||||
|
||||
Generate the proxy classes and evaluate them on the fly via eval(),
|
||||
avoiding writing the proxies to disk.
|
||||
This strategy is only sane for development.
|
||||
|
||||
In a production environment, it is highly recommended to use
|
||||
AUTOGENERATE_NEVER to allow for optimal performances. The other
|
||||
options are interesting in development environment.
|
||||
|
||||
Before v2.4, ``setAutoGenerateProxyClasses`` would accept a boolean
|
||||
value. This is still possible, ``FALSE`` being equivalent to
|
||||
AUTOGENERATE_NEVER and ``TRUE`` to AUTOGENERATE_ALWAYS.
|
||||
|
||||
Development vs Production Configuration
|
||||
---------------------------------------
|
||||
@@ -251,7 +290,7 @@ instance of ``Doctrine\DBAL\Connection``. If an array is passed it
|
||||
is directly passed along to the DBAL Factory
|
||||
``Doctrine\DBAL\DriverManager::getConnection()``. The DBAL
|
||||
configuration is explained in the
|
||||
`DBAL section <./../../../../../dbal/2.0/docs/reference/configuration/en>`_.
|
||||
`DBAL section <./../../../../../projects/doctrine-dbal/en/latest/reference/configuration.html>`_.
|
||||
|
||||
Proxy Objects
|
||||
-------------
|
||||
@@ -322,31 +361,28 @@ transparently initialize itself on first access.
|
||||
Generating Proxy classes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Proxy classes can either be generated manually through the Doctrine
|
||||
Console or automatically by Doctrine. The configuration option that
|
||||
controls this behavior is:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config->setAutoGenerateProxyClasses($bool);
|
||||
$config->getAutoGenerateProxyClasses();
|
||||
|
||||
The default value is ``TRUE`` for convenient development. However,
|
||||
this setting is not optimal for performance and therefore not
|
||||
recommended for a production environment. To eliminate the overhead
|
||||
of proxy class generation during runtime, set this configuration
|
||||
option to ``FALSE``. When you do this in a development environment,
|
||||
note that you may get class/file not found errors if certain proxy
|
||||
classes are not available or failing lazy-loads if new methods were
|
||||
added to the entity class that are not yet in the proxy class. In
|
||||
such a case, simply use the Doctrine Console to (re)generate the
|
||||
proxy classes like so:
|
||||
In a production environment, it is highly recommended to use
|
||||
``AUTOGENERATE_NEVER`` to allow for optimal performances.
|
||||
However you will be required to generate the proxies manually
|
||||
using the Doctrine Console:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ ./doctrine orm:generate-proxies
|
||||
|
||||
The other options are interesting in development environment:
|
||||
|
||||
- ``AUTOGENERATE_ALWAYS`` will require you to create and configure
|
||||
a proxy directory. Proxies will be generated and written to file
|
||||
on each request, so any modification to your code will be acknowledged.
|
||||
|
||||
- ``AUTOGENERATE_FILE_NOT_EXISTS`` will not overwrite an existing
|
||||
proxy file. If your code changes, you will need to regenerate the
|
||||
proxies manually.
|
||||
|
||||
- ``AUTOGENERATE_EVAL`` will regenerate each proxy on each request,
|
||||
but without writing them to disk.
|
||||
|
||||
Autoloading Proxies
|
||||
-------------------
|
||||
|
||||
|
||||
@@ -1,6 +1,32 @@
|
||||
Annotations Reference
|
||||
=====================
|
||||
|
||||
You've probably used docblock annotations in some form already,
|
||||
most likely to provide documentation metadata for a tool like
|
||||
``PHPDocumentor`` (@author, @link, ...). Docblock annotations are a
|
||||
tool to embed metadata inside the documentation section which can
|
||||
then be processed by some tool. Doctrine 2 generalizes the concept
|
||||
of docblock annotations so that they can be used for any kind of
|
||||
metadata and so that it is easy to define new docblock annotations.
|
||||
In order to allow more involved annotation values and to reduce the
|
||||
chances of clashes with other docblock annotations, the Doctrine 2
|
||||
docblock annotations feature an alternative syntax that is heavily
|
||||
inspired by the Annotation syntax introduced in Java 5.
|
||||
|
||||
The implementation of these enhanced docblock annotations is
|
||||
located in the ``Doctrine\Common\Annotations`` namespace and
|
||||
therefore part of the Common package. Doctrine 2 docblock
|
||||
annotations support namespaces and nested annotations among other
|
||||
things. The Doctrine 2 ORM defines its own set of docblock
|
||||
annotations for supplying object-relational mapping metadata.
|
||||
|
||||
.. note::
|
||||
|
||||
If you're not comfortable with the concept of docblock
|
||||
annotations, don't worry, as mentioned earlier Doctrine 2 provides
|
||||
XML and YAML alternatives and you could easily implement your own
|
||||
favourite mechanism for defining ORM metadata.
|
||||
|
||||
In this chapter a reference of every Doctrine 2 Annotation is given
|
||||
with short explanations on their context and usage.
|
||||
|
||||
@@ -9,6 +35,7 @@ Index
|
||||
|
||||
- :ref:`@Column <annref_column>`
|
||||
- :ref:`@ColumnResult <annref_column_result>`
|
||||
- :ref:`@Cache <annref_cache>`
|
||||
- :ref:`@ChangeTrackingPolicy <annref_changetrackingpolicy>`
|
||||
- :ref:`@DiscriminatorColumn <annref_discriminatorcolumn>`
|
||||
- :ref:`@DiscriminatorMap <annref_discriminatormap>`
|
||||
@@ -72,16 +99,38 @@ Optional attributes:
|
||||
string values for you.
|
||||
|
||||
- **precision**: The precision for a decimal (exact numeric) column
|
||||
(Applies only for decimal column)
|
||||
(applies only for decimal column), which is the maximum number of
|
||||
digits that are stored for the values.
|
||||
|
||||
- **scale**: The scale for a decimal (exact numeric) column (Applies
|
||||
only for decimal column)
|
||||
- **scale**: The scale for a decimal (exact numeric) column (applies
|
||||
only for decimal column), which represents the number of digits
|
||||
to the right of the decimal point and must not be greater than
|
||||
*precision*.
|
||||
|
||||
- **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.
|
||||
|
||||
- **options**: Array of additional options:
|
||||
|
||||
- ``default``: The default value to set for the column if no value
|
||||
is supplied.
|
||||
|
||||
- ``unsigned``: Boolean value to determine if the column should
|
||||
be capable of representing only non-negative integers
|
||||
(applies only for integer column and might not be supported by
|
||||
all vendors).
|
||||
|
||||
- ``fixed``: Boolean value to determine if the specified length of
|
||||
a string column should be fixed or varying (applies only for
|
||||
string/binary column and might not be supported by all vendors).
|
||||
|
||||
- ``comment``: The comment of the column in the schema (might not
|
||||
be supported by all vendors).
|
||||
|
||||
- ``collation``: The collation of the column (only supported by Drizzle, Mysql, PostgreSQL>=9.1, Sqlite and SQLServer).
|
||||
|
||||
- **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.
|
||||
@@ -93,7 +142,12 @@ Optional attributes:
|
||||
attribute still handles the conversion between PHP and Database
|
||||
values. If you use this attribute on a column that is used for
|
||||
joins between tables you should also take a look at
|
||||
:ref:`@JoinColumn <annref_joincolumn>`.
|
||||
:ref:`@JoinColumn <annref_joincolumn>`.
|
||||
|
||||
.. note::
|
||||
|
||||
For more detailed information on each attribute, please refer to
|
||||
the DBAL ``Schema-Representation`` documentation.
|
||||
|
||||
Examples:
|
||||
|
||||
@@ -104,17 +158,27 @@ Examples:
|
||||
* @Column(type="string", length=32, unique=true, nullable=false)
|
||||
*/
|
||||
protected $username;
|
||||
|
||||
|
||||
/**
|
||||
* @Column(type="string", columnDefinition="CHAR(2) NOT NULL")
|
||||
*/
|
||||
protected $country;
|
||||
|
||||
|
||||
/**
|
||||
* @Column(type="decimal", precision=2, scale=1)
|
||||
*/
|
||||
protected $height;
|
||||
|
||||
/**
|
||||
* @Column(type="string", length=2, options={"fixed":true, "comment":"Initial letters of first and last name"})
|
||||
*/
|
||||
protected $initials;
|
||||
|
||||
/**
|
||||
* @Column(type="integer", name="login_count" nullable=false, options={"unsigned":true, "default":0})
|
||||
*/
|
||||
protected $loginCount;
|
||||
|
||||
.. _annref_column_result:
|
||||
|
||||
@ColumnResult
|
||||
@@ -126,6 +190,17 @@ Required attributes:
|
||||
|
||||
- **name**: The name of a column in the SELECT clause of a SQL query
|
||||
|
||||
.. _annref_cache:
|
||||
|
||||
@Cache
|
||||
~~~~~~~~~~~~~~
|
||||
Add caching strategy to a root entity or a collection.
|
||||
|
||||
Optional attributes:
|
||||
|
||||
- **usage**: One of ``READ_ONLY``, ``READ_WRITE`` or ``NONSTRICT_READ_WRITE``, By default this is ``READ_ONLY``.
|
||||
- **region**: An specific region name
|
||||
|
||||
.. _annref_changetrackingpolicy:
|
||||
|
||||
@ChangeTrackingPolicy
|
||||
@@ -184,11 +259,11 @@ Optional attributes:
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The discriminator map is a required annotation on the
|
||||
top-most/super class in an inheritance hierarchy. It takes an array
|
||||
as only argument which defines which class should be saved under
|
||||
topmost/super class in an inheritance hierarchy. Its only argument is an
|
||||
array which defines which class should be saved under
|
||||
which name in the database. Keys are the database value and values
|
||||
are the classes, either as fully- or as unqualified class names
|
||||
depending if the classes are in the namespace or not.
|
||||
depending on whether the classes are in the namespace or not.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -209,8 +284,8 @@ depending if the classes are in the namespace or not.
|
||||
@Entity
|
||||
~~~~~~~
|
||||
|
||||
Required annotation to mark a PHP class as Entity. Doctrine manages
|
||||
the persistence of all classes marked as entity.
|
||||
Required annotation to mark a PHP class as an entity. Doctrine manages
|
||||
the persistence of all classes marked as entities.
|
||||
|
||||
Optional attributes:
|
||||
|
||||
@@ -306,7 +381,7 @@ Example:
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Annotation which has to be set on the entity-class PHP DocBlock to
|
||||
notify Doctrine that this entity has entity life-cycle callback
|
||||
notify Doctrine that this entity has entity lifecycle callback
|
||||
annotations set on at least one of its methods. Using @PostLoad,
|
||||
@PrePersist, @PostPersist, @PreRemove, @PostRemove, @PreUpdate or
|
||||
@PostUpdate without this marker annotation will make Doctrine
|
||||
@@ -335,7 +410,7 @@ Example:
|
||||
~~~~~~~
|
||||
|
||||
Annotation is used inside the :ref:`@Table <annref_table>` annotation on
|
||||
the entity-class level. It allows to hint the SchemaTool to
|
||||
the entity-class level. It provides a hint to the SchemaTool to
|
||||
generate a database index on the specified table columns. It only
|
||||
has meaning in the SchemaTool schema generation context.
|
||||
|
||||
@@ -345,7 +420,14 @@ Required attributes:
|
||||
- **name**: Name of the Index
|
||||
- **columns**: Array of columns.
|
||||
|
||||
Example:
|
||||
Optional attributes:
|
||||
|
||||
- **options**: Array of platform specific options:
|
||||
|
||||
- ``where``: SQL WHERE condition to be used for partial indexes. It will
|
||||
only have effect on supported platforms.
|
||||
|
||||
Basic example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -358,6 +440,19 @@ Example:
|
||||
{
|
||||
}
|
||||
|
||||
Example with partial indexes:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
* @Table(name="ecommerce_products",indexes={@Index(name="search_idx", columns={"name", "email"}, options={"where": "(((id IS NOT NULL) AND (name IS NULL)) AND (email IS NULL))"})})
|
||||
*/
|
||||
class ECommerceProduct
|
||||
{
|
||||
}
|
||||
|
||||
.. _annref_id:
|
||||
|
||||
@Id
|
||||
@@ -409,7 +504,7 @@ Examples:
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @InheritanceType("JOINED")
|
||||
@@ -429,7 +524,7 @@ Examples:
|
||||
This annotation is used in the context of relations in
|
||||
:ref:`@ManyToOne <annref_manytoone>`, :ref:`@OneToOne <annref_onetoone>` fields
|
||||
and in the Context of :ref:`@JoinTable <annref_jointable>` nested inside
|
||||
a @ManyToMany. This annotation is not required. If its not
|
||||
a @ManyToMany. This annotation is not required. If it is not
|
||||
specified the attributes *name* and *referencedColumnName* are
|
||||
inferred from the table and primary key names.
|
||||
|
||||
@@ -445,15 +540,15 @@ Required attributes:
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- **unique**: Determines if this relation exclusive between the
|
||||
affected entities and should be enforced so on the database
|
||||
- **unique**: Determines whether this relation is exclusive between the
|
||||
affected entities and should be enforced as such on the database
|
||||
constraint level. Defaults to false.
|
||||
- **nullable**: Determine if the related entity is required, or if
|
||||
- **nullable**: Determine whether the related entity is required, or if
|
||||
null is an allowed state for the relation. Defaults to true.
|
||||
- **onDelete**: Cascade Action (Database-level)
|
||||
- **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. Using
|
||||
This attribute enables the use of advanced RMDBS features. Using
|
||||
this attribute on @JoinColumn is necessary if you need slightly
|
||||
different column definitions for joining columns, for example
|
||||
regarding NULL/NOT NULL defaults. However by default a
|
||||
@@ -493,7 +588,7 @@ details of the database join table. If you do not specify
|
||||
@JoinTable on these relations reasonable mapping defaults apply
|
||||
using the affected table and the column names.
|
||||
|
||||
Required attributes:
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- **name**: Database name of the join-table
|
||||
@@ -555,7 +650,7 @@ Example:
|
||||
@ManyToMany
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Defines an instance variable holds a many-to-many relationship
|
||||
Defines that the annotated instance variable holds a many-to-many relationship
|
||||
between two entities. :ref:`@JoinTable <annref_jointable>` is an
|
||||
additional, optional annotation that has reasonable default
|
||||
configuration values using the table and names of the two related
|
||||
@@ -572,9 +667,9 @@ Optional attributes:
|
||||
|
||||
|
||||
- **mappedBy**: This option specifies the property name on the
|
||||
targetEntity that is the owning side of this relation. Its a
|
||||
targetEntity that is the owning side of this relation. It is a
|
||||
required attribute for the inverse side of a relationship.
|
||||
- **inversedBy**: The inversedBy attribute designates the field in the
|
||||
- **inversedBy**: The inversedBy attribute designates the field in the
|
||||
entity that is the inverse side of the relationship.
|
||||
- **cascade**: Cascade Option
|
||||
- **fetch**: One of LAZY, EXTRA_LAZY or EAGER
|
||||
@@ -602,7 +697,7 @@ Example:
|
||||
* )
|
||||
*/
|
||||
private $groups;
|
||||
|
||||
|
||||
/**
|
||||
* Inverse Side
|
||||
*
|
||||
@@ -615,7 +710,7 @@ Example:
|
||||
@MappedSuperclass
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
An mapped superclass is an abstract or concrete class that provides
|
||||
A mapped superclass is an abstract or concrete class that provides
|
||||
persistent entity state and mapping information for its subclasses,
|
||||
but which is not itself an entity. This annotation is specified on
|
||||
the Class docblock and has no additional attributes.
|
||||
@@ -728,7 +823,7 @@ Example:
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
The @OneToOne annotation works almost exactly as the
|
||||
:ref:`@ManyToOne <annref_manytoone>` with one additional option that can
|
||||
:ref:`@ManyToOne <annref_manytoone>` with one additional option which can
|
||||
be specified. The configuration defaults for
|
||||
:ref:`@JoinColumn <annref_joincolumn>` using the target entity table and
|
||||
primary key column names apply here too.
|
||||
@@ -748,7 +843,7 @@ Optional attributes:
|
||||
- **orphanRemoval**: Boolean that specifies if orphans, inverse
|
||||
OneToOne entities that are not connected to any owning instance,
|
||||
should be removed by Doctrine. Defaults to false.
|
||||
- **inversedBy**: The inversedBy attribute designates the field in the
|
||||
- **inversedBy**: The inversedBy attribute designates the field in the
|
||||
entity that is the inverse side of the relationship.
|
||||
|
||||
Example:
|
||||
@@ -895,7 +990,7 @@ DocBlock.
|
||||
@SequenceGenerator
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
For the use with @generatedValue(strategy="SEQUENCE") this
|
||||
For use with @GeneratedValue(strategy="SEQUENCE") this
|
||||
annotation allows to specify details about the sequence, such as
|
||||
the increment size and initial values of the sequence.
|
||||
|
||||
@@ -908,10 +1003,10 @@ Optional attributes:
|
||||
|
||||
|
||||
- **allocationSize**: Increment the sequence by the allocation size
|
||||
when its fetched. A value larger than 1 allows to optimize for
|
||||
when its fetched. A value larger than 1 allows optimization for
|
||||
scenarios where you create more than one new entity per request.
|
||||
Defaults to 10
|
||||
- **initialValue**: Where does the sequence start, defaults to 1.
|
||||
- **initialValue**: Where the sequence starts, defaults to 1.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -1033,7 +1128,7 @@ Example:
|
||||
|
||||
Annotation describes the table an entity is persisted in. It is
|
||||
placed on the entity-class PHP DocBlock and is optional. If it is
|
||||
not specified the table name will default to the entities
|
||||
not specified the table name will default to the entity's
|
||||
unqualified classname.
|
||||
|
||||
Required attributes:
|
||||
@@ -1046,6 +1141,7 @@ Optional attributes:
|
||||
|
||||
- **indexes**: Array of @Index annotations
|
||||
- **uniqueConstraints**: Array of @UniqueConstraint annotations.
|
||||
- **schema**: (>= 2.5) Name of the schema the table lies in.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -1057,6 +1153,7 @@ Example:
|
||||
* @Table(name="user",
|
||||
* uniqueConstraints={@UniqueConstraint(name="user_unique",columns={"username"})},
|
||||
* indexes={@Index(name="user_idx", columns={"email"})}
|
||||
* schema="schema_name"
|
||||
* )
|
||||
*/
|
||||
class User { }
|
||||
@@ -1078,7 +1175,14 @@ Required attributes:
|
||||
- **name**: Name of the Index
|
||||
- **columns**: Array of columns.
|
||||
|
||||
Example:
|
||||
Optional attributes:
|
||||
|
||||
- **options**: Array of platform specific options:
|
||||
|
||||
- ``where``: SQL WHERE condition to be used for partial indexes. It will
|
||||
only have effect on supported platforms.
|
||||
|
||||
Basic example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -1091,6 +1195,19 @@ Example:
|
||||
{
|
||||
}
|
||||
|
||||
Example with partial indexes:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
* @Table(name="ecommerce_products",uniqueConstraints={@UniqueConstraint(name="search_idx", columns={"name", "email"}, options={"where": "(((id IS NOT NULL) AND (name IS NULL)) AND (email IS NULL))"})})
|
||||
*/
|
||||
class ECommerceProduct
|
||||
{
|
||||
}
|
||||
|
||||
.. _annref_version:
|
||||
|
||||
@Version
|
||||
|
||||
@@ -12,13 +12,13 @@ As the term ORM already hints at, Doctrine 2 aims to simplify the
|
||||
translation between database rows and the PHP object model. The
|
||||
primary use case for Doctrine are therefore applications that
|
||||
utilize the Object-Oriented Programming Paradigm. For applications
|
||||
that not primarily work with objects Doctrine 2 is not suited very
|
||||
that do not primarily work with objects Doctrine 2 is not suited very
|
||||
well.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
Doctrine 2 requires a minimum of PHP 5.3.0. For greatly improved
|
||||
Doctrine 2 requires a minimum of PHP 5.4. For greatly improved
|
||||
performance it is also recommended that you use APC with PHP.
|
||||
|
||||
Doctrine 2 Packages
|
||||
@@ -83,7 +83,7 @@ be any regular PHP class observing the following restrictions:
|
||||
- An entity class must not implement ``__wakeup`` or
|
||||
:doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
|
||||
Also consider implementing
|
||||
`Serializable <http://de3.php.net/manual/en/class.serializable.php>`_
|
||||
`Serializable <http://php.net/manual/en/class.serializable.php>`_
|
||||
instead.
|
||||
- Any two entity classes in a class hierarchy that inherit
|
||||
directly or indirectly from one another must not have a mapped
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
Association Mapping
|
||||
===================
|
||||
|
||||
This chapter introduces association mappings which are used to explain
|
||||
references between objects and are mapped to a relational database using
|
||||
foreign keys.
|
||||
This chapter explains mapping associations between objects.
|
||||
|
||||
Instead of working with the foreign keys directly you will always work with
|
||||
references to objects:
|
||||
Instead of working with foreign keys in your code, you will always work with
|
||||
references to objects instead and Doctrine will convert those references
|
||||
to foreign keys internally.
|
||||
|
||||
- A reference to a single object is represented by a foreign key.
|
||||
- A collection of objects is represented by many foreign keys pointing to the object holding the collection
|
||||
@@ -17,15 +16,89 @@ 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.
|
||||
|
||||
To master associations you should also learn about :doc:`owning and inverse sides of associations <unitofwork-associations>`
|
||||
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.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity **/
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* @ManyToOne(targetEntity="Address")
|
||||
* @JoinColumn(name="address_id", referencedColumnName="id")
|
||||
**/
|
||||
private $address;
|
||||
}
|
||||
|
||||
/** @Entity **/
|
||||
class Address
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="User">
|
||||
<many-to-one field="address" target-entity="Address">
|
||||
<join-column name="address_id" referenced-column-name="id" />
|
||||
</many-to-one>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
User:
|
||||
type: entity
|
||||
manyToOne:
|
||||
address:
|
||||
targetEntity: Address
|
||||
joinColumn:
|
||||
name: address_id
|
||||
referencedColumnName: id
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
The above ``@JoinColumn`` is optional as it would default
|
||||
to ``address_id`` and ``id`` anyways. You can omit it and let it
|
||||
use the defaults.
|
||||
|
||||
Generated MySQL Schema:
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
CREATE TABLE User (
|
||||
id INT AUTO_INCREMENT NOT NULL,
|
||||
address_id INT DEFAULT NULL,
|
||||
PRIMARY KEY(id)
|
||||
) ENGINE = InnoDB;
|
||||
|
||||
CREATE TABLE Address (
|
||||
id INT AUTO_INCREMENT NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
) ENGINE = InnoDB;
|
||||
|
||||
ALTER TABLE User ADD FOREIGN KEY (address_id) REFERENCES Address(id);
|
||||
|
||||
One-To-One, Unidirectional
|
||||
--------------------------
|
||||
|
||||
A unidirectional one-to-one association is very common. Here is an
|
||||
example of a ``Product`` that has one ``Shipping`` object
|
||||
associated to it. The ``Shipping`` side does not reference back to
|
||||
the ``Product`` so it is 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.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
@@ -184,7 +257,7 @@ relation, the table ``Cart``.
|
||||
One-To-One, Self-referencing
|
||||
----------------------------
|
||||
|
||||
You can easily have self referencing one-to-one relationships like
|
||||
You can define a self-referencing one-to-one relationships like
|
||||
below.
|
||||
|
||||
.. code-block:: php
|
||||
@@ -218,6 +291,102 @@ With the generated MySQL Schema:
|
||||
) ENGINE = InnoDB;
|
||||
ALTER TABLE Student ADD FOREIGN KEY (mentor_id) REFERENCES Student(id);
|
||||
|
||||
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.
|
||||
|
||||
This bidirectional mapping requires the ``mappedBy`` attribute on the
|
||||
``OneToMany`` association and the ``inversedBy`` attribute on the ``ManyToOne``
|
||||
association.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
/** @Entity **/
|
||||
class Product
|
||||
{
|
||||
// ...
|
||||
/**
|
||||
* @OneToMany(targetEntity="Feature", mappedBy="product")
|
||||
**/
|
||||
private $features;
|
||||
// ...
|
||||
|
||||
public function __construct() {
|
||||
$this->features = new ArrayCollection();
|
||||
}
|
||||
}
|
||||
|
||||
/** @Entity **/
|
||||
class Feature
|
||||
{
|
||||
// ...
|
||||
/**
|
||||
* @ManyToOne(targetEntity="Product", inversedBy="features")
|
||||
* @JoinColumn(name="product_id", referencedColumnName="id")
|
||||
**/
|
||||
private $product;
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="Product">
|
||||
<one-to-many field="features" target-entity="Feature" mapped-by="product" />
|
||||
</entity>
|
||||
<entity name="Feature">
|
||||
<many-to-one field="product" target-entity="Product" inversed-by="features">
|
||||
<join-column name="product_id" referenced-column-name="id" />
|
||||
</many-to-one>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
Product:
|
||||
type: entity
|
||||
oneToMany:
|
||||
features:
|
||||
targetEntity: Feature
|
||||
mappedBy: product
|
||||
Feature:
|
||||
type: entity
|
||||
manyToOne:
|
||||
product:
|
||||
targetEntity: Product
|
||||
inversedBy: features
|
||||
joinColumn:
|
||||
name: product_id
|
||||
referencedColumnName: id
|
||||
|
||||
Note that the @JoinColumn is not really necessary in this example,
|
||||
as the defaults would be the same.
|
||||
|
||||
Generated MySQL Schema:
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
CREATE TABLE Product (
|
||||
id INT AUTO_INCREMENT NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
) ENGINE = InnoDB;
|
||||
CREATE TABLE Feature (
|
||||
id INT AUTO_INCREMENT NOT NULL,
|
||||
product_id INT DEFAULT NULL,
|
||||
PRIMARY KEY(id)
|
||||
) ENGINE = InnoDB;
|
||||
ALTER TABLE Feature ADD FOREIGN KEY (product_id) REFERENCES Product(id);
|
||||
|
||||
One-To-Many, Unidirectional with Join Table
|
||||
-------------------------------------------
|
||||
|
||||
@@ -226,12 +395,6 @@ join table. From Doctrine's point of view, it is simply mapped as a
|
||||
unidirectional many-to-many whereby a unique constraint on one of
|
||||
the join columns enforces the one-to-many cardinality.
|
||||
|
||||
.. note::
|
||||
|
||||
One-To-Many uni-directional relations with join-table only
|
||||
work using the @ManyToMany annotation and a unique-constraint.
|
||||
|
||||
|
||||
The following example sets up such a unidirectional one-to-many association:
|
||||
|
||||
.. configuration-block::
|
||||
@@ -326,171 +489,6 @@ Generates the following MySQL Schema:
|
||||
ALTER TABLE users_phonenumbers ADD FOREIGN KEY (user_id) REFERENCES User(id);
|
||||
ALTER TABLE users_phonenumbers ADD FOREIGN KEY (phonenumber_id) REFERENCES Phonenumber(id);
|
||||
|
||||
|
||||
Many-To-One, Unidirectional
|
||||
---------------------------
|
||||
|
||||
You can easily implement a many-to-one unidirectional association
|
||||
with the following:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity **/
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* @ManyToOne(targetEntity="Address")
|
||||
* @JoinColumn(name="address_id", referencedColumnName="id")
|
||||
**/
|
||||
private $address;
|
||||
}
|
||||
|
||||
/** @Entity **/
|
||||
class Address
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="User">
|
||||
<many-to-one field="address" target-entity="Address">
|
||||
<join-column name="address_id" referenced-column-name="id" />
|
||||
</many-to-one>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
User:
|
||||
type: entity
|
||||
manyToOne:
|
||||
address:
|
||||
targetEntity: Address
|
||||
joinColumn:
|
||||
name: address_id
|
||||
referencedColumnName: id
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
The above ``@JoinColumn`` is optional as it would default
|
||||
to ``address_id`` and ``id`` anyways. You can omit it and let it
|
||||
use the defaults.
|
||||
|
||||
|
||||
Generated MySQL Schema:
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
CREATE TABLE User (
|
||||
id INT AUTO_INCREMENT NOT NULL,
|
||||
address_id INT DEFAULT NULL,
|
||||
PRIMARY KEY(id)
|
||||
) ENGINE = InnoDB;
|
||||
|
||||
CREATE TABLE Address (
|
||||
id INT AUTO_INCREMENT NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
) ENGINE = InnoDB;
|
||||
|
||||
ALTER TABLE User ADD FOREIGN KEY (address_id) REFERENCES Address(id);
|
||||
|
||||
One-To-Many, Bidirectional
|
||||
--------------------------
|
||||
|
||||
Bidirectional one-to-many associations are very common. The
|
||||
following code shows an example with a Product and a Feature
|
||||
class:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity **/
|
||||
class Product
|
||||
{
|
||||
// ...
|
||||
/**
|
||||
* @OneToMany(targetEntity="Feature", mappedBy="product")
|
||||
**/
|
||||
private $features;
|
||||
// ...
|
||||
|
||||
public function __construct() {
|
||||
$this->features = new \Doctrine\Common\Collections\ArrayCollection();
|
||||
}
|
||||
}
|
||||
|
||||
/** @Entity **/
|
||||
class Feature
|
||||
{
|
||||
// ...
|
||||
/**
|
||||
* @ManyToOne(targetEntity="Product", inversedBy="features")
|
||||
* @JoinColumn(name="product_id", referencedColumnName="id")
|
||||
**/
|
||||
private $product;
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="Product">
|
||||
<one-to-many field="features" target-entity="Feature" mapped-by="product" />
|
||||
</entity>
|
||||
<entity name="Feature">
|
||||
<many-to-one field="product" target-entity="Product" inversed-by="features">
|
||||
<join-column name="product_id" referenced-column-name="id" />
|
||||
</many-to-one>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
Product:
|
||||
type: entity
|
||||
oneToMany:
|
||||
features:
|
||||
targetEntity: Feature
|
||||
mappedBy: product
|
||||
Feature:
|
||||
type: entity
|
||||
manyToOne:
|
||||
product:
|
||||
targetEntity: Product
|
||||
inversedBy: features
|
||||
joinColumn:
|
||||
name: product_id
|
||||
referencedColumnName: id
|
||||
|
||||
|
||||
Note that the @JoinColumn is not really necessary in this example,
|
||||
as the defaults would be the same.
|
||||
|
||||
Generated MySQL Schema:
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
CREATE TABLE Product (
|
||||
id INT AUTO_INCREMENT NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
) ENGINE = InnoDB;
|
||||
CREATE TABLE Feature (
|
||||
id INT AUTO_INCREMENT NOT NULL,
|
||||
product_id INT DEFAULT NULL,
|
||||
PRIMARY KEY(id)
|
||||
) ENGINE = InnoDB;
|
||||
ALTER TABLE Feature ADD FOREIGN KEY (product_id) REFERENCES Product(id);
|
||||
|
||||
One-To-Many, Self-referencing
|
||||
-----------------------------
|
||||
|
||||
@@ -756,8 +754,8 @@ one is bidirectional.
|
||||
The MySQL schema is exactly the same as for the Many-To-Many
|
||||
uni-directional case above.
|
||||
|
||||
Picking Owning and Inverse Side
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
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
|
||||
@@ -869,11 +867,9 @@ Generated MySQL Schema:
|
||||
Mapping Defaults
|
||||
----------------
|
||||
|
||||
Before we introduce all the association mappings in detail, you
|
||||
should note that the @JoinColumn and @JoinTable definitions are
|
||||
usually optional and have sensible default values. The defaults for
|
||||
a join column in a one-to-one/many-to-one association is as
|
||||
follows:
|
||||
The ``@JoinColumn`` and ``@JoinTable`` definitions are usually optional and have
|
||||
sensible default values. The defaults for a join column in a
|
||||
one-to-one/many-to-one association is as follows:
|
||||
|
||||
::
|
||||
|
||||
@@ -973,8 +969,7 @@ similar defaults. As an example, consider this mapping:
|
||||
groups:
|
||||
targetEntity: Group
|
||||
|
||||
This is essentially the same as the following, more verbose,
|
||||
mapping:
|
||||
This is essentially the same as the following, more verbose, mapping:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
@@ -1043,73 +1038,28 @@ minimum.
|
||||
Collections
|
||||
-----------
|
||||
|
||||
In all the examples of many-valued associations in this manual we
|
||||
will make use of a ``Collection`` interface and a corresponding
|
||||
default implementation ``ArrayCollection`` that are defined in the
|
||||
``Doctrine\Common\Collections`` namespace. Why do we need that?
|
||||
Doesn't that couple my domain model to Doctrine? Unfortunately, PHP
|
||||
arrays, while being great for many things, do not make up for good
|
||||
collections of business objects, especially not in the context of
|
||||
an ORM. The reason is that plain PHP arrays can not be
|
||||
transparently extended / instrumented in PHP code, which is
|
||||
necessary for a lot of advanced ORM features. The classes /
|
||||
interfaces that come closest to an OO collection are ArrayAccess
|
||||
and ArrayObject but until instances of these types can be used in
|
||||
all places where a plain array can be used (something that may
|
||||
happen in PHP6) their usability is fairly limited. You "can"
|
||||
type-hint on ``ArrayAccess`` instead of ``Collection``, since the
|
||||
Collection interface extends ``ArrayAccess``, but this will
|
||||
severely limit you in the way you can work with the collection,
|
||||
because the ``ArrayAccess`` API is (intentionally) very primitive
|
||||
and more importantly because you can not pass this collection to
|
||||
all the useful PHP array functions, which makes it very hard to
|
||||
work with.
|
||||
Unfortunately, PHP arrays, while being great for many things, are missing
|
||||
features that make them suitable for lazy loading in the context of an ORM.
|
||||
This is why in all the examples of many-valued associations in this manual we
|
||||
will make use of a ``Collection`` interface and its
|
||||
default implementation ``ArrayCollection`` that are both defined in the
|
||||
``Doctrine\Common\Collections`` namespace. A collection implements
|
||||
the PHP interfaces ``ArrayAccess``, ``Traversable`` and ``Countable``.
|
||||
|
||||
.. warning::
|
||||
.. note::
|
||||
|
||||
The Collection interface and ArrayCollection class,
|
||||
like everything else in the Doctrine namespace, are neither part of
|
||||
the ORM, nor the DBAL, it is a plain PHP class that has no outside
|
||||
dependencies apart from dependencies on PHP itself (and the SPL).
|
||||
Therefore using this class in your domain classes and elsewhere
|
||||
does not introduce a coupling to the persistence layer. The
|
||||
Collection class, like everything else in the Common namespace, is
|
||||
not part of the persistence layer. You could even copy that class
|
||||
over to your project if you want to remove Doctrine from your
|
||||
project and all your domain classes will work the same as before.
|
||||
|
||||
|
||||
Therefore using this class in your model and elsewhere
|
||||
does not introduce a coupling to the ORM.
|
||||
|
||||
Initializing Collections
|
||||
------------------------
|
||||
|
||||
You have to be careful when using entity fields that contain a
|
||||
collection of related entities. Say we have a User entity that
|
||||
contains a collection of groups:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity **/
|
||||
class User
|
||||
{
|
||||
/** @ManyToMany(targetEntity="Group") **/
|
||||
private $groups;
|
||||
|
||||
public function getGroups()
|
||||
{
|
||||
return $this->groups;
|
||||
}
|
||||
}
|
||||
|
||||
With this code alone the ``$groups`` field only contains an
|
||||
instance of ``Doctrine\Common\Collections\Collection`` if the user
|
||||
is retrieved from Doctrine, however not after you instantiated a
|
||||
fresh instance of the User. When your user entity is still new
|
||||
``$groups`` will obviously be null.
|
||||
|
||||
This is why we recommend to initialize all collection fields to an
|
||||
empty ``ArrayCollection`` in your entities constructor:
|
||||
You should always initialize the collections of your ``@OneToMany``
|
||||
and ``@ManyToMany`` associations in the constructor of your entities:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -1133,13 +1083,12 @@ empty ``ArrayCollection`` in your entities constructor:
|
||||
}
|
||||
}
|
||||
|
||||
Now the following code will work even if the Entity hasn't
|
||||
The following code will then work even if the Entity hasn't
|
||||
been associated with an EntityManager yet:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$group = $entityManager->find('Group', $groupId);
|
||||
$group = new Group();
|
||||
$user = new User();
|
||||
$user->getGroups()->add($group);
|
||||
|
||||
|
||||
@@ -1,77 +1,72 @@
|
||||
Basic Mapping
|
||||
=============
|
||||
|
||||
This chapter explains the basic mapping of objects and properties.
|
||||
Mapping of associations will be covered in the next chapter
|
||||
"Association Mapping".
|
||||
This guide explains the basic mapping of entities and properties.
|
||||
After working through this guide you should know:
|
||||
|
||||
Mapping Drivers
|
||||
---------------
|
||||
- How to create PHP objects that can be saved to the database with Doctrine;
|
||||
- How to configure the mapping between columns on tables and properties on
|
||||
entities;
|
||||
- What Doctrine mapping types are;
|
||||
- Defining primary keys and how identifiers are generated by Doctrine;
|
||||
- How quoting of reserved symbols works in Doctrine.
|
||||
|
||||
Doctrine provides several different ways for specifying
|
||||
object-relational mapping metadata:
|
||||
Mapping of associations will be covered in the next chapter on
|
||||
:doc:`Association Mapping <association-mapping>`.
|
||||
|
||||
Guide Assumptions
|
||||
-----------------
|
||||
|
||||
- Docblock Annotations
|
||||
- XML
|
||||
- YAML
|
||||
You should have already :doc:`installed and configure <configuration>`
|
||||
Doctrine.
|
||||
|
||||
This manual usually mentions docblock annotations in all the examples
|
||||
that are spread throughout all chapters, however for many examples
|
||||
alternative YAML and XML examples are given as well. There are dedicated
|
||||
reference chapters for XML and YAML mapping, respectively that explain them
|
||||
in more detail. There is also an Annotation reference chapter.
|
||||
Creating Classes for the Database
|
||||
---------------------------------
|
||||
|
||||
Every PHP object that you want to save in the database using Doctrine
|
||||
is called an "Entity". The term "Entity" describes objects
|
||||
that have an identity over many independent requests. This identity is
|
||||
usually achieved by assigning a unique identifier to an entity.
|
||||
In this tutorial the following ``Message`` PHP class will serve as the
|
||||
example Entity:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class Message
|
||||
{
|
||||
private $id;
|
||||
private $text;
|
||||
private $postedAt;
|
||||
}
|
||||
|
||||
Because Doctrine is a generic library, it only knows about your
|
||||
entities because you will describe their existence and structure using
|
||||
mapping metadata, which is configuration that tells Doctrine how your
|
||||
entity should be stored in the database. The documentation will often
|
||||
speak of "mapping something", which means writing the mapping metadata
|
||||
that describes your entity.
|
||||
|
||||
Doctrine provides several different ways to specify object-relational
|
||||
mapping metadata:
|
||||
|
||||
- :doc:`Docblock Annotations <annotations-reference>`
|
||||
- :doc:`XML <xml-mapping>`
|
||||
- :doc:`YAML <yaml-mapping>`
|
||||
- :doc:`PHP code <php-mapping>`
|
||||
|
||||
This manual will usually show mapping metadata via docblock annotations, though
|
||||
many examples also show the equivalent configuration in YAML and XML.
|
||||
|
||||
.. note::
|
||||
|
||||
If you're wondering which mapping driver gives the best
|
||||
performance, the answer is: They all give exactly the same performance.
|
||||
Once the metadata of a class has
|
||||
been read from the source (annotations, xml or yaml) it is stored
|
||||
in an instance of the ``Doctrine\ORM\Mapping\ClassMetadata`` class
|
||||
and these instances are stored in the metadata cache. Therefore at
|
||||
the end of the day all drivers perform equally well. If you're not
|
||||
using a metadata cache (not recommended!) then the XML driver might
|
||||
have a slight edge in performance due to the powerful native XML
|
||||
support in PHP.
|
||||
All metadata drivers perform equally. Once the metadata of a class has been
|
||||
read from the source (annotations, xml or yaml) it is stored in an instance
|
||||
of the ``Doctrine\ORM\Mapping\ClassMetadata`` class and these instances are
|
||||
stored in the metadata cache. If you're not using a metadata cache (not
|
||||
recommended!) then the XML driver is the fastest.
|
||||
|
||||
|
||||
Introduction to Docblock Annotations
|
||||
------------------------------------
|
||||
|
||||
You've probably used docblock annotations in some form already,
|
||||
most likely to provide documentation metadata for a tool like
|
||||
``PHPDocumentor`` (@author, @link, ...). Docblock annotations are a
|
||||
tool to embed metadata inside the documentation section which can
|
||||
then be processed by some tool. Doctrine 2 generalizes the concept
|
||||
of docblock annotations so that they can be used for any kind of
|
||||
metadata and so that it is easy to define new docblock annotations.
|
||||
In order to allow more involved annotation values and to reduce the
|
||||
chances of clashes with other docblock annotations, the Doctrine 2
|
||||
docblock annotations feature an alternative syntax that is heavily
|
||||
inspired by the Annotation syntax introduced in Java 5.
|
||||
|
||||
The implementation of these enhanced docblock annotations is
|
||||
located in the ``Doctrine\Common\Annotations`` namespace and
|
||||
therefore part of the Common package. Doctrine 2 docblock
|
||||
annotations support namespaces and nested annotations among other
|
||||
things. The Doctrine 2 ORM defines its own set of docblock
|
||||
annotations for supplying object-relational mapping metadata.
|
||||
|
||||
.. note::
|
||||
|
||||
If you're not comfortable with the concept of docblock
|
||||
annotations, don't worry, as mentioned earlier Doctrine 2 provides
|
||||
XML and YAML alternatives and you could easily implement your own
|
||||
favourite mechanism for defining ORM metadata.
|
||||
|
||||
|
||||
Persistent classes
|
||||
------------------
|
||||
|
||||
In order to mark a class for object-relational persistence it needs
|
||||
to be designated as an entity. This can be done through the
|
||||
``@Entity`` marker annotation.
|
||||
Marking our ``Message`` class as an entity for Doctrine is straightforward:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
@@ -79,7 +74,7 @@ to be designated as an entity. This can be done through the
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
class MyPersistentClass
|
||||
class Message
|
||||
{
|
||||
//...
|
||||
}
|
||||
@@ -87,20 +82,20 @@ to be designated as an entity. This can be done through the
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="MyPersistentClass">
|
||||
<entity name="Message">
|
||||
<!-- ... -->
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
MyPersistentClass:
|
||||
Message:
|
||||
type: entity
|
||||
# ...
|
||||
|
||||
By default, the entity will be persisted to a table with the same
|
||||
name as the class name. In order to change that, you can use the
|
||||
``@Table`` annotation as follows:
|
||||
With no additional information, Doctrine expects the entity to be saved
|
||||
into a table with the same name as the class in our case ``Message``.
|
||||
You can change this by configuring information about the table:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
@@ -109,9 +104,9 @@ name as the class name. In order to change that, you can use the
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
* @Table(name="my_persistent_class")
|
||||
* @Table(name="message")
|
||||
*/
|
||||
class MyPersistentClass
|
||||
class Message
|
||||
{
|
||||
//...
|
||||
}
|
||||
@@ -119,42 +114,125 @@ name as the class name. In order to change that, you can use the
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="MyPersistentClass" table="my_persistent_class">
|
||||
<entity name="Message" table="message">
|
||||
<!-- ... -->
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
MyPersistentClass:
|
||||
Message:
|
||||
type: entity
|
||||
table: my_persistent_class
|
||||
table: message
|
||||
# ...
|
||||
|
||||
Now instances of MyPersistentClass will be persisted into a table
|
||||
named ``my_persistent_class``.
|
||||
Now the class ``Message`` will be saved and fetched from the table ``message``.
|
||||
|
||||
Property Mapping
|
||||
----------------
|
||||
|
||||
The next step after marking a PHP class as an entity is mapping its properties
|
||||
to columns in a table.
|
||||
|
||||
To configure a property use the ``@Column`` docblock annotation. The ``type``
|
||||
attribute specifies the :ref:`Doctrine Mapping Type <reference-mapping-types>`
|
||||
to use for the field. If the type is not specified, ``string`` is used as the
|
||||
default.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
class Message
|
||||
{
|
||||
/** @Column(type="integer") */
|
||||
private $id;
|
||||
/** @Column(length=140) */
|
||||
private $text;
|
||||
/** @Column(type="datetime", name="posted_at") */
|
||||
private $postedAt;
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="Message">
|
||||
<field name="id" type="integer" />
|
||||
<field name="text" length="140" />
|
||||
<field name="postedAt" column="posted_at" type="datetime" />
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
Message:
|
||||
type: entity
|
||||
fields:
|
||||
id:
|
||||
type: integer
|
||||
text:
|
||||
length: 140
|
||||
postedAt:
|
||||
type: datetime
|
||||
column: posted_at
|
||||
|
||||
When we don't explicitly specify a column name via the ``name`` option, Doctrine
|
||||
assumes the field name is also the column name. This means that:
|
||||
|
||||
* the ``id`` property will map to the column ``id`` using the type ``integer``;
|
||||
* the ``text`` property will map to the column ``text`` with the default mapping type ``string``;
|
||||
* the ``postedAt`` property will map to the ``posted_at`` column with the ``datetime`` type.
|
||||
|
||||
The Column annotation has some more attributes. Here is a complete
|
||||
list:
|
||||
|
||||
- ``type``: (optional, defaults to 'string') The mapping type to
|
||||
use for the column.
|
||||
- ``name``: (optional, defaults to field name) The name of the
|
||||
column in the database.
|
||||
- ``length``: (optional, default 255) The length of the column in
|
||||
the database. (Applies only if a string-valued column is used).
|
||||
- ``unique``: (optional, default FALSE) Whether the column is a
|
||||
unique key.
|
||||
- ``nullable``: (optional, default FALSE) Whether the database
|
||||
column is nullable.
|
||||
- ``precision``: (optional, default 0) The precision for a decimal
|
||||
(exact numeric) column (applies only for decimal column),
|
||||
which is the maximum number of digits that are stored for the values.
|
||||
- ``scale``: (optional, default 0) The scale for a decimal (exact
|
||||
numeric) column (applies only for decimal column), which represents
|
||||
the number of digits to the right of the decimal point and must
|
||||
not be greater than *precision*.
|
||||
- ``columnDefinition``: (optional) Allows to define a custom
|
||||
DDL snippet that is used to create the column. Warning: This normally
|
||||
confuses the SchemaTool to always detect the column as changed.
|
||||
- ``options``: (optional) Key-value pairs of options that get passed
|
||||
to the underlying database platform when generating DDL statements.
|
||||
|
||||
.. _reference-mapping-types:
|
||||
|
||||
Doctrine Mapping Types
|
||||
----------------------
|
||||
|
||||
A Doctrine Mapping Type defines the mapping between a PHP type and
|
||||
a SQL type. All Doctrine Mapping Types that ship with Doctrine are
|
||||
fully portable between different RDBMS. You can even write your own
|
||||
custom mapping types that might or might not be portable, which is
|
||||
explained later in this chapter.
|
||||
The ``type`` option used in the ``@Column`` accepts any of the existing
|
||||
Doctrine types or even your own custom types. A Doctrine type defines
|
||||
the conversion between PHP and SQL types, independent from the database vendor
|
||||
you are using. All Mapping Types that ship with Doctrine are fully portable
|
||||
between the supported database systems.
|
||||
|
||||
For example, the Doctrine Mapping Type ``string`` defines the
|
||||
As an example, the Doctrine Mapping Type ``string`` defines the
|
||||
mapping from a PHP string to a SQL VARCHAR (or VARCHAR2 etc.
|
||||
depending on the RDBMS brand). Here is a quick overview of the
|
||||
built-in mapping types:
|
||||
|
||||
|
||||
- ``string``: Type that maps a SQL VARCHAR to a PHP string.
|
||||
- ``integer``: Type that maps a SQL INT to a PHP integer.
|
||||
- ``smallint``: Type that maps a database SMALLINT to a PHP
|
||||
integer.
|
||||
- ``bigint``: Type that maps a database BIGINT to a PHP string.
|
||||
- ``boolean``: Type that maps a SQL boolean to a PHP boolean.
|
||||
- ``boolean``: Type that maps a SQL boolean or equivalent (TINYINT) to a PHP boolean.
|
||||
- ``decimal``: Type that maps a SQL DECIMAL to a PHP string.
|
||||
- ``date``: Type that maps a SQL DATETIME to a PHP DateTime
|
||||
object.
|
||||
@@ -180,18 +258,14 @@ built-in mapping types:
|
||||
varchar but uses a specific type if the platform supports it.
|
||||
- ``blob``: Type that maps a SQL BLOB to a PHP resource stream
|
||||
|
||||
.. note::
|
||||
|
||||
Doctrine Mapping Types are NOT SQL types and NOT PHP
|
||||
types! They are mapping types between 2 types.
|
||||
Additionally Mapping types are *case-sensitive*. For example, using
|
||||
a DateTime column will NOT match the datetime type that ships with
|
||||
Doctrine 2.
|
||||
A cookbook article shows how to define :doc:`your own custom mapping types
|
||||
<../cookbook/custom-mapping-types>`.
|
||||
|
||||
.. note::
|
||||
|
||||
DateTime and Object types are compared by reference, not by value. Doctrine updates this values
|
||||
if the reference changes and therefore behaves as if these objects are immutable value objects.
|
||||
DateTime and Object types are compared by reference, not by value. Doctrine
|
||||
updates this values if the reference changes and therefore behaves as if
|
||||
these objects are immutable value objects.
|
||||
|
||||
.. warning::
|
||||
|
||||
@@ -206,303 +280,42 @@ built-in mapping types:
|
||||
on working with datetimes that gives hints for implementing
|
||||
multi timezone applications.
|
||||
|
||||
|
||||
Property Mapping
|
||||
----------------
|
||||
|
||||
After a class has been marked as an entity it can specify mappings
|
||||
for its instance fields. Here we will only look at simple fields
|
||||
that hold scalar values like strings, numbers, etc. Associations to
|
||||
other objects are covered in the chapter "Association Mapping".
|
||||
|
||||
To mark a property for relational persistence the ``@Column``
|
||||
docblock annotation is used. This annotation usually requires at
|
||||
least 1 attribute to be set, the ``type``. The ``type`` attribute
|
||||
specifies the Doctrine Mapping Type to use for the field. If the
|
||||
type is not specified, 'string' is used as the default mapping type
|
||||
since it is the most flexible.
|
||||
|
||||
Example:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
class MyPersistentClass
|
||||
{
|
||||
/** @Column(type="integer") */
|
||||
private $id;
|
||||
/** @Column(length=50) */
|
||||
private $name; // type defaults to string
|
||||
//...
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="MyPersistentClass">
|
||||
<field name="id" type="integer" />
|
||||
<field name="name" length="50" />
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
MyPersistentClass:
|
||||
type: entity
|
||||
fields:
|
||||
id:
|
||||
type: integer
|
||||
name:
|
||||
length: 50
|
||||
|
||||
In that example we mapped the field ``id`` to the column ``id``
|
||||
using the mapping type ``integer`` and the field ``name`` is mapped
|
||||
to the column ``name`` with the default mapping type ``string``. As
|
||||
you can see, by default the column names are assumed to be the same
|
||||
as the field names. To specify a different name for the column, you
|
||||
can use the ``name`` attribute of the Column annotation as
|
||||
follows:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Column(name="db_name") */
|
||||
private $name;
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="MyPersistentClass">
|
||||
<field name="name" column="db_name" />
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
MyPersistentClass:
|
||||
type: entity
|
||||
fields:
|
||||
name:
|
||||
length: 50
|
||||
column: db_name
|
||||
|
||||
The Column annotation has some more attributes. Here is a complete
|
||||
list:
|
||||
|
||||
|
||||
- ``type``: (optional, defaults to 'string') The mapping type to
|
||||
use for the column.
|
||||
- ``column``: (optional, defaults to field name) The name of the
|
||||
column in the database.
|
||||
- ``length``: (optional, default 255) The length of the column in
|
||||
the database. (Applies only if a string-valued column is used).
|
||||
- ``unique``: (optional, default FALSE) Whether the column is a
|
||||
unique key.
|
||||
- ``nullable``: (optional, default FALSE) Whether the database
|
||||
column is nullable.
|
||||
- ``precision``: (optional, default 0) The precision for a decimal
|
||||
(exact numeric) column. (Applies only if a decimal column is used.)
|
||||
- ``scale``: (optional, default 0) The scale for a decimal (exact
|
||||
numeric) column. (Applies only if a decimal column is used.)
|
||||
|
||||
.. _reference-basic-mapping-custom-mapping-types:
|
||||
|
||||
Custom Mapping Types
|
||||
--------------------
|
||||
|
||||
Doctrine allows you to create new mapping types. This can come in
|
||||
handy when you're missing a specific mapping type or when you want
|
||||
to replace the existing implementation of a mapping type.
|
||||
|
||||
In order to create a new mapping type you need to subclass
|
||||
``Doctrine\DBAL\Types\Type`` and implement/override the methods as
|
||||
you wish. Here is an example skeleton of such a custom type class:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace My\Project\Types;
|
||||
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
|
||||
/**
|
||||
* My custom datatype.
|
||||
*/
|
||||
class MyType extends Type
|
||||
{
|
||||
const MYTYPE = 'mytype'; // modify to match your type name
|
||||
|
||||
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||
{
|
||||
// return the SQL used to create your column type. To create a portable column type, use the $platform.
|
||||
}
|
||||
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
// This is executed when the value is read from the database. Make your conversions here, optionally using the $platform.
|
||||
}
|
||||
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
// This is executed when the value is written to the database. Make your conversions here, optionally using the $platform.
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return self::MYTYPE; // modify to match your constant name
|
||||
}
|
||||
}
|
||||
|
||||
Restrictions to keep in mind:
|
||||
|
||||
|
||||
- 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.
|
||||
|
||||
When you have implemented the type you still need to let Doctrine
|
||||
know about it. This can be achieved through the
|
||||
``Doctrine\DBAL\Types\Type#addType($name, $className)``
|
||||
method. See the following example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// in bootstrapping code
|
||||
|
||||
// ...
|
||||
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
|
||||
// ...
|
||||
|
||||
// Register my type
|
||||
Type::addType('mytype', 'My\Project\Types\MyType');
|
||||
|
||||
As can be seen above, when registering the custom types in the
|
||||
configuration you specify a unique name for the mapping type and
|
||||
map that to the corresponding fully qualified class name. Now you
|
||||
can use your new type in your mapping like this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class MyPersistentClass
|
||||
{
|
||||
/** @Column(type="mytype") */
|
||||
private $field;
|
||||
}
|
||||
|
||||
To have Schema-Tool convert the underlying database type of your
|
||||
new "mytype" directly into an instance of ``MyType`` you have to
|
||||
additionally register this mapping with your database platform:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$conn = $em->getConnection();
|
||||
$conn->getDatabasePlatform()->registerDoctrineTypeMapping('db_mytype', 'mytype');
|
||||
|
||||
Now using Schema-Tool, whenever it detects a column having the
|
||||
``db_mytype`` it will convert it into a ``mytype`` Doctrine Type
|
||||
instance for Schema representation. Keep in mind that you can
|
||||
easily produce clashes this way, each database type can only map to
|
||||
exactly one Doctrine mapping type.
|
||||
|
||||
Custom ColumnDefinition
|
||||
-----------------------
|
||||
|
||||
You can define a custom definition for each column using the "columnDefinition"
|
||||
attribute of ``@Column``. You have to define all the definitions that follow
|
||||
the name of a column here.
|
||||
|
||||
.. note::
|
||||
|
||||
Using columnDefinition will break change-detection in SchemaTool.
|
||||
|
||||
Identifiers / Primary Keys
|
||||
--------------------------
|
||||
|
||||
Every entity class needs an identifier/primary key. You designate
|
||||
the field that serves as the identifier with the ``@Id`` marker
|
||||
annotation. Here is an example:
|
||||
Every entity class must have an identifier/primary key. You can select
|
||||
the field that serves as the identifier with the ``@Id``
|
||||
annotation.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class MyPersistentClass
|
||||
{
|
||||
/** @Id @Column(type="integer") */
|
||||
private $id;
|
||||
//...
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="MyPersistentClass">
|
||||
<id name="id" type="integer" />
|
||||
<field name="name" length="50" />
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
MyPersistentClass:
|
||||
type: entity
|
||||
id:
|
||||
id:
|
||||
type: integer
|
||||
fields:
|
||||
name:
|
||||
length: 50
|
||||
|
||||
Without doing anything else, the identifier is assumed to be
|
||||
manually assigned. That means your code would need to properly set
|
||||
the identifier property before passing a new entity to
|
||||
``EntityManager#persist($entity)``.
|
||||
|
||||
A common alternative strategy is to use a generated value as the
|
||||
identifier. To do this, you use the ``@GeneratedValue`` annotation
|
||||
like this:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class MyPersistentClass
|
||||
class Message
|
||||
{
|
||||
/**
|
||||
* @Id @Column(type="integer")
|
||||
* @GeneratedValue
|
||||
*/
|
||||
private $id;
|
||||
//...
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="MyPersistentClass">
|
||||
<entity name="Message">
|
||||
<id name="id" type="integer">
|
||||
<generator strategy="AUTO" />
|
||||
</id>
|
||||
<field name="name" length="50" />
|
||||
<!-- -->
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
MyPersistentClass:
|
||||
Message:
|
||||
type: entity
|
||||
id:
|
||||
id:
|
||||
@@ -510,15 +323,12 @@ like this:
|
||||
generator:
|
||||
strategy: AUTO
|
||||
fields:
|
||||
name:
|
||||
length: 50
|
||||
# fields here
|
||||
|
||||
This tells Doctrine to automatically generate a value for the
|
||||
identifier. How this value is generated is specified by the
|
||||
``strategy`` attribute, which is optional and defaults to 'AUTO'. A
|
||||
value of ``AUTO`` tells Doctrine to use the generation strategy
|
||||
that is preferred by the currently used database platform. See
|
||||
below for details.
|
||||
In most cases using the automatic generator strategy (``@GeneratedValue``) is
|
||||
what you want. It defaults to the identifier generation mechanism your current
|
||||
database vendor prefers: AUTO_INCREMENT with MySQL, SERIAL with PostgreSQL,
|
||||
Sequences with Oracle and so on.
|
||||
|
||||
Identifier Generation Strategies
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -526,23 +336,23 @@ Identifier Generation Strategies
|
||||
The previous example showed how to use the default identifier
|
||||
generation strategy without knowing the underlying database with
|
||||
the AUTO-detection strategy. It is also possible to specify the
|
||||
identifier generation strategy more explicitly, which allows to
|
||||
identifier generation strategy more explicitly, which allows you to
|
||||
make use of some additional features.
|
||||
|
||||
Here is the list of possible generation strategies:
|
||||
|
||||
|
||||
- ``AUTO`` (default): Tells Doctrine to pick the strategy that is
|
||||
preferred by the used database platform. The preferred strategies
|
||||
are IDENTITY for MySQL, SQLite and MsSQL and SEQUENCE for Oracle
|
||||
and PostgreSQL. This strategy provides full portability.
|
||||
are IDENTITY for MySQL, SQLite, MsSQL and SQL Anywhere and SEQUENCE
|
||||
for Oracle and PostgreSQL. This strategy provides full portability.
|
||||
- ``SEQUENCE``: Tells Doctrine to use a database sequence for ID
|
||||
generation. This strategy does currently not provide full
|
||||
portability. Sequences are supported by Oracle and PostgreSql.
|
||||
portability. Sequences are supported by Oracle, PostgreSql and
|
||||
SQL Anywhere.
|
||||
- ``IDENTITY``: Tells Doctrine to use special identity columns in
|
||||
the database that generate a value on insertion of a row. This
|
||||
strategy does currently not provide full portability and is
|
||||
supported by the following platforms: MySQL/SQLite
|
||||
supported by the following platforms: MySQL/SQLite/SQL Anywhere
|
||||
(AUTO\_INCREMENT), MSSQL (IDENTITY) and PostgreSQL (SERIAL).
|
||||
- ``TABLE``: Tells Doctrine to use a separate table for ID
|
||||
generation. This strategy provides full portability.
|
||||
@@ -564,30 +374,31 @@ besides specifying the sequence's name:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class User
|
||||
class Message
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @GeneratedValue(strategy="SEQUENCE")
|
||||
* @SequenceGenerator(sequenceName="tablename_seq", initialValue=1, allocationSize=100)
|
||||
* @SequenceGenerator(sequenceName="message_seq", initialValue=1, allocationSize=100)
|
||||
*/
|
||||
protected $id = null;
|
||||
//...
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="User">
|
||||
<entity name="Message">
|
||||
<id name="id" type="integer">
|
||||
<generator strategy="SEQUENCE" />
|
||||
<sequence-generator sequence-name="tablename_seq" allocation-size="100" initial-value="1" />
|
||||
<sequence-generator sequence-name="message_seq" allocation-size="100" initial-value="1" />
|
||||
</id>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
MyPersistentClass:
|
||||
Message:
|
||||
type: entity
|
||||
id:
|
||||
id:
|
||||
@@ -595,7 +406,7 @@ besides specifying the sequence's name:
|
||||
generator:
|
||||
strategy: SEQUENCE
|
||||
sequenceGenerator:
|
||||
sequenceName: tablename_seq
|
||||
sequenceName: message_seq
|
||||
allocationSize: 100
|
||||
initialValue: 1
|
||||
|
||||
@@ -635,25 +446,22 @@ need to access the sequence once to generate the identifiers for
|
||||
Composite Keys
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Doctrine 2 allows to use composite primary keys. There are however
|
||||
some restrictions opposed to using a single identifier. The use of
|
||||
the ``@GeneratedValue`` annotation is only supported for simple
|
||||
(not composite) primary keys, which means you can only use
|
||||
composite keys if you generate the primary key values yourself
|
||||
before calling ``EntityManager#persist()`` on the entity.
|
||||
with Doctrine 2 you can use composite primary keys, using ``@Id`` on more then
|
||||
one column. Some restrictions exist opposed to using a single identifier in
|
||||
this case: The use of the ``@GeneratedValue`` annotation is not supported,
|
||||
which means you can only use composite keys if you generate the primary key
|
||||
values yourself before calling ``EntityManager#persist()`` on the entity.
|
||||
|
||||
To designate a composite primary key / identifier, simply put the
|
||||
@Id marker annotation on all fields that make up the primary key.
|
||||
More details on composite primary keys are discussed in a :doc:`dedicated tutorial
|
||||
<../tutorials/composite-primary-keys>`.
|
||||
|
||||
Quoting Reserved Words
|
||||
----------------------
|
||||
|
||||
It may sometimes be necessary to quote a column or table name
|
||||
because it conflicts with a reserved word of the particular RDBMS
|
||||
in use. This is often referred to as "Identifier Quoting". To let
|
||||
Doctrine know that you would like a table or column name to be
|
||||
quoted in all SQL statements, enclose the table or column name in
|
||||
backticks. Here is an example:
|
||||
Sometimes it is necessary to quote a column or table name because of reserved
|
||||
word conflicts. Doctrine does not quote identifiers automatically, because it
|
||||
leads to more problems than it would solve. Quoting tables and column names
|
||||
needs to be done explicitly using ticks in the definition.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -666,18 +474,26 @@ according to the used database platform.
|
||||
|
||||
.. warning::
|
||||
|
||||
Identifier Quoting is not supported for join column
|
||||
names or discriminator column names.
|
||||
Identifier Quoting does not work for join column names or discriminator
|
||||
column names unless you are using a custom ``QuoteStrategy``.
|
||||
|
||||
.. warning::
|
||||
.. _reference-basic-mapping-custom-mapping-types:
|
||||
|
||||
Identifier Quoting is a feature that is mainly intended
|
||||
to support legacy database schemas. The use of reserved words and
|
||||
identifier quoting is generally discouraged. Identifier quoting
|
||||
should not be used to enable the use non-standard-characters such
|
||||
as a dash in a hypothetical column ``test-name``. Also Schema-Tool
|
||||
will likely have troubles when quoting is used for case-sensitivity
|
||||
reasons (in Oracle for example).
|
||||
.. versionadded: 2.3
|
||||
|
||||
For more control over column quoting the ``Doctrine\ORM\Mapping\QuoteStrategy`` interface
|
||||
was introduced in 2.3. It is invoked for every column, table, alias and other
|
||||
SQL names. You can implement the QuoteStrategy and set it by calling
|
||||
``Doctrine\ORM\Configuration#setQuoteStrategy()``.
|
||||
|
||||
.. versionadded: 2.4
|
||||
|
||||
The ANSI Quote Strategy was added, which assumes quoting is not necessary for any SQL name.
|
||||
You can use it with the following code:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Mapping\AnsiQuoteStrategy;
|
||||
|
||||
$configuration->setQuoteStrategy(new AnsiQuoteStrategy());
|
||||
|
||||
@@ -42,6 +42,8 @@ internally but also mean more work during ``flush``.
|
||||
$em->clear(); // Detaches all objects from Doctrine!
|
||||
}
|
||||
}
|
||||
$em->flush(); //Persist objects that did not make up an entire batch
|
||||
$em->clear();
|
||||
|
||||
Bulk Updates
|
||||
------------
|
||||
@@ -76,7 +78,7 @@ with the batching strategy that was already used for bulk inserts:
|
||||
$i = 0;
|
||||
$q = $em->createQuery('select u from MyProject\Model\User u');
|
||||
$iterableResult = $q->iterate();
|
||||
foreach($iterableResult AS $row) {
|
||||
foreach ($iterableResult as $row) {
|
||||
$user = $row[0];
|
||||
$user->increaseCredit();
|
||||
$user->calculateNewBonuses();
|
||||
@@ -94,6 +96,12 @@ with the batching strategy that was already used for bulk inserts:
|
||||
fetch-join a collection-valued association. The nature of such SQL
|
||||
result sets is not suitable for incremental hydration.
|
||||
|
||||
.. note::
|
||||
|
||||
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.
|
||||
|
||||
|
||||
Bulk Deletes
|
||||
------------
|
||||
@@ -162,7 +170,7 @@ problems using the following approach:
|
||||
<?php
|
||||
$q = $this->_em->createQuery('select u from MyProject\Model\User u');
|
||||
$iterableResult = $q->iterate();
|
||||
foreach ($iterableResult AS $row) {
|
||||
foreach ($iterableResult as $row) {
|
||||
// do stuff with the data in the row, $row[0] is always the object
|
||||
|
||||
// detach from Doctrine, so that it can be Garbage-Collected immediately
|
||||
|
||||
@@ -6,22 +6,6 @@ design generally refer to best practices when working with Doctrine
|
||||
and do not necessarily reflect best practices for database design
|
||||
in general.
|
||||
|
||||
|
||||
Don't use public properties on entities
|
||||
---------------------------------------
|
||||
|
||||
It is very important that you don't map public properties on
|
||||
entities, but only protected or private ones. The reason for this
|
||||
is simple, whenever you access a public property of a proxy object
|
||||
that hasn't been initialized yet the return value will be null.
|
||||
Doctrine cannot hook into this process and magically make the
|
||||
entity lazy load.
|
||||
|
||||
This can create situations where it is very hard to debug the
|
||||
current failure. We therefore urge you to map only private and
|
||||
protected properties on entities and use getter methods or magic
|
||||
\_\_get() to access them.
|
||||
|
||||
Constrain relationships as much as possible
|
||||
-------------------------------------------
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ Caching
|
||||
Doctrine provides cache drivers in the ``Common`` package for some
|
||||
of the most popular caching implementations such as APC, Memcache
|
||||
and Xcache. We also provide an ``ArrayCache`` driver which stores
|
||||
the data in a PHP array. Obviously, the cache does not live between
|
||||
requests but this is useful for testing in a development
|
||||
environment.
|
||||
the data in a PHP array. Obviously, when using ``ArrayCache``, the
|
||||
cache does not persist between requests, but this is useful for
|
||||
testing in a development environment.
|
||||
|
||||
Cache Drivers
|
||||
-------------
|
||||
@@ -14,20 +14,19 @@ 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
|
||||
the before mentioned interface.
|
||||
this interface.
|
||||
|
||||
The interface defines the following methods for you to publicly
|
||||
use.
|
||||
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.
|
||||
- delete($id) - Deletes a cache entry.
|
||||
- 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
|
||||
- delete($id) - Deletes a cache entry
|
||||
|
||||
Each driver extends the ``AbstractCache`` class which defines a few
|
||||
abstract protected methods that each of the drivers must
|
||||
implement.
|
||||
implement:
|
||||
|
||||
|
||||
- \_doFetch($id)
|
||||
@@ -35,8 +34,8 @@ implement.
|
||||
- \_doSave($id, $data, $lifeTime = false)
|
||||
- \_doDelete($id)
|
||||
|
||||
The public methods ``fetch()``, ``contains()``, etc. utilize the
|
||||
above protected methods that are implemented by the drivers. The
|
||||
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
|
||||
@@ -65,7 +64,7 @@ Memcache
|
||||
|
||||
In order to use the Memcache cache driver you must have it compiled
|
||||
and enabled in your php.ini. You can read about Memcache
|
||||
` on the PHP website <http://us2.php.net/memcache>`_. It will
|
||||
`on the PHP website <http://php.net/memcache>`_. It will
|
||||
give you a little background information about what it is and how
|
||||
you can use it as well as how to install it.
|
||||
|
||||
@@ -82,6 +81,31 @@ driver by itself.
|
||||
$cacheDriver->setMemcache($memcache);
|
||||
$cacheDriver->save('cache_id', 'my_data');
|
||||
|
||||
Memcached
|
||||
~~~~~~~~
|
||||
|
||||
Memcached is a more recent and complete alternative extension to
|
||||
Memcache.
|
||||
|
||||
In order to use the Memcached cache driver you must have it compiled
|
||||
and enabled in your php.ini. You can read about Memcached
|
||||
`on the PHP website <http://php.net/memcached>`_. It will
|
||||
give you a little background information about what it is and how
|
||||
you can use it as well as how to install it.
|
||||
|
||||
Below is a simple example of how you could use the Memcached cache
|
||||
driver by itself.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$memcached = new Memcached();
|
||||
$memcached->addServer('memcache_host', 11211);
|
||||
|
||||
$cacheDriver = new \Doctrine\Common\Cache\MemcachedCache();
|
||||
$cacheDriver->setMemcached($memcached);
|
||||
$cacheDriver->save('cache_id', 'my_data');
|
||||
|
||||
Xcache
|
||||
~~~~~~
|
||||
|
||||
@@ -104,10 +128,10 @@ Redis
|
||||
~~~~~
|
||||
|
||||
In order to use the Redis cache driver you must have it compiled
|
||||
and enabled in your php.ini. You can read about what is Redis
|
||||
and enabled in your php.ini. You can read about what Redis is
|
||||
`from here <http://redis.io/>`_. Also check
|
||||
`A PHP extension for Redis <https://github.com/nicolasff/phpredis/>`_ for how you can use
|
||||
and install Redis PHP extension.
|
||||
and install the Redis PHP extension.
|
||||
|
||||
Below is a simple example of how you could use the Redis cache
|
||||
driver by itself.
|
||||
@@ -126,8 +150,8 @@ Using Cache Drivers
|
||||
-------------------
|
||||
|
||||
In this section we'll describe how you can fully utilize the API of
|
||||
the cache drivers to save cache, check if some cache exists, fetch
|
||||
the cached data and delete the cached data. We'll use the
|
||||
the cache drivers to save data to a cache, check if some cached data
|
||||
exists, fetch the cached data and delete the cached data. We'll use the
|
||||
``ArrayCache`` implementation as our example here.
|
||||
|
||||
.. code-block:: php
|
||||
@@ -138,7 +162,7 @@ the cached data and delete the cached data. We'll use the
|
||||
Saving
|
||||
~~~~~~
|
||||
|
||||
To save some data to the cache driver it is as simple as using the
|
||||
Saving some data to the cache driver is as simple as using the
|
||||
``save()`` method.
|
||||
|
||||
.. code-block:: php
|
||||
@@ -147,7 +171,7 @@ To save some data to the cache driver it is as simple as using the
|
||||
$cacheDriver->save('cache_id', 'my_data');
|
||||
|
||||
The ``save()`` method accepts three arguments which are described
|
||||
below.
|
||||
below:
|
||||
|
||||
|
||||
- ``$id`` - The cache id
|
||||
@@ -170,7 +194,7 @@ object, etc.
|
||||
Checking
|
||||
~~~~~~~~
|
||||
|
||||
Checking whether some cache exists is very simple, just use the
|
||||
Checking whether cached data exists is very simple: just use the
|
||||
``contains()`` method. It accepts a single argument which is the ID
|
||||
of the cache entry.
|
||||
|
||||
@@ -188,7 +212,7 @@ Fetching
|
||||
|
||||
Now if you want to retrieve some cache entry you can use the
|
||||
``fetch()`` method. It also accepts a single argument just like
|
||||
``contains()`` which is the ID of the cache entry.
|
||||
``contains()`` which is again the ID of the cache entry.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -199,9 +223,8 @@ Deleting
|
||||
~~~~~~~~
|
||||
|
||||
As you might guess, deleting is just as easy as saving, checking
|
||||
and fetching. We have a few ways to delete cache entries. You can
|
||||
delete by an individual ID, regular expression, prefix, suffix or
|
||||
you can delete all entries.
|
||||
and fetching. You can delete by an individual ID, or you can
|
||||
delete all entries.
|
||||
|
||||
By Cache ID
|
||||
^^^^^^^^^^^
|
||||
@@ -225,7 +248,7 @@ the ``deleteAll()`` method.
|
||||
Namespaces
|
||||
~~~~~~~~~~
|
||||
|
||||
If you heavily use caching in your application and utilize it in
|
||||
If you heavily use caching in your application and use it in
|
||||
multiple parts of your application, or use it in different
|
||||
applications on the same server you may have issues with cache
|
||||
naming collisions. This can be worked around by using namespaces.
|
||||
@@ -241,8 +264,8 @@ Integrating with the ORM
|
||||
------------------------
|
||||
|
||||
The Doctrine ORM package is tightly integrated with the cache
|
||||
drivers to allow you to improve performance of various aspects of
|
||||
Doctrine by just simply making some additional configurations and
|
||||
drivers to allow you to improve the performance of various aspects of
|
||||
Doctrine by simply making some additional configurations and
|
||||
method calls.
|
||||
|
||||
Query Cache
|
||||
@@ -350,88 +373,46 @@ the cache driver.
|
||||
Clearing the Cache
|
||||
------------------
|
||||
|
||||
We've already shown you previously how you can use the API of the
|
||||
We've already shown you how you can use the API of the
|
||||
cache drivers to manually delete cache entries. For your
|
||||
convenience we offer a command line task for you to help you with
|
||||
convenience we offer command line tasks to help you with
|
||||
clearing the query, result and metadata cache.
|
||||
|
||||
From the Doctrine command line you can run the following command.
|
||||
From the Doctrine command line you can run the following commands:
|
||||
|
||||
To clear the query cache use the ``orm:clear-cache:query`` task.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ ./doctrine clear-cache
|
||||
$ ./doctrine orm:clear-cache:query
|
||||
|
||||
Running this task with no arguments will clear all the cache for
|
||||
all the configured drivers. If you want to be more specific about
|
||||
what you clear you can use the following options.
|
||||
|
||||
To clear the query cache use the ``--query`` option.
|
||||
To clear the metadata cache use the ``orm:clear-cache:metadata`` task.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ ./doctrine clear-cache --query
|
||||
$ ./doctrine orm:clear-cache:metadata
|
||||
|
||||
To clear the metadata cache use the ``--metadata`` option.
|
||||
To clear the result cache use the ``orm:clear-cache:result`` task.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ ./doctrine clear-cache --metadata
|
||||
|
||||
To clear the result cache use the ``--result`` option.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ ./doctrine clear-cache --result
|
||||
|
||||
When you use the ``--result`` option you can use some other options
|
||||
to be more specific about what queries result sets you want to
|
||||
clear.
|
||||
|
||||
Just like the API of the cache drivers you can clear based on an
|
||||
ID, regular expression, prefix or suffix.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ ./doctrine clear-cache --result --id=cache_id
|
||||
|
||||
Or if you want to clear based on a regular expressions.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ ./doctrine clear-cache --result --regex=users_.*
|
||||
|
||||
Or with a prefix.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ ./doctrine clear-cache --result --prefix=users_
|
||||
|
||||
And finally with a suffix.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ ./doctrine clear-cache --result --suffix=_my_account
|
||||
|
||||
.. note::
|
||||
|
||||
Using the ``--id``, ``--regex``, etc. options with the
|
||||
``--query`` and ``--metadata`` are not allowed as it is not
|
||||
necessary to be specific about what you clear. You only ever need
|
||||
to completely clear the cache to remove stale entries.
|
||||
$ ./doctrine orm:clear-cache:result
|
||||
|
||||
All these tasks accept a ``--flush`` option to flush the entire
|
||||
contents of the cache instead of invalidating the entries.
|
||||
|
||||
Cache Slams
|
||||
-----------
|
||||
|
||||
Something to be careful of when utilizing the cache drivers is
|
||||
cache slams. If you have a heavily trafficked website with some
|
||||
Something to be careful of when using the cache drivers is
|
||||
"cache slams". Imagine you have a heavily trafficked website with some
|
||||
code that checks for the existence of a cache record and if it does
|
||||
not exist it generates the information and saves it to the cache.
|
||||
Now if 100 requests were issued all at the same time and each one
|
||||
sees the cache does not exist and they all try and insert the same
|
||||
Now, if 100 requests were issued all at the same time and each one
|
||||
sees the cache does not exist and they all try to insert the same
|
||||
cache entry it could lock up APC, Xcache, etc. and cause problems.
|
||||
Ways exist to work around this, like pre-populating your cache and
|
||||
not letting your users requests populate the cache.
|
||||
not letting your users' requests populate the cache.
|
||||
|
||||
You can read more about cache slams
|
||||
`in this blog post <http://notmysock.org/blog/php/user-cache-timebomb.html>`_.
|
||||
|
||||
@@ -47,7 +47,7 @@ access point to ORM functionality provided by Doctrine.
|
||||
use Doctrine\ORM\Tools\Setup;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
|
||||
$paths = array("/path/to/entities-or-mapping-files");
|
||||
$paths = array("/path/to/entity-files");
|
||||
$isDevMode = false;
|
||||
|
||||
// the connection configuration
|
||||
@@ -66,6 +66,7 @@ Or if you prefer XML:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$paths = array("/path/to/xml-mappings");
|
||||
$config = Setup::createXMLMetadataConfiguration($paths, $isDevMode);
|
||||
$entityManager = EntityManager::create($dbParams, $config);
|
||||
|
||||
@@ -74,15 +75,15 @@ Or if you prefer YAML:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$paths = array("/path/to/yml-mappings");
|
||||
$config = Setup::createYAMLMetadataConfiguration($paths, $isDevMode);
|
||||
$entityManager = EntityManager::create($dbParams, $config);
|
||||
|
||||
Inside the ``Setup`` methods several assumptions are made:
|
||||
|
||||
- If `$devMode` is true always use an ``ArrayCache`` (in-memory) and regenerate proxies on every request.
|
||||
- If `$devMode` is false, check for Caches in the order APC, Xcache, Memcache (127.0.0.1:11211), Redis (127.0.0.1:6379) unless `$cache` is passed as fourth argument.
|
||||
- If `$devMode` is false, set then proxy classes have to be explicitly created
|
||||
through the command line.
|
||||
- If `$isDevMode` is true caching is done in memory with the ``ArrayCache``. Proxy objects are recreated on every request.
|
||||
- If `$isDevMode` is false, check for Caches in the order APC, Xcache, Memcache (127.0.0.1:11211), Redis (127.0.0.1:6379) unless `$cache` is passed as fourth argument.
|
||||
- If `$isDevMode` is false, set then proxy classes have to be explicitly created through the command line.
|
||||
- If third argument `$proxyDir` is not set, use the systems temporary directory.
|
||||
|
||||
If you want to configure Doctrine in more detail, take a look at the :doc:`Advanced
|
||||
|
||||
@@ -278,7 +278,7 @@ With Nested Conditions in WHERE Clause:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT u from ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id');
|
||||
$query = $em->createQuery('SELECT u FROM ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id');
|
||||
$query->setParameters(array(
|
||||
'name' => 'Bob',
|
||||
'name2' => 'Alice',
|
||||
@@ -302,6 +302,14 @@ With Arithmetic Expression in WHERE clause:
|
||||
$query = $em->createQuery('SELECT u FROM CmsUser u WHERE ((u.id + 5000) * u.id + 3) < 10000000');
|
||||
$users = $query->getResult(); // array of ForumUser objects
|
||||
|
||||
Retrieve user entities with Arithmetic Expression in ORDER clause, using the ``HIDDEN`` keyword:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT u, u.posts_count + u.likes_count AS HIDDEN score FROM CmsUser u ORDER BY score');
|
||||
$users = $query->getResult(); // array of User objects
|
||||
|
||||
Using a LEFT JOIN to hydrate all user-ids and optionally associated
|
||||
article-ids:
|
||||
|
||||
@@ -427,13 +435,22 @@ Get all users visible on a given website that have chosen certain gender:
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT u FROM User u WHERE u.gender IN (SELECT IDENTITY(agl.gender) FROM Site s JOIN s.activeGenderList agl WHERE s.id = ?1)');
|
||||
|
||||
.. versionadded:: 2.4
|
||||
|
||||
Starting with 2.4, the IDENTITY() DQL function also works for composite primary keys:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT IDENTITY(c.location, 'latitude') AS latitude, IDENTITY(c.location, 'longitude') AS longitude FROM Checkpoint c WHERE c.user = ?1');
|
||||
$query = $em->createQuery("SELECT IDENTITY(c.location, 'latitude') AS latitude, IDENTITY(c.location, 'longitude') AS longitude FROM Checkpoint c WHERE c.user = ?1");
|
||||
|
||||
Joins between entities without associations were not possible until version
|
||||
2.4, where you can generate an arbitrary join with the following syntax:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT u FROM User u JOIN Blacklist b WITH u.email = b.email');
|
||||
|
||||
Partial Object Syntax
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -500,6 +517,8 @@ And then use the ``NEW`` DQL keyword :
|
||||
$query = $em->createQuery('SELECT NEW CustomerDTO(c.name, e.email, a.city, SUM(o.value)) FROM Customer c JOIN c.email e JOIN c.address a JOIN c.orders o GROUP BY c');
|
||||
$users = $query->getResult(); // array of CustomerDTO
|
||||
|
||||
Note that you can only pass scalar expressions to the constructor.
|
||||
|
||||
Using INDEX BY
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
@@ -920,8 +939,9 @@ the Query class. Here they are:
|
||||
result is either a plain collection of objects (pure) or an array
|
||||
where the objects are nested in the result rows (mixed).
|
||||
- ``Query#getSingleResult()``: Retrieves a single object. If the
|
||||
result contains more than one or no object, an exception is thrown. The
|
||||
pure/mixed distinction does not apply.
|
||||
result contains more than one object, an ``NonUniqueResultException``
|
||||
is thrown. If the result contains no objects, an ``NoResultException``
|
||||
is thrown. The pure/mixed distinction does not apply.
|
||||
- ``Query#getOneOrNullResult()``: Retrieve a single object. If no
|
||||
object is found null will be returned.
|
||||
- ``Query#getArrayResult()``: Retrieves an array graph (a nested
|
||||
@@ -1129,7 +1149,7 @@ Scalar Hydration:
|
||||
Single Scalar Hydration
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you a query which returns just a single scalar value you can use
|
||||
If you have a query which returns just a single scalar value you can use
|
||||
single scalar hydration:
|
||||
|
||||
.. code-block:: php
|
||||
@@ -1189,7 +1209,7 @@ There are situations when a query you want to execute returns a
|
||||
very large result-set that needs to be processed. All the
|
||||
previously described hydration modes completely load a result-set
|
||||
into memory which might not be feasible with large result sets. See
|
||||
the `Batch Processing <batch-processing>`_ section on details how
|
||||
the `Batch Processing <batch-processing.html>`_ section on details how
|
||||
to iterate large result sets.
|
||||
|
||||
Functions
|
||||
@@ -1351,7 +1371,7 @@ can mark a many-to-one or one-to-one association as fetched temporarily to batch
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery("SELECT u FROM MyProject\User u");
|
||||
$query->setFetchMode("MyProject\User", "address", "EAGER");
|
||||
$query->setFetchMode("MyProject\User", "address", \Doctrine\ORM\Mapping\ClassMetadata::FETCH_EAGER);
|
||||
$query->execute();
|
||||
|
||||
Given that there are 10 users and corresponding addresses in the database the executed queries will look something like:
|
||||
@@ -1361,6 +1381,9 @@ Given that there are 10 users and corresponding addresses in the database the ex
|
||||
SELECT * FROM users;
|
||||
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.
|
||||
|
||||
|
||||
EBNF
|
||||
----
|
||||
@@ -1381,8 +1404,8 @@ Document syntax:
|
||||
e.g. zero or one time
|
||||
- curly brackets {...} are used for repetition, e.g. zero or more
|
||||
times
|
||||
- double quotation marks "..." define a terminal string a vertical
|
||||
bar \| represents an alternative
|
||||
- double quotation marks "..." define a terminal string
|
||||
- a vertical bar \| represents an alternative
|
||||
|
||||
Terminals
|
||||
~~~~~~~~~
|
||||
@@ -1425,6 +1448,12 @@ Identifiers
|
||||
/* identifier that must be a class name (the "User" of "FROM User u") */
|
||||
AbstractSchemaName ::= identifier
|
||||
|
||||
/* Alias ResultVariable declaration (the "total" of "COUNT(*) AS total") */
|
||||
AliasResultVariable = identifier
|
||||
|
||||
/* ResultVariable identifier usage of mapped field aliases (the "total" of "COUNT(*) AS total") */
|
||||
ResultVariable = identifier
|
||||
|
||||
/* identifier that must be a field (the "name" of "u.name") */
|
||||
/* This is responsible to know if the field exists in Object, no matter if it's a relation or a simple field */
|
||||
FieldIdentificationVariable ::= identifier
|
||||
@@ -1435,19 +1464,13 @@ Identifiers
|
||||
/* identifier that must be a single-valued association field (to-one) (the "Group" of "u.Group") */
|
||||
SingleValuedAssociationField ::= FieldIdentificationVariable
|
||||
|
||||
/* identifier that must be an embedded class state field (for the future) */
|
||||
/* identifier that must be an embedded class state field */
|
||||
EmbeddedClassStateField ::= FieldIdentificationVariable
|
||||
|
||||
/* identifier that must be a simple state field (name, email, ...) (the "name" of "u.name") */
|
||||
/* The difference between this and FieldIdentificationVariable is only semantical, because it points to a single field (not mapping to a relation) */
|
||||
SimpleStateField ::= FieldIdentificationVariable
|
||||
|
||||
/* Alias ResultVariable declaration (the "total" of "COUNT(*) AS total") */
|
||||
AliasResultVariable = identifier
|
||||
|
||||
/* ResultVariable identifier usage of mapped field aliases (the "total" of "COUNT(*) AS total") */
|
||||
ResultVariable = identifier
|
||||
|
||||
Path Expressions
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -1497,7 +1520,7 @@ Items
|
||||
.. code-block:: php
|
||||
|
||||
UpdateItem ::= SingleValuedPathExpression "=" NewValue
|
||||
OrderByItem ::= (SimpleArithmeticExpression | SingleValuedPathExpression | ScalarExpression | ResultVariable) ["ASC" | "DESC"]
|
||||
OrderByItem ::= (SimpleArithmeticExpression | SingleValuedPathExpression | ScalarExpression | ResultVariable | FunctionDeclaration) ["ASC" | "DESC"]
|
||||
GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression
|
||||
NewValue ::= SimpleArithmeticExpression | "NULL"
|
||||
|
||||
@@ -1506,11 +1529,11 @@ From, Join and Index by
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}*
|
||||
SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable)
|
||||
JoinVariableDeclaration ::= Join [IndexBy]
|
||||
IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}*
|
||||
SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration
|
||||
RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
|
||||
Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression ["AS"] AliasIdentificationVariable ["WITH" ConditionalExpression]
|
||||
JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy]
|
||||
Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" (JoinAssociationDeclaration | RangeVariableDeclaration) ["WITH" ConditionalExpression]
|
||||
IndexBy ::= "INDEX" "BY" StateFieldPathExpression
|
||||
|
||||
Select Expressions
|
||||
@@ -1518,10 +1541,12 @@ Select Expressions
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
SelectExpression ::= (IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression) [["AS"] ["HIDDEN"] AliasResultVariable]
|
||||
SelectExpression ::= (IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression | NewObjectExpression) [["AS"] ["HIDDEN"] AliasResultVariable]
|
||||
SimpleSelectExpression ::= (StateFieldPathExpression | IdentificationVariable | FunctionDeclaration | AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable]
|
||||
PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
|
||||
PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
|
||||
NewObjectExpression ::= "NEW" IdentificationVariable "(" NewObjectArg {"," NewObjectArg}* ")"
|
||||
NewObjectArg ::= ScalarExpression | "(" Subselect ")"
|
||||
|
||||
Conditional Expressions
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -1583,7 +1608,7 @@ Scalar and Type Expressions
|
||||
.. code-block:: php
|
||||
|
||||
ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | StateFieldPathExpression | BooleanPrimary | CaseExpression | InstanceOfExpression
|
||||
StringExpression ::= StringPrimary | "(" Subselect ")"
|
||||
StringExpression ::= StringPrimary | ResultVariable | "(" Subselect ")"
|
||||
StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression
|
||||
BooleanExpression ::= BooleanPrimary | "(" Subselect ")"
|
||||
BooleanPrimary ::= StateFieldPathExpression | boolean | InputParameter
|
||||
@@ -1601,8 +1626,7 @@ Aggregate Expressions
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
AggregateExpression ::= ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" |
|
||||
"COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedPathExpression) ")"
|
||||
AggregateExpression ::= ("AVG" | "MAX" | "MIN" | "SUM" | "COUNT") "(" ["DISTINCT"] SimpleArithmeticExpression ")"
|
||||
|
||||
Case Expressions
|
||||
~~~~~~~~~~~~~~~~
|
||||
@@ -1632,7 +1656,7 @@ QUANTIFIED/BETWEEN/COMPARISON/LIKE/NULL/EXISTS
|
||||
InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")")
|
||||
InstanceOfParameter ::= AbstractSchemaName | InputParameter
|
||||
LikeExpression ::= StringExpression ["NOT"] "LIKE" StringPrimary ["ESCAPE" char]
|
||||
NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL"
|
||||
NullComparisonExpression ::= (InputParameter | NullIfExpression | CoalesceExpression | AggregateExpression | FunctionDeclaration | IdentificationVariable | SingleValuedPathExpression | ResultVariable) "IS" ["NOT"] "NULL"
|
||||
ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")"
|
||||
ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!="
|
||||
|
||||
@@ -1667,6 +1691,6 @@ Functions
|
||||
"TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" |
|
||||
"LOWER" "(" StringPrimary ")" |
|
||||
"UPPER" "(" StringPrimary ")" |
|
||||
"IDENTITY" "(" SingleValuedAssociationPathExpression ")"
|
||||
"IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")"
|
||||
|
||||
|
||||
|
||||
@@ -20,12 +20,12 @@ manager.
|
||||
$evm = new EventManager();
|
||||
|
||||
Now we can add some event listeners to the ``$evm``. Let's create a
|
||||
``EventTest`` class to play around with.
|
||||
``TestEvent`` class to play around with.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class EventTest
|
||||
class TestEvent
|
||||
{
|
||||
const preFoo = 'preFoo';
|
||||
const postFoo = 'postFoo';
|
||||
@@ -52,15 +52,15 @@ Now we can add some event listeners to the ``$evm``. Let's create a
|
||||
}
|
||||
|
||||
// Create a new instance
|
||||
$test = new EventTest($evm);
|
||||
$test = new TestEvent($evm);
|
||||
|
||||
Events can be dispatched by using the ``dispatchEvent()`` method.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$evm->dispatchEvent(EventTest::preFoo);
|
||||
$evm->dispatchEvent(EventTest::postFoo);
|
||||
$evm->dispatchEvent(TestEvent::preFoo);
|
||||
$evm->dispatchEvent(TestEvent::postFoo);
|
||||
|
||||
You can easily remove a listener with the ``removeEventListener()``
|
||||
method.
|
||||
@@ -133,13 +133,12 @@ several reasons:
|
||||
- It is easy to read.
|
||||
- Simplicity.
|
||||
- Each method within an EventSubscriber is named after the
|
||||
corresponding constant. If constant name and constant value differ,
|
||||
you MUST use the new value and thus, your code might be subject to
|
||||
codechanges when the value changes. This contradicts the intention
|
||||
of a constant.
|
||||
corresponding constant's value. If the constant's name and value differ
|
||||
it contradicts the intention of using the constant and makes your code
|
||||
harder to maintain.
|
||||
|
||||
An example for a correct notation can be found in the example
|
||||
``EventTest`` above.
|
||||
``TestEvent`` above.
|
||||
|
||||
.. _reference-events-lifecycle-events:
|
||||
|
||||
@@ -159,7 +158,7 @@ the life-time of their registered entities.
|
||||
- prePersist - The prePersist event occurs for a given entity
|
||||
before the respective EntityManager persist operation for that
|
||||
entity is executed. It should be noted that this event is only triggered on
|
||||
*initial* persist of an entity
|
||||
*initial* persist of an entity (i.e. it does not trigger on future updates).
|
||||
- postPersist - The postPersist event occurs for an entity after
|
||||
the entity has been made persistent. It will be invoked after the
|
||||
database insert operations. Generated primary key values are
|
||||
@@ -173,7 +172,11 @@ the life-time of their registered entities.
|
||||
database or after the refresh operation has been applied to it.
|
||||
- loadClassMetadata - The loadClassMetadata event occurs after the
|
||||
mapping metadata for a class has been loaded from a mapping source
|
||||
(annotations/xml/yaml).
|
||||
(annotations/xml/yaml). This event is not a lifecycle callback.
|
||||
- onClassMetadataNotFound - Loading class metadata for a particular
|
||||
requested class name failed. Manipulating the given event args instance
|
||||
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.
|
||||
- onFlush - The onFlush event occurs after the change-sets of all
|
||||
@@ -183,15 +186,22 @@ the life-time of their registered entities.
|
||||
event is not a lifecycle callback.
|
||||
- onClear - The onClear event occurs when the EntityManager#clear() operation is
|
||||
invoked, after all references to entities have been removed from the unit of
|
||||
work.
|
||||
work. This event is not a lifecycle callback.
|
||||
|
||||
.. warning::
|
||||
|
||||
Note that the postLoad event occurs for an entity
|
||||
before any associations have been initialized. Therefore it is not
|
||||
safe to access associations in a postLoad callback or event
|
||||
handler.
|
||||
Note that, when using ``Doctrine\ORM\AbstractQuery#iterate()``, ``postLoad``
|
||||
events will be executed immediately after objects are being hydrated, and therefore
|
||||
associations are not guaranteed to be initialized. It is not safe to combine
|
||||
usage of ``Doctrine\ORM\AbstractQuery#iterate()`` and ``postLoad`` event
|
||||
handlers.
|
||||
|
||||
.. warning::
|
||||
|
||||
Note that the postRemove event or any events triggered after an entity removal
|
||||
can receive an uninitializable proxy in case you have configured an entity to
|
||||
cascade remove relations. In this case, you should load yourself the proxy in
|
||||
the associated pre event.
|
||||
|
||||
You can access the Event constants from the ``Events`` class in the
|
||||
ORM package.
|
||||
@@ -205,7 +215,6 @@ ORM package.
|
||||
These can be hooked into by two different types of event
|
||||
listeners:
|
||||
|
||||
|
||||
- Lifecycle Callbacks are methods on the entity classes that are
|
||||
called when the event is triggered. As of v2.4 they receive some kind
|
||||
of ``EventArgs`` instance.
|
||||
@@ -297,7 +306,7 @@ can do it with the following.
|
||||
name:
|
||||
type: string(50)
|
||||
lifecycleCallbacks:
|
||||
prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ]
|
||||
prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersist ]
|
||||
postPersist: [ doStuffOnPostPersist ]
|
||||
|
||||
In YAML the ``key`` of the lifecycleCallbacks entry is the event that you
|
||||
@@ -349,15 +358,17 @@ defined on your ``User`` model.
|
||||
// ...
|
||||
}
|
||||
|
||||
public function doOtherStuffOnPrePersist()
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
public function doStuffOnPostPersist()
|
||||
{
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
The ``key`` of the lifecycleCallbacks is the name of the method and
|
||||
the value is the event type. The allowed event types are the ones
|
||||
listed in the previous Lifecycle Events section.
|
||||
|
||||
Lifecycle Callbacks Event Argument
|
||||
-----------------------------------
|
||||
@@ -582,23 +593,23 @@ mentioned sets. See this example:
|
||||
$em = $eventArgs->getEntityManager();
|
||||
$uow = $em->getUnitOfWork();
|
||||
|
||||
foreach ($uow->getScheduledEntityInsertions() AS $entity) {
|
||||
foreach ($uow->getScheduledEntityInsertions() as $entity) {
|
||||
|
||||
}
|
||||
|
||||
foreach ($uow->getScheduledEntityUpdates() AS $entity) {
|
||||
foreach ($uow->getScheduledEntityUpdates() as $entity) {
|
||||
|
||||
}
|
||||
|
||||
foreach ($uow->getScheduledEntityDeletions() AS $entity) {
|
||||
foreach ($uow->getScheduledEntityDeletions() as $entity) {
|
||||
|
||||
}
|
||||
|
||||
foreach ($uow->getScheduledCollectionDeletions() AS $col) {
|
||||
foreach ($uow->getScheduledCollectionDeletions() as $col) {
|
||||
|
||||
}
|
||||
|
||||
foreach ($uow->getScheduledCollectionUpdates() AS $col) {
|
||||
foreach ($uow->getScheduledCollectionUpdates() as $col) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -607,13 +618,13 @@ mentioned sets. See this example:
|
||||
The following restrictions apply to the onFlush event:
|
||||
|
||||
|
||||
- If you create and persist a new entity in "onFlush", then
|
||||
- If you create and persist a new entity in ``onFlush``, then
|
||||
calling ``EntityManager#persist()`` is not enough.
|
||||
You have to execute an additional call to
|
||||
``$unitOfWork->computeChangeSet($classMetadata, $entity)``.
|
||||
- Changing primitive fields or associations requires you to
|
||||
explicitly trigger a re-computation of the changeset of the
|
||||
affected entity. This can be done by either calling
|
||||
affected entity. This can be done by calling
|
||||
``$unitOfWork->recomputeSingleEntityChangeSet($classMetadata, $entity)``.
|
||||
|
||||
postFlush
|
||||
@@ -740,9 +751,9 @@ Entity listeners
|
||||
|
||||
.. versionadded:: 2.4
|
||||
|
||||
An entity listeners is a lifecycle listener classes used for an entity.
|
||||
An entity listener is a lifecycle listener class used for an entity.
|
||||
|
||||
- The entity listeners mapping may be applied to an entity class or mapped superclass.
|
||||
- The entity listener's mapping may be applied to an entity class or mapped superclass.
|
||||
- An entity listener is defined by mapping the entity class with the corresponding mapping.
|
||||
|
||||
.. configuration-block::
|
||||
@@ -800,8 +811,8 @@ An ``Entity Listener`` could be any class, by default it should be a class with
|
||||
}
|
||||
}
|
||||
|
||||
To define a specific event listener method
|
||||
you should map the listener method using the event type mapping.
|
||||
To define a specific event listener method (one that does not follow the naming convention)
|
||||
you need to map the listener method using the event type mapping:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
@@ -879,9 +890,9 @@ you should map the listener method using the event type mapping.
|
||||
|
||||
Entity listeners resolver
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Doctrine invoke the listener resolver to get the listener instance.
|
||||
Doctrine invokes the listener resolver to get the listener instance.
|
||||
|
||||
- An resolver allows you register a specific ``Entity Listener`` instance.
|
||||
- A resolver allows you register a specific entity listener instance.
|
||||
- You can also implement your own resolver by extending ``Doctrine\ORM\Mapping\DefaultEntityListenerResolver`` or implementing ``Doctrine\ORM\Mapping\EntityListenerResolver``
|
||||
|
||||
Specifying an entity listener instance :
|
||||
@@ -936,8 +947,9 @@ Implementing your own resolver :
|
||||
}
|
||||
}
|
||||
|
||||
// configure the listener resolver.
|
||||
$em->getConfiguration()->setEntityListenerResolver($container->get('my_resolver'));
|
||||
// Configure the listener resolver only before instantiating the EntityManager
|
||||
$configurations->setEntityListenerResolver(new MyEntityListenerResolver);
|
||||
EntityManager::create(.., $configurations, ..);
|
||||
|
||||
Load ClassMetadata Event
|
||||
------------------------
|
||||
@@ -949,12 +961,12 @@ process and manipulate the instance.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$test = new EventTest();
|
||||
$test = new TestEvent();
|
||||
$metadataFactory = $em->getMetadataFactory();
|
||||
$evm = $em->getEventManager();
|
||||
$evm->addEventListener(Events::loadClassMetadata, $test);
|
||||
|
||||
class EventTest
|
||||
class TestEvent
|
||||
{
|
||||
public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs)
|
||||
{
|
||||
|
||||
@@ -62,3 +62,7 @@ A lot of the points mentioned in the Best Practices chapter will
|
||||
also positively affect the performance of Doctrine.
|
||||
|
||||
|
||||
Change Tracking policies
|
||||
------------------------
|
||||
|
||||
See: :doc:`Change Tracking Policies <reference/change-tracking-policies>`
|
||||
|
||||
@@ -81,30 +81,47 @@ discriminator column is used.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: php
|
||||
.. configuration-block::
|
||||
|
||||
<?php
|
||||
namespace MyProject\Model;
|
||||
.. code-block:: php
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @InheritanceType("SINGLE_TABLE")
|
||||
* @DiscriminatorColumn(name="discr", type="string")
|
||||
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
|
||||
*/
|
||||
class Person
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class Employee extends Person
|
||||
{
|
||||
// ...
|
||||
}
|
||||
<?php
|
||||
namespace MyProject\Model;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @InheritanceType("SINGLE_TABLE")
|
||||
* @DiscriminatorColumn(name="discr", type="string")
|
||||
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
|
||||
*/
|
||||
class Person
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class Employee extends Person
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
MyProject\Model\Person:
|
||||
type: entity
|
||||
inheritanceType: SINGLE_TABLE
|
||||
discriminatorColumn:
|
||||
name: discr
|
||||
type: string
|
||||
discriminatorMap:
|
||||
person: Person
|
||||
employee: Employee
|
||||
|
||||
MyProject\Model\Employee:
|
||||
type: entity
|
||||
|
||||
Things to note:
|
||||
|
||||
|
||||
@@ -116,10 +133,16 @@ Things to note:
|
||||
the case above a value of "person" identifies a row as being of
|
||||
type ``Person`` and "employee" identifies a row as being of type
|
||||
``Employee``.
|
||||
- All entity classes that is part of the mapped entity hierarchy
|
||||
(including the topmost class) should be specified in the
|
||||
@DiscriminatorMap. In the case above Person class included.
|
||||
- The names of the classes in the discriminator map do not need to
|
||||
be fully qualified if the classes are contained in the same
|
||||
namespace as the entity class on which the discriminator map is
|
||||
applied.
|
||||
- If no discriminator map is provided, then the map is generated
|
||||
automatically. The automatically generated discriminator map
|
||||
contains the lowercase short name of each class as key.
|
||||
|
||||
Design-time considerations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -207,6 +230,9 @@ Things to note:
|
||||
be fully qualified if the classes are contained in the same
|
||||
namespace as the entity class on which the discriminator map is
|
||||
applied.
|
||||
- If no discriminator map is provided, then the map is generated
|
||||
automatically. The automatically generated discriminator map
|
||||
contains the lowercase short name of each class as key.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -260,6 +286,7 @@ or auto-increment details). Furthermore each child table has to
|
||||
have a foreign key pointing from the id column to the root table id
|
||||
column and cascading on delete.
|
||||
|
||||
.. _inheritence_mapping_overrides:
|
||||
|
||||
Overrides
|
||||
---------
|
||||
@@ -555,5 +582,24 @@ Could be used by an entity that extends a mapped superclass to override a field
|
||||
Things to note:
|
||||
|
||||
- The "attribute override" specifies the overrides base on the property name.
|
||||
- The column type *CANNOT* be changed. if the column type is not equals you got a ``MappingException``
|
||||
- The override can redefine all the column except the type.
|
||||
- The column type *CANNOT* be changed. If the column type is not equal you get a ``MappingException``
|
||||
- The override can redefine all the columns except the type.
|
||||
|
||||
Query the Type
|
||||
--------------
|
||||
|
||||
It may happen that the entities of a special type should be queried. Because there
|
||||
is no direct access to the discriminator column, Doctrine provides the
|
||||
``INSTANCE OF`` construct.
|
||||
|
||||
The following example shows how to use ``INSTANCE OF``. There is a three level hierarchy
|
||||
with a base entity ``NaturalPerson`` which is extended by ``Staff`` which in turn
|
||||
is extended by ``Technician``.
|
||||
|
||||
Querying for the staffs without getting any technicians can be achieved by this DQL:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery("SELECT staff FROM MyProject\Model\Staff staff WHERE staff NOT INSTANCE OF MyProject\Model\Technician");
|
||||
$staffs = $query->getResult();
|
||||
|
||||
@@ -65,18 +65,6 @@ Where the ``attribute_name`` column contains the key and
|
||||
The feature request for persistence of primitive value arrays
|
||||
`is described in the DDC-298 ticket <http://www.doctrine-project.org/jira/browse/DDC-298>`_.
|
||||
|
||||
Value Objects
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
There is currently no native support value objects in Doctrine
|
||||
other than for ``DateTime`` instances or if you serialize the
|
||||
objects using ``serialize()/deserialize()`` which the DBAL Type
|
||||
"object" supports.
|
||||
|
||||
The feature request for full value-object support
|
||||
`is described in the DDC-93 ticket <http://www.doctrine-project.org/jira/browse/DDC-93>`_.
|
||||
|
||||
|
||||
Cascade Merge with Bi-directional Associations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -187,3 +175,10 @@ Microsoft SQL Server and Doctrine "datetime"
|
||||
|
||||
Doctrine assumes that you use ``DateTime2`` data-types. If your legacy database contains DateTime
|
||||
datatypes then you have to add your own data-type (see Basic Mapping for an example).
|
||||
|
||||
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.
|
||||
|
||||
@@ -78,7 +78,7 @@ a "naming standard" for database tables and columns.
|
||||
* @param string $propertyName A property
|
||||
* @return string A join column name
|
||||
*/
|
||||
function joinColumnName($propertyName);
|
||||
function joinColumnName($propertyName, $className = null);
|
||||
|
||||
/**
|
||||
* Return a join table name
|
||||
@@ -124,7 +124,7 @@ You need to implements NamingStrategy first. Following is an example
|
||||
{
|
||||
return 'id';
|
||||
}
|
||||
public function joinColumnName($propertyName)
|
||||
public function joinColumnName($propertyName, $className = null)
|
||||
{
|
||||
return $propertyName . '_' . $this->referenceColumnName();
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ with inheritance hierachies.
|
||||
|
||||
The builder extends the ``ResultSetMapping`` class and as such has all the functionality of it as well.
|
||||
|
||||
..versionadded:: 2.4
|
||||
.. versionadded:: 2.4
|
||||
|
||||
Starting with Doctrine ORM 2.4 you can generate the ``SELECT`` clause
|
||||
from a ``ResultSetMappingBuilder``. You can either cast the builder
|
||||
|
||||
@@ -27,7 +27,7 @@ to write a mapping file for it using the above configured
|
||||
|
||||
<?php
|
||||
namespace Entities;
|
||||
|
||||
|
||||
class User
|
||||
{
|
||||
private $id;
|
||||
@@ -42,16 +42,30 @@ named ``Entities.User.php`` inside of the
|
||||
|
||||
<?php
|
||||
// /path/to/php/mapping/files/Entities.User.php
|
||||
|
||||
|
||||
$metadata->mapField(array(
|
||||
'id' => true,
|
||||
'fieldName' => 'id',
|
||||
'type' => 'integer'
|
||||
));
|
||||
|
||||
|
||||
$metadata->mapField(array(
|
||||
'fieldName' => 'username',
|
||||
'type' => 'string'
|
||||
'type' => 'string',
|
||||
'options' => array(
|
||||
'fixed' => true,
|
||||
'comment' => "User's login name"
|
||||
)
|
||||
));
|
||||
|
||||
$metadata->mapField(array(
|
||||
'fieldName' => 'login_count',
|
||||
'type' => 'integer',
|
||||
'nullable' => false,
|
||||
'options' => array(
|
||||
'unsigned' => true,
|
||||
'default' => 0
|
||||
)
|
||||
));
|
||||
|
||||
Now we can easily retrieve the populated ``ClassMetadata`` instance
|
||||
@@ -87,13 +101,13 @@ Now you just need to define a static function named
|
||||
|
||||
<?php
|
||||
namespace Entities;
|
||||
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
|
||||
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
|
||||
|
||||
public static function loadMetadata(ClassMetadata $metadata)
|
||||
{
|
||||
$metadata->mapField(array(
|
||||
@@ -101,7 +115,7 @@ Now you just need to define a static function named
|
||||
'fieldName' => 'id',
|
||||
'type' => 'integer'
|
||||
));
|
||||
|
||||
|
||||
$metadata->mapField(array(
|
||||
'fieldName' => 'username',
|
||||
'type' => 'string'
|
||||
@@ -227,6 +241,7 @@ General Getters
|
||||
|
||||
|
||||
- ``getTableName()``
|
||||
- ``getSchemaName()``
|
||||
- ``getTemporaryIdTableName()``
|
||||
|
||||
Identifier Getters
|
||||
|
||||
@@ -126,6 +126,12 @@ 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');
|
||||
// ->addSelect('p.area_code');
|
||||
public function addSelect($select = null);
|
||||
|
||||
// Example - $qb->delete('User', 'u')
|
||||
public function delete($delete = null, $alias = null);
|
||||
@@ -139,15 +145,23 @@ Here is a complete list of helper methods available in ``QueryBuilder``:
|
||||
public function set($key, $value);
|
||||
|
||||
// Example - $qb->from('Phonenumber', 'p')
|
||||
public function from($from, $alias = null);
|
||||
// Example - $qb->from('Phonenumber', 'p', 'p.id')
|
||||
public function from($from, $alias, $indexBy = null);
|
||||
|
||||
// Example - $qb->join('u.Group', 'g', Expr\Join::WITH, $qb->expr()->eq('u.status_id', '?1'))
|
||||
// Example - $qb->join('u.Group', 'g', 'WITH', 'u.status = ?1')
|
||||
// Example - $qb->join('u.Group', 'g', 'WITH', 'u.status = ?1', 'g.id')
|
||||
public function join($join, $alias, $conditionType = null, $condition = null, $indexBy = null);
|
||||
|
||||
// Example - $qb->innerJoin('u.Group', 'g', Expr\Join::WITH, $qb->expr()->eq('u.status_id', '?1'))
|
||||
// Example - $qb->innerJoin('u.Group', 'g', 'WITH', 'u.status = ?1')
|
||||
public function innerJoin($join, $alias = null, $conditionType = null, $condition = null);
|
||||
// Example - $qb->innerJoin('u.Group', 'g', 'WITH', 'u.status = ?1', 'g.id')
|
||||
public function innerJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null);
|
||||
|
||||
// Example - $qb->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, $qb->expr()->eq('p.area_code', 55))
|
||||
// Example - $qb->leftJoin('u.Phonenumbers', 'p', 'WITH', 'p.area_code = 55')
|
||||
public function leftJoin($join, $alias = null, $conditionType = null, $condition = null);
|
||||
// Example - $qb->leftJoin('u.Phonenumbers', 'p', 'WITH', 'p.area_code = 55', 'p.id')
|
||||
public function leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null);
|
||||
|
||||
// NOTE: ->where() overrides all previously set conditions
|
||||
//
|
||||
@@ -156,6 +170,8 @@ Here is a complete list of helper methods available in ``QueryBuilder``:
|
||||
// Example - $qb->where('u.firstName = ?1 AND u.surname = ?2')
|
||||
public function where($where);
|
||||
|
||||
// NOTE: ->andWhere() can be used directly, without any ->where() before
|
||||
//
|
||||
// Example - $qb->andWhere($qb->expr()->orX($qb->expr()->lte('u.age', 40), 'u.numChild = 0'))
|
||||
public function andWhere($where);
|
||||
|
||||
@@ -206,9 +222,9 @@ allowed. Binding parameters can simply be achieved as follows:
|
||||
// $qb instanceof QueryBuilder
|
||||
|
||||
$qb->select('u')
|
||||
->from('User u')
|
||||
->from('User', 'u')
|
||||
->where('u.id = ?1')
|
||||
->orderBy('u.name', 'ASC');
|
||||
->orderBy('u.name', 'ASC')
|
||||
->setParameter(1, 100); // Sets ?1 to 100, and thus we will fetch a user with u.id = 100
|
||||
|
||||
You are not forced to enumerate your placeholders as the
|
||||
@@ -220,9 +236,9 @@ alternative syntax is available:
|
||||
// $qb instanceof QueryBuilder
|
||||
|
||||
$qb->select('u')
|
||||
->from('User u')
|
||||
->from('User', 'u')
|
||||
->where('u.id = :identifier')
|
||||
->orderBy('u.name', 'ASC');
|
||||
->orderBy('u.name', 'ASC')
|
||||
->setParameter('identifier', 100); // Sets :identifier to 100, and thus we will fetch a user with u.id = 100
|
||||
|
||||
Note that numeric placeholders start with a ? followed by a number
|
||||
@@ -336,7 +352,8 @@ set of useful methods to help build expressions:
|
||||
<?php
|
||||
// $qb instanceof QueryBuilder
|
||||
|
||||
// example8: QueryBuilder port of: "SELECT u FROM User u WHERE u.id = ? OR u.nickname LIKE ? ORDER BY u.surname DESC" using Expr class
|
||||
// example8: QueryBuilder port of:
|
||||
// "SELECT u FROM User u WHERE u.id = ? OR u.nickname LIKE ? ORDER BY u.name ASC" using Expr class
|
||||
$qb->add('select', new Expr\Select(array('u')))
|
||||
->add('from', new Expr\From('User', 'u'))
|
||||
->add('where', $qb->expr()->orX(
|
||||
@@ -433,6 +450,9 @@ complete list of supported helper methods available:
|
||||
// Example - $qb->expr()->like('u.firstname', $qb->expr()->literal('Gui%'))
|
||||
public function like($x, $y); // Returns Expr\Comparison instance
|
||||
|
||||
// Example - $qb->expr()->notLike('u.firstname', $qb->expr()->literal('Gui%'))
|
||||
public function notLike($x, $y); // Returns Expr\Comparison instance
|
||||
|
||||
// Example - $qb->expr()->between('u.id', '1', '10')
|
||||
public function between($val, $x, $y); // Returns Expr\Func
|
||||
|
||||
@@ -445,8 +465,8 @@ complete list of supported helper methods available:
|
||||
// Example - $qb->expr()->concat('u.firstname', $qb->expr()->concat($qb->expr()->literal(' '), 'u.lastname'))
|
||||
public function concat($x, $y); // Returns Expr\Func
|
||||
|
||||
// Example - $qb->expr()->substr('u.firstname', 0, 1)
|
||||
public function substr($x, $from, $len); // Returns Expr\Func
|
||||
// Example - $qb->expr()->substring('u.firstname', 0, 1)
|
||||
public function substring($x, $from, $len); // Returns Expr\Func
|
||||
|
||||
// Example - $qb->expr()->lower('u.firstname')
|
||||
public function lower($x); // Returns Expr\Func
|
||||
@@ -510,7 +530,9 @@ of DQL. It takes 3 parameters: ``$dqlPartName``, ``$dqlPart`` and
|
||||
<?php
|
||||
// $qb instanceof QueryBuilder
|
||||
|
||||
// example6: how to define: "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC" using QueryBuilder string support
|
||||
// example6: how to define:
|
||||
// "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC"
|
||||
// using QueryBuilder string support
|
||||
$qb->add('select', 'u')
|
||||
->add('from', 'User u')
|
||||
->add('where', 'u.id = ?1')
|
||||
@@ -529,7 +551,9 @@ same query of example 6 written using
|
||||
<?php
|
||||
// $qb instanceof QueryBuilder
|
||||
|
||||
// example7: how to define: "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC" using QueryBuilder using Expr\* instances
|
||||
// example7: how to define:
|
||||
// "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC"
|
||||
// using QueryBuilder using Expr\* instances
|
||||
$qb->add('select', new Expr\Select(array('u')))
|
||||
->add('from', new Expr\From('User', 'u'))
|
||||
->add('where', new Expr\Comparison('u.id', '=', '?1'))
|
||||
|
||||
731
docs/en/reference/second-level-cache.rst
Normal file
731
docs/en/reference/second-level-cache.rst
Normal file
@@ -0,0 +1,731 @@
|
||||
The Second Level Cache
|
||||
======================
|
||||
|
||||
.. note::
|
||||
|
||||
The second level cache functionality is marked as experimental for now. It
|
||||
is a very complex feature and we cannot guarantee yet that it works stable
|
||||
in all cases.
|
||||
|
||||
The Second Level Cache is designed to reduce the amount of necessary database access.
|
||||
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.
|
||||
|
||||
There are some flavors of caching available, but is better to cache read-only data.
|
||||
|
||||
Be aware that caches are not aware of changes made to the persistent store by another application.
|
||||
They can, however, be configured to regularly expire cached data.
|
||||
|
||||
|
||||
Caching Regions
|
||||
---------------
|
||||
|
||||
Second level cache does not store instances of an entity, instead it caches only entity identifier and values.
|
||||
Each entity class, collection association and query has its region, where values of each instance are stored.
|
||||
|
||||
Caching Regions are specific region into the cache provider that might store entities, collection or queries.
|
||||
Each cache region resides in a specific cache namespace and has its own lifetime configuration.
|
||||
|
||||
Notice that when caching collection and queries only identifiers are stored.
|
||||
The entity values will be stored in its own region
|
||||
|
||||
Something like below for an entity region :
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
[
|
||||
'region_name:entity_1_hash' => ['id'=> 1, 'name' => 'FooBar', 'associationName'=>null],
|
||||
'region_name:entity_2_hash' => ['id'=> 2, 'name' => 'Foo', 'associationName'=>['id'=>11]],
|
||||
'region_name:entity_3_hash' => ['id'=> 3, 'name' => 'Bar', 'associationName'=>['id'=>22]]
|
||||
];
|
||||
|
||||
|
||||
If the entity holds a collection that also needs to be cached.
|
||||
An collection region could look something like :
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
[
|
||||
'region_name:entity_1_coll_assoc_name_hash' => ['ownerId'=> 1, 'list' => [1, 2, 3]],
|
||||
'region_name:entity_2_coll_assoc_name_hash' => ['ownerId'=> 2, 'list' => [2, 3]],
|
||||
'region_name:entity_3_coll_assoc_name_hash' => ['ownerId'=> 3, 'list' => [2, 4]]
|
||||
];
|
||||
|
||||
A query region might be something like :
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
[
|
||||
'region_name:query_1_hash' => ['list' => [1, 2, 3]],
|
||||
'region_name:query_2_hash' => ['list' => [2, 3]],
|
||||
'region_name:query_3_hash' => ['list' => [2, 4]]
|
||||
];
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
The following data structures represents now the cache will looks like, this is not actual cached data.
|
||||
|
||||
|
||||
.. _reference-second-level-cache-regions:
|
||||
|
||||
Cache Regions
|
||||
-------------
|
||||
|
||||
``Doctrine\ORM\Cache\Region\DefaultRegion`` It's the default implementation.
|
||||
A simplest cache region compatible with all doctrine-cache drivers but does not support locking.
|
||||
|
||||
``Doctrine\ORM\Cache\Region`` and ``Doctrine\ORM\Cache\ConcurrentRegion``
|
||||
Defines contracts that should be implemented by a cache provider.
|
||||
|
||||
It allows you to provide your own cache implementation that might take advantage of specific cache driver.
|
||||
|
||||
If you want to support locking for ``READ_WRITE`` strategies you should implement ``ConcurrentRegion``; ``CacheRegion`` otherwise.
|
||||
|
||||
|
||||
Cache region
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Defines a contract for accessing a particular region.
|
||||
|
||||
``Doctrine\ORM\Cache\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/>`_.
|
||||
|
||||
Concurrent cache region
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A ``Doctrine\ORM\Cache\ConcurrentRegion`` is designed to store concurrently managed data region.
|
||||
By default, Doctrine provides a very simple implementation based on file locks ``Doctrine\ORM\Cache\Region\FileLockRegion``.
|
||||
|
||||
If you want to use an ``READ_WRITE`` cache, you should consider providing your own cache region.
|
||||
|
||||
``Doctrine\ORM\Cache\ConcurrentRegion``
|
||||
|
||||
Defines contract for concurrently managed data region.
|
||||
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.ConcurrentRegion.html/>`_.
|
||||
|
||||
Timestamp region
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
``Doctrine\ORM\Cache\TimestampRegion``
|
||||
|
||||
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/>`_.
|
||||
|
||||
.. _reference-second-level-cache-mode:
|
||||
|
||||
Caching mode
|
||||
------------
|
||||
|
||||
* ``READ_ONLY`` (DEFAULT)
|
||||
|
||||
* Can do reads, inserts and deletes, cannot perform updates or employ any locks.
|
||||
* Useful for data that is read frequently but never updated.
|
||||
* Best performer.
|
||||
* It is Simple.
|
||||
|
||||
* ``NONSTRICT_READ_WRITE``
|
||||
|
||||
* Read Write Cache doesn’t employ any locks but can do reads, inserts, updates and deletes.
|
||||
* Good if the application needs to update data rarely.
|
||||
|
||||
|
||||
* ``READ_WRITE``
|
||||
|
||||
* Read Write cache employs locks before update/delete.
|
||||
* Use if data needs to be updated.
|
||||
* Slowest strategy.
|
||||
* To use it a the cache region implementation must support locking.
|
||||
|
||||
|
||||
Built-in cached persisters
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Cached persisters are responsible to access cache regions.
|
||||
|
||||
+-----------------------+-------------------------------------------------------------------------------+
|
||||
| Cache Usage | Persister |
|
||||
+=======================+===============================================================================+
|
||||
| READ_ONLY | Doctrine\\ORM\\Cache\\Persister\\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 |
|
||||
+-----------------------+-------------------------------------------------------------------------------+
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
Doctrine allows you to specify configurations and some points of extension for the second-level-cache
|
||||
|
||||
|
||||
Enable Second Level Cache
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To enable the second-level-cache, you should provide a cache factory
|
||||
``\Doctrine\ORM\Cache\DefaultCacheFactory`` is the default implementation.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/* @var $config \Doctrine\ORM\Cache\RegionsConfiguration */
|
||||
/* @var $cache \Doctrine\Common\Cache\Cache */
|
||||
|
||||
$factory = new \Doctrine\ORM\Cache\DefaultCacheFactory($config, $cache);
|
||||
|
||||
// Enable second-level-cache
|
||||
$config->setSecondLevelCacheEnabled();
|
||||
|
||||
// Cache factory
|
||||
$config->getSecondLevelCacheConfiguration()
|
||||
->setCacheFactory($factory);
|
||||
|
||||
|
||||
Cache Factory
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Cache Factory is the main point of extension.
|
||||
|
||||
It allows you to provide a specific implementation of the following components :
|
||||
|
||||
* ``QueryCache`` Store and retrieve query cache results.
|
||||
* ``CachedEntityPersister`` Store and retrieve entity results.
|
||||
* ``CachedCollectionPersister`` Store and retrieve query results.
|
||||
* ``EntityHydrator`` Transform an entity into a cache entry and cache entry into entities
|
||||
* ``CollectionHydrator`` Transform a collection into a cache entry and cache entry into collection
|
||||
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.DefaultCacheFactory.html/>`_.
|
||||
|
||||
Region Lifetime
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
To specify a default lifetime for all regions or specify a different lifetime for a specific region.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/* @var $config \Doctrine\ORM\Configuration */
|
||||
/* @var $cacheConfig \Doctrine\ORM\Configuration */
|
||||
$cacheConfig = $config->getSecondLevelCacheConfiguration();
|
||||
$regionConfig = $cacheConfig->getRegionsConfiguration();
|
||||
|
||||
// Cache Region lifetime
|
||||
$regionConfig->setLifetime('my_entity_region', 3600); // Time to live for a specific region; In seconds
|
||||
$regionConfig->setDefaultLifetime(7200); // Default time to live; In seconds
|
||||
|
||||
|
||||
Cache Log
|
||||
~~~~~~~~~
|
||||
By providing a cache logger you should be able to get information about all cache operations such as hits, misses and puts.
|
||||
|
||||
``\Doctrine\ORM\Cache\Logging\StatisticsCacheLogger`` is a built-in implementation that provides basic statistics.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/* @var $config \Doctrine\ORM\Configuration */
|
||||
$logger = \Doctrine\ORM\Cache\Logging\StatisticsCacheLogger();
|
||||
|
||||
// Cache logger
|
||||
$config->setSecondLevelCacheEnabled(true);
|
||||
$config->getSecondLevelCacheConfiguration()
|
||||
->setCacheLogger($logger);
|
||||
|
||||
|
||||
// Collect cache statistics
|
||||
|
||||
// Get the number of entries successfully retrieved from a specific region.
|
||||
$logger->getRegionHitCount('my_entity_region');
|
||||
|
||||
// Get the number of cached entries *not* found in a specific region.
|
||||
$logger->getRegionMissCount('my_entity_region');
|
||||
|
||||
// Get the number of cacheable entries put in cache.
|
||||
$logger->getRegionPutCount('my_entity_region');
|
||||
|
||||
// Get the total number of put in all regions.
|
||||
$logger->getPutCount();
|
||||
|
||||
// Get the total number of entries successfully retrieved from all regions.
|
||||
$logger->getHitCount();
|
||||
|
||||
// Get the total number of cached entries *not* found in all regions.
|
||||
$logger->getMissCount();
|
||||
|
||||
If you want to get more information you should implement ``\Doctrine\ORM\Cache\Logging\CacheLogger``.
|
||||
and collect all information you want.
|
||||
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.CacheLogger.html/>`_.
|
||||
|
||||
|
||||
Entity cache definition
|
||||
-----------------------
|
||||
* Entity cache configuration allows you to define the caching strategy and region for an entity.
|
||||
|
||||
* ``usage`` Specifies the caching strategy: ``READ_ONLY``, ``NONSTRICT_READ_WRITE``, ``READ_WRITE``. see :ref:`reference-second-level-cache-mode`
|
||||
* ``region`` Optional value that specifies the name of the second level cache region.
|
||||
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
* @Cache(usage="READ_ONLY", region="my_entity_region")
|
||||
*/
|
||||
class Country
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @GeneratedValue
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @Column(unique=true)
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
// other properties and methods
|
||||
}
|
||||
|
||||
.. 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">
|
||||
<entity name="Country">
|
||||
<cache usage="READ_ONLY" region="my_entity_region" />
|
||||
<id name="id" type="integer" column="id">
|
||||
<generator strategy="IDENTITY"/>
|
||||
</id>
|
||||
<field name="name" type="string" column="name"/>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
Country:
|
||||
type: entity
|
||||
cache:
|
||||
usage : READ_ONLY
|
||||
region : my_entity_region
|
||||
id:
|
||||
id:
|
||||
type: integer
|
||||
id: true
|
||||
generator:
|
||||
strategy: IDENTITY
|
||||
fields:
|
||||
name:
|
||||
type: string
|
||||
|
||||
|
||||
Association cache definition
|
||||
----------------------------
|
||||
The most common use case is to cache entities. But we can also cache relationships.
|
||||
It caches the primary keys of association and cache each element will be cached into its region.
|
||||
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
* @Cache("NONSTRICT_READ_WRITE")
|
||||
*/
|
||||
class State
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @GeneratedValue
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @Column(unique=true)
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @Cache("NONSTRICT_READ_WRITE")
|
||||
* @ManyToOne(targetEntity="Country")
|
||||
* @JoinColumn(name="country_id", referencedColumnName="id")
|
||||
*/
|
||||
protected $country;
|
||||
|
||||
/**
|
||||
* @Cache("NONSTRICT_READ_WRITE")
|
||||
* @OneToMany(targetEntity="City", mappedBy="state")
|
||||
*/
|
||||
protected $cities;
|
||||
|
||||
// other properties and methods
|
||||
}
|
||||
|
||||
.. 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">
|
||||
<entity name="State">
|
||||
|
||||
<cache usage="NONSTRICT_READ_WRITE" />
|
||||
|
||||
<id name="id" type="integer" column="id">
|
||||
<generator strategy="IDENTITY"/>
|
||||
</id>
|
||||
|
||||
<field name="name" type="string" column="name"/>
|
||||
|
||||
<many-to-one field="country" target-entity="Country">
|
||||
<cache usage="NONSTRICT_READ_WRITE" />
|
||||
|
||||
<join-columns>
|
||||
<join-column name="country_id" referenced-column-name="id"/>
|
||||
</join-columns>
|
||||
</many-to-one>
|
||||
|
||||
<one-to-many field="cities" target-entity="City" mapped-by="state">
|
||||
<cache usage="NONSTRICT_READ_WRITE"/>
|
||||
</one-to-many>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
State:
|
||||
type: entity
|
||||
cache:
|
||||
usage : NONSTRICT_READ_WRITE
|
||||
id:
|
||||
id:
|
||||
type: integer
|
||||
id: true
|
||||
generator:
|
||||
strategy: IDENTITY
|
||||
fields:
|
||||
name:
|
||||
type: string
|
||||
|
||||
manyToOne:
|
||||
state:
|
||||
targetEntity: Country
|
||||
joinColumns:
|
||||
country_id:
|
||||
referencedColumnName: id
|
||||
cache:
|
||||
usage : NONSTRICT_READ_WRITE
|
||||
|
||||
oneToMany:
|
||||
cities:
|
||||
targetEntity:City
|
||||
mappedBy: state
|
||||
cache:
|
||||
usage : NONSTRICT_READ_WRITE
|
||||
|
||||
|
||||
> Note: for this to work, the target entity must also be marked as cacheable.
|
||||
|
||||
Cache usage
|
||||
~~~~~~~~~~~
|
||||
|
||||
Basic entity cache
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$em->persist(new Country($name));
|
||||
$em->flush(); // Hit database to insert the row and put into cache
|
||||
|
||||
$em->clear(); // Clear entity manager
|
||||
|
||||
$country1 = $em->find('Country', 1); // Retrieve item from cache
|
||||
|
||||
$country->setName("New Name");
|
||||
$em->persist($country);
|
||||
$em->flush(); // Hit database to update the row and update cache
|
||||
|
||||
$em->clear(); // Clear entity manager
|
||||
|
||||
$country2 = $em->find('Country', 1); // Retrieve item from cache
|
||||
// Notice that $country1 and $country2 are not the same instance.
|
||||
|
||||
|
||||
Association cache
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// Hit database to insert the row and put into cache
|
||||
$em->persist(new State($name, $country));
|
||||
$em->flush();
|
||||
|
||||
// Clear entity manager
|
||||
$em->clear();
|
||||
|
||||
// Retrieve item from cache
|
||||
$state = $em->find('State', 1);
|
||||
|
||||
// Hit database to update the row and update cache entry
|
||||
$state->setName("New Name");
|
||||
$em->persist($state);
|
||||
$em->flush();
|
||||
|
||||
// Create a new collection item
|
||||
$city = new City($name, $state);
|
||||
$state->addCity($city);
|
||||
|
||||
// Hit database to insert new collection item,
|
||||
// put entity and collection cache into cache.
|
||||
$em->persist($city);
|
||||
$em->persist($state);
|
||||
$em->flush();
|
||||
|
||||
// Clear entity manager
|
||||
$em->clear();
|
||||
|
||||
// Retrieve item from cache
|
||||
$state = $em->find('State', 1);
|
||||
|
||||
// Retrieve association from cache
|
||||
$country = $state->getCountry();
|
||||
|
||||
// Retrieve collection from cache
|
||||
$cities = $state->getCities();
|
||||
|
||||
echo $country->getName();
|
||||
echo $state->getName();
|
||||
|
||||
// Retrieve each collection item from cache
|
||||
foreach ($cities as $city) {
|
||||
echo $city->getName();
|
||||
}
|
||||
|
||||
.. note::
|
||||
|
||||
Notice that all entities should be marked as cacheable.
|
||||
|
||||
Using the query cache
|
||||
---------------------
|
||||
|
||||
The second level cache stores the entities, associations and collections.
|
||||
The query cache stores the results of the query but as identifiers, entity values are actually stored in the 2nd level cache.
|
||||
|
||||
.. note::
|
||||
|
||||
Query cache should always be used in conjunction with the second-level-cache for those entities which should be cached.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/* @var $em \Doctrine\ORM\EntityManager */
|
||||
|
||||
// Execute database query, store query cache and entity cache
|
||||
$result1 = $em->createQuery('SELECT c FROM Country c ORDER BY c.name')
|
||||
->setCacheable(true)
|
||||
->getResult();
|
||||
|
||||
$em->clear()
|
||||
|
||||
// Check if query result is valid and load entities from cache
|
||||
$result2 = $em->createQuery('SELECT c FROM Country c ORDER BY c.name')
|
||||
->setCacheable(true)
|
||||
->getResult();
|
||||
|
||||
Cache mode
|
||||
~~~~~~~~~~
|
||||
|
||||
The Cache Mode controls how a particular query interacts with the second-level cache:
|
||||
|
||||
* ``Cache::MODE_GET`` - May read items from the cache, but will not add items.
|
||||
* ``Cache::MODE_PUT`` - Will never read items from the cache, but will add items to the cache as it reads them from the database.
|
||||
* ``Cache::MODE_NORMAL`` - May read items from the cache, and add items to the cache.
|
||||
* ``Cache::MODE_REFRESH`` - The query will never read items from the cache, but will refresh items to the cache as it reads them from the database.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/* @var $em \Doctrine\ORM\EntityManager */
|
||||
// Will refresh the query cache and all entities the cache as it reads from the database.
|
||||
$result1 = $em->createQuery('SELECT c FROM Country c ORDER BY c.name')
|
||||
->setCacheMode(Cache::MODE_GET)
|
||||
->setCacheable(true)
|
||||
->getResult();
|
||||
|
||||
.. note::
|
||||
|
||||
The the default query cache mode is ```Cache::MODE_NORMAL```
|
||||
|
||||
DELETE / UPDATE queries
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
DQL UPDATE / DELETE statements are ported directly into a database and bypass the second-level cache,
|
||||
Entities that are already cached will NOT be invalidated.
|
||||
However the cached data could be evicted using the cache API or an special query hint.
|
||||
|
||||
|
||||
Execute the ``UPDATE`` and invalidate ``all cache entries`` using ``Query::HINT_CACHE_EVICT``
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// Execute and invalidate
|
||||
$this->_em->createQuery("UPDATE Entity\Country u SET u.name = 'unknown' WHERE u.id = 1")
|
||||
->setHint(Query::HINT_CACHE_EVICT, true)
|
||||
->execute();
|
||||
|
||||
|
||||
Execute the ``UPDATE`` and invalidate ``all cache entries`` using the cache API
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// Execute
|
||||
$this->_em->createQuery("UPDATE Entity\Country u SET u.name = 'unknown' WHERE u.id = 1")
|
||||
->execute();
|
||||
// Invoke Cache API
|
||||
$em->getCache()->evictEntityRegion('Entity\Country');
|
||||
|
||||
|
||||
Execute the ``UPDATE`` and invalidate ``a specific cache entry`` using the cache API
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// Execute
|
||||
$this->_em->createQuery("UPDATE Entity\Country u SET u.name = 'unknown' WHERE u.id = 1")
|
||||
->execute();
|
||||
// Invoke Cache API
|
||||
$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,
|
||||
When a query is loaded from cache, the timestamp region is checked for the last update for that persister.
|
||||
Using the last update timestamps as part of the query key invalidate the cache key when an update occurs.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// load from database and store cache query key hashing the query + parameters + last timestamp cache region..
|
||||
$entities = $em->getRepository('Entity\Country')->findAll();
|
||||
|
||||
// load from query and entities from cache..
|
||||
$entities = $em->getRepository('Entity\Country')->findAll();
|
||||
|
||||
// update the timestamp cache region for Country
|
||||
$em->persist(new Country('zombieland'));
|
||||
$em->flush();
|
||||
$em->clear();
|
||||
|
||||
// Reload from database.
|
||||
// At this point the query cache key if not logger valid, the select goes straight
|
||||
$entities = $em->getRepository('Entity\Country')->findAll();
|
||||
|
||||
Cache API
|
||||
---------
|
||||
|
||||
Caches are not aware of changes made by another application.
|
||||
However, you can use the cache API to check / invalidate cache entries.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/* @var $cache \Doctrine\ORM\Cache */
|
||||
$cache = $em->getCache();
|
||||
|
||||
$cache->containsEntity('Entity\State', 1) // Check if the cache exists
|
||||
$cache->evictEntity('Entity\State', 1); // Remove an entity from cache
|
||||
$cache->evictEntityRegion('Entity\State'); // Remove all entities from cache
|
||||
|
||||
$cache->containsCollection('Entity\State', 'cities', 1); // Check if the cache exists
|
||||
$cache->evictCollection('Entity\State', 'cities', 1); // Remove an entity collection from cache
|
||||
$cache->evictCollectionRegion('Entity\State', 'cities'); // Remove all collections from cache
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
Composite primary key
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Composite primary key are supported by second level cache,
|
||||
however when one of the keys is an association the cached entity should always be retrieved using the association identifier.
|
||||
For performance reasons the cache API does not extract from composite primary key.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class Reference
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @ManyToOne(targetEntity="Article", inversedBy="references")
|
||||
* @JoinColumn(name="source_id", referencedColumnName="article_id")
|
||||
*/
|
||||
private $source;
|
||||
|
||||
/**
|
||||
* @Id
|
||||
* @ManyToOne(targetEntity="Article")
|
||||
* @JoinColumn(name="target_id", referencedColumnName="article_id")
|
||||
*/
|
||||
private $target;
|
||||
}
|
||||
|
||||
// Supported
|
||||
/* @var $article Article */
|
||||
$article = $em->find('Article', 1);
|
||||
|
||||
// Supported
|
||||
/* @var $article Article */
|
||||
$article = $em->find('Article', $article);
|
||||
|
||||
// Supported
|
||||
$id = array('source' => 1, 'target' => 2);
|
||||
$reference = $em->find('Reference', $id);
|
||||
|
||||
// NOT Supported
|
||||
$id = array('source' => new Article(1), 'target' => new Article(2));
|
||||
$reference = $em->find('Reference', $id);
|
||||
|
||||
Distributed environments
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Some cache driver are not meant to be used in a distributed environment.
|
||||
Load-balancer for distributing workloads across multiple computing resources
|
||||
should be used in conjunction with distributed caching system such as memcached, redis, riak ...
|
||||
|
||||
Caches should be used with care when using a load-balancer if you don't share the cache.
|
||||
While using APC or any file based cache update occurred in a specific machine would not reflect to the cache in other machines.
|
||||
|
||||
|
||||
Paginator
|
||||
~~~~~~~~~
|
||||
|
||||
Count queries generated by ``Doctrine\ORM\Tools\Pagination\Paginator`` are not cached by second-level cache.
|
||||
Although entities and query result are cached count queries will hit the database every time.
|
||||
151
docs/en/reference/security.rst
Normal file
151
docs/en/reference/security.rst
Normal file
@@ -0,0 +1,151 @@
|
||||
Security
|
||||
========
|
||||
|
||||
The Doctrine library is operating very close to your database and as such needs
|
||||
to handle and make assumptions about SQL injection vulnerabilities.
|
||||
|
||||
It is vital that you understand how Doctrine approaches security, because
|
||||
we cannot protect you from SQL injection.
|
||||
|
||||
Please also read the documentation chapter on Security in Doctrine DBAL. This
|
||||
page only handles Security issues in the ORM.
|
||||
|
||||
- [DBAL Security Page](https://github.com/doctrine/dbal/blob/master/docs/en/reference/security.rst)
|
||||
|
||||
If you find a Security bug in Doctrine, please report it on Jira and change the
|
||||
Security Level to "Security Issues". It will be visible to Doctrine Core
|
||||
developers and you only.
|
||||
|
||||
User input and Doctrine ORM
|
||||
---------------------------
|
||||
|
||||
The ORM is much better at protecting against SQL injection than the DBAL alone.
|
||||
You can consider the following APIs to be safe from SQL injection:
|
||||
|
||||
- ``\Doctrine\ORM\EntityManager#find()`` and ``getReference()``.
|
||||
- All values on Objects inserted and updated through ``Doctrine\ORM\EntityManager#persist()``
|
||||
- All find methods on ``Doctrine\ORM\EntityRepository``.
|
||||
- User Input set to DQL Queries or QueryBuilder methods through
|
||||
- ``setParameter()`` or variants
|
||||
- ``setMaxResults()``
|
||||
- ``setFirstResult()``
|
||||
- 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:
|
||||
|
||||
- Expression API of ``Doctrine\ORM\QueryBuilder``
|
||||
- Concatenating user input into DQL SELECT, UPDATE or DELETE statements or
|
||||
Native SQL.
|
||||
|
||||
This means SQL injections can only occur with Doctrine ORM when working with
|
||||
Query Objects of any kind. The safe rule is to always use prepared statement
|
||||
parameters for user objects when using a Query object.
|
||||
|
||||
.. warning::
|
||||
|
||||
Insecure code follows, don't copy paste this.
|
||||
|
||||
The following example shows insecure DQL usage:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
// INSECURE
|
||||
$dql = "SELECT u
|
||||
FROM MyProject\Entity\User u
|
||||
WHERE u.status = '" . $_GET['status'] . "'
|
||||
ORDER BY " . $_GET['orderField'] . " ASC";
|
||||
|
||||
For Doctrine there is absolutely no way to find out which parts of ``$dql`` are
|
||||
from user input and which are not, even if we have our own parsing process
|
||||
this is technically impossible. The correct way is:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
$orderFieldWhitelist = array('email', 'username');
|
||||
$orderField = "email";
|
||||
|
||||
if (in_array($_GET['orderField'], $orderFieldWhitelist)) {
|
||||
$orderField = $_GET['orderField'];
|
||||
}
|
||||
|
||||
$dql = "SELECT u
|
||||
FROM MyProject\Entity\User u
|
||||
WHERE u.status = ?1
|
||||
ORDER BY u." . $orderField . " ASC";
|
||||
|
||||
$query = $entityManager->createQuery($dql);
|
||||
$query->setParameter(1, $_GET['status']);
|
||||
|
||||
|
||||
Preventing Mass Assignment Vulnerabilities
|
||||
------------------------------------------
|
||||
|
||||
ORMs are very convenient for CRUD applications and Doctrine is no exception.
|
||||
However CRUD apps are often vulnerable to mass assignment security problems
|
||||
when implemented naively.
|
||||
|
||||
Doctrine is not vulnerable to this problem out of the box, but you can easily
|
||||
make your entities vulnerable to mass assignment when you add methods of
|
||||
the kind ``updateFromArray()`` or ``updateFromJson()`` to them. A vulnerable
|
||||
entity might look like this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class InsecureEntity
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private $id;
|
||||
/** @Column */
|
||||
private $email;
|
||||
/** @Column(type="boolean") */
|
||||
private $isAdmin;
|
||||
|
||||
public function fromArray(array $userInput)
|
||||
{
|
||||
foreach ($userInput as $key => $value) {
|
||||
$this->$key = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Now the possiblity of mass-asignment exists on this entity and can
|
||||
be exploitet by attackers to set the "isAdmin" flag to true on any
|
||||
object when you pass the whole request data to this method like:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$entity = new InsecureEntity();
|
||||
$entity->fromArray($_POST);
|
||||
|
||||
$entityManager->persist($entity);
|
||||
$entityManager->flush();
|
||||
|
||||
You can spot this problem in this very simple example easily. However
|
||||
in combination with frameworks and form libraries it might not be
|
||||
so obvious when this issue arises. Be careful to avoid this
|
||||
kind of mistake.
|
||||
|
||||
How to fix this problem? You should always have a whitelist
|
||||
of allowed key to set via mass assignment functions.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
public function fromArray(array $userInput, $allowedFields = array())
|
||||
{
|
||||
foreach ($userInput as $key => $value) {
|
||||
if (in_array($key, $allowedFields)) {
|
||||
$this->$key = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,8 +33,8 @@ already registers all the commands that currently ship with
|
||||
Doctrine DBAL and ORM. If you want to use additional commands you
|
||||
have to register them yourself.
|
||||
|
||||
All the commands of the Doctrine Console require access to the EntityManager
|
||||
or DBAL Connection. You have to inject them into the console application
|
||||
All the commands of the Doctrine Console require access to the ``EntityManager``
|
||||
or ``DBAL`` Connection. You have to inject them into the console application
|
||||
using so called Helper-Sets. This requires either the ``db``
|
||||
or the ``em`` helpers to be defined in order to work correctly.
|
||||
|
||||
@@ -507,3 +507,22 @@ defined ones) is possible through the command:
|
||||
new \MyProject\Tools\Console\Commands\AnotherCommand(),
|
||||
new \MyProject\Tools\Console\Commands\OneMoreCommand(),
|
||||
));
|
||||
|
||||
|
||||
Re-use console application
|
||||
--------------------------
|
||||
|
||||
You are also able to retrieve and re-use the default console application.
|
||||
Just call ``ConsoleRunner::createApplication(...)`` with an appropriate
|
||||
HelperSet, like it is described in the configuration section.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
// Retrieve default console application
|
||||
$cli = ConsoleRunner::createApplication($helperSet);
|
||||
|
||||
// Runs console application
|
||||
$cli->run();
|
||||
|
||||
|
||||
@@ -70,7 +70,6 @@ looks like this:
|
||||
$em->getConnection()->commit();
|
||||
} catch (Exception $e) {
|
||||
$em->getConnection()->rollback();
|
||||
$em->close();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
@@ -81,14 +80,12 @@ require an active transaction. Such methods will throw a
|
||||
``TransactionRequiredException`` to inform you of that
|
||||
requirement.
|
||||
|
||||
A more convenient alternative for explicit transaction demarcation
|
||||
is the use of provided control abstractions in the form of
|
||||
``Connection#transactional($func)`` and
|
||||
``EntityManager#transactional($func)``. When used, these control
|
||||
abstractions ensure that you never forget to rollback the
|
||||
transaction or close the ``EntityManager``, apart from the obvious
|
||||
code reduction. An example that is functionally equivalent to the
|
||||
previously shown code looks as follows:
|
||||
A more convenient alternative for explicit transaction demarcation is the use
|
||||
of provided control abstractions in the form of
|
||||
``Connection#transactional($func)`` and ``EntityManager#transactional($func)``.
|
||||
When used, these control abstractions ensure that you never forget to rollback
|
||||
the transaction, in addition to the obvious code reduction. An example that is
|
||||
functionally equivalent to the previously shown code looks as follows:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -104,8 +101,8 @@ previously shown code looks as follows:
|
||||
The difference between ``Connection#transactional($func)`` and
|
||||
``EntityManager#transactional($func)`` is that the latter
|
||||
abstraction flushes the ``EntityManager`` prior to transaction
|
||||
commit and also closes the ``EntityManager`` properly when an
|
||||
exception occurs (in addition to rolling back the transaction).
|
||||
commit and rolls back the transaction when an
|
||||
exception occurs.
|
||||
|
||||
Exception Handling
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -157,7 +157,7 @@ wishes to be hydrated. Default result-types include:
|
||||
- SQL to a single result variable
|
||||
|
||||
Hydration to entities and arrays is one of most complex parts of Doctrine
|
||||
algorithm-wise. It can built results with for example:
|
||||
algorithm-wise. It can build results with for example:
|
||||
|
||||
- Single table selects
|
||||
- Joins with n:1 or 1:n cardinality, grouping belonging to the same parent.
|
||||
|
||||
@@ -2,27 +2,25 @@ Working with Associations
|
||||
=========================
|
||||
|
||||
Associations between entities are represented just like in regular
|
||||
object-oriented PHP, with references to other objects or
|
||||
collections of objects. When it comes to persistence, it is
|
||||
important to understand three main things:
|
||||
object-oriented PHP code using references to other objects or
|
||||
collections of objects.
|
||||
|
||||
Changes to associations in your code are not synchronized to the
|
||||
database directly, only when calling ``EntityManager#flush()``.
|
||||
|
||||
There are other concepts you should know about when working
|
||||
with associations in Doctrine:
|
||||
|
||||
- The :doc:`concept of owning and inverse sides <unitofwork-associations>`
|
||||
in bidirectional associations.
|
||||
- If an entity is removed from a collection, the association is
|
||||
removed, not the entity itself. A collection of entities always
|
||||
only represents the association to the containing entities, not the
|
||||
entity itself.
|
||||
- Collection-valued :ref:`persistent fields <architecture_persistent_fields>` have to be instances of the
|
||||
- When a bidirectional assocation 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
|
||||
``Doctrine\Common\Collections\Collection`` interface.
|
||||
|
||||
Changes to associations in your code are not synchronized to the
|
||||
database directly, but upon calling ``EntityManager#flush()``.
|
||||
|
||||
To describe all the concepts of working with associations we
|
||||
introduce a specific set of example entities that show all the
|
||||
different flavors of association management in Doctrine.
|
||||
|
||||
Association Example Entities
|
||||
----------------------------
|
||||
|
||||
@@ -44,10 +42,7 @@ information about its type and if it's the owning or inverse side.
|
||||
* Bidirectional - Many users have Many favorite comments (OWNING SIDE)
|
||||
*
|
||||
* @ManyToMany(targetEntity="Comment", inversedBy="userFavorites")
|
||||
* @JoinTable(name="user_favorite_comments",
|
||||
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
|
||||
* inverseJoinColumns={@JoinColumn(name="favorite_comment_id", referencedColumnName="id")}
|
||||
* )
|
||||
* @JoinTable(name="user_favorite_comments")
|
||||
*/
|
||||
private $favorites;
|
||||
|
||||
@@ -55,10 +50,7 @@ information about its type and if it's the owning or inverse side.
|
||||
* Unidirectional - Many users have marked many comments as read
|
||||
*
|
||||
* @ManyToMany(targetEntity="Comment")
|
||||
* @JoinTable(name="user_read_comments",
|
||||
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
|
||||
* inverseJoinColumns={@JoinColumn(name="comment_id", referencedColumnName="id")}
|
||||
* )
|
||||
* @JoinTable(name="user_read_comments")
|
||||
*/
|
||||
private $commentsRead;
|
||||
|
||||
@@ -413,7 +405,7 @@ There are two approaches to handle this problem in your code:
|
||||
Transitive persistence / Cascade Operations
|
||||
-------------------------------------------
|
||||
|
||||
Persisting, removing, detaching and merging individual entities can
|
||||
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
|
||||
@@ -429,7 +421,8 @@ The following cascade options exist:
|
||||
- remove : Cascades remove operations to the associated entities.
|
||||
- merge : Cascades merge operations to the associated entities.
|
||||
- detach : Cascades detach operations to the associated entities.
|
||||
- all : Cascades persist, remove, merge and detach operations to
|
||||
- refresh : Cascades refresh operations to the associated entities.
|
||||
- all : Cascades persist, remove, merge, refresh and detach operations to
|
||||
associated entities.
|
||||
|
||||
.. note::
|
||||
@@ -474,9 +467,10 @@ removed from the system:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$user = $em->find('User', $deleteUserId);
|
||||
|
||||
foreach ($user->getAuthoredComments() AS $comment) {
|
||||
foreach ($user->getAuthoredComments() as $comment) {
|
||||
$em->remove($comment);
|
||||
}
|
||||
$em->remove($user);
|
||||
@@ -630,7 +624,7 @@ large collections.
|
||||
|
||||
$criteria = Criteria::create()
|
||||
->where(Criteria::expr()->eq("birthday", "1982-02-17"))
|
||||
->orderBy(array("username" => "ASC"))
|
||||
->orderBy(array("username" => Criteria::ASC))
|
||||
->setFirstResult(0)
|
||||
->setMaxResults(20)
|
||||
;
|
||||
@@ -708,5 +702,11 @@ methods:
|
||||
* ``isNull($field)``
|
||||
* ``in($field, array $values)``
|
||||
* ``notIn($field, array $values)``
|
||||
* ``contains($field, $value)``
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
There is a limitation on the compatibility of Criteria comparisons.
|
||||
You have to use scalar values only as the value in a comparison or
|
||||
the behaviour between different backends is not the same.
|
||||
|
||||
@@ -103,7 +103,8 @@ from newly opened EntityManager.
|
||||
/** @OneToMany(targetEntity="Comment", mappedBy="article") */
|
||||
private $comments;
|
||||
|
||||
public function __construct {
|
||||
public function __construct()
|
||||
{
|
||||
$this->comments = new ArrayCollection();
|
||||
}
|
||||
|
||||
@@ -114,7 +115,7 @@ from newly opened EntityManager.
|
||||
$article = $em->find('Article', 1);
|
||||
|
||||
This code only retrieves the ``Article`` instance with id 1 executing
|
||||
a single SELECT statement against the user table in the database.
|
||||
a single SELECT statement against the articles table in the database.
|
||||
You can still access the associated properties author and comments
|
||||
and the associated objects they contain.
|
||||
|
||||
@@ -143,7 +144,7 @@ your code. See the following code:
|
||||
// accessing the comments as an iterator triggers the lazy-load
|
||||
// retrieving ALL the comments of this article from the database
|
||||
// using a single SELECT statement
|
||||
foreach ($article->getComments() AS $comment) {
|
||||
foreach ($article->getComments() as $comment) {
|
||||
echo $comment->getText() . "\n\n";
|
||||
}
|
||||
|
||||
@@ -820,9 +821,10 @@ in a central location.
|
||||
namespace MyDomain\Model;
|
||||
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @entity(repositoryClass="MyDomain\Model\UserRepository")
|
||||
* @ORM\Entity(repositoryClass="MyDomain\Model\UserRepository")
|
||||
*/
|
||||
class User
|
||||
{
|
||||
|
||||
@@ -22,9 +22,9 @@ setup for the latest code in trunk.
|
||||
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">
|
||||
|
||||
|
||||
...
|
||||
|
||||
|
||||
</doctrine-mapping>
|
||||
|
||||
The XML mapping document of a class is loaded on-demand the first
|
||||
@@ -44,8 +44,6 @@ In order to work, this requires certain conventions:
|
||||
convention and you are not forced to do this. You can change the
|
||||
file extension easily enough.
|
||||
|
||||
-
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
@@ -64,6 +62,16 @@ of the constructor, like this:
|
||||
$driver = new \Doctrine\ORM\Mapping\Driver\XmlDriver(array('/path/to/files1', '/path/to/files2'));
|
||||
$config->setMetadataDriverImpl($driver);
|
||||
|
||||
.. warning::
|
||||
|
||||
Note that Doctrine ORM does not modify any settings for ``libxml``,
|
||||
therefore, external XML entities may or may not be enabled or
|
||||
configured correctly.
|
||||
XML mappings are not XXE/XEE attack vectors since they are not
|
||||
related with user input, but it is recommended that you do not
|
||||
use external XML entities in your mapping files to avoid running
|
||||
into unexpected behaviour.
|
||||
|
||||
Simplified XML Driver
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -80,8 +88,8 @@ Configuration of this client works a little bit different:
|
||||
|
||||
<?php
|
||||
$namespaces = array(
|
||||
'MyProject\Entities' => '/path/to/files1',
|
||||
'OtherProject\Entities' => '/path/to/files2'
|
||||
'/path/to/files1' => 'MyProject\Entities',
|
||||
'/path/to/files2' => 'OtherProject\Entities'
|
||||
);
|
||||
$driver = new \Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver($namespaces);
|
||||
$driver->setGlobalBasename('global'); // global.orm.xml
|
||||
@@ -100,37 +108,37 @@ of several common elements:
|
||||
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">
|
||||
|
||||
|
||||
<entity name="Doctrine\Tests\ORM\Mapping\User" table="cms_users">
|
||||
|
||||
|
||||
<indexes>
|
||||
<index name="name_idx" columns="name"/>
|
||||
<index columns="user_email"/>
|
||||
</indexes>
|
||||
|
||||
|
||||
<unique-constraints>
|
||||
<unique-constraint columns="name,user_email" name="search_idx" />
|
||||
</unique-constraints>
|
||||
|
||||
|
||||
<lifecycle-callbacks>
|
||||
<lifecycle-callback type="prePersist" method="doStuffOnPrePersist"/>
|
||||
<lifecycle-callback type="prePersist" method="doOtherStuffOnPrePersistToo"/>
|
||||
<lifecycle-callback type="postPersist" method="doStuffOnPostPersist"/>
|
||||
</lifecycle-callbacks>
|
||||
|
||||
|
||||
<id name="id" type="integer" column="id">
|
||||
<generator strategy="AUTO"/>
|
||||
<sequence-generator sequence-name="tablename_seq" allocation-size="100" initial-value="1" />
|
||||
</id>
|
||||
|
||||
|
||||
<field name="name" column="name" type="string" length="50" nullable="true" unique="true" />
|
||||
<field name="email" column="user_email" type="string" column-definition="CHAR(32) NOT NULL" />
|
||||
|
||||
|
||||
<one-to-one field="address" target-entity="Address" inversed-by="user">
|
||||
<cascade><cascade-remove /></cascade>
|
||||
<join-column name="address_id" referenced-column-name="id" on-delete="CASCADE" on-update="CASCADE"/>
|
||||
</one-to-one>
|
||||
|
||||
|
||||
<one-to-many field="phonenumbers" target-entity="Phonenumber" mapped-by="user">
|
||||
<cascade>
|
||||
<cascade-persist/>
|
||||
@@ -139,7 +147,7 @@ of several common elements:
|
||||
<order-by-field name="number" direction="ASC" />
|
||||
</order-by>
|
||||
</one-to-many>
|
||||
|
||||
|
||||
<many-to-many field="groups" target-entity="Group">
|
||||
<cascade>
|
||||
<cascade-all/>
|
||||
@@ -153,9 +161,9 @@ of several common elements:
|
||||
</inverse-join-columns>
|
||||
</join-table>
|
||||
</many-to-many>
|
||||
|
||||
|
||||
</entity>
|
||||
|
||||
|
||||
</doctrine-mapping>
|
||||
|
||||
Be aware that class-names specified in the XML files should be
|
||||
@@ -179,7 +187,7 @@ specified as the ``<entity />`` element as a direct child of the
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="MyProject\User" table="cms_users" repository-class="MyProject\UserRepository">
|
||||
<entity name="MyProject\User" table="cms_users" schema="schema_name" repository-class="MyProject\UserRepository">
|
||||
<!-- definition here -->
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
@@ -203,6 +211,7 @@ Optional attributes:
|
||||
- **read-only** - (>= 2.1) Specifies that this entity is marked as read only and not
|
||||
considered for change-tracking. Entities of this type can be persisted
|
||||
and removed though.
|
||||
- **schema** - (>= 2.5) The schema the table lies in, for platforms that support schemas
|
||||
|
||||
Defining Fields
|
||||
~~~~~~~~~~~~~~~
|
||||
@@ -216,12 +225,18 @@ entity. For the ID mapping you have to use the ``<id />`` element.
|
||||
.. code-block:: xml
|
||||
|
||||
<entity name="MyProject\User">
|
||||
|
||||
|
||||
<field name="name" type="string" length="50" />
|
||||
<field name="username" type="string" unique="true" />
|
||||
<field name="age" type="integer" nullable="true" />
|
||||
<field name="isActive" column="is_active" type="boolean" />
|
||||
<field name="weight" type="decimal" scale="5" precision="2" />
|
||||
<field name="login_count" type="integer" nullable="false">
|
||||
<options>
|
||||
<option name="comment">The number of times the user has logged in.</option>
|
||||
<option name="default">0</option>
|
||||
</options>
|
||||
</field>
|
||||
</entity>
|
||||
|
||||
Required attributes:
|
||||
@@ -247,12 +262,32 @@ Optional attributes:
|
||||
works on fields with type integer or datetime.
|
||||
- scale - Scale of a decimal type.
|
||||
- precision - Precision of a decimal type.
|
||||
- options - Array of additional options:
|
||||
|
||||
- default - The default value to set for the column if no value
|
||||
is supplied.
|
||||
- unsigned - Boolean value to determine if the column should
|
||||
be capable of representing only non-negative integers
|
||||
(applies only for integer column and might not be supported by
|
||||
all vendors).
|
||||
- fixed - Boolean value to determine if the specified length of
|
||||
a string column should be fixed or varying (applies only for
|
||||
string/binary column and might not be supported by all vendors).
|
||||
- comment - The comment of the column in the schema (might not
|
||||
be supported by all vendors).
|
||||
- customSchemaOptions - Array of additional schema options
|
||||
which are mostly vendor specific.
|
||||
- column-definition - Optional alternative SQL representation for
|
||||
this column. This definition begin after the field-name and has to
|
||||
specify the complete column definition. Using this feature will
|
||||
turn this field dirty for Schema-Tool update commands at all
|
||||
times.
|
||||
|
||||
.. note::
|
||||
|
||||
For more detailed information on each attribute, please refer to
|
||||
the DBAL ``Schema-Representation`` documentation.
|
||||
|
||||
Defining Identity and Generator Strategies
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -415,7 +450,7 @@ using the ``<lifecycle-callbacks />`` element:
|
||||
.. code-block:: xml
|
||||
|
||||
<entity name="Doctrine\Tests\ORM\Mapping\User" table="cms_users">
|
||||
|
||||
|
||||
<lifecycle-callbacks>
|
||||
<lifecycle-callback type="prePersist" method="onPrePersist" />
|
||||
</lifecycle-callbacks>
|
||||
@@ -708,12 +743,12 @@ table you can use the ``<indexes />`` and
|
||||
.. code-block:: xml
|
||||
|
||||
<entity name="Doctrine\Tests\ORM\Mapping\User" table="cms_users">
|
||||
|
||||
|
||||
<indexes>
|
||||
<index name="name_idx" columns="name"/>
|
||||
<index columns="user_email"/>
|
||||
</indexes>
|
||||
|
||||
|
||||
<unique-constraints>
|
||||
<unique-constraint columns="name,user_email" name="search_idx" />
|
||||
</unique-constraints>
|
||||
@@ -742,6 +777,6 @@ entity relationship. You can define this in XML with the "association-key" attri
|
||||
<field name="value" type="string" />
|
||||
|
||||
<many-to-one field="article" target-entity="Article" inversed-by="attributes" />
|
||||
<entity>
|
||||
</entity>
|
||||
|
||||
</doctrine-mapping>
|
||||
|
||||
@@ -72,7 +72,10 @@ of several common elements:
|
||||
# Doctrine.Tests.ORM.Mapping.User.dcm.yml
|
||||
Doctrine\Tests\ORM\Mapping\User:
|
||||
type: entity
|
||||
repositoryClass: Doctrine\Tests\ORM\Mapping\UserRepository
|
||||
table: cms_users
|
||||
schema: schema_name # The schema the table lies in, for platforms that support schemas (Optional, >= 2.5)
|
||||
readOnly: true
|
||||
indexes:
|
||||
name_index:
|
||||
columns: [ name ]
|
||||
@@ -85,12 +88,28 @@ of several common elements:
|
||||
name:
|
||||
type: string
|
||||
length: 50
|
||||
email:
|
||||
type: string
|
||||
length: 32
|
||||
column: user_email
|
||||
unique: true
|
||||
options:
|
||||
fixed: true
|
||||
comment: User's email address
|
||||
loginCount:
|
||||
type: integer
|
||||
column: login_count
|
||||
nullable: false
|
||||
options:
|
||||
unsigned: true
|
||||
default: 0
|
||||
oneToOne:
|
||||
address:
|
||||
targetEntity: Address
|
||||
joinColumn:
|
||||
name: address_id
|
||||
referencedColumnName: id
|
||||
onDelete: CASCADE
|
||||
oneToMany:
|
||||
phonenumbers:
|
||||
targetEntity: Phonenumber
|
||||
@@ -114,4 +133,22 @@ of several common elements:
|
||||
Be aware that class-names specified in the YAML files should be
|
||||
fully qualified.
|
||||
|
||||
Reference
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Unique Constraints
|
||||
------------------
|
||||
|
||||
It is possible to define unique constraints by the following declaration:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# ECommerceProduct.orm.yml
|
||||
ECommerceProduct:
|
||||
type: entity
|
||||
fields:
|
||||
# definition of some fields
|
||||
uniqueConstraints:
|
||||
search_idx:
|
||||
columns: [ name, email ]
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ Tutorials
|
||||
tutorials/ordered-associations
|
||||
tutorials/override-field-association-mappings-in-subclasses
|
||||
tutorials/pagination.rst
|
||||
tutorials/embeddables.rst
|
||||
|
||||
Reference Guide
|
||||
---------------
|
||||
@@ -25,7 +26,6 @@ Reference Guide
|
||||
:numbered:
|
||||
|
||||
reference/architecture
|
||||
reference/installation
|
||||
reference/configuration.rst
|
||||
reference/faq
|
||||
reference/basic-mapping
|
||||
@@ -53,10 +53,13 @@ Reference Guide
|
||||
reference/metadata-drivers
|
||||
reference/best-practices
|
||||
reference/limitations-and-known-issues
|
||||
reference/filters.rst
|
||||
reference/namingstrategy.rst
|
||||
reference/advanced-configuration.rst
|
||||
|
||||
tutorials/pagination
|
||||
reference/filters
|
||||
reference/namingstrategy
|
||||
reference/advanced-configuration
|
||||
reference/second-level-cache
|
||||
reference/security
|
||||
|
||||
|
||||
Cookbook
|
||||
--------
|
||||
|
||||
162
docs/en/tutorials/embeddables.rst
Normal file
162
docs/en/tutorials/embeddables.rst
Normal file
@@ -0,0 +1,162 @@
|
||||
Separating Concerns using Embeddables
|
||||
-------------------------------------
|
||||
|
||||
Embeddables are classes which are not entities themself, 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.
|
||||
|
||||
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
|
||||
the ``User`` class. We will model the ``Address`` class as an embeddable
|
||||
instead of simply adding the respective columns to the ``User`` class.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
/** @Entity */
|
||||
class User
|
||||
{
|
||||
/** @Embedded(class = "Address") */
|
||||
private $address;
|
||||
}
|
||||
|
||||
/** @Embeddable */
|
||||
class Address
|
||||
{
|
||||
/** @Column(type = "string") */
|
||||
private $street;
|
||||
|
||||
/** @Column(type = "string") */
|
||||
private $postalCode;
|
||||
|
||||
/** @Column(type = "string") */
|
||||
private $city;
|
||||
|
||||
/** @Column(type = "string") */
|
||||
private $country;
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="User">
|
||||
<embedded name="address" class="Address" />
|
||||
</entity>
|
||||
|
||||
<embeddable name="Address">
|
||||
<field name="street" type="string" />
|
||||
<field name="postalCode" type="string" />
|
||||
<field name="city" type="string" />
|
||||
<field name="country" type="string" />
|
||||
</embeddable>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
User:
|
||||
type: entity
|
||||
embedded:
|
||||
address:
|
||||
class: Address
|
||||
|
||||
Address:
|
||||
type: embeddable
|
||||
fields:
|
||||
street: { type: string }
|
||||
postalCode: { type: string }
|
||||
city: { type: string }
|
||||
country: { type: string }
|
||||
|
||||
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.
|
||||
|
||||
Column Prefixing
|
||||
----------------
|
||||
|
||||
By default, Doctrine names your columns by prefixing them, using the value
|
||||
object name.
|
||||
|
||||
Following the example above, your columns would be named as ``address_street``,
|
||||
``address_postalCode``...
|
||||
|
||||
You can change this behaviour to meet your needs by changing the
|
||||
``columnPrefix`` attribute in the ``@Embedded`` notation.
|
||||
|
||||
The following example shows you how to set your prefix to ``myPrefix_``:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
/** @Entity */
|
||||
class User
|
||||
{
|
||||
/** @Embedded(class = "Address", columnPrefix = "myPrefix_") */
|
||||
private $address;
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<entity name="User">
|
||||
<embedded name="address" class="Address" column-prefix="myPrefix_" />
|
||||
</entity>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
User:
|
||||
type: entity
|
||||
embedded:
|
||||
address:
|
||||
class: Address
|
||||
columnPrefix: myPrefix_
|
||||
|
||||
To have Doctrine drop the prefix and use the value object's property name
|
||||
directly, set ``columnPrefix=false`` (``use-column-prefix="false"`` for XML):
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
/** @Entity */
|
||||
class User
|
||||
{
|
||||
/** @Embedded(class = "Address", columnPrefix = false) */
|
||||
private $address;
|
||||
}
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
User:
|
||||
type: entity
|
||||
embedded:
|
||||
address:
|
||||
class: Address
|
||||
columnPrefix: false
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<entity name="User">
|
||||
<embedded name="address" class="Address" use-column-prefix="false" />
|
||||
</entity>
|
||||
|
||||
|
||||
DQL
|
||||
---
|
||||
|
||||
You can also use mapped fields of embedded classes in DQL queries, just
|
||||
as if they were declared in the ``User`` class:
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
SELECT u FROM User u WHERE u.address.city = :myCity
|
||||
|
||||
@@ -15,10 +15,12 @@ the first time its accessed. If you mark an association as extra lazy the follow
|
||||
can be called without triggering a full load of the collection:
|
||||
|
||||
- ``Collection#contains($entity)``
|
||||
- ``Collection#containsKey($key)`` (available with Doctrine 2.5)
|
||||
- ``Collection#count()``
|
||||
- ``Collection#get($key)`` (available with Doctrine 2.4)
|
||||
- ``Collection#slice($offset, $length = null)``
|
||||
|
||||
For each of this three methods the following semantics apply:
|
||||
For each of the above methods the following semantics apply:
|
||||
|
||||
- For each call, if the Collection is not yet loaded, issue a straight SELECT statement against the database.
|
||||
- For each call, if the collection is already loaded, fallback to the default functionality for lazy collections. No additional SELECT statements are executed.
|
||||
|
||||
@@ -17,7 +17,7 @@ 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
|
||||
installed:
|
||||
|
||||
- PHP 5.3.3 or above
|
||||
- PHP 5.4 or above
|
||||
- Composer Package Manager (`Install Composer
|
||||
<http://getcomposer.org/doc/00-intro.md>`_)
|
||||
|
||||
@@ -25,14 +25,14 @@ 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.4** and above.
|
||||
Some of the code will not work with lower versions.
|
||||
|
||||
What is Doctrine?
|
||||
-----------------
|
||||
|
||||
Doctrine 2 is an `object-relational mapper (ORM)
|
||||
<http://en.wikipedia.org/wiki/Object-relational_mapping>`_ for PHP 5.3.3+ that
|
||||
<http://en.wikipedia.org/wiki/Object-relational_mapping>`_ for PHP 5.4+ that
|
||||
provides transparent persistence for PHP objects. It uses the Data Mapper
|
||||
pattern at the heart, aiming for a complete separation of your domain/business
|
||||
logic from the persistence in a relational database management system.
|
||||
@@ -66,17 +66,17 @@ Bug Tracker domain model from the
|
||||
documentation. Reading their documentation we can extract the
|
||||
requirements:
|
||||
|
||||
- A Bugs has a description, creation date, status, reporter and
|
||||
- A Bug has a description, creation date, status, reporter and
|
||||
engineer
|
||||
- A bug can occur on different products (platforms)
|
||||
- Products have a name.
|
||||
- Bug Reporter and Engineers are both Users of the System.
|
||||
- A user can create new bugs.
|
||||
- The assigned engineer can close a bug.
|
||||
- A user can see all his reported or assigned bugs.
|
||||
- A Bug can occur on different Products (platforms)
|
||||
- A Product has a name.
|
||||
- Bug reporters and engineers are both Users of the system.
|
||||
- A User can create new Bugs.
|
||||
- The assigned engineer can close a Bug.
|
||||
- A User can see all his reported or assigned Bugs.
|
||||
- Bugs can be paginated through a list-view.
|
||||
|
||||
Setup Project
|
||||
Project Setup
|
||||
-------------
|
||||
|
||||
Create a new empty folder for this tutorial project, for example
|
||||
@@ -87,7 +87,7 @@ the following contents:
|
||||
|
||||
{
|
||||
"require": {
|
||||
"doctrine/orm": "2.*",
|
||||
"doctrine/orm": "2.4.*",
|
||||
"symfony/yaml": "2.*"
|
||||
},
|
||||
"autoload": {
|
||||
@@ -95,6 +95,7 @@ the following contents:
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Install Doctrine using the Composer Dependency Management tool, by calling:
|
||||
|
||||
::
|
||||
@@ -102,15 +103,13 @@ 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. Both Symfony dependencies are optional
|
||||
but will be used in this tutorial.
|
||||
|
||||
You can prepare the directory structure:
|
||||
Symfony YAML and Symfony Console into the `vendor` directory. The Symfony
|
||||
dependencies are not required by Doctrine but will be used in this tutorial.
|
||||
|
||||
Add the following directories:
|
||||
::
|
||||
|
||||
project
|
||||
|-- composer.json
|
||||
doctrine2-tutorial
|
||||
|-- config
|
||||
| |-- xml
|
||||
| `-- yaml
|
||||
@@ -132,22 +131,22 @@ step:
|
||||
// bootstrap.php
|
||||
use Doctrine\ORM\Tools\Setup;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
|
||||
|
||||
require_once "vendor/autoload.php";
|
||||
|
||||
|
||||
// Create a simple "default" Doctrine ORM configuration for Annotations
|
||||
$isDevMode = true;
|
||||
$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/src"), $isDevMode);
|
||||
// or if you prefer yaml or XML
|
||||
//$config = Setup::createXMLMetadataConfiguration(array(__DIR__."/config/xml"), $isDevMode);
|
||||
//$config = Setup::createYAMLMetadataConfiguration(array(__DIR__."/config/yaml"), $isDevMode);
|
||||
|
||||
|
||||
// database configuration parameters
|
||||
$conn = array(
|
||||
'driver' => 'pdo_sqlite',
|
||||
'path' => __DIR__ . '/db.sqlite',
|
||||
);
|
||||
|
||||
|
||||
// obtaining the entity manager
|
||||
$entityManager = EntityManager::create($conn, $config);
|
||||
|
||||
@@ -171,9 +170,9 @@ factory method.
|
||||
Generating the Database Schema
|
||||
------------------------------
|
||||
|
||||
Now that we have defined the Metadata Mappings and bootstrapped the
|
||||
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
|
||||
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.
|
||||
|
||||
@@ -186,7 +185,7 @@ doctrine command. Its a fairly simple file:
|
||||
<?php
|
||||
// cli-config.php
|
||||
require_once "bootstrap.php";
|
||||
|
||||
|
||||
return \Doctrine\ORM\Tools\Console\ConsoleRunner::createHelperSet($entityManager);
|
||||
|
||||
You can then change into your project directory and call the
|
||||
@@ -195,34 +194,37 @@ Doctrine command-line tool:
|
||||
::
|
||||
|
||||
$ cd project/
|
||||
$ php vendor/bin/doctrine orm:schema-tool:create
|
||||
$ vendor/bin/doctrine orm:schema-tool:create
|
||||
|
||||
During the development you probably need to re-create the database
|
||||
several times when changing the Entity metadata. You can then
|
||||
either re-create the database:
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
::
|
||||
|
||||
$ php vendor/bin/doctrine orm:schema-tool:drop --force
|
||||
$ php vendor/bin/doctrine orm:schema-tool:create
|
||||
$ vendor/bin/doctrine orm:schema-tool:drop --force
|
||||
$ vendor/bin/doctrine orm:schema-tool:create
|
||||
|
||||
Or use the update functionality:
|
||||
|
||||
::
|
||||
|
||||
$ php vendor/bin/doctrine orm:schema-tool:update --force
|
||||
$ 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,
|
||||
which can even be used without the Doctrine ORM package. However
|
||||
its not available in SQLite since it does not support ALTER TABLE.
|
||||
which can even be used without the Doctrine ORM package.
|
||||
|
||||
Starting with the Product
|
||||
-------------------------
|
||||
|
||||
We start with the Product entity requirements, because it is the most simple one
|
||||
to get started. Create a ``src/Product.php`` file and put the ``Product``
|
||||
entity definition in there:
|
||||
We start with the simplest entity, the Product. Create a ``src/Product.php`` file to contain the ``Product``
|
||||
entity definition:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -255,10 +257,16 @@ entity definition in there:
|
||||
}
|
||||
}
|
||||
|
||||
Note how the properties have getter and setter methods defined except
|
||||
``$id``. To access data from entities Doctrine 2 uses the Reflection API, so it
|
||||
is possible for Doctrine to access the value of ``$id``. You don't have to
|
||||
take Doctrine into account when designing access to the state of your objects.
|
||||
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
|
||||
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 next step for persistence with Doctrine is to describe the
|
||||
structure of the ``Product`` entity to Doctrine using a metadata
|
||||
@@ -322,19 +330,19 @@ References in the text will be made to the XML mapping.
|
||||
type: string
|
||||
|
||||
The top-level ``entity`` definition tag specifies information about
|
||||
the class and table-name. The primitive type ``Product::$name`` is
|
||||
defined as ``field`` attributes. The Id property is defined with
|
||||
the ``id`` tag. The id has a ``generator`` tag nested inside which
|
||||
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
|
||||
uses the database platforms native id generation strategy (for
|
||||
example AUTO INCREMENT in the case of MySql or Sequences in the
|
||||
case of PostgreSql and Oracle.
|
||||
case of PostgreSql and Oracle).
|
||||
|
||||
You have to update the database now, because we have a first Entity now:
|
||||
Now that we have defined our first entity, let's update the database:
|
||||
|
||||
::
|
||||
|
||||
$ php vendor/bin/doctrine orm:schema-tool:update --force --dump-sql
|
||||
$ vendor/bin/doctrine orm:schema-tool:update --force --dump-sql
|
||||
|
||||
Specifying both flags ``--force`` and ``-dump-sql`` prints and executes the DDL
|
||||
statements.
|
||||
@@ -357,7 +365,7 @@ Now create a new script that will insert products into the database:
|
||||
|
||||
echo "Created Product with ID " . $product->getId() . "\n";
|
||||
|
||||
Call this script from the command line to see how new products are created:
|
||||
Call this script from the command-line to see how new products are created:
|
||||
|
||||
::
|
||||
|
||||
@@ -367,7 +375,7 @@ Call this script from the command line to see how new products are created:
|
||||
What is happening here? Using the ``Product`` 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 intiate a transaction to actually perform
|
||||
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
|
||||
@@ -377,9 +385,9 @@ better than in a scenario where updates are done for 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 knowns about them.
|
||||
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
|
||||
As a next step we want to fetch a list of all the Products. Let's create a
|
||||
new script for this:
|
||||
|
||||
.. code-block:: php
|
||||
@@ -396,7 +404,7 @@ new script for this:
|
||||
}
|
||||
|
||||
The ``EntityManager#getRepository()`` method can create a finder object (called
|
||||
repository) for every entity. It is provided by Doctrine and contains some
|
||||
a repository) for every entity. It is provided by Doctrine and contains some
|
||||
finder methods such as ``findAll()``.
|
||||
|
||||
Let's continue with displaying the name of a product based on its ID:
|
||||
@@ -555,12 +563,12 @@ 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. With just the scalar values this model cannot describe the dynamics that we want. We
|
||||
want to model references between entities.
|
||||
change dates. Next we will model the dynamic relationships between the entities
|
||||
by defining the references between entities.
|
||||
|
||||
References between objects are foreign keys in the database. You never have to
|
||||
work with the foreign keys directly, only with objects that represent the
|
||||
foreign key through their own identity.
|
||||
(and never should) work with the foreign keys directly, only with the objects
|
||||
that represent the foreign key through their own identity.
|
||||
|
||||
For every foreign key you either have a Doctrine ManyToOne or OneToOne
|
||||
association. On the inverse sides of these foreign keys you can have
|
||||
@@ -709,8 +717,8 @@ the bi-directional reference:
|
||||
{
|
||||
// ... (previous code)
|
||||
|
||||
protected $reportedBugs = null;
|
||||
protected $assignedBugs = null;
|
||||
private $reportedBugs = null;
|
||||
private $assignedBugs = null;
|
||||
|
||||
public function addReportedBug($bug)
|
||||
{
|
||||
@@ -729,20 +737,17 @@ methods are only used for ensuring consistency of the references.
|
||||
This approach is my personal preference, you can choose whatever
|
||||
method to make this work.
|
||||
|
||||
You can see from ``User::addReportedBug()`` and
|
||||
``User::assignedToBug()`` that using this method in userland alone
|
||||
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
|
||||
``Bug#reporter`` or ``Bug#engineer``. Using these methods and
|
||||
calling Doctrine for persistence would not update the collections
|
||||
representation in the database.
|
||||
|
||||
Only using ``Bug::setEngineer()`` or ``Bug::setReporter()``
|
||||
correctly saves the relation information. We also set both
|
||||
collection instance variables to protected, however with PHP 5.3's
|
||||
new features Doctrine is still able to use Reflection to set and
|
||||
get values from protected and private properties.
|
||||
Only using ``Bug#setEngineer()`` or ``Bug#setReporter()``
|
||||
correctly saves the relation information.
|
||||
|
||||
The ``Bug::$reporter`` and ``Bug::$engineer`` properties are
|
||||
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
|
||||
in our object-relation model the Bug is at the owning side of the
|
||||
@@ -778,8 +783,8 @@ the database that points from Bugs to Products.
|
||||
}
|
||||
|
||||
We are now finished with the domain model given the requirements.
|
||||
Now we continue adding metadata mappings for the ``User`` and ``Bug``
|
||||
as we did for the ``Product`` before:
|
||||
Lets add metadata mappings for the ``User`` and ``Bug`` as we did for
|
||||
the ``Product`` before:
|
||||
|
||||
.. configuration-block::
|
||||
.. code-block:: php
|
||||
@@ -881,11 +886,9 @@ as we did for the ``Product`` before:
|
||||
|
||||
|
||||
Here we have the entity, id and primitive type definitions.
|
||||
The column names are used from the Zend\_Db\_Table examples and
|
||||
have different names than the properties on the Bug class.
|
||||
Additionally for the "created" field it is specified that it is of
|
||||
the Type "DATETIME", which translates the YYYY-mm-dd HH:mm:ss
|
||||
Database format into a PHP DateTime instance and back.
|
||||
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
|
||||
user entity are defined. They are created by the ``many-to-one``
|
||||
@@ -899,14 +902,10 @@ side of the relationship. We will see in the next example that the ``inversed-by
|
||||
attribute has a counterpart ``mapped-by`` which makes that
|
||||
the inverse side.
|
||||
|
||||
The last missing property is the ``Bug::$products`` collection. It
|
||||
holds all products where the specific bug is occurring in. Again
|
||||
The last definition is for the ``Bug#products`` collection. It
|
||||
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. Furthermore you have to specify the
|
||||
details of the many-to-many join-table and its foreign key columns.
|
||||
The definition is rather complex, however relying on the XML
|
||||
auto-completion I got it working easily, although I forget the
|
||||
schema details all the time.
|
||||
on the ``many-to-many`` tag.
|
||||
|
||||
The last missing definition is that of the User entity:
|
||||
|
||||
@@ -1000,6 +999,12 @@ 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:
|
||||
::
|
||||
|
||||
$ vendor/bin/doctrine orm:schema-tool:update --force
|
||||
|
||||
|
||||
Implementing more Requirements
|
||||
------------------------------
|
||||
|
||||
@@ -1037,7 +1042,7 @@ like this:
|
||||
require_once "bootstrap.php";
|
||||
|
||||
$theReporterId = $argv[1];
|
||||
$theDefaultEngineerId = $argv[1];
|
||||
$theDefaultEngineerId = $argv[2];
|
||||
$productIds = explode(",", $argv[3]);
|
||||
|
||||
$reporter = $entityManager->find("User", $theReporterId);
|
||||
@@ -1052,7 +1057,7 @@ like this:
|
||||
$bug->setCreated(new DateTime("now"));
|
||||
$bug->setStatus("OPEN");
|
||||
|
||||
foreach ($productIds AS $productId) {
|
||||
foreach ($productIds as $productId) {
|
||||
$product = $entityManager->find("Product", $productId);
|
||||
$bug->assignToProduct($product);
|
||||
}
|
||||
@@ -1106,11 +1111,11 @@ the first read-only use-case:
|
||||
$query->setMaxResults(30);
|
||||
$bugs = $query->getResult();
|
||||
|
||||
foreach($bugs AS $bug) {
|
||||
foreach ($bugs as $bug) {
|
||||
echo $bug->getDescription()." - ".$bug->getCreated()->format('d.m.Y')."\n";
|
||||
echo " Reported by: ".$bug->getReporter()->getName()."\n";
|
||||
echo " Assigned to: ".$bug->getEngineer()->getName()."\n";
|
||||
foreach($bug->getProducts() AS $product) {
|
||||
foreach ($bug->getProducts() as $product) {
|
||||
echo " Platform: ".$product->getName()."\n";
|
||||
}
|
||||
echo "\n";
|
||||
@@ -1129,7 +1134,7 @@ The console output of this script is then:
|
||||
|
||||
.. note::
|
||||
|
||||
**Dql is not Sql**
|
||||
**DQL is not SQL**
|
||||
|
||||
You may wonder why we start writing SQL at the beginning of this
|
||||
use-case. Don't we use an ORM to get rid of all the endless
|
||||
@@ -1142,6 +1147,7 @@ The console output of this script is then:
|
||||
of Entity-Class and property. Using the Metadata we defined before
|
||||
it allows for very short distinctive and powerful queries.
|
||||
|
||||
|
||||
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,
|
||||
@@ -1151,30 +1157,31 @@ The console output of this script is then:
|
||||
throw your ORM into the dumpster, because it doesn't support some
|
||||
the more powerful SQL concepts.
|
||||
|
||||
Besides handwriting DQL you can however also use the
|
||||
``QueryBuilder`` retrieved by calling
|
||||
``$entityManager->createQueryBuilder()`` which is a Query Object
|
||||
around the DQL language.
|
||||
|
||||
As a last resort you can however also use Native SQL and a
|
||||
description of the result set to retrieve entities from the
|
||||
database. DQL boils down to a Native SQL statement and a
|
||||
``ResultSetMapping`` instance itself. Using Native SQL you could
|
||||
even use stored procedures for data retrieval, or make use of
|
||||
advanced non-portable database queries like PostgreSql's recursive
|
||||
queries.
|
||||
Instead of handwriting DQL you can use the ``QueryBuilder`` retrieved
|
||||
by calling ``$entityManager->createQueryBuilder()``. There are more
|
||||
details about this in the relevant part of the documentation.
|
||||
|
||||
|
||||
As a last resort you can still use Native SQL and a description of the
|
||||
result set to retrieve entities from the database. DQL boils down to a
|
||||
Native SQL statement and a ``ResultSetMapping`` instance itself. Using
|
||||
Native SQL you could even use stored procedures for data retrieval, or
|
||||
make use of advanced non-portable database queries like PostgreSql's
|
||||
recursive queries.
|
||||
|
||||
|
||||
Array Hydration of the Bug List
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In the previous use-case we retrieved the result as their
|
||||
In the previous use-case we retrieved the results as their
|
||||
respective object instances. We are not limited to retrieving
|
||||
objects only from Doctrine however. For a simple list view like the
|
||||
previous one we only need read access to our entities and can
|
||||
switch the hydration from objects to simple PHP arrays instead.
|
||||
This can obviously yield considerable performance benefits for
|
||||
read-only requests.
|
||||
|
||||
Hydration can be an expensive process so only retrieving what you need can
|
||||
yield considerable performance benefits for read-only requests.
|
||||
|
||||
Implementing the same list view as before using array hydration we
|
||||
can rewrite our code:
|
||||
@@ -1190,11 +1197,11 @@ can rewrite our code:
|
||||
$query = $entityManager->createQuery($dql);
|
||||
$bugs = $query->getArrayResult();
|
||||
|
||||
foreach ($bugs AS $bug) {
|
||||
foreach ($bugs as $bug) {
|
||||
echo $bug['description'] . " - " . $bug['created']->format('d.m.Y')."\n";
|
||||
echo " Reported by: ".$bug['reporter']['name']."\n";
|
||||
echo " Assigned to: ".$bug['engineer']['name']."\n";
|
||||
foreach($bug['products'] AS $product) {
|
||||
foreach ($bug['products'] as $product) {
|
||||
echo " Platform: ".$product['name']."\n";
|
||||
}
|
||||
echo "\n";
|
||||
@@ -1228,7 +1235,7 @@ write scenarios:
|
||||
echo "Bug: ".$bug->getDescription()."\n";
|
||||
echo "Engineer: ".$bug->getEngineer()->getName()."\n";
|
||||
|
||||
The output of the engineers name is fetched from the database! What is happening?
|
||||
The output of the engineer’s name is fetched from the database! What is happening?
|
||||
|
||||
Since we only retrieved the bug by primary key both the engineer and reporter
|
||||
are not immediately loaded from the database but are replaced by LazyLoading
|
||||
@@ -1274,6 +1281,14 @@ The call prints:
|
||||
Bug: Something does not work!
|
||||
Engineer: beberlei
|
||||
|
||||
.. warning::
|
||||
|
||||
Lazy loading additional data can be very convenient but the additional
|
||||
queries create an overhead. If you know that certain fields will always
|
||||
(or usually) be required by the query then you will get better performance
|
||||
by explicitly retrieving them all in the first query.
|
||||
|
||||
|
||||
Dashboard of the User
|
||||
---------------------
|
||||
|
||||
@@ -1300,7 +1315,7 @@ and usage of bound parameters:
|
||||
|
||||
echo "You have created or assigned to " . count($myBugs) . " open bugs:\n\n";
|
||||
|
||||
foreach ($myBugs AS $bug) {
|
||||
foreach ($myBugs as $bug) {
|
||||
echo $bug->getId() . " - " . $bug->getDescription()."\n";
|
||||
}
|
||||
|
||||
@@ -1325,7 +1340,7 @@ grouped by product:
|
||||
"JOIN b.products p WHERE b.status = 'OPEN' GROUP BY p.id";
|
||||
$productBugs = $entityManager->createQuery($dql)->getScalarResult();
|
||||
|
||||
foreach($productBugs as $productBug) {
|
||||
foreach ($productBugs as $productBug) {
|
||||
echo $productBug['name']." has " . $productBug['openBugs'] . " open bugs!\n";
|
||||
}
|
||||
|
||||
@@ -1405,7 +1420,7 @@ example querying for all closed bugs:
|
||||
$bugs = $entityManager->getRepository('Bug')
|
||||
->findBy(array('status' => 'CLOSED'));
|
||||
|
||||
foreach ($bugs AS $bug) {
|
||||
foreach ($bugs as $bug) {
|
||||
// do stuff
|
||||
}
|
||||
|
||||
@@ -1461,8 +1476,6 @@ the previously discussed query functionality in it:
|
||||
}
|
||||
}
|
||||
|
||||
Don't forget to add a `require_once` call for this class to the bootstrap.php
|
||||
|
||||
To be able to use this query logic through ``$this->getEntityManager()->getRepository('Bug')``
|
||||
we have to adjust the metadata slightly.
|
||||
|
||||
@@ -1509,11 +1522,11 @@ As an example here is the code of the first use case "List of Bugs":
|
||||
|
||||
$bugs = $entityManager->getRepository('Bug')->getRecentBugs();
|
||||
|
||||
foreach($bugs AS $bug) {
|
||||
foreach ($bugs as $bug) {
|
||||
echo $bug->getDescription()." - ".$bug->getCreated()->format('d.m.Y')."\n";
|
||||
echo " Reported by: ".$bug->getReporter()->getName()."\n";
|
||||
echo " Assigned to: ".$bug->getEngineer()->getName()."\n";
|
||||
foreach($bug->getProducts() AS $product) {
|
||||
foreach ($bug->getProducts() as $product) {
|
||||
echo " Platform: ".$product->getName()."\n";
|
||||
}
|
||||
echo "\n";
|
||||
@@ -1534,4 +1547,3 @@ will be added to this tutorial incrementally, topics will include:
|
||||
|
||||
Additional details on all the topics discussed here can be found in
|
||||
the respective manual chapters.
|
||||
|
||||
|
||||
@@ -87,4 +87,4 @@ The case for just extending a class would be just the same but:
|
||||
// ...
|
||||
}
|
||||
|
||||
Overriding is also supported via XML and YAML.
|
||||
Overriding is also supported via XML and YAML (:ref:`examples <inheritence_mapping_overrides>`).
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
xmlns:orm="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xs:annotation>
|
||||
<xs:documentation><![CDATA[
|
||||
This is the XML Schema for the object/relational
|
||||
<xs:annotation>
|
||||
<xs:documentation><![CDATA[
|
||||
This is the XML Schema for the object/relational
|
||||
mapping file used by the Doctrine ORM.
|
||||
]]></xs:documentation>
|
||||
</xs:annotation>
|
||||
@@ -17,32 +17,33 @@
|
||||
<xs:sequence>
|
||||
<xs:element name="mapped-superclass" type="orm:mapped-superclass" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xs:element name="entity" type="orm:entity" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xs:element name="embeddable" type="orm:embeddable" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
|
||||
<xs:complexType name="emptyType">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
|
||||
<xs:complexType name="cascade-type">
|
||||
<xs:sequence>
|
||||
<xs:element name="cascade-all" type="orm:emptyType" minOccurs="0"/>
|
||||
<xs:element name="cascade-persist" type="orm:emptyType" minOccurs="0"/>
|
||||
<xs:element name="cascade-merge" type="orm:emptyType" minOccurs="0"/>
|
||||
<xs:element name="cascade-remove" type="orm:emptyType" minOccurs="0"/>
|
||||
<xs:element name="cascade-refresh" type="orm:emptyType" minOccurs="0"/>
|
||||
<xs:element name="cascade-all" type="orm:emptyType" minOccurs="0"/>
|
||||
<xs:element name="cascade-persist" type="orm:emptyType" minOccurs="0"/>
|
||||
<xs:element name="cascade-merge" type="orm:emptyType" minOccurs="0"/>
|
||||
<xs:element name="cascade-remove" type="orm:emptyType" minOccurs="0"/>
|
||||
<xs:element name="cascade-refresh" type="orm:emptyType" minOccurs="0"/>
|
||||
<xs:element name="cascade-detach" type="orm:emptyType" minOccurs="0"/>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
|
||||
<xs:simpleType name="lifecycle-callback-type">
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:enumeration value="prePersist"/>
|
||||
@@ -55,7 +56,15 @@
|
||||
<xs:enumeration value="preFlush"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
|
||||
<xs:simpleType name="cache-usage-type">
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:enumeration value="READ_ONLY"/>
|
||||
<xs:enumeration value="READ_WRITE"/>
|
||||
<xs:enumeration value="NONSTRICT_READ_WRITE"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:complexType name="lifecycle-callback">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
@@ -64,7 +73,7 @@
|
||||
<xs:attribute name="method" type="xs:NMTOKEN" use="required" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
|
||||
<xs:complexType name="lifecycle-callbacks">
|
||||
<xs:sequence>
|
||||
<xs:element name="lifecycle-callback" type="orm:lifecycle-callback" minOccurs="1" maxOccurs="unbounded" />
|
||||
@@ -152,8 +161,14 @@
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="cache">
|
||||
<xs:attribute name="usage" type="orm:cache-usage-type" />
|
||||
<xs:attribute name="region" type="xs:string" />
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="entity">
|
||||
<xs:sequence>
|
||||
<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"/>
|
||||
<xs:element name="unique-constraints" type="orm:unique-constraints" minOccurs="0"/>
|
||||
@@ -166,6 +181,7 @@
|
||||
<xs:element name="sql-result-set-mappings" type="orm:sql-result-set-mappings" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xs:element name="id" type="orm:id" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xs:element name="field" type="orm:field" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="embedded" type="orm:embedded" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="one-to-one" type="orm:one-to-one" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="one-to-many" type="orm:one-to-many" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xs:element name="many-to-one" type="orm:many-to-one" minOccurs="0" maxOccurs="unbounded" />
|
||||
@@ -183,7 +199,7 @@
|
||||
<xs:attribute name="read-only" type="xs:boolean" default="false" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
|
||||
<xs:complexType name="option" mixed="true">
|
||||
<xs:sequence minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:element name="option" type="orm:option"/>
|
||||
@@ -212,6 +228,16 @@
|
||||
</xs:complexContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="embeddable">
|
||||
<xs:complexContent>
|
||||
<xs:extension base="orm:entity">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
</xs:extension>
|
||||
</xs:complexContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:simpleType name="change-tracking-policy">
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:enumeration value="DEFERRED_IMPLICIT"/>
|
||||
@@ -219,7 +245,7 @@
|
||||
<xs:enumeration value="NOTIFY"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
|
||||
<xs:simpleType name="inheritance-type">
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:enumeration value="SINGLE_TABLE"/>
|
||||
@@ -227,33 +253,33 @@
|
||||
<xs:enumeration value="TABLE_PER_CLASS"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="generator-strategy">
|
||||
<xs:restriction base="xs:token">
|
||||
|
||||
<xs:simpleType name="generator-strategy">
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:enumeration value="NONE"/>
|
||||
<xs:enumeration value="TABLE"/>
|
||||
<xs:enumeration value="SEQUENCE"/>
|
||||
<xs:enumeration value="IDENTITY"/>
|
||||
<xs:enumeration value="AUTO"/>
|
||||
<xs:enumeration value="UUID"/>
|
||||
<xs:enumeration value="TABLE"/>
|
||||
<xs:enumeration value="SEQUENCE"/>
|
||||
<xs:enumeration value="IDENTITY"/>
|
||||
<xs:enumeration value="AUTO"/>
|
||||
<xs:enumeration value="UUID"/>
|
||||
<xs:enumeration value="CUSTOM" />
|
||||
</xs:restriction>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="fk-action">
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:enumeration value="CASCADE"/>
|
||||
<xs:enumeration value="RESTRICT"/>
|
||||
|
||||
<xs:simpleType name="fk-action">
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:enumeration value="CASCADE"/>
|
||||
<xs:enumeration value="RESTRICT"/>
|
||||
<xs:enumeration value="SET NULL"/>
|
||||
</xs:restriction>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="fetch-type">
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:enumeration value="EAGER"/>
|
||||
|
||||
<xs:simpleType name="fetch-type">
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:enumeration value="EAGER"/>
|
||||
<xs:enumeration value="LAZY"/>
|
||||
<xs:enumeration value="EXTRA_LAZY"/>
|
||||
</xs:restriction>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:complexType name="field">
|
||||
@@ -273,7 +299,14 @@
|
||||
<xs:attribute name="scale" type="xs:integer" use="optional" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
|
||||
<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="column-prefix" type="xs:string" use="optional" />
|
||||
<xs:attribute name="use-column-prefix" type="xs:boolean" default="true" use="optional" />
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="discriminator-column">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
@@ -285,16 +318,17 @@
|
||||
<xs:attribute name="column-definition" type="xs:string" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
|
||||
<xs:complexType name="unique-constraint">
|
||||
<xs:sequence>
|
||||
<xs:element name="options" type="orm:options" minOccurs="0" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="optional"/>
|
||||
<xs:attribute name="columns" type="xs:string" use="required"/>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
|
||||
<xs:complexType name="unique-constraints">
|
||||
<xs:sequence>
|
||||
<xs:element name="unique-constraint" type="orm:unique-constraint" minOccurs="1" maxOccurs="unbounded"/>
|
||||
@@ -302,16 +336,18 @@
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
|
||||
<xs:complexType name="index">
|
||||
<xs:sequence>
|
||||
<xs:element name="options" type="orm:options" minOccurs="0" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="optional"/>
|
||||
<xs:attribute name="columns" type="xs:string" use="required"/>
|
||||
<xs:attribute name="flags" type="xs:string" use="optional"/>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
|
||||
<xs:complexType name="indexes">
|
||||
<xs:sequence>
|
||||
<xs:element name="index" type="orm:index" minOccurs="1" maxOccurs="unbounded"/>
|
||||
@@ -319,7 +355,7 @@
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
|
||||
<xs:complexType name="discriminator-mapping">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
@@ -328,7 +364,7 @@
|
||||
<xs:attribute name="class" type="xs:string" use="required"/>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
|
||||
<xs:complexType name="discriminator-map">
|
||||
<xs:sequence>
|
||||
<xs:element name="discriminator-mapping" type="orm:discriminator-mapping" minOccurs="1" maxOccurs="unbounded"/>
|
||||
@@ -396,7 +432,6 @@
|
||||
<xs:attribute name="unique" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="nullable" type="xs:boolean" default="true" />
|
||||
<xs:attribute name="on-delete" type="orm:fk-action" />
|
||||
<xs:attribute name="on-update" type="orm:fk-action" />
|
||||
<xs:attribute name="column-definition" type="xs:string" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
@@ -446,6 +481,7 @@
|
||||
|
||||
<xs:complexType name="many-to-many">
|
||||
<xs:sequence>
|
||||
<xs:element name="cache" type="orm:cache" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="cascade" type="orm:cascade-type" minOccurs="0" />
|
||||
<xs:element name="join-table" type="orm:join-table" minOccurs="0" />
|
||||
<xs:element name="order-by" type="orm:order-by" minOccurs="0" />
|
||||
@@ -463,6 +499,7 @@
|
||||
|
||||
<xs:complexType name="one-to-many">
|
||||
<xs:sequence>
|
||||
<xs:element name="cache" type="orm:cache" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="cascade" type="orm:cascade-type" minOccurs="0" />
|
||||
<xs:element name="order-by" type="orm:order-by" minOccurs="0" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
@@ -475,9 +512,10 @@
|
||||
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
|
||||
<xs:complexType name="many-to-one">
|
||||
<xs:sequence>
|
||||
<xs:element name="cache" type="orm:cache" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="cascade" type="orm:cascade-type" minOccurs="0" />
|
||||
<xs:choice minOccurs="0" maxOccurs="1">
|
||||
<xs:element name="join-column" type="orm:join-column"/>
|
||||
@@ -493,9 +531,10 @@
|
||||
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
|
||||
<xs:complexType name="one-to-one">
|
||||
<xs:sequence>
|
||||
<xs:element name="cache" type="orm:cache" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="cascade" type="orm:cascade-type" minOccurs="0" />
|
||||
<xs:choice minOccurs="0" maxOccurs="1">
|
||||
<xs:element name="join-column" type="orm:join-column"/>
|
||||
|
||||
@@ -20,13 +20,15 @@
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\ORM\Query\Parameter;
|
||||
use Doctrine\ORM\Cache\QueryCacheKey;
|
||||
use Doctrine\DBAL\Cache\QueryCacheProfile;
|
||||
|
||||
use Doctrine\ORM\Cache;
|
||||
use Doctrine\ORM\Query\QueryException;
|
||||
use Doctrine\ORM\ORMInvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Base contract for ORM queries. Base class for Query and NativeQuery.
|
||||
@@ -85,7 +87,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* The entity manager used by this query object.
|
||||
*
|
||||
* @var \Doctrine\ORM\EntityManager
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
protected $_em;
|
||||
|
||||
@@ -120,15 +122,154 @@ abstract class AbstractQuery
|
||||
*/
|
||||
protected $_hydrationCacheProfile;
|
||||
|
||||
/**
|
||||
* Whether to use second level cache, if available.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $cacheable = false;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected $hasCache = false;
|
||||
|
||||
/**
|
||||
* Second level cache region name.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $cacheRegion;
|
||||
|
||||
/**
|
||||
* Second level query cache mode.
|
||||
*
|
||||
* @var integer|null
|
||||
*/
|
||||
protected $cacheMode;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\Logging\CacheLogger|null
|
||||
*/
|
||||
protected $cacheLogger;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
protected $lifetime = 0;
|
||||
|
||||
/**
|
||||
* Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
|
||||
*
|
||||
* @param \Doctrine\ORM\EntityManager $em
|
||||
* @param \Doctrine\ORM\EntityManagerInterface $em
|
||||
*/
|
||||
public function __construct(EntityManager $em)
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
$this->_em = $em;
|
||||
$this->parameters = new ArrayCollection();
|
||||
$this->_em = $em;
|
||||
$this->parameters = new ArrayCollection();
|
||||
$this->_hints = $em->getConfiguration()->getDefaultQueryHints();
|
||||
$this->hasCache = $this->_em->getConfiguration()->isSecondLevelCacheEnabled();
|
||||
|
||||
if ($this->hasCache) {
|
||||
$this->cacheLogger = $em->getConfiguration()
|
||||
->getSecondLevelCacheConfiguration()
|
||||
->getCacheLogger();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Enable/disable second level query (result) caching for this query.
|
||||
*
|
||||
* @param boolean $cacheable
|
||||
*
|
||||
* @return static This query instance.
|
||||
*/
|
||||
public function setCacheable($cacheable)
|
||||
{
|
||||
$this->cacheable = (boolean) $cacheable;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean TRUE if the query results are enable for second level cache, FALSE otherwise.
|
||||
*/
|
||||
public function isCacheable()
|
||||
{
|
||||
return $this->cacheable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $cacheRegion
|
||||
*
|
||||
* @return static This query instance.
|
||||
*/
|
||||
public function setCacheRegion($cacheRegion)
|
||||
{
|
||||
$this->cacheRegion = (string) $cacheRegion;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public function getCacheRegion()
|
||||
{
|
||||
return $this->cacheRegion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean TRUE if the query cache and second level cache are enabled, FALSE otherwise.
|
||||
*/
|
||||
protected function isCacheEnabled()
|
||||
{
|
||||
return $this->cacheable && $this->hasCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return integer
|
||||
*/
|
||||
public function getLifetime()
|
||||
{
|
||||
return $this->lifetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the life-time for this query into second level cache.
|
||||
*
|
||||
* @param integer $lifetime
|
||||
*
|
||||
* @return static This query instance.
|
||||
*/
|
||||
public function setLifetime($lifetime)
|
||||
{
|
||||
$this->lifetime = (integer) $lifetime;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return integer
|
||||
*/
|
||||
public function getCacheMode()
|
||||
{
|
||||
return $this->cacheMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param integer $cacheMode
|
||||
*
|
||||
* @return static This query instance.
|
||||
*/
|
||||
public function setCacheMode($cacheMode)
|
||||
{
|
||||
$this->cacheMode = (integer) $cacheMode;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,7 +302,7 @@ abstract class AbstractQuery
|
||||
{
|
||||
$this->parameters = new ArrayCollection();
|
||||
|
||||
$this->_hints = array();
|
||||
$this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,7 +320,7 @@ abstract class AbstractQuery
|
||||
*
|
||||
* @param mixed $key The key (index or name) of the bound parameter.
|
||||
*
|
||||
* @return mixed The value of the bound parameter.
|
||||
* @return Query\Parameter|null The value of the bound parameter, or NULL if not available.
|
||||
*/
|
||||
public function getParameter($key)
|
||||
{
|
||||
@@ -199,7 +340,7 @@ abstract class AbstractQuery
|
||||
*
|
||||
* @param \Doctrine\Common\Collections\ArrayCollection|array $parameters
|
||||
*
|
||||
* @return \Doctrine\ORM\AbstractQuery This query instance.
|
||||
* @return static This query instance.
|
||||
*/
|
||||
public function setParameters($parameters)
|
||||
{
|
||||
@@ -208,9 +349,7 @@ abstract class AbstractQuery
|
||||
$parameterCollection = new ArrayCollection();
|
||||
|
||||
foreach ($parameters as $key => $value) {
|
||||
$parameter = new Query\Parameter($key, $value);
|
||||
|
||||
$parameterCollection->add($parameter);
|
||||
$parameterCollection->add(new Parameter($key, $value));
|
||||
}
|
||||
|
||||
$parameters = $parameterCollection;
|
||||
@@ -230,7 +369,7 @@ abstract class AbstractQuery
|
||||
* the type conversion of this type. This is usually not needed for
|
||||
* strings and numeric types.
|
||||
*
|
||||
* @return \Doctrine\ORM\AbstractQuery This query instance.
|
||||
* @return static This query instance.
|
||||
*/
|
||||
public function setParameter($key, $value, $type = null)
|
||||
{
|
||||
@@ -249,9 +388,7 @@ abstract class AbstractQuery
|
||||
return $this;
|
||||
}
|
||||
|
||||
$parameter = new Query\Parameter($key, $value, $type);
|
||||
|
||||
$this->parameters->add($parameter);
|
||||
$this->parameters->add(new Parameter($key, $value, $type));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -263,10 +400,18 @@ abstract class AbstractQuery
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws ORMInvalidArgumentException
|
||||
* @throws \Doctrine\ORM\ORMInvalidArgumentException
|
||||
*/
|
||||
public function processParameterValue($value)
|
||||
{
|
||||
if (is_scalar($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ($value instanceof Collection) {
|
||||
$value = $value->toArray();
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $key => $paramValue) {
|
||||
$paramValue = $this->processParameterValue($paramValue);
|
||||
@@ -296,7 +441,7 @@ abstract class AbstractQuery
|
||||
*
|
||||
* @param \Doctrine\ORM\Query\ResultSetMapping $rsm
|
||||
*
|
||||
* @return \Doctrine\ORM\AbstractQuery
|
||||
* @return static This query instance.
|
||||
*/
|
||||
public function setResultSetMapping(Query\ResultSetMapping $rsm)
|
||||
{
|
||||
@@ -306,6 +451,16 @@ abstract class AbstractQuery
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ResultSetMapping used for hydration.
|
||||
*
|
||||
* @return \Doctrine\ORM\Query\ResultSetMapping
|
||||
*/
|
||||
protected function getResultSetMapping()
|
||||
{
|
||||
return $this->_resultSetMapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to translate entity namespaces to full qualified names.
|
||||
*
|
||||
@@ -315,10 +470,8 @@ abstract class AbstractQuery
|
||||
*/
|
||||
private function translateNamespaces(Query\ResultSetMapping $rsm)
|
||||
{
|
||||
$entityManager = $this->_em;
|
||||
|
||||
$translate = function ($alias) use ($entityManager) {
|
||||
return $entityManager->getClassMetadata($alias)->getName();
|
||||
$translate = function ($alias) {
|
||||
return $this->_em->getClassMetadata($alias)->getName();
|
||||
};
|
||||
|
||||
$rsm->aliasMap = array_map($translate, $rsm->aliasMap);
|
||||
@@ -345,7 +498,7 @@ abstract class AbstractQuery
|
||||
*
|
||||
* @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
|
||||
*
|
||||
* @return \Doctrine\ORM\AbstractQuery
|
||||
* @return static This query instance.
|
||||
*/
|
||||
public function setHydrationCacheProfile(QueryCacheProfile $profile = null)
|
||||
{
|
||||
@@ -375,7 +528,7 @@ abstract class AbstractQuery
|
||||
*
|
||||
* @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
|
||||
*
|
||||
* @return \Doctrine\ORM\AbstractQuery
|
||||
* @return static This query instance.
|
||||
*/
|
||||
public function setResultCacheProfile(QueryCacheProfile $profile = null)
|
||||
{
|
||||
@@ -394,7 +547,7 @@ abstract class AbstractQuery
|
||||
*
|
||||
* @param \Doctrine\Common\Cache\Cache|null $resultCacheDriver Cache driver
|
||||
*
|
||||
* @return \Doctrine\ORM\AbstractQuery
|
||||
* @return static This query instance.
|
||||
*
|
||||
* @throws ORMException
|
||||
*/
|
||||
@@ -435,7 +588,7 @@ abstract class AbstractQuery
|
||||
* @param integer $lifetime
|
||||
* @param string $resultCacheId
|
||||
*
|
||||
* @return \Doctrine\ORM\AbstractQuery This query instance.
|
||||
* @return static This query instance.
|
||||
*/
|
||||
public function useResultCache($bool, $lifetime = null, $resultCacheId = null)
|
||||
{
|
||||
@@ -456,7 +609,7 @@ abstract class AbstractQuery
|
||||
*
|
||||
* @param integer $lifetime How long the cache entry is valid.
|
||||
*
|
||||
* @return \Doctrine\ORM\AbstractQuery This query instance.
|
||||
* @return static This query instance.
|
||||
*/
|
||||
public function setResultCacheLifetime($lifetime)
|
||||
{
|
||||
@@ -486,7 +639,7 @@ abstract class AbstractQuery
|
||||
*
|
||||
* @param boolean $expire Whether or not to force resultset cache expiration.
|
||||
*
|
||||
* @return \Doctrine\ORM\AbstractQuery This query instance.
|
||||
* @return static This query instance.
|
||||
*/
|
||||
public function expireResultCache($expire = true)
|
||||
{
|
||||
@@ -522,7 +675,7 @@ abstract class AbstractQuery
|
||||
* @param string $assocName
|
||||
* @param int $fetchMode
|
||||
*
|
||||
* @return AbstractQuery
|
||||
* @return static This query instance.
|
||||
*/
|
||||
public function setFetchMode($class, $assocName, $fetchMode)
|
||||
{
|
||||
@@ -541,7 +694,7 @@ abstract class AbstractQuery
|
||||
* @param integer $hydrationMode Doctrine processing mode to be used during hydration process.
|
||||
* One of the Query::HYDRATE_* constants.
|
||||
*
|
||||
* @return \Doctrine\ORM\AbstractQuery This query instance.
|
||||
* @return static This query instance.
|
||||
*/
|
||||
public function setHydrationMode($hydrationMode)
|
||||
{
|
||||
@@ -609,7 +762,12 @@ abstract class AbstractQuery
|
||||
*/
|
||||
public function getOneOrNullResult($hydrationMode = null)
|
||||
{
|
||||
$result = $this->execute(null, $hydrationMode);
|
||||
try {
|
||||
$result = $this->execute(null, $hydrationMode);
|
||||
} catch (NoResultException $e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
|
||||
return null;
|
||||
@@ -667,7 +825,8 @@ abstract class AbstractQuery
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws QueryException If the query result is not unique.
|
||||
* @throws NonUniqueResultException If the query result is not unique.
|
||||
* @throws NoResultException If the query returned no result.
|
||||
*/
|
||||
public function getSingleScalarResult()
|
||||
{
|
||||
@@ -680,7 +839,7 @@ abstract class AbstractQuery
|
||||
* @param string $name The name of the hint.
|
||||
* @param mixed $value The value of the hint.
|
||||
*
|
||||
* @return \Doctrine\ORM\AbstractQuery
|
||||
* @return static This query instance.
|
||||
*/
|
||||
public function setHint($name, $value)
|
||||
{
|
||||
@@ -742,11 +901,10 @@ abstract class AbstractQuery
|
||||
$this->setParameters($parameters);
|
||||
}
|
||||
|
||||
$rsm = $this->getResultSetMapping();
|
||||
$stmt = $this->_doExecute();
|
||||
|
||||
return $this->_em->newHydrator($this->_hydrationMode)->iterate(
|
||||
$stmt, $this->_resultSetMapping, $this->_hints
|
||||
);
|
||||
return $this->_em->newHydrator($this->_hydrationMode)->iterate($stmt, $rsm, $this->_hints);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -758,6 +916,23 @@ abstract class AbstractQuery
|
||||
* @return mixed
|
||||
*/
|
||||
public function execute($parameters = null, $hydrationMode = null)
|
||||
{
|
||||
if ($this->cacheable && $this->isCacheEnabled()) {
|
||||
return $this->executeUsingQueryCache($parameters, $hydrationMode);
|
||||
}
|
||||
|
||||
return $this->executeIgnoreQueryCache($parameters, $hydrationMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute query ignoring second level cache.
|
||||
*
|
||||
* @param ArrayCollection|array|null $parameters
|
||||
* @param integer|null $hydrationMode
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function executeIgnoreQueryCache($parameters = null, $hydrationMode = null)
|
||||
{
|
||||
if ($hydrationMode !== null) {
|
||||
$this->setHydrationMode($hydrationMode);
|
||||
@@ -799,15 +974,51 @@ abstract class AbstractQuery
|
||||
return $stmt;
|
||||
}
|
||||
|
||||
$data = $this->_em->newHydrator($this->_hydrationMode)->hydrateAll(
|
||||
$stmt, $this->_resultSetMapping, $this->_hints
|
||||
);
|
||||
$rsm = $this->getResultSetMapping();
|
||||
$data = $this->_em->newHydrator($this->_hydrationMode)->hydrateAll($stmt, $rsm, $this->_hints);
|
||||
|
||||
$setCacheEntry($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load from second level cache or executes the query and put into cache.
|
||||
*
|
||||
* @param ArrayCollection|array|null $parameters
|
||||
* @param integer|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);
|
||||
|
||||
if ($result !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $querykey);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
$result = $this->executeIgnoreQueryCache($parameters, $hydrationMode);
|
||||
$cached = $queryCache->put($querykey, $rsm, $result, $this->_hints);
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $querykey);
|
||||
|
||||
if ($cached) {
|
||||
$this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $querykey);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -840,7 +1051,7 @@ abstract class AbstractQuery
|
||||
*
|
||||
* @param string $id
|
||||
*
|
||||
* @return \Doctrine\ORM\AbstractQuery This query instance.
|
||||
* @return static This query instance.
|
||||
*/
|
||||
public function setResultCacheId($id)
|
||||
{
|
||||
@@ -880,5 +1091,30 @@ abstract class AbstractQuery
|
||||
$this->parameters = new ArrayCollection();
|
||||
|
||||
$this->_hints = array();
|
||||
$this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a string of currently query to use for the cache second level cache.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getHash()
|
||||
{
|
||||
$query = $this->getSQL();
|
||||
$hints = $this->getHints();
|
||||
$params = array_map(function(Parameter $parameter) {
|
||||
// Small optimization
|
||||
// Does not invoke processParameterValue for scalar values
|
||||
if (is_scalar($value = $parameter->getValue())) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return $this->processParameterValue($value);
|
||||
}, $this->parameters->getValues());
|
||||
|
||||
ksort($hints);
|
||||
|
||||
return sha1($query . '-' . serialize($params) . '-' . serialize($hints));
|
||||
}
|
||||
}
|
||||
|
||||
178
lib/Doctrine/ORM/Cache.php
Normal file
178
lib/Doctrine/ORM/Cache.php
Normal file
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
/**
|
||||
* Provides an API for querying/managing the second level cache regions.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
interface Cache
|
||||
{
|
||||
const DEFAULT_QUERY_REGION_NAME = 'query_cache_region';
|
||||
|
||||
const DEFAULT_TIMESTAMP_REGION_NAME = 'timestamp_cache_region';
|
||||
|
||||
/**
|
||||
* May read items from the cache, but will not add items.
|
||||
*/
|
||||
const MODE_GET = 1;
|
||||
|
||||
/**
|
||||
* Will never read items from the cache,
|
||||
* but will add items to the cache as it reads them from the database.
|
||||
*/
|
||||
const MODE_PUT = 2;
|
||||
|
||||
/**
|
||||
* May read items from the cache, and add items to the cache.
|
||||
*/
|
||||
const MODE_NORMAL = 3;
|
||||
|
||||
/**
|
||||
* The query will never read items from the cache,
|
||||
* but will refresh items to the cache as it reads them from the database.
|
||||
*/
|
||||
const MODE_REFRESH = 4;
|
||||
|
||||
/**
|
||||
* @param string $className The entity class.
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\Region|null
|
||||
*/
|
||||
public function getEntityCacheRegion($className);
|
||||
|
||||
/**
|
||||
* @param string $className The entity class.
|
||||
* @param string $association The field name that represents the association.
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\Region|null
|
||||
*/
|
||||
public function getCollectionCacheRegion($className, $association);
|
||||
|
||||
/**
|
||||
* Determine whether the cache contains data for the given entity "instance".
|
||||
*
|
||||
* @param string $className The entity class.
|
||||
* @param mixed $identifier The entity identifier
|
||||
*
|
||||
* @return boolean true if the underlying cache contains corresponding data; false otherwise.
|
||||
*/
|
||||
public function containsEntity($className, $identifier);
|
||||
|
||||
/**
|
||||
* Evicts the entity data for a particular entity "instance".
|
||||
*
|
||||
* @param string $className The entity class.
|
||||
* @param mixed $identifier The entity identifier.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function evictEntity($className, $identifier);
|
||||
|
||||
/**
|
||||
* Evicts all entity data from the given region.
|
||||
*
|
||||
* @param string $className The entity metadata.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function evictEntityRegion($className);
|
||||
|
||||
/**
|
||||
* Evict data from all entity regions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function evictEntityRegions();
|
||||
|
||||
/**
|
||||
* Determine whether the cache contains data for the given collection.
|
||||
*
|
||||
* @param string $className The entity class.
|
||||
* @param string $association The field name that represents the association.
|
||||
* @param mixed $ownerIdentifier The identifier of the owning entity.
|
||||
*
|
||||
* @return boolean true if the underlying cache contains corresponding data; false otherwise.
|
||||
*/
|
||||
public function containsCollection($className, $association, $ownerIdentifier);
|
||||
|
||||
/**
|
||||
* Evicts the cache data for the given identified collection instance.
|
||||
*
|
||||
* @param string $className The entity class.
|
||||
* @param string $association The field name that represents the association.
|
||||
* @param mixed $ownerIdentifier The identifier of the owning entity.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function evictCollection($className, $association, $ownerIdentifier);
|
||||
|
||||
/**
|
||||
* Evicts all entity data from the given region.
|
||||
*
|
||||
* @param string $className The entity class.
|
||||
* @param string $association The field name that represents the association.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function evictCollectionRegion($className, $association);
|
||||
|
||||
/**
|
||||
* Evict data from all collection regions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function evictCollectionRegions();
|
||||
|
||||
/**
|
||||
* Determine whether the cache contains data for the given query.
|
||||
*
|
||||
* @param string $regionName The cache name given to the query.
|
||||
*
|
||||
* @return boolean true if the underlying cache contains corresponding data; false otherwise.
|
||||
*/
|
||||
public function containsQuery($regionName);
|
||||
|
||||
/**
|
||||
* Evicts all cached query results under the given name, or default query cache if the region name is NULL.
|
||||
*
|
||||
* @param string|null $regionName The cache name associated to the queries being cached.
|
||||
*/
|
||||
public function evictQueryRegion($regionName = null);
|
||||
|
||||
/**
|
||||
* Evict data from all query regions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function evictQueryRegions();
|
||||
|
||||
/**
|
||||
* Get query cache by region name or create a new one if none exist.
|
||||
*
|
||||
* @param string|null $regionName Query cache region name, or default query cache if the region name is NULL.
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\QueryCache The Query Cache associated with the region name.
|
||||
*/
|
||||
public function getQueryCache($regionName = null);
|
||||
}
|
||||
66
lib/Doctrine/ORM/Cache/AssociationCacheEntry.php
Normal file
66
lib/Doctrine/ORM/Cache/AssociationCacheEntry.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
/**
|
||||
* Association cache entry
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class AssociationCacheEntry implements CacheEntry
|
||||
{
|
||||
/**
|
||||
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
|
||||
*
|
||||
* @var array The entity identifier
|
||||
*/
|
||||
public $identifier;
|
||||
|
||||
/**
|
||||
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
|
||||
*
|
||||
* @var string The entity class name
|
||||
*/
|
||||
public $class;
|
||||
|
||||
/**
|
||||
* @param string $class The entity class.
|
||||
* @param array $identifier The entity identifier.
|
||||
*/
|
||||
public function __construct($class, array $identifier)
|
||||
{
|
||||
$this->class = $class;
|
||||
$this->identifier = $identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new AssociationCacheEntry
|
||||
*
|
||||
* This method allow Doctrine\Common\Cache\PhpFileCache compatibility
|
||||
*
|
||||
* @param array $values array containing property values
|
||||
*/
|
||||
public static function __set_state(array $values)
|
||||
{
|
||||
return new self($values['class'], $values['identifier']);
|
||||
}
|
||||
}
|
||||
126
lib/Doctrine/ORM/Cache/CacheConfiguration.php
Normal file
126
lib/Doctrine/ORM/Cache/CacheConfiguration.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
use Doctrine\ORM\Cache\Logging\CacheLogger;
|
||||
|
||||
/**
|
||||
* Configuration container for second-level cache.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class CacheConfiguration
|
||||
{
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\CacheFactory|null
|
||||
*/
|
||||
private $cacheFactory;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\RegionsConfiguration|null
|
||||
*/
|
||||
private $regionsConfig;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\Logging\CacheLogger|null
|
||||
*/
|
||||
private $cacheLogger;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\QueryCacheValidator|null
|
||||
*/
|
||||
private $queryValidator;
|
||||
|
||||
/**
|
||||
* @return \Doctrine\ORM\Cache\CacheFactory|null
|
||||
*/
|
||||
public function getCacheFactory()
|
||||
{
|
||||
return $this->cacheFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Cache\CacheFactory $factory
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setCacheFactory(CacheFactory $factory)
|
||||
{
|
||||
$this->cacheFactory = $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Doctrine\ORM\Cache\Logging\CacheLogger|null
|
||||
*/
|
||||
public function getCacheLogger()
|
||||
{
|
||||
return $this->cacheLogger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Cache\Logging\CacheLogger $logger
|
||||
*/
|
||||
public function setCacheLogger(CacheLogger $logger)
|
||||
{
|
||||
$this->cacheLogger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Doctrine\ORM\Cache\RegionsConfiguration
|
||||
*/
|
||||
public function getRegionsConfiguration()
|
||||
{
|
||||
if ($this->regionsConfig === null) {
|
||||
$this->regionsConfig = new RegionsConfiguration();
|
||||
}
|
||||
|
||||
return $this->regionsConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Cache\RegionsConfiguration $regionsConfig
|
||||
*/
|
||||
public function setRegionsConfiguration(RegionsConfiguration $regionsConfig)
|
||||
{
|
||||
$this->regionsConfig = $regionsConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Doctrine\ORM\Cache\QueryCacheValidator
|
||||
*/
|
||||
public function getQueryValidator()
|
||||
{
|
||||
if ($this->queryValidator === null) {
|
||||
$this->queryValidator = new TimestampQueryCacheValidator();
|
||||
}
|
||||
|
||||
return $this->queryValidator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Cache\QueryCacheValidator $validator
|
||||
*/
|
||||
public function setQueryValidator(QueryCacheValidator $validator)
|
||||
{
|
||||
$this->queryValidator = $validator;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
@@ -16,14 +17,20 @@
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
namespace Doctrine\ORM\Persisters;
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
/**
|
||||
* Persister for collections of basic elements / value types.
|
||||
* Cache entry interface
|
||||
*
|
||||
* @author robo
|
||||
* @todo Implementation once support for collections of basic elements (i.e. strings) is added.
|
||||
* <b>IMPORTANT NOTE:</b>
|
||||
*
|
||||
* Fields of classes that implement CacheEntry are public for performance reason.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
abstract class ElementCollectionPersister extends AbstractCollectionPersister
|
||||
interface CacheEntry
|
||||
{
|
||||
|
||||
}
|
||||
72
lib/Doctrine/ORM/Cache/CacheException.php
Normal file
72
lib/Doctrine/ORM/Cache/CacheException.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
use Doctrine\ORM\ORMException;
|
||||
|
||||
/**
|
||||
* Exception for cache.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class CacheException extends ORMException
|
||||
{
|
||||
/**
|
||||
* @param string $sourceEntity
|
||||
* @param string $fieldName
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\CacheException
|
||||
*/
|
||||
public static function updateReadOnlyCollection($sourceEntity, $fieldName)
|
||||
{
|
||||
return new self(sprintf('Cannot update a readonly collection "%s#%s"', $sourceEntity, $fieldName));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $entityName
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\CacheException
|
||||
*/
|
||||
public static function updateReadOnlyEntity($entityName)
|
||||
{
|
||||
return new self(sprintf('Cannot update a readonly entity "%s"', $entityName));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $entityName
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\CacheException
|
||||
*/
|
||||
public static function nonCacheableEntity($entityName)
|
||||
{
|
||||
return new self(sprintf('Entity "%s" not configured as part of the second-level cache.', $entityName));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $entityName
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\CacheException
|
||||
*/
|
||||
public static function nonCacheableEntityAssociation($entityName, $field)
|
||||
{
|
||||
return new self(sprintf('Entity association field "%s#%s" not configured as part of the second-level cache.', $entityName, $field));
|
||||
}
|
||||
}
|
||||
112
lib/Doctrine/ORM/Cache/CacheFactory.php
Normal file
112
lib/Doctrine/ORM/Cache/CacheFactory.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
|
||||
use Doctrine\ORM\Persisters\Entity\EntityPersister;
|
||||
|
||||
/**
|
||||
* Contract for building second level cache regions components.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
interface CacheFactory
|
||||
{
|
||||
/**
|
||||
* Build an entity persister for the given entity metadata.
|
||||
*
|
||||
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
|
||||
* @param \Doctrine\ORM\Persisters\Entity\EntityPersister $persister The entity persister that will be cached.
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\Persister\Entity\CachedEntityPersister
|
||||
*/
|
||||
public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPersister $persister, ClassMetadata $metadata);
|
||||
|
||||
/**
|
||||
* Build a collection persister for the given relation mapping.
|
||||
*
|
||||
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
|
||||
* @param \Doctrine\ORM\Persisters\Collection\CollectionPersister $persister The collection persister that will be cached.
|
||||
* @param array $mapping The association mapping.
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\Persister\Collection\CachedCollectionPersister
|
||||
*/
|
||||
public function buildCachedCollectionPersister(EntityManagerInterface $em, CollectionPersister $persister, array $mapping);
|
||||
|
||||
/**
|
||||
* Build a query cache based on the given region name
|
||||
*
|
||||
* @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager.
|
||||
* @param string $regionName The region name.
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\QueryCache The built query cache.
|
||||
*/
|
||||
public function buildQueryCache(EntityManagerInterface $em, $regionName = null);
|
||||
|
||||
/**
|
||||
* Build an entity hydrator
|
||||
*
|
||||
* @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager.
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\EntityHydrator The built entity hydrator.
|
||||
*/
|
||||
public function buildEntityHydrator(EntityManagerInterface $em, ClassMetadata $metadata);
|
||||
|
||||
/**
|
||||
* Build a collection hydrator
|
||||
*
|
||||
* @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager.
|
||||
* @param array $mapping The association mapping.
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\CollectionHydrator The built collection hydrator.
|
||||
*/
|
||||
public function buildCollectionHydrator(EntityManagerInterface $em, array $mapping);
|
||||
|
||||
/**
|
||||
* Build a cache region
|
||||
*
|
||||
* @param array $cache The cache configuration.
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\Region The cache region.
|
||||
*/
|
||||
public function getRegion(array $cache);
|
||||
|
||||
/**
|
||||
* Build timestamp cache region
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\TimestampRegion The timestamp region.
|
||||
*/
|
||||
public function getTimestampRegion();
|
||||
|
||||
/**
|
||||
* Build \Doctrine\ORM\Cache
|
||||
*
|
||||
* @param EntityManagerInterface $entityManager
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache
|
||||
*/
|
||||
public function createCache(EntityManagerInterface $entityManager);
|
||||
}
|
||||
38
lib/Doctrine/ORM/Cache/CacheKey.php
Normal file
38
lib/Doctrine/ORM/Cache/CacheKey.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
/**
|
||||
* Defines entity / collection / query key to be stored in the cache region.
|
||||
* Allows multiple roles to be stored in the same cache region.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
abstract class CacheKey
|
||||
{
|
||||
/**
|
||||
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
|
||||
*
|
||||
* @var string Unique identifier
|
||||
*/
|
||||
public $hash;
|
||||
}
|
||||
59
lib/Doctrine/ORM/Cache/CollectionCacheEntry.php
Normal file
59
lib/Doctrine/ORM/Cache/CollectionCacheEntry.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
/**
|
||||
* Collection cache entry
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class CollectionCacheEntry implements CacheEntry
|
||||
{
|
||||
/**
|
||||
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
|
||||
*
|
||||
* @var CacheKey[] The list of entity identifiers hold by the collection
|
||||
*/
|
||||
public $identifiers;
|
||||
|
||||
/**
|
||||
* @param CacheKey[] $identifiers List of entity identifiers hold by the collection
|
||||
*/
|
||||
public function __construct(array $identifiers)
|
||||
{
|
||||
$this->identifiers = $identifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new CollectionCacheEntry
|
||||
*
|
||||
* This method allows for Doctrine\Common\Cache\PhpFileCache compatibility
|
||||
*
|
||||
* @param array $values array containing property values
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function __set_state(array $values)
|
||||
{
|
||||
return new self($values['identifiers']);
|
||||
}
|
||||
}
|
||||
66
lib/Doctrine/ORM/Cache/CollectionCacheKey.php
Normal file
66
lib/Doctrine/ORM/Cache/CollectionCacheKey.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
/**
|
||||
* Defines entity collection roles to be stored in the cache region.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class CollectionCacheKey extends CacheKey
|
||||
{
|
||||
/**
|
||||
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
|
||||
*
|
||||
* @var array The owner entity identifier
|
||||
*/
|
||||
public $ownerIdentifier;
|
||||
|
||||
/**
|
||||
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
|
||||
*
|
||||
* @var string The owner entity class
|
||||
*/
|
||||
public $entityClass;
|
||||
|
||||
/**
|
||||
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
|
||||
*
|
||||
* @var string The association name
|
||||
*/
|
||||
public $association;
|
||||
|
||||
/**
|
||||
* @param string $entityClass The entity class.
|
||||
* @param string $association The field name that represents the association.
|
||||
* @param array $ownerIdentifier The identifier of the owning entity.
|
||||
*/
|
||||
public function __construct($entityClass, $association, array $ownerIdentifier)
|
||||
{
|
||||
ksort($ownerIdentifier);
|
||||
|
||||
$this->ownerIdentifier = $ownerIdentifier;
|
||||
$this->entityClass = (string) $entityClass;
|
||||
$this->association = (string) $association;
|
||||
$this->hash = str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association;
|
||||
}
|
||||
}
|
||||
52
lib/Doctrine/ORM/Cache/CollectionHydrator.php
Normal file
52
lib/Doctrine/ORM/Cache/CollectionHydrator.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
|
||||
/**
|
||||
* Hydrator cache entry for collections
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
interface CollectionHydrator
|
||||
{
|
||||
/**
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
|
||||
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cached collection key.
|
||||
* @param array|\Doctrine\Common\Collections\Collection $collection The collection.
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\CollectionCacheEntry
|
||||
*/
|
||||
public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, $collection);
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The owning entity metadata.
|
||||
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cached collection key.
|
||||
* @param \Doctrine\ORM\Cache\CollectionCacheEntry $entry The cached collection entry.
|
||||
* @param \Doctrine\ORM\PersistentCollection $collection The collection to load the cache into.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, CollectionCacheEntry $entry, PersistentCollection $collection);
|
||||
}
|
||||
57
lib/Doctrine/ORM/Cache/ConcurrentRegion.php
Normal file
57
lib/Doctrine/ORM/Cache/ConcurrentRegion.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
/**
|
||||
* Defines contract for concurrently managed data region.
|
||||
* It should be able to lock an specific cache entry in an atomic operation.
|
||||
*
|
||||
* When a entry is locked another process should not be able to read or write the entry.
|
||||
* All evict operation should not consider locks, even though an entry is locked evict should be able to delete the entry and its lock.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
interface ConcurrentRegion extends Region
|
||||
{
|
||||
/**
|
||||
* Attempts to read lock the mapping for the given key.
|
||||
*
|
||||
* @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to lock.
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\Lock A lock instance or NULL if the lock already exists.
|
||||
*
|
||||
* @throws \Doctrine\ORM\Cache\LockException Indicates a problem accessing the region.
|
||||
*/
|
||||
public function lock(CacheKey $key);
|
||||
|
||||
/**
|
||||
* Attempts to read unlock the mapping for the given key.
|
||||
*
|
||||
* @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to unlock.
|
||||
* @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained from {@link readLock}
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \Doctrine\ORM\Cache\LockException Indicates a problem accessing the region.
|
||||
*/
|
||||
public function unlock(CacheKey $key, Lock $lock);
|
||||
}
|
||||
342
lib/Doctrine/ORM/Cache/DefaultCache.php
Normal file
342
lib/Doctrine/ORM/Cache/DefaultCache.php
Normal file
@@ -0,0 +1,342 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
use Doctrine\ORM\Cache;
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Cache\Persister\CachedPersister;
|
||||
use Doctrine\ORM\ORMInvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Provides an API for querying/managing the second level cache regions.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class DefaultCache implements Cache
|
||||
{
|
||||
/**
|
||||
* @var \Doctrine\ORM\EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\UnitOfWork
|
||||
*/
|
||||
private $uow;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\CacheFactory
|
||||
*/
|
||||
private $cacheFactory;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\QueryCache[]
|
||||
*/
|
||||
private $queryCaches = array();
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\QueryCache
|
||||
*/
|
||||
private $defaultQueryCache;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->uow = $em->getUnitOfWork();
|
||||
$this->cacheFactory = $em->getConfiguration()
|
||||
->getSecondLevelCacheConfiguration()
|
||||
->getCacheFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEntityCacheRegion($className)
|
||||
{
|
||||
$metadata = $this->em->getClassMetadata($className);
|
||||
$persister = $this->uow->getEntityPersister($metadata->rootEntityName);
|
||||
|
||||
if ( ! ($persister instanceof CachedPersister)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $persister->getCacheRegion();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCollectionCacheRegion($className, $association)
|
||||
{
|
||||
$metadata = $this->em->getClassMetadata($className);
|
||||
$persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association));
|
||||
|
||||
if ( ! ($persister instanceof CachedPersister)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $persister->getCacheRegion();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function containsEntity($className, $identifier)
|
||||
{
|
||||
$metadata = $this->em->getClassMetadata($className);
|
||||
$persister = $this->uow->getEntityPersister($metadata->rootEntityName);
|
||||
|
||||
if ( ! ($persister instanceof CachedPersister)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $persister->getCacheRegion()->contains($this->buildEntityCacheKey($metadata, $identifier));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function evictEntity($className, $identifier)
|
||||
{
|
||||
$metadata = $this->em->getClassMetadata($className);
|
||||
$persister = $this->uow->getEntityPersister($metadata->rootEntityName);
|
||||
|
||||
if ( ! ($persister instanceof CachedPersister)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$persister->getCacheRegion()->evict($this->buildEntityCacheKey($metadata, $identifier));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function evictEntityRegion($className)
|
||||
{
|
||||
$metadata = $this->em->getClassMetadata($className);
|
||||
$persister = $this->uow->getEntityPersister($metadata->rootEntityName);
|
||||
|
||||
if ( ! ($persister instanceof CachedPersister)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$persister->getCacheRegion()->evictAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function evictEntityRegions()
|
||||
{
|
||||
$metadatas = $this->em->getMetadataFactory()->getAllMetadata();
|
||||
|
||||
foreach ($metadatas as $metadata) {
|
||||
$persister = $this->uow->getEntityPersister($metadata->rootEntityName);
|
||||
|
||||
if ( ! ($persister instanceof CachedPersister)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$persister->getCacheRegion()->evictAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function containsCollection($className, $association, $ownerIdentifier)
|
||||
{
|
||||
$metadata = $this->em->getClassMetadata($className);
|
||||
$persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association));
|
||||
|
||||
if ( ! ($persister instanceof CachedPersister)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $persister->getCacheRegion()->contains($this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function evictCollection($className, $association, $ownerIdentifier)
|
||||
{
|
||||
$metadata = $this->em->getClassMetadata($className);
|
||||
$persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association));
|
||||
|
||||
if ( ! ($persister instanceof CachedPersister)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$persister->getCacheRegion()->evict($this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function evictCollectionRegion($className, $association)
|
||||
{
|
||||
$metadata = $this->em->getClassMetadata($className);
|
||||
$persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association));
|
||||
|
||||
if ( ! ($persister instanceof CachedPersister)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$persister->getCacheRegion()->evictAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function evictCollectionRegions()
|
||||
{
|
||||
$metadatas = $this->em->getMetadataFactory()->getAllMetadata();
|
||||
|
||||
foreach ($metadatas as $metadata) {
|
||||
|
||||
foreach ($metadata->associationMappings as $association) {
|
||||
|
||||
if ( ! $association['type'] & ClassMetadata::TO_MANY) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$persister = $this->uow->getCollectionPersister($association);
|
||||
|
||||
if ( ! ($persister instanceof CachedPersister)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$persister->getCacheRegion()->evictAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function containsQuery($regionName)
|
||||
{
|
||||
return isset($this->queryCaches[$regionName]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function evictQueryRegion($regionName = null)
|
||||
{
|
||||
if ($regionName === null && $this->defaultQueryCache !== null) {
|
||||
$this->defaultQueryCache->clear();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($this->queryCaches[$regionName])) {
|
||||
$this->queryCaches[$regionName]->clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function evictQueryRegions()
|
||||
{
|
||||
$this->getQueryCache()->clear();
|
||||
|
||||
foreach ($this->queryCaches as $queryCache) {
|
||||
$queryCache->clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQueryCache($regionName = null)
|
||||
{
|
||||
if ($regionName === null) {
|
||||
return $this->defaultQueryCache ?:
|
||||
$this->defaultQueryCache = $this->cacheFactory->buildQueryCache($this->em);
|
||||
}
|
||||
|
||||
if ( ! isset($this->queryCaches[$regionName])) {
|
||||
$this->queryCaches[$regionName] = $this->cacheFactory->buildQueryCache($this->em, $regionName);
|
||||
}
|
||||
|
||||
return $this->queryCaches[$regionName];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
|
||||
* @param mixed $identifier The entity identifier.
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\EntityCacheKey
|
||||
*/
|
||||
private function buildEntityCacheKey(ClassMetadata $metadata, $identifier)
|
||||
{
|
||||
if ( ! is_array($identifier)) {
|
||||
$identifier = $this->toIdentifierArray($metadata, $identifier);
|
||||
}
|
||||
|
||||
return new EntityCacheKey($metadata->rootEntityName, $identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
|
||||
* @param string $association The field name that represents the association.
|
||||
* @param mixed $ownerIdentifier The identifier of the owning entity.
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\CollectionCacheKey
|
||||
*/
|
||||
private function buildCollectionCacheKey(ClassMetadata $metadata, $association, $ownerIdentifier)
|
||||
{
|
||||
if ( ! is_array($ownerIdentifier)) {
|
||||
$ownerIdentifier = $this->toIdentifierArray($metadata, $ownerIdentifier);;
|
||||
}
|
||||
|
||||
return new CollectionCacheKey($metadata->rootEntityName, $association, $ownerIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
|
||||
* @param mixed $identifier The entity identifier.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function toIdentifierArray(ClassMetadata $metadata, $identifier)
|
||||
{
|
||||
if (is_object($identifier) && $this->em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($identifier))) {
|
||||
$identifier = $this->uow->getSingleIdentifierValue($identifier);
|
||||
|
||||
if ($identifier === null) {
|
||||
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
|
||||
}
|
||||
}
|
||||
|
||||
return array($metadata->identifier[0] => $identifier);
|
||||
}
|
||||
|
||||
}
|
||||
254
lib/Doctrine/ORM/Cache/DefaultCacheFactory.php
Normal file
254
lib/Doctrine/ORM/Cache/DefaultCacheFactory.php
Normal file
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
use Doctrine\Common\Cache\Cache as CacheAdapter;
|
||||
use Doctrine\Common\Cache\CacheProvider;
|
||||
use Doctrine\Common\Cache\MultiGetCache;
|
||||
use Doctrine\ORM\Cache;
|
||||
use Doctrine\ORM\Cache\Persister\Collection\NonStrictReadWriteCachedCollectionPersister;
|
||||
use Doctrine\ORM\Cache\Persister\Collection\ReadOnlyCachedCollectionPersister;
|
||||
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;
|
||||
use Doctrine\ORM\Cache\Region\UpdateTimestampCache;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
|
||||
use Doctrine\ORM\Persisters\Entity\EntityPersister;
|
||||
|
||||
/**
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class DefaultCacheFactory implements CacheFactory
|
||||
{
|
||||
/**
|
||||
* @var CacheAdapter
|
||||
*/
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\RegionsConfiguration
|
||||
*/
|
||||
private $regionsConfig;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\TimestampRegion|null
|
||||
*/
|
||||
private $timestampRegion;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\Region[]
|
||||
*/
|
||||
private $regions = array();
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $fileLockRegionDirectory;
|
||||
|
||||
/**
|
||||
* @param RegionsConfiguration $cacheConfig
|
||||
* @param CacheAdapter $cache
|
||||
*/
|
||||
public function __construct(RegionsConfiguration $cacheConfig, CacheAdapter $cache)
|
||||
{
|
||||
$this->cache = $cache;
|
||||
$this->regionsConfig = $cacheConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fileLockRegionDirectory
|
||||
*/
|
||||
public function setFileLockRegionDirectory($fileLockRegionDirectory)
|
||||
{
|
||||
$this->fileLockRegionDirectory = (string) $fileLockRegionDirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFileLockRegionDirectory()
|
||||
{
|
||||
return $this->fileLockRegionDirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Cache\Region $region
|
||||
*/
|
||||
public function setRegion(Region $region)
|
||||
{
|
||||
$this->regions[$region->getName()] = $region;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Cache\TimestampRegion $region
|
||||
*/
|
||||
public function setTimestampRegion(TimestampRegion $region)
|
||||
{
|
||||
$this->timestampRegion = $region;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPersister $persister, ClassMetadata $metadata)
|
||||
{
|
||||
$region = $this->getRegion($metadata->cache);
|
||||
$usage = $metadata->cache['usage'];
|
||||
|
||||
if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) {
|
||||
return new ReadOnlyCachedEntityPersister($persister, $region, $em, $metadata);
|
||||
}
|
||||
|
||||
if ($usage === ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE) {
|
||||
return new NonStrictReadWriteCachedEntityPersister($persister, $region, $em, $metadata);
|
||||
}
|
||||
|
||||
if ($usage === ClassMetadata::CACHE_USAGE_READ_WRITE) {
|
||||
return new ReadWriteCachedEntityPersister($persister, $region, $em, $metadata);
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(sprintf("Unrecognized access strategy type [%s]", $usage));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildCachedCollectionPersister(EntityManagerInterface $em, CollectionPersister $persister, array $mapping)
|
||||
{
|
||||
$usage = $mapping['cache']['usage'];
|
||||
$region = $this->getRegion($mapping['cache']);
|
||||
|
||||
if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) {
|
||||
return new ReadOnlyCachedCollectionPersister($persister, $region, $em, $mapping);
|
||||
}
|
||||
|
||||
if ($usage === ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE) {
|
||||
return new NonStrictReadWriteCachedCollectionPersister($persister, $region, $em, $mapping);
|
||||
}
|
||||
|
||||
if ($usage === ClassMetadata::CACHE_USAGE_READ_WRITE) {
|
||||
return new ReadWriteCachedCollectionPersister($persister, $region, $em, $mapping);
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(sprintf("Unrecognized access strategy type [%s]", $usage));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildQueryCache(EntityManagerInterface $em, $regionName = null)
|
||||
{
|
||||
return new DefaultQueryCache(
|
||||
$em,
|
||||
$this->getRegion(
|
||||
array(
|
||||
'region' => $regionName ?: Cache::DEFAULT_QUERY_REGION_NAME,
|
||||
'usage' => ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildCollectionHydrator(EntityManagerInterface $em, array $mapping)
|
||||
{
|
||||
return new DefaultCollectionHydrator($em);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildEntityHydrator(EntityManagerInterface $em, ClassMetadata $metadata)
|
||||
{
|
||||
return new DefaultEntityHydrator($em);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRegion(array $cache)
|
||||
{
|
||||
if (isset($this->regions[$cache['region']])) {
|
||||
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']);
|
||||
|
||||
$region = ($cacheAdapter instanceof MultiGetCache)
|
||||
? new DefaultMultiGetRegion($name, $cacheAdapter, $lifetime)
|
||||
: new DefaultRegion($name, $cacheAdapter, $lifetime);
|
||||
|
||||
if ($cache['usage'] === ClassMetadata::CACHE_USAGE_READ_WRITE) {
|
||||
|
||||
if ( ! $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(). '
|
||||
);
|
||||
}
|
||||
|
||||
$directory = $this->fileLockRegionDirectory . DIRECTORY_SEPARATOR . $cache['region'];
|
||||
$region = new FileLockRegion($region, $directory, $this->regionsConfig->getLockLifetime($cache['region']));
|
||||
}
|
||||
|
||||
return $this->regions[$cache['region']] = $region;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTimestampRegion()
|
||||
{
|
||||
if ($this->timestampRegion === null) {
|
||||
$name = Cache::DEFAULT_TIMESTAMP_REGION_NAME;
|
||||
$lifetime = $this->regionsConfig->getLifetime($name);
|
||||
|
||||
$this->timestampRegion = new UpdateTimestampCache($name, clone $this->cache, $lifetime);
|
||||
}
|
||||
|
||||
return $this->timestampRegion;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createCache(EntityManagerInterface $em)
|
||||
{
|
||||
return new DefaultCache($em);
|
||||
}
|
||||
}
|
||||
103
lib/Doctrine/ORM/Cache/DefaultCollectionHydrator.php
Normal file
103
lib/Doctrine/ORM/Cache/DefaultCollectionHydrator.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
/**
|
||||
* Default hydrator cache for collections
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class DefaultCollectionHydrator implements CollectionHydrator
|
||||
{
|
||||
/**
|
||||
* @var \Doctrine\ORM\EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\UnitOfWork
|
||||
*/
|
||||
private $uow;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $hints = array(Query::HINT_CACHE_ENABLED => true);
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->uow = $em->getUnitOfWork();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, $collection)
|
||||
{
|
||||
$data = array();
|
||||
|
||||
foreach ($collection as $index => $entity) {
|
||||
$data[$index] = new EntityCacheKey($metadata->name, $this->uow->getEntityIdentifier($entity));
|
||||
}
|
||||
return new CollectionCacheEntry($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, CollectionCacheEntry $entry, PersistentCollection $collection)
|
||||
{
|
||||
$assoc = $metadata->associationMappings[$key->association];
|
||||
/* @var $targetPersister \Doctrine\ORM\Cache\Persister\CachedPersister */
|
||||
$targetPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
|
||||
$targetRegion = $targetPersister->getCacheRegion();
|
||||
$list = array();
|
||||
|
||||
$entityEntries = $targetRegion->getMultiple($entry);
|
||||
|
||||
if ($entityEntries === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/* @var $entityEntries \Doctrine\ORM\Cache\EntityCacheEntry[] */
|
||||
foreach ($entityEntries as $index => $entityEntry) {
|
||||
$list[$index] = $this->uow->createEntity($entityEntry->class, $entityEntry->resolveAssociationEntries($this->em), self::$hints);
|
||||
}
|
||||
|
||||
array_walk($list, function($entity, $index) use ($collection) {
|
||||
$collection->hydrateSet($index, $entity);
|
||||
});
|
||||
|
||||
$this->uow->hydrationComplete();
|
||||
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
192
lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php
Normal file
192
lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php
Normal file
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Utility\IdentifierFlattener;
|
||||
|
||||
/**
|
||||
* Default hydrator cache for entities
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class DefaultEntityHydrator implements EntityHydrator
|
||||
{
|
||||
/**
|
||||
* @var \Doctrine\ORM\EntityManager
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\UnitOfWork
|
||||
*/
|
||||
private $uow;
|
||||
|
||||
/**
|
||||
* The IdentifierFlattener used for manipulating identifiers
|
||||
*
|
||||
* @var \Doctrine\ORM\Utility\IdentifierFlattener
|
||||
*/
|
||||
private $identifierFlattener;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $hints = array(Query::HINT_CACHE_ENABLED => true);
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->uow = $em->getUnitOfWork();
|
||||
$this->identifierFlattener = new IdentifierFlattener($em->getUnitOfWork(), $em->getMetadataFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $entity)
|
||||
{
|
||||
$data = $this->uow->getOriginalEntityData($entity);
|
||||
$data = array_merge($data, $key->identifier); // why update has no identifier values ?
|
||||
|
||||
foreach ($metadata->associationMappings as $name => $assoc) {
|
||||
|
||||
if ( ! isset($data[$name])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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]));
|
||||
unset($data[$name]);
|
||||
|
||||
foreach ($associationIds as $fieldName => $fieldValue) {
|
||||
|
||||
if (isset($targetClassMetadata->associationMappings[$fieldName])){
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! isset($assoc['id'])) {
|
||||
$targetClass = ClassUtils::getClass($data[$name]);
|
||||
$targetId = $this->uow->getEntityIdentifier($data[$name]);
|
||||
$data[$name] = new AssociationCacheEntry($targetClass, $targetId);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// handle association identifier
|
||||
$targetId = is_object($data[$name]) && $this->uow->isInIdentityMap($data[$name])
|
||||
? $this->uow->getEntityIdentifier($data[$name])
|
||||
: $data[$name];
|
||||
|
||||
// @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);
|
||||
}
|
||||
|
||||
$data[$name] = new AssociationCacheEntry($assoc['targetEntity'], $targetId);
|
||||
}
|
||||
|
||||
return new EntityCacheEntry($metadata->name, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, EntityCacheEntry $entry, $entity = null)
|
||||
{
|
||||
$data = $entry->data;
|
||||
$hints = self::$hints;
|
||||
|
||||
if ($entity !== null) {
|
||||
$hints[Query::HINT_REFRESH] = true;
|
||||
$hints[Query::HINT_REFRESH_ENTITY] = $entity;
|
||||
}
|
||||
|
||||
foreach ($metadata->associationMappings as $name => $assoc) {
|
||||
if ( ! isset($assoc['cache']) || ! isset($data[$name])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$assocClass = $data[$name]->class;
|
||||
$assocId = $data[$name]->identifier;
|
||||
$isEagerLoad = ($assoc['fetch'] === ClassMetadata::FETCH_EAGER || ($assoc['type'] === ClassMetadata::ONE_TO_ONE && ! $assoc['isOwningSide']));
|
||||
|
||||
if ( ! $isEagerLoad) {
|
||||
$data[$name] = $this->em->getReference($assocClass, $assocId);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$assocKey = new EntityCacheKey($assoc['targetEntity'], $assocId);
|
||||
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
|
||||
$assocRegion = $assocPersister->getCacheRegion();
|
||||
$assocEntry = $assocRegion->get($assocKey);
|
||||
|
||||
if ($assocEntry === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$data[$name] = $this->uow->createEntity($assocEntry->class, $assocEntry->resolveAssociationEntries($this->em), $hints);
|
||||
}
|
||||
|
||||
if ($entity !== null) {
|
||||
$this->uow->registerManaged($entity, $key->identifier, $data);
|
||||
}
|
||||
|
||||
$result = $this->uow->createEntity($entry->class, $data, $hints);
|
||||
|
||||
$this->uow->hydrationComplete();
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
342
lib/Doctrine/ORM/Cache/DefaultQueryCache.php
Normal file
342
lib/Doctrine/ORM/Cache/DefaultQueryCache.php
Normal file
@@ -0,0 +1,342 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\Cache\Persister\CachedPersister;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\Common\Proxy\Proxy;
|
||||
use Doctrine\ORM\Cache;
|
||||
use Doctrine\ORM\Query;
|
||||
|
||||
/**
|
||||
* Default query cache implementation.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class DefaultQueryCache implements QueryCache
|
||||
{
|
||||
/**
|
||||
* @var \Doctrine\ORM\EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\UnitOfWork
|
||||
*/
|
||||
private $uow;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\Region
|
||||
*/
|
||||
private $region;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\QueryCacheValidator
|
||||
*/
|
||||
private $validator;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\Logging\CacheLogger
|
||||
*/
|
||||
protected $cacheLogger;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $hints = array(Query::HINT_CACHE_ENABLED => true);
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
|
||||
* @param \Doctrine\ORM\Cache\Region $region The query region.
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $em, Region $region)
|
||||
{
|
||||
$cacheConfig = $em->getConfiguration()->getSecondLevelCacheConfiguration();
|
||||
|
||||
$this->em = $em;
|
||||
$this->region = $region;
|
||||
$this->uow = $em->getUnitOfWork();
|
||||
$this->cacheLogger = $cacheConfig->getCacheLogger();
|
||||
$this->validator = $cacheConfig->getQueryValidator();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = array())
|
||||
{
|
||||
if ( ! ($key->cacheMode & Cache::MODE_GET)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$entry = $this->region->get($key);
|
||||
|
||||
if ( ! $entry instanceof QueryCacheEntry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( ! $this->validator->isValid($key, $entry)) {
|
||||
$this->region->evict($key);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$result = array();
|
||||
$entityName = reset($rsm->aliasMap);
|
||||
$hasRelation = ( ! empty($rsm->relationMap));
|
||||
$persister = $this->uow->getEntityPersister($entityName);
|
||||
$region = $persister->getCacheRegion();
|
||||
$regionName = $region->getName();
|
||||
|
||||
// @TODO - move to cache hydration component
|
||||
foreach ($entry->result as $index => $entry) {
|
||||
|
||||
if (($entityEntry = $region->get($entityKey = new EntityCacheKey($entityName, $entry['identifier']))) === null) {
|
||||
|
||||
if ($this->cacheLogger !== null) {
|
||||
$this->cacheLogger->entityCacheMiss($regionName, $entityKey);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->cacheLogger !== null) {
|
||||
$this->cacheLogger->entityCacheHit($regionName, $entityKey);
|
||||
}
|
||||
|
||||
if ( ! $hasRelation) {
|
||||
|
||||
$result[$index] = $this->uow->createEntity($entityEntry->class, $entityEntry->resolveAssociationEntries($this->em), self::$hints);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$data = $entityEntry->data;
|
||||
|
||||
foreach ($entry['associations'] as $name => $assoc) {
|
||||
|
||||
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
|
||||
$assocRegion = $assocPersister->getCacheRegion();
|
||||
|
||||
if ($assoc['type'] & ClassMetadata::TO_ONE) {
|
||||
|
||||
if (($assocEntry = $assocRegion->get($assocKey = new EntityCacheKey($assoc['targetEntity'], $assoc['identifier']))) === null) {
|
||||
|
||||
if ($this->cacheLogger !== null) {
|
||||
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey);
|
||||
}
|
||||
|
||||
$this->uow->hydrationComplete();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$data[$name] = $this->uow->createEntity($assocEntry->class, $assocEntry->resolveAssociationEntries($this->em), self::$hints);
|
||||
|
||||
if ($this->cacheLogger !== null) {
|
||||
$this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKey);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! isset($assoc['list']) || empty($assoc['list'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
|
||||
$collection = new PersistentCollection($this->em, $targetClass, new ArrayCollection());
|
||||
|
||||
foreach ($assoc['list'] as $assocIndex => $assocId) {
|
||||
|
||||
if (($assocEntry = $assocRegion->get($assocKey = new EntityCacheKey($assoc['targetEntity'], $assocId))) === null) {
|
||||
|
||||
if ($this->cacheLogger !== null) {
|
||||
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey);
|
||||
}
|
||||
|
||||
$this->uow->hydrationComplete();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$element = $this->uow->createEntity($assocEntry->class, $assocEntry->resolveAssociationEntries($this->em), self::$hints);
|
||||
|
||||
$collection->hydrateSet($assocIndex, $element);
|
||||
|
||||
if ($this->cacheLogger !== null) {
|
||||
$this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKey);
|
||||
}
|
||||
}
|
||||
|
||||
$data[$name] = $collection;
|
||||
|
||||
$collection->setInitialized(true);
|
||||
}
|
||||
|
||||
$result[$index] = $this->uow->createEntity($entityEntry->class, $data, self::$hints);
|
||||
}
|
||||
|
||||
$this->uow->hydrationComplete();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = array())
|
||||
{
|
||||
if ($rsm->scalarMappings) {
|
||||
throw new CacheException("Second level cache does not support scalar results.");
|
||||
}
|
||||
|
||||
if (count($rsm->entityMappings) > 1) {
|
||||
throw new CacheException("Second level cache does not support multiple root entities.");
|
||||
}
|
||||
|
||||
if ( ! $rsm->isSelect) {
|
||||
throw new CacheException("Second-level cache query supports only select statements.");
|
||||
}
|
||||
|
||||
if (isset($hints[Query::HINT_FORCE_PARTIAL_LOAD]) && $hints[Query::HINT_FORCE_PARTIAL_LOAD]) {
|
||||
throw new CacheException("Second level cache does not support partial entities.");
|
||||
}
|
||||
|
||||
if ( ! ($key->cacheMode & Cache::MODE_PUT)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = array();
|
||||
$entityName = reset($rsm->aliasMap);
|
||||
$hasRelation = ( ! empty($rsm->relationMap));
|
||||
$metadata = $this->em->getClassMetadata($entityName);
|
||||
$persister = $this->uow->getEntityPersister($entityName);
|
||||
|
||||
if ( ! ($persister instanceof CachedPersister)) {
|
||||
throw CacheException::nonCacheableEntity($entityName);
|
||||
}
|
||||
|
||||
$region = $persister->getCacheRegion();
|
||||
|
||||
foreach ($result as $index => $entity) {
|
||||
$identifier = $this->uow->getEntityIdentifier($entity);
|
||||
$data[$index]['identifier'] = $identifier;
|
||||
$data[$index]['associations'] = array();
|
||||
|
||||
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $region->contains($entityKey = new EntityCacheKey($entityName, $identifier))) {
|
||||
// Cancel put result if entity put fail
|
||||
if ( ! $persister->storeEntityCache($entity, $entityKey)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $hasRelation) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// @TODO - move to cache hydration components
|
||||
foreach ($rsm->relationMap as $name) {
|
||||
$assoc = $metadata->associationMappings[$name];
|
||||
|
||||
if (($assocValue = $metadata->getFieldValue($entity, $name)) === null || $assocValue instanceof Proxy) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
$data[$index]['associations'][$name] = array(
|
||||
'targetEntity' => $assocMetadata->rootEntityName,
|
||||
'identifier' => $assocIdentifier,
|
||||
'type' => $assoc['type']
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
$list[$assocItemIndex] = $assocIdentifier;
|
||||
}
|
||||
|
||||
$data[$index]['associations'][$name] = array(
|
||||
'targetEntity' => $assocMetadata->rootEntityName,
|
||||
'type' => $assoc['type'],
|
||||
'list' => $list,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->region->put($key, new QueryCacheEntry($data));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
return $this->region->evictAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRegion()
|
||||
{
|
||||
return $this->region;
|
||||
}
|
||||
}
|
||||
86
lib/Doctrine/ORM/Cache/EntityCacheEntry.php
Normal file
86
lib/Doctrine/ORM/Cache/EntityCacheEntry.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
/**
|
||||
* Entity cache entry
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class EntityCacheEntry implements CacheEntry
|
||||
{
|
||||
/**
|
||||
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
|
||||
*
|
||||
* @var array The entity map data
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
|
||||
*
|
||||
* @var string The entity class name
|
||||
*/
|
||||
public $class;
|
||||
|
||||
/**
|
||||
* @param string $class The entity class.
|
||||
* @param array $data The entity data.
|
||||
*/
|
||||
public function __construct($class, array $data)
|
||||
{
|
||||
$this->class = $class;
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new EntityCacheEntry
|
||||
*
|
||||
* This method allow Doctrine\Common\Cache\PhpFileCache compatibility
|
||||
*
|
||||
* @param array $values array containing property values
|
||||
*/
|
||||
public static function __set_state(array $values)
|
||||
{
|
||||
return new self($values['class'], $values['data']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the entity data resolving cache entries
|
||||
*
|
||||
* @param \Doctrine\ORM\EntityManagerInterfac $em
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function resolveAssociationEntries(EntityManagerInterface $em)
|
||||
{
|
||||
return array_map(function($value) use ($em) {
|
||||
if ( ! ($value instanceof AssociationCacheEntry)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return $em->getReference($value->class, $value->identifier);
|
||||
}, $this->data);
|
||||
}
|
||||
}
|
||||
57
lib/Doctrine/ORM/Cache/EntityCacheKey.php
Normal file
57
lib/Doctrine/ORM/Cache/EntityCacheKey.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
/**
|
||||
* Defines entity classes roles to be stored in the cache region.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class EntityCacheKey extends CacheKey
|
||||
{
|
||||
/**
|
||||
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
|
||||
*
|
||||
* @var array The entity identifier
|
||||
*/
|
||||
public $identifier;
|
||||
|
||||
/**
|
||||
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
|
||||
*
|
||||
* @var string The entity class name
|
||||
*/
|
||||
public $entityClass;
|
||||
|
||||
/**
|
||||
* @param string $entityClass The entity class name. In a inheritance hierarchy it should always be the root entity class.
|
||||
* @param array $identifier The entity identifier
|
||||
*/
|
||||
public function __construct($entityClass, array $identifier)
|
||||
{
|
||||
ksort($identifier);
|
||||
|
||||
$this->identifier = $identifier;
|
||||
$this->entityClass = $entityClass;
|
||||
$this->hash = str_replace('\\', '.', strtolower($entityClass) . '_' . implode(' ', $identifier));
|
||||
}
|
||||
}
|
||||
49
lib/Doctrine/ORM/Cache/EntityHydrator.php
Normal file
49
lib/Doctrine/ORM/Cache/EntityHydrator.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
|
||||
/**
|
||||
* Hydrator cache entry for entities
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
interface EntityHydrator
|
||||
{
|
||||
/**
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
|
||||
* @param \Doctrine\ORM\Cache\EntityCacheKey $key The entity cache key.
|
||||
* @param object $entity The entity.
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\EntityCacheEntry
|
||||
*/
|
||||
public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $entity);
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
|
||||
* @param \Doctrine\ORM\Cache\EntityCacheKey $key The entity cache key.
|
||||
* @param \Doctrine\ORM\Cache\EntityCacheEntry $entry The entity cache entry.
|
||||
* @param object $entity The entity to load the cache into. If not specified, a new entity is created.
|
||||
*/
|
||||
public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, EntityCacheEntry $entry, $entity = null);
|
||||
}
|
||||
58
lib/Doctrine/ORM/Cache/Lock.php
Normal file
58
lib/Doctrine/ORM/Cache/Lock.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
/**
|
||||
* Cache Lock
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class Lock
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $value;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
public $time;
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
* @param integer $time
|
||||
*/
|
||||
public function __construct($value, $time = null)
|
||||
{
|
||||
$this->value = $value;
|
||||
$this->time = $time ? : time();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Doctrine\ORM\Cache\Lock
|
||||
*/
|
||||
public static function createLockRead()
|
||||
{
|
||||
return new self(uniqid(time()));
|
||||
}
|
||||
}
|
||||
32
lib/Doctrine/ORM/Cache/LockException.php
Normal file
32
lib/Doctrine/ORM/Cache/LockException.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
/**
|
||||
* Lock exception for cache.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class LockException extends CacheException
|
||||
{
|
||||
|
||||
}
|
||||
106
lib/Doctrine/ORM/Cache/Logging/CacheLogger.php
Normal file
106
lib/Doctrine/ORM/Cache/Logging/CacheLogger.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache\Logging;
|
||||
|
||||
use Doctrine\ORM\Cache\CollectionCacheKey;
|
||||
use Doctrine\ORM\Cache\EntityCacheKey;
|
||||
use Doctrine\ORM\Cache\QueryCacheKey;
|
||||
|
||||
/**
|
||||
* Interface for logging.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
interface CacheLogger
|
||||
{
|
||||
/**
|
||||
* Log an entity put into second level cache.
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param \Doctrine\ORM\Cache\EntityCacheKey $key The cache key of the entity.
|
||||
*/
|
||||
public function entityCachePut($regionName, EntityCacheKey $key);
|
||||
|
||||
/**
|
||||
* Log an entity get from second level cache resulted in a hit.
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param \Doctrine\ORM\Cache\EntityCacheKey $key The cache key of the entity.
|
||||
*/
|
||||
public function entityCacheHit($regionName, EntityCacheKey $key);
|
||||
|
||||
/**
|
||||
* Log an entity get from second level cache resulted in a miss.
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param \Doctrine\ORM\Cache\EntityCacheKey $key The cache key of the entity.
|
||||
*/
|
||||
public function entityCacheMiss($regionName, EntityCacheKey $key);
|
||||
|
||||
/**
|
||||
* Log an entity put into second level cache.
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cache key of the collection.
|
||||
*/
|
||||
public function collectionCachePut($regionName, CollectionCacheKey $key);
|
||||
|
||||
/**
|
||||
* Log an entity get from second level cache resulted in a hit.
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cache key of the collection.
|
||||
*/
|
||||
public function collectionCacheHit($regionName, CollectionCacheKey $key);
|
||||
|
||||
/**
|
||||
* Log an entity get from second level cache resulted in a miss.
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cache key of the collection.
|
||||
*/
|
||||
public function collectionCacheMiss($regionName, CollectionCacheKey $key);
|
||||
|
||||
/**
|
||||
* Log a query put into the query cache.
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param \Doctrine\ORM\Cache\QueryCacheKey $key The cache key of the query.
|
||||
*/
|
||||
public function queryCachePut($regionName, QueryCacheKey $key);
|
||||
|
||||
/**
|
||||
* Log a query get from the query cache resulted in a hit.
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param \Doctrine\ORM\Cache\QueryCacheKey $key The cache key of the query.
|
||||
*/
|
||||
public function queryCacheHit($regionName, QueryCacheKey $key);
|
||||
|
||||
/**
|
||||
* Log a query get from the query cache resulted in a miss.
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param \Doctrine\ORM\Cache\QueryCacheKey $key The cache key of the query.
|
||||
*/
|
||||
public function queryCacheMiss($regionName, QueryCacheKey $key);
|
||||
}
|
||||
156
lib/Doctrine/ORM/Cache/Logging/CacheLoggerChain.php
Normal file
156
lib/Doctrine/ORM/Cache/Logging/CacheLoggerChain.php
Normal file
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache\Logging;
|
||||
|
||||
use Doctrine\ORM\Cache\CollectionCacheKey;
|
||||
use Doctrine\ORM\Cache\EntityCacheKey;
|
||||
use Doctrine\ORM\Cache\QueryCacheKey;
|
||||
|
||||
/**
|
||||
* Cache logger chain
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class CacheLoggerChain implements CacheLogger
|
||||
{
|
||||
/**
|
||||
* @var array<\Doctrine\ORM\Cache\Logging\CacheLogger>
|
||||
*/
|
||||
private $loggers = array();
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param \Doctrine\ORM\Cache\Logging\CacheLogger $logger
|
||||
*/
|
||||
public function setLogger($name, CacheLogger $logger)
|
||||
{
|
||||
$this->loggers[$name] = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\Logging\CacheLogger|null
|
||||
*/
|
||||
public function getLogger($name)
|
||||
{
|
||||
return isset($this->loggers[$name]) ? $this->loggers[$name] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<\Doctrine\ORM\Cache\Logging\CacheLogger>
|
||||
*/
|
||||
public function getLoggers()
|
||||
{
|
||||
return $this->loggers;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function collectionCacheHit($regionName, CollectionCacheKey $key)
|
||||
{
|
||||
foreach ($this->loggers as $logger) {
|
||||
$logger->collectionCacheHit($regionName, $key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function collectionCacheMiss($regionName, CollectionCacheKey $key)
|
||||
{
|
||||
foreach ($this->loggers as $logger) {
|
||||
$logger->collectionCacheMiss($regionName, $key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function collectionCachePut($regionName, CollectionCacheKey $key)
|
||||
{
|
||||
foreach ($this->loggers as $logger) {
|
||||
$logger->collectionCachePut($regionName, $key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function entityCacheHit($regionName, EntityCacheKey $key)
|
||||
{
|
||||
foreach ($this->loggers as $logger) {
|
||||
$logger->entityCacheHit($regionName, $key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function entityCacheMiss($regionName, EntityCacheKey $key)
|
||||
{
|
||||
foreach ($this->loggers as $logger) {
|
||||
$logger->entityCacheMiss($regionName, $key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function entityCachePut($regionName, EntityCacheKey $key)
|
||||
{
|
||||
foreach ($this->loggers as $logger) {
|
||||
$logger->entityCachePut($regionName, $key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function queryCacheHit($regionName, QueryCacheKey $key)
|
||||
{
|
||||
foreach ($this->loggers as $logger) {
|
||||
$logger->queryCacheHit($regionName, $key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function queryCacheMiss($regionName, QueryCacheKey $key)
|
||||
{
|
||||
foreach ($this->loggers as $logger) {
|
||||
$logger->queryCacheMiss($regionName, $key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function queryCachePut($regionName, QueryCacheKey $key)
|
||||
{
|
||||
foreach ($this->loggers as $logger) {
|
||||
$logger->queryCachePut($regionName, $key);
|
||||
}
|
||||
}
|
||||
}
|
||||
251
lib/Doctrine/ORM/Cache/Logging/StatisticsCacheLogger.php
Normal file
251
lib/Doctrine/ORM/Cache/Logging/StatisticsCacheLogger.php
Normal file
@@ -0,0 +1,251 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache\Logging;
|
||||
|
||||
use Doctrine\ORM\Cache\CollectionCacheKey;
|
||||
use Doctrine\ORM\Cache\EntityCacheKey;
|
||||
use Doctrine\ORM\Cache\QueryCacheKey;
|
||||
|
||||
/**
|
||||
* Provide basic second level cache statistics.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class StatisticsCacheLogger implements CacheLogger
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $cacheMissCountMap = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $cacheHitCountMap = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $cachePutCountMap = array();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function collectionCacheMiss($regionName, CollectionCacheKey $key)
|
||||
{
|
||||
$this->cacheMissCountMap[$regionName] = isset($this->cacheMissCountMap[$regionName])
|
||||
? $this->cacheMissCountMap[$regionName] + 1
|
||||
: 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function collectionCacheHit($regionName, CollectionCacheKey $key)
|
||||
{
|
||||
$this->cacheHitCountMap[$regionName] = isset($this->cacheHitCountMap[$regionName])
|
||||
? $this->cacheHitCountMap[$regionName] + 1
|
||||
: 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function collectionCachePut($regionName, CollectionCacheKey $key)
|
||||
{
|
||||
$this->cachePutCountMap[$regionName] = isset($this->cachePutCountMap[$regionName])
|
||||
? $this->cachePutCountMap[$regionName] + 1
|
||||
: 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function entityCacheMiss($regionName, EntityCacheKey $key)
|
||||
{
|
||||
$this->cacheMissCountMap[$regionName] = isset($this->cacheMissCountMap[$regionName])
|
||||
? $this->cacheMissCountMap[$regionName] + 1
|
||||
: 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function entityCacheHit($regionName, EntityCacheKey $key)
|
||||
{
|
||||
$this->cacheHitCountMap[$regionName] = isset($this->cacheHitCountMap[$regionName])
|
||||
? $this->cacheHitCountMap[$regionName] + 1
|
||||
: 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function entityCachePut($regionName, EntityCacheKey $key)
|
||||
{
|
||||
$this->cachePutCountMap[$regionName] = isset($this->cachePutCountMap[$regionName])
|
||||
? $this->cachePutCountMap[$regionName] + 1
|
||||
: 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function queryCacheHit($regionName, QueryCacheKey $key)
|
||||
{
|
||||
$this->cacheHitCountMap[$regionName] = isset($this->cacheHitCountMap[$regionName])
|
||||
? $this->cacheHitCountMap[$regionName] + 1
|
||||
: 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function queryCacheMiss($regionName, QueryCacheKey $key)
|
||||
{
|
||||
$this->cacheMissCountMap[$regionName] = isset($this->cacheMissCountMap[$regionName])
|
||||
? $this->cacheMissCountMap[$regionName] + 1
|
||||
: 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function queryCachePut($regionName, QueryCacheKey $key)
|
||||
{
|
||||
$this->cachePutCountMap[$regionName] = isset($this->cachePutCountMap[$regionName])
|
||||
? $this->cachePutCountMap[$regionName] + 1
|
||||
: 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of entries successfully retrieved from cache.
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getRegionHitCount($regionName)
|
||||
{
|
||||
return isset($this->cacheHitCountMap[$regionName]) ? $this->cacheHitCountMap[$regionName] : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of cached entries *not* found in cache.
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getRegionMissCount($regionName)
|
||||
{
|
||||
return isset($this->cacheMissCountMap[$regionName]) ? $this->cacheMissCountMap[$regionName] : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of cacheable entries put in cache.
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getRegionPutCount($regionName)
|
||||
{
|
||||
return isset($this->cachePutCountMap[$regionName]) ? $this->cachePutCountMap[$regionName] : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getRegionsMiss()
|
||||
{
|
||||
return $this->cacheMissCountMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getRegionsHit()
|
||||
{
|
||||
return $this->cacheHitCountMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getRegionsPut()
|
||||
{
|
||||
return $this->cachePutCountMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear region statistics
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
*/
|
||||
public function clearRegionStats($regionName)
|
||||
{
|
||||
$this->cachePutCountMap[$regionName] = 0;
|
||||
$this->cacheHitCountMap[$regionName] = 0;
|
||||
$this->cacheMissCountMap[$regionName] = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all statistics
|
||||
*/
|
||||
public function clearStats()
|
||||
{
|
||||
$this->cachePutCountMap = array();
|
||||
$this->cacheHitCountMap = array();
|
||||
$this->cacheMissCountMap = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total number of put in cache.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getPutCount()
|
||||
{
|
||||
return array_sum($this->cachePutCountMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total number of entries successfully retrieved from cache.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getHitCount()
|
||||
{
|
||||
return array_sum($this->cacheHitCountMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total number of cached entries *not* found in cache.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getMissCount()
|
||||
{
|
||||
return array_sum($this->cacheMissCountMap);
|
||||
}
|
||||
}
|
||||
42
lib/Doctrine/ORM/Cache/MultiGetRegion.php
Normal file
42
lib/Doctrine/ORM/Cache/MultiGetRegion.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
/**
|
||||
* Defines a region that supports multi-get reading.
|
||||
*
|
||||
* With one method call we can get multiple items.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Asmir Mustafic
|
||||
*/
|
||||
interface MultiGetRegion
|
||||
{
|
||||
/**
|
||||
* Get all items from the cache identified by $keys.
|
||||
* It returns NULL if some elements can not be found.
|
||||
*
|
||||
* @param CollectionCacheEntry $collection The collection of the items to be retrieved.
|
||||
*
|
||||
* @return CacheEntry[]|null The cached entries or NULL if one or more entries can not be found
|
||||
*/
|
||||
public function getMultiple(CollectionCacheEntry $collection);
|
||||
}
|
||||
46
lib/Doctrine/ORM/Cache/Persister/CachedPersister.php
Normal file
46
lib/Doctrine/ORM/Cache/Persister/CachedPersister.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache\Persister;
|
||||
|
||||
/**
|
||||
* Interface for persister that support second level cache.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
interface CachedPersister
|
||||
{
|
||||
/**
|
||||
* Perform whatever processing is encapsulated here after completion of the transaction.
|
||||
*/
|
||||
public function afterTransactionComplete();
|
||||
|
||||
/**
|
||||
* Perform whatever processing is encapsulated here after completion of the rolled-back.
|
||||
*/
|
||||
public function afterTransactionRolledBack();
|
||||
|
||||
/**
|
||||
* Gets the The region access.
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\Region
|
||||
*/
|
||||
public function getCacheRegion();
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache\Persister\Collection;
|
||||
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\ORM\Cache\EntityCacheKey;
|
||||
use Doctrine\ORM\Cache\CollectionCacheKey;
|
||||
use Doctrine\ORM\Cache\Persister\Entity\CachedEntityPersister;
|
||||
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Cache\Region;
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
|
||||
/**
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
* @since 2.5
|
||||
*/
|
||||
abstract class AbstractCollectionPersister implements CachedCollectionPersister
|
||||
{
|
||||
/**
|
||||
* @var \Doctrine\ORM\UnitOfWork
|
||||
*/
|
||||
protected $uow;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Mapping\ClassMetadataFactory
|
||||
*/
|
||||
protected $metadataFactory;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Persisters\Collection\CollectionPersister
|
||||
*/
|
||||
protected $persister;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Mapping\ClassMetadata
|
||||
*/
|
||||
protected $sourceEntity;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Mapping\ClassMetadata
|
||||
*/
|
||||
protected $targetEntity;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $association;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $queuedCache = array();
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\Region
|
||||
*/
|
||||
protected $region;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $regionName;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\CollectionHydrator
|
||||
*/
|
||||
protected $hydrator;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\Logging\CacheLogger
|
||||
*/
|
||||
protected $cacheLogger;
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Persisters\Collection\CollectionPersister $persister The collection persister that will be cached.
|
||||
* @param \Doctrine\ORM\Cache\Region $region The collection region.
|
||||
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
|
||||
* @param array $association The association mapping.
|
||||
*/
|
||||
public function __construct(CollectionPersister $persister, Region $region, EntityManagerInterface $em, array $association)
|
||||
{
|
||||
$configuration = $em->getConfiguration();
|
||||
$cacheConfig = $configuration->getSecondLevelCacheConfiguration();
|
||||
$cacheFactory = $cacheConfig->getCacheFactory();
|
||||
|
||||
$this->region = $region;
|
||||
$this->persister = $persister;
|
||||
$this->association = $association;
|
||||
$this->regionName = $region->getName();
|
||||
$this->uow = $em->getUnitOfWork();
|
||||
$this->metadataFactory = $em->getMetadataFactory();
|
||||
$this->cacheLogger = $cacheConfig->getCacheLogger();
|
||||
$this->hydrator = $cacheFactory->buildCollectionHydrator($em, $association);
|
||||
$this->sourceEntity = $em->getClassMetadata($association['sourceEntity']);
|
||||
$this->targetEntity = $em->getClassMetadata($association['targetEntity']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheRegion()
|
||||
{
|
||||
return $this->region;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSourceEntityMetadata()
|
||||
{
|
||||
return $this->sourceEntity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTargetEntityMetadata()
|
||||
{
|
||||
return $this->targetEntity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\PersistentCollection $collection
|
||||
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key
|
||||
*
|
||||
* @return \Doctrine\ORM\PersistentCollection|null
|
||||
*/
|
||||
public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key)
|
||||
{
|
||||
if (($cache = $this->region->get($key)) === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (($cache = $this->hydrator->loadCacheEntry($this->sourceEntity, $key, $cache, $collection)) === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function storeCollectionCache(CollectionCacheKey $key, $elements)
|
||||
{
|
||||
/* @var $targetPersister CachedEntityPersister */
|
||||
$targetPersister = $this->uow->getEntityPersister($this->targetEntity->rootEntityName);
|
||||
$targetRegion = $targetPersister->getCacheRegion();
|
||||
$targetHydrator = $targetPersister->getEntityHydrator();
|
||||
$entry = $this->hydrator->buildCacheEntry($this->targetEntity, $key, $elements);
|
||||
|
||||
foreach ($entry->identifiers as $index => $entityKey) {
|
||||
if ($targetRegion->contains($entityKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$class = $this->targetEntity;
|
||||
$className = ClassUtils::getClass($elements[$index]);
|
||||
|
||||
if ($className !== $this->targetEntity->name) {
|
||||
$class = $this->metadataFactory->getMetadataFor($className);
|
||||
}
|
||||
|
||||
$entity = $elements[$index];
|
||||
$entityEntry = $targetHydrator->buildCacheEntry($class, $entityKey, $entity);
|
||||
|
||||
$targetRegion->put($entityKey, $entityEntry);
|
||||
}
|
||||
|
||||
$cached = $this->region->put($key, $entry);
|
||||
|
||||
if ($this->cacheLogger && $cached) {
|
||||
$this->cacheLogger->collectionCachePut($this->regionName, $key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function contains(PersistentCollection $collection, $element)
|
||||
{
|
||||
return $this->persister->contains($collection, $element);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function containsKey(PersistentCollection $collection, $key)
|
||||
{
|
||||
return $this->persister->containsKey($collection, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function count(PersistentCollection $collection)
|
||||
{
|
||||
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
|
||||
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId);
|
||||
$entry = $this->region->get($key);
|
||||
|
||||
if ($entry !== null) {
|
||||
return count($entry->identifiers);
|
||||
}
|
||||
|
||||
return $this->persister->count($collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get(PersistentCollection $collection, $index)
|
||||
{
|
||||
return $this->persister->get($collection, $index);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeElement(PersistentCollection $collection, $element)
|
||||
{
|
||||
if ($persisterResult = $this->persister->removeElement($collection, $element)) {
|
||||
$this->evictCollectionCache($collection);
|
||||
$this->evictElementCache($this->sourceEntity->rootEntityName, $collection->getOwner());
|
||||
$this->evictElementCache($this->targetEntity->rootEntityName, $element);
|
||||
}
|
||||
|
||||
return $persisterResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function slice(PersistentCollection $collection, $offset, $length = null)
|
||||
{
|
||||
return $this->persister->slice($collection, $offset, $length);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function loadCriteria(PersistentCollection $collection, Criteria $criteria)
|
||||
{
|
||||
return $this->persister->loadCriteria($collection, $criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears cache entries related to the current collection
|
||||
*
|
||||
* @param PersistentCollection $collection
|
||||
*/
|
||||
protected function evictCollectionCache(PersistentCollection $collection)
|
||||
{
|
||||
$key = new CollectionCacheKey(
|
||||
$this->sourceEntity->rootEntityName,
|
||||
$this->association['fieldName'],
|
||||
$this->uow->getEntityIdentifier($collection->getOwner())
|
||||
);
|
||||
|
||||
$this->region->evict($key);
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->collectionCachePut($this->regionName, $key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $targetEntity
|
||||
* @param object $element
|
||||
*/
|
||||
protected function evictElementCache($targetEntity, $element)
|
||||
{
|
||||
/* @var $targetPersister CachedEntityPersister */
|
||||
$targetPersister = $this->uow->getEntityPersister($targetEntity);
|
||||
$targetRegion = $targetPersister->getCacheRegion();
|
||||
$key = new EntityCacheKey($targetEntity, $this->uow->getEntityIdentifier($element));
|
||||
|
||||
$targetRegion->evict($key);
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->entityCachePut($targetRegion->getName(), $key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache\Persister\Collection;
|
||||
|
||||
use Doctrine\ORM\Cache\CollectionCacheKey;
|
||||
use Doctrine\ORM\Cache\Persister\CachedPersister;
|
||||
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
|
||||
/**
|
||||
* Interface for second level cache collection persisters.
|
||||
*
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
* @since 2.5
|
||||
*/
|
||||
interface CachedCollectionPersister extends CachedPersister, CollectionPersister
|
||||
{
|
||||
/**
|
||||
* @return \Doctrine\ORM\Mapping\ClassMetadata
|
||||
*/
|
||||
public function getSourceEntityMetadata();
|
||||
|
||||
/**
|
||||
* @return \Doctrine\ORM\Mapping\ClassMetadata
|
||||
*/
|
||||
public function getTargetEntityMetadata();
|
||||
|
||||
/**
|
||||
* Loads a collection from cache
|
||||
*
|
||||
* @param \Doctrine\ORM\PersistentCollection $collection
|
||||
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key
|
||||
*
|
||||
* @return \Doctrine\ORM\PersistentCollection|null
|
||||
*/
|
||||
public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key);
|
||||
|
||||
/**
|
||||
* Stores a collection into cache
|
||||
*
|
||||
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key
|
||||
* @param array|\Doctrine\Common\Collections\Collection $elements
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function storeCollectionCache(CollectionCacheKey $key, $elements);
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache\Persister\Collection;
|
||||
|
||||
use Doctrine\ORM\Cache\CollectionCacheKey;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
|
||||
/**
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
* @since 2.5
|
||||
*/
|
||||
class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPersister
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function afterTransactionComplete()
|
||||
{
|
||||
if (isset($this->queuedCache['update'])) {
|
||||
foreach ($this->queuedCache['update'] as $item) {
|
||||
$this->storeCollectionCache($item['key'], $item['list']);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->queuedCache['delete'])) {
|
||||
foreach ($this->queuedCache['delete'] as $key) {
|
||||
$this->region->evict($key);
|
||||
}
|
||||
}
|
||||
|
||||
$this->queuedCache = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function afterTransactionRolledBack()
|
||||
{
|
||||
$this->queuedCache = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete(PersistentCollection $collection)
|
||||
{
|
||||
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
|
||||
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId);
|
||||
|
||||
$this->persister->delete($collection);
|
||||
|
||||
$this->queuedCache['delete'][spl_object_hash($collection)] = $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function update(PersistentCollection $collection)
|
||||
{
|
||||
$isInitialized = $collection->isInitialized();
|
||||
$isDirty = $collection->isDirty();
|
||||
|
||||
if ( ! $isInitialized && ! $isDirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
|
||||
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId);
|
||||
|
||||
// Invalidate non initialized collections OR ordered collection
|
||||
if ($isDirty && ! $isInitialized || isset($this->association['orderBy'])) {
|
||||
$this->persister->update($collection);
|
||||
|
||||
$this->queuedCache['delete'][spl_object_hash($collection)] = $key;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->persister->update($collection);
|
||||
|
||||
$this->queuedCache['update'][spl_object_hash($collection)] = array(
|
||||
'key' => $key,
|
||||
'list' => $collection
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache\Persister\Collection;
|
||||
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\Cache\CacheException;
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
|
||||
/**
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
* @since 2.5
|
||||
*/
|
||||
class ReadOnlyCachedCollectionPersister extends NonStrictReadWriteCachedCollectionPersister
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function update(PersistentCollection $collection)
|
||||
{
|
||||
if ($collection->isDirty() && count($collection->getSnapshot()) > 0) {
|
||||
throw CacheException::updateReadOnlyCollection(ClassUtils::getClass($collection->getOwner()), $this->association['fieldName']);
|
||||
}
|
||||
|
||||
parent::update($collection);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache\Persister\Collection;
|
||||
|
||||
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Cache\CollectionCacheKey;
|
||||
use Doctrine\ORM\Cache\ConcurrentRegion;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
|
||||
/**
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
* @since 2.5
|
||||
*/
|
||||
class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
|
||||
{
|
||||
/**
|
||||
* @param \Doctrine\ORM\Persisters\Collection\CollectionPersister $persister The collection persister that will be cached.
|
||||
* @param \Doctrine\ORM\Cache\ConcurrentRegion $region The collection region.
|
||||
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
|
||||
* @param array $association The association mapping.
|
||||
*/
|
||||
public function __construct(CollectionPersister $persister, ConcurrentRegion $region, EntityManagerInterface $em, array $association)
|
||||
{
|
||||
parent::__construct($persister, $region, $em, $association);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function afterTransactionComplete()
|
||||
{
|
||||
if (isset($this->queuedCache['update'])) {
|
||||
foreach ($this->queuedCache['update'] as $item) {
|
||||
$this->region->evict($item['key']);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->queuedCache['delete'])) {
|
||||
foreach ($this->queuedCache['delete'] as $item) {
|
||||
$this->region->evict($item['key']);
|
||||
}
|
||||
}
|
||||
|
||||
$this->queuedCache = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function afterTransactionRolledBack()
|
||||
{
|
||||
if (isset($this->queuedCache['update'])) {
|
||||
foreach ($this->queuedCache['update'] as $item) {
|
||||
$this->region->evict($item['key']);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->queuedCache['delete'])) {
|
||||
foreach ($this->queuedCache['delete'] as $item) {
|
||||
$this->region->evict($item['key']);
|
||||
}
|
||||
}
|
||||
|
||||
$this->queuedCache = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete(PersistentCollection $collection)
|
||||
{
|
||||
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
|
||||
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId);
|
||||
$lock = $this->region->lock($key);
|
||||
|
||||
$this->persister->delete($collection);
|
||||
|
||||
if ($lock === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->queuedCache['delete'][spl_object_hash($collection)] = array(
|
||||
'key' => $key,
|
||||
'lock' => $lock
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function update(PersistentCollection $collection)
|
||||
{
|
||||
$isInitialized = $collection->isInitialized();
|
||||
$isDirty = $collection->isDirty();
|
||||
|
||||
if ( ! $isInitialized && ! $isDirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->persister->update($collection);
|
||||
|
||||
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
|
||||
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId);
|
||||
$lock = $this->region->lock($key);
|
||||
|
||||
if ($lock === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->queuedCache['update'][spl_object_hash($collection)] = array(
|
||||
'key' => $key,
|
||||
'lock' => $lock
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,646 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache\Persister\Entity;
|
||||
|
||||
use Doctrine\ORM\Cache;
|
||||
use Doctrine\ORM\Cache\Region;
|
||||
use Doctrine\ORM\Cache\EntityCacheKey;
|
||||
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;
|
||||
use Doctrine\ORM\Persisters\Entity\EntityPersister;
|
||||
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
|
||||
/**
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
* @since 2.5
|
||||
*/
|
||||
abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
{
|
||||
/**
|
||||
* @var \Doctrine\ORM\UnitOfWork
|
||||
*/
|
||||
protected $uow;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Mapping\ClassMetadataFactory
|
||||
*/
|
||||
protected $metadataFactory;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Persisters\Entity\EntityPersister
|
||||
*/
|
||||
protected $persister;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Mapping\ClassMetadata
|
||||
*/
|
||||
protected $class;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $queuedCache = array();
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\Region
|
||||
*/
|
||||
protected $region;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\TimestampRegion
|
||||
*/
|
||||
protected $timestampRegion;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\TimestampCacheKey
|
||||
*/
|
||||
protected $timestampKey;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\EntityHydrator
|
||||
*/
|
||||
protected $hydrator;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\Logging\CacheLogger
|
||||
*/
|
||||
protected $cacheLogger;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $regionName;
|
||||
|
||||
/**
|
||||
* Associations configured as FETCH_EAGER, as well as all inverse one-to-one associations.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $joinedAssociations;
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Persisters\Entity\EntityPersister $persister The entity persister to cache.
|
||||
* @param \Doctrine\ORM\Cache\Region $region The entity cache region.
|
||||
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $class The entity metadata.
|
||||
*/
|
||||
public function __construct(EntityPersister $persister, Region $region, EntityManagerInterface $em, ClassMetadata $class)
|
||||
{
|
||||
$configuration = $em->getConfiguration();
|
||||
$cacheConfig = $configuration->getSecondLevelCacheConfiguration();
|
||||
$cacheFactory = $cacheConfig->getCacheFactory();
|
||||
|
||||
$this->class = $class;
|
||||
$this->region = $region;
|
||||
$this->persister = $persister;
|
||||
$this->cache = $em->getCache();
|
||||
$this->regionName = $region->getName();
|
||||
$this->uow = $em->getUnitOfWork();
|
||||
$this->metadataFactory = $em->getMetadataFactory();
|
||||
$this->cacheLogger = $cacheConfig->getCacheLogger();
|
||||
$this->timestampRegion = $cacheFactory->getTimestampRegion();
|
||||
$this->hydrator = $cacheFactory->buildEntityHydrator($em, $class);
|
||||
$this->timestampKey = new TimestampCacheKey($this->class->getTableName());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addInsert($entity)
|
||||
{
|
||||
$this->persister->addInsert($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInserts()
|
||||
{
|
||||
return $this->persister->getInserts();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSelectSQL($criteria, $assoc = null, $lockMode = null, $limit = null, $offset = null, array $orderBy = null)
|
||||
{
|
||||
return $this->persister->getSelectSQL($criteria, $assoc, $lockMode, $limit, $offset, $orderBy);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getCountSQL($criteria = array())
|
||||
{
|
||||
return $this->persister->getCountSQL($criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInsertSQL()
|
||||
{
|
||||
return $this->persister->getInsertSQL();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getResultSetMapping()
|
||||
{
|
||||
return $this->persister->getResultSetMapping();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSelectConditionStatementSQL($field, $value, $assoc = null, $comparison = null)
|
||||
{
|
||||
return $this->persister->getSelectConditionStatementSQL($field, $value, $assoc, $comparison);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function exists($entity, Criteria $extraConditions = null)
|
||||
{
|
||||
if (null === $extraConditions) {
|
||||
$key = new EntityCacheKey($this->class->rootEntityName, $this->class->getIdentifierValues($entity));
|
||||
|
||||
if ($this->region->contains($key)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->persister->exists($entity, $extraConditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheRegion()
|
||||
{
|
||||
return $this->region;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Doctrine\ORM\Cache\EntityHydrator
|
||||
*/
|
||||
public function getEntityHydrator()
|
||||
{
|
||||
return $this->hydrator;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function storeEntityCache($entity, EntityCacheKey $key)
|
||||
{
|
||||
$class = $this->class;
|
||||
$className = ClassUtils::getClass($entity);
|
||||
|
||||
if ($className !== $this->class->name) {
|
||||
$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);
|
||||
|
||||
if ($this->cacheLogger && $cached) {
|
||||
$this->cacheLogger->entityCachePut($this->regionName, $key);
|
||||
}
|
||||
|
||||
return $cached;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $entity
|
||||
*/
|
||||
private function storeJoinedAssociations($entity)
|
||||
{
|
||||
if ($this->joinedAssociations === null) {
|
||||
$associations = array();
|
||||
|
||||
foreach ($this->class->associationMappings as $name => $assoc) {
|
||||
if (isset($assoc['cache']) &&
|
||||
($assoc['type'] & ClassMetadata::TO_ONE) &&
|
||||
($assoc['fetch'] === ClassMetadata::FETCH_EAGER || ! $assoc['isOwningSide'])) {
|
||||
|
||||
$associations[] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
$this->joinedAssociations = $associations;
|
||||
}
|
||||
|
||||
foreach ($this->joinedAssociations as $name) {
|
||||
$assoc = $this->class->associationMappings[$name];
|
||||
$assocEntity = $this->class->getFieldValue($entity, $name);
|
||||
|
||||
if ($assocEntity === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$assocId = $this->uow->getEntityIdentifier($assocEntity);
|
||||
$assocKey = new EntityCacheKey($assoc['targetEntity'], $assocId);
|
||||
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
|
||||
|
||||
$assocPersister->storeEntityCache($assocEntity, $assocKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a string of currently query
|
||||
*
|
||||
* @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)
|
||||
{
|
||||
list($params) = ($criteria instanceof Criteria)
|
||||
? $this->persister->expandCriteriaParameters($criteria)
|
||||
: $this->persister->expandParameters($criteria);
|
||||
|
||||
return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset . $timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function expandParameters($criteria)
|
||||
{
|
||||
return $this->persister->expandParameters($criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function expandCriteriaParameters(Criteria $criteria)
|
||||
{
|
||||
return $this->persister->expandCriteriaParameters($criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getClassMetadata()
|
||||
{
|
||||
return $this->persister->getClassMetadata();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getManyToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null)
|
||||
{
|
||||
return $this->persister->getManyToManyCollection($assoc, $sourceEntity, $offset, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null)
|
||||
{
|
||||
return $this->persister->getOneToManyCollection($assoc, $sourceEntity, $offset, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOwningTable($fieldName)
|
||||
{
|
||||
return $this->persister->getOwningTable($fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function executeInserts()
|
||||
{
|
||||
$this->queuedCache['insert'] = $this->persister->getInserts();
|
||||
|
||||
return $this->persister->executeInserts();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load(array $criteria, $entity = null, $assoc = null, array $hints = array(), $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);
|
||||
$rsm = $this->getResultSetMapping();
|
||||
$querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL);
|
||||
$queryCache = $this->cache->getQueryCache($this->regionName);
|
||||
$result = $queryCache->get($querykey, $rsm);
|
||||
|
||||
if ($result !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->queryCacheHit($this->regionName, $querykey);
|
||||
}
|
||||
|
||||
return $result[0];
|
||||
}
|
||||
|
||||
if (($result = $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy)) === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$cached = $queryCache->put($querykey, $rsm, array($result));
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
if ($result) {
|
||||
$this->cacheLogger->queryCacheMiss($this->regionName, $querykey);
|
||||
}
|
||||
|
||||
if ($cached) {
|
||||
$this->cacheLogger->queryCachePut($this->regionName, $querykey);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadAll(array $criteria = array(), 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);
|
||||
$rsm = $this->getResultSetMapping();
|
||||
$querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL);
|
||||
$queryCache = $this->cache->getQueryCache($this->regionName);
|
||||
$result = $queryCache->get($querykey, $rsm);
|
||||
|
||||
if ($result !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->queryCacheHit($this->regionName, $querykey);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
$result = $this->persister->loadAll($criteria, $orderBy, $limit, $offset);
|
||||
$cached = $queryCache->put($querykey, $rsm, $result);
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
if ($result) {
|
||||
$this->cacheLogger->queryCacheMiss($this->regionName, $querykey);
|
||||
}
|
||||
|
||||
if ($cached) {
|
||||
$this->cacheLogger->queryCachePut($this->regionName, $querykey);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadById(array $identifier, $entity = null)
|
||||
{
|
||||
$cacheKey = new EntityCacheKey($this->class->rootEntityName, $identifier);
|
||||
$cacheEntry = $this->region->get($cacheKey);
|
||||
$class = $this->class;
|
||||
|
||||
if ($cacheEntry !== null) {
|
||||
if ($cacheEntry->class !== $this->class->name) {
|
||||
$class = $this->metadataFactory->getMetadataFor($cacheEntry->class);
|
||||
}
|
||||
|
||||
if (($entity = $this->hydrator->loadCacheEntry($class, $cacheKey, $cacheEntry, $entity)) !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->entityCacheHit($this->regionName, $cacheKey);
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
}
|
||||
|
||||
$entity = $this->persister->loadById($identifier, $entity);
|
||||
|
||||
if ($entity === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$class = $this->class;
|
||||
$className = ClassUtils::getClass($entity);
|
||||
|
||||
if ($className !== $this->class->name) {
|
||||
$class = $this->metadataFactory->getMetadataFor($className);
|
||||
}
|
||||
|
||||
$cacheEntry = $this->hydrator->buildCacheEntry($class, $cacheKey, $entity);
|
||||
$cached = $this->region->put($cacheKey, $cacheEntry);
|
||||
|
||||
if ($cached && ($this->joinedAssociations === null || count($this->joinedAssociations) > 0)) {
|
||||
$this->storeJoinedAssociations($entity);
|
||||
}
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
if ($cached) {
|
||||
$this->cacheLogger->entityCachePut($this->regionName, $cacheKey);
|
||||
}
|
||||
|
||||
$this->cacheLogger->entityCacheMiss($this->regionName, $cacheKey);
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function count($criteria = array())
|
||||
{
|
||||
return $this->persister->count($criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadCriteria(Criteria $criteria)
|
||||
{
|
||||
$query = $this->persister->getSelectSQL($criteria);
|
||||
$timestamp = $this->timestampRegion->get($this->timestampKey);
|
||||
$hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null);
|
||||
$rsm = $this->getResultSetMapping();
|
||||
$querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL);
|
||||
$queryCache = $this->cache->getQueryCache($this->regionName);
|
||||
$cacheResult = $queryCache->get($querykey, $rsm);
|
||||
|
||||
if ($cacheResult !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->queryCacheHit($this->regionName, $querykey);
|
||||
}
|
||||
|
||||
return $cacheResult;
|
||||
}
|
||||
|
||||
$result = $this->persister->loadCriteria($criteria);
|
||||
$cached = $queryCache->put($querykey, $rsm, $result);
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
if ($result) {
|
||||
$this->cacheLogger->queryCacheMiss($this->regionName, $querykey);
|
||||
}
|
||||
|
||||
if ($cached) {
|
||||
$this->cacheLogger->queryCachePut($this->regionName, $querykey);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll)
|
||||
{
|
||||
$persister = $this->uow->getCollectionPersister($assoc);
|
||||
$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 ($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);
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
|
||||
}
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll)
|
||||
{
|
||||
$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 ($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);
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
|
||||
}
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = array())
|
||||
{
|
||||
return $this->persister->loadOneToOneEntity($assoc, $sourceEntity, $identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lock(array $criteria, $lockMode)
|
||||
{
|
||||
$this->persister->lock($criteria, $lockMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function refresh(array $id, $entity, $lockMode = null)
|
||||
{
|
||||
$this->persister->refresh($id, $entity, $lockMode);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache\Persister\Entity;
|
||||
|
||||
use Doctrine\ORM\Cache\EntityCacheKey;
|
||||
use Doctrine\ORM\Cache\Persister\CachedPersister;
|
||||
use Doctrine\ORM\Persisters\Entity\EntityPersister;
|
||||
|
||||
/**
|
||||
* Interface for second level cache entity persisters.
|
||||
*
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
* @since 2.5
|
||||
*/
|
||||
interface CachedEntityPersister extends CachedPersister, EntityPersister
|
||||
{
|
||||
/**
|
||||
* @return \Doctrine\ORM\Cache\EntityHydrator
|
||||
*/
|
||||
public function getEntityHydrator();
|
||||
|
||||
/**
|
||||
* @param object $entity
|
||||
* @param \Doctrine\ORM\Cache\EntityCacheKey $key
|
||||
* @return boolean
|
||||
*/
|
||||
public function storeEntityCache($entity, EntityCacheKey $key);
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
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>
|
||||
* @since 2.5
|
||||
*/
|
||||
class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function afterTransactionComplete()
|
||||
{
|
||||
$isChanged = false;
|
||||
|
||||
if (isset($this->queuedCache['insert'])) {
|
||||
foreach ($this->queuedCache['insert'] as $entity) {
|
||||
$isChanged = $this->updateCache($entity, $isChanged);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->queuedCache['update'])) {
|
||||
foreach ($this->queuedCache['update'] as $entity) {
|
||||
$isChanged = $this->updateCache($entity, $isChanged);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->queuedCache['delete'])) {
|
||||
foreach ($this->queuedCache['delete'] as $key) {
|
||||
$this->region->evict($key);
|
||||
|
||||
$isChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($isChanged) {
|
||||
$this->timestampRegion->update($this->timestampKey);
|
||||
}
|
||||
|
||||
$this->queuedCache = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function afterTransactionRolledBack()
|
||||
{
|
||||
$this->queuedCache = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete($entity)
|
||||
{
|
||||
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
|
||||
|
||||
if ($this->persister->delete($entity)) {
|
||||
$this->region->evict($key);
|
||||
}
|
||||
|
||||
$this->queuedCache['delete'][] = $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function update($entity)
|
||||
{
|
||||
$this->persister->update($entity);
|
||||
|
||||
$this->queuedCache['update'][] = $entity;
|
||||
}
|
||||
|
||||
private function updateCache($entity, $isChanged)
|
||||
{
|
||||
$class = $this->metadataFactory->getMetadataFor(get_class($entity));
|
||||
$key = new EntityCacheKey($class->rootEntityName, $this->uow->getEntityIdentifier($entity));
|
||||
$entry = $this->hydrator->buildCacheEntry($class, $key, $entity);
|
||||
$cached = $this->region->put($key, $entry);
|
||||
$isChanged = $isChanged ?: $cached;
|
||||
|
||||
if ($this->cacheLogger && $cached) {
|
||||
$this->cacheLogger->entityCachePut($this->regionName, $key);
|
||||
}
|
||||
|
||||
return $isChanged;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache\Persister\Entity;
|
||||
|
||||
use Doctrine\ORM\Cache\CacheException;
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
|
||||
/**
|
||||
* Specific read-only region entity persister
|
||||
*
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
* @since 2.5
|
||||
*/
|
||||
class ReadOnlyCachedEntityPersister extends NonStrictReadWriteCachedEntityPersister
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function update($entity)
|
||||
{
|
||||
throw CacheException::updateReadOnlyEntity(ClassUtils::getClass($entity));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache\Persister\Entity;
|
||||
|
||||
use Doctrine\ORM\Persisters\Entity\EntityPersister;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Cache\ConcurrentRegion;
|
||||
use Doctrine\ORM\Cache\EntityCacheKey;
|
||||
|
||||
/**
|
||||
* Specific read-write entity persister
|
||||
*
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
* @since 2.5
|
||||
*/
|
||||
class ReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
{
|
||||
/**
|
||||
* @param \Doctrine\ORM\Persisters\Entity\EntityPersister $persister The entity persister to cache.
|
||||
* @param \Doctrine\ORM\Cache\ConcurrentRegion $region The entity cache region.
|
||||
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $class The entity metadata.
|
||||
*/
|
||||
public function __construct(EntityPersister $persister, ConcurrentRegion $region, EntityManagerInterface $em, ClassMetadata $class)
|
||||
{
|
||||
parent::__construct($persister, $region, $em, $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function afterTransactionComplete()
|
||||
{
|
||||
$isChanged = true;
|
||||
|
||||
if (isset($this->queuedCache['update'])) {
|
||||
foreach ($this->queuedCache['update'] as $item) {
|
||||
$this->region->evict($item['key']);
|
||||
|
||||
$isChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->queuedCache['delete'])) {
|
||||
foreach ($this->queuedCache['delete'] as $item) {
|
||||
$this->region->evict($item['key']);
|
||||
|
||||
$isChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($isChanged) {
|
||||
$this->timestampRegion->update($this->timestampKey);
|
||||
}
|
||||
|
||||
$this->queuedCache = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function afterTransactionRolledBack()
|
||||
{
|
||||
if (isset($this->queuedCache['update'])) {
|
||||
foreach ($this->queuedCache['update'] as $item) {
|
||||
$this->region->evict($item['key']);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->queuedCache['delete'])) {
|
||||
foreach ($this->queuedCache['delete'] as $item) {
|
||||
$this->region->evict($item['key']);
|
||||
}
|
||||
}
|
||||
|
||||
$this->queuedCache = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete($entity)
|
||||
{
|
||||
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
|
||||
$lock = $this->region->lock($key);
|
||||
|
||||
if ($this->persister->delete($entity)) {
|
||||
$this->region->evict($key);
|
||||
}
|
||||
|
||||
if ($lock === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->queuedCache['delete'][] = array(
|
||||
'lock' => $lock,
|
||||
'key' => $key
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function update($entity)
|
||||
{
|
||||
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
|
||||
$lock = $this->region->lock($key);
|
||||
|
||||
$this->persister->update($entity);
|
||||
|
||||
if ($lock === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->queuedCache['update'][] = array(
|
||||
'lock' => $lock,
|
||||
'key' => $key
|
||||
);
|
||||
}
|
||||
}
|
||||
62
lib/Doctrine/ORM/Cache/QueryCache.php
Normal file
62
lib/Doctrine/ORM/Cache/QueryCache.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
|
||||
/**
|
||||
* Defines the contract for caches capable of storing query results.
|
||||
* These caches should only concern themselves with storing the matching result ids.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
interface QueryCache
|
||||
{
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function clear();
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Cache\QueryCacheKey $key
|
||||
* @param \Doctrine\ORM\Query\ResultSetMapping $rsm
|
||||
* @param mixed $result
|
||||
* @param array $hints
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = array());
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Cache\QueryCacheKey $key
|
||||
* @param \Doctrine\ORM\Query\ResultSetMapping $rsm
|
||||
* @param array $hints
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = array());
|
||||
|
||||
/**
|
||||
* @return \Doctrine\ORM\Cache\Region
|
||||
*/
|
||||
public function getRegion();
|
||||
}
|
||||
62
lib/Doctrine/ORM/Cache/QueryCacheEntry.php
Normal file
62
lib/Doctrine/ORM/Cache/QueryCacheEntry.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
/**
|
||||
* Query cache entry
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class QueryCacheEntry implements CacheEntry
|
||||
{
|
||||
/**
|
||||
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
|
||||
*
|
||||
* @var array List of entity identifiers
|
||||
*/
|
||||
public $result;
|
||||
|
||||
/**
|
||||
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
|
||||
*
|
||||
* @var integer Time creation of this cache entry
|
||||
*/
|
||||
public $time;
|
||||
|
||||
/**
|
||||
* @param array $result
|
||||
* @param integer $time
|
||||
*/
|
||||
public function __construct($result, $time = null)
|
||||
{
|
||||
$this->result = $result;
|
||||
$this->time = $time ?: time();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $values
|
||||
*/
|
||||
public static function __set_state(array $values)
|
||||
{
|
||||
return new self($values['result'], $values['time']);
|
||||
}
|
||||
}
|
||||
58
lib/Doctrine/ORM/Cache/QueryCacheKey.php
Normal file
58
lib/Doctrine/ORM/Cache/QueryCacheKey.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
use Doctrine\ORM\Cache;
|
||||
|
||||
/**
|
||||
* A cache key that identifies a particular query.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class QueryCacheKey extends CacheKey
|
||||
{
|
||||
/**
|
||||
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
|
||||
*
|
||||
* @var integer Cache key lifetime
|
||||
*/
|
||||
public $lifetime;
|
||||
|
||||
/**
|
||||
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
|
||||
*
|
||||
* @var integer Cache mode (Doctrine\ORM\Cache::MODE_*)
|
||||
*/
|
||||
public $cacheMode;
|
||||
|
||||
/**
|
||||
* @param string $hash Result cache id
|
||||
* @param integer $lifetime Query lifetime
|
||||
* @param integer $cacheMode Query cache mode
|
||||
*/
|
||||
public function __construct($hash, $lifetime = 0, $cacheMode = Cache::MODE_NORMAL)
|
||||
{
|
||||
$this->hash = $hash;
|
||||
$this->lifetime = $lifetime;
|
||||
$this->cacheMode = $cacheMode;
|
||||
}
|
||||
}
|
||||
40
lib/Doctrine/ORM/Cache/QueryCacheValidator.php
Normal file
40
lib/Doctrine/ORM/Cache/QueryCacheValidator.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
/**
|
||||
* Cache query validator interface.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
interface QueryCacheValidator
|
||||
{
|
||||
/**
|
||||
* Checks if the query entry is valid
|
||||
*
|
||||
* @param \Doctrine\ORM\Cache\QueryCacheKey $key
|
||||
* @param \Doctrine\ORM\Cache\QueryCacheEntry $entry
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isValid(QueryCacheKey $key, QueryCacheEntry $entry);
|
||||
}
|
||||
84
lib/Doctrine/ORM/Cache/Region.php
Normal file
84
lib/Doctrine/ORM/Cache/Region.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache;
|
||||
|
||||
/**
|
||||
* Defines a contract for accessing a particular named region.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
interface Region extends MultiGetRegion
|
||||
{
|
||||
/**
|
||||
* Retrieve the name of this region.
|
||||
*
|
||||
* @return string The region name
|
||||
*/
|
||||
public function getName();
|
||||
|
||||
/**
|
||||
* Determine whether this region contains data for the given key.
|
||||
*
|
||||
* @param \Doctrine\ORM\Cache\CacheKey $key The cache key
|
||||
*
|
||||
* @return boolean TRUE if the underlying cache contains corresponding data; FALSE otherwise.
|
||||
*/
|
||||
public function contains(CacheKey $key);
|
||||
|
||||
/**
|
||||
* Get an item from the cache.
|
||||
*
|
||||
* @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to be retrieved.
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\CacheEntry|null The cached entry or NULL
|
||||
*
|
||||
* @throws \Doctrine\ORM\Cache\CacheException Indicates a problem accessing the item or region.
|
||||
*/
|
||||
public function get(CacheKey $key);
|
||||
|
||||
/**
|
||||
* Put an item into the cache.
|
||||
*
|
||||
* @param \Doctrine\ORM\Cache\CacheKey $key The key under which to cache the item.
|
||||
* @param \Doctrine\ORM\Cache\CacheEntry $entry The entry to cache.
|
||||
* @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained.
|
||||
*
|
||||
* @throws \Doctrine\ORM\Cache\CacheException Indicates a problem accessing the region.
|
||||
*/
|
||||
public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null);
|
||||
|
||||
/**
|
||||
* Remove an item from the cache.
|
||||
*
|
||||
* @param \Doctrine\ORM\Cache\CacheKey $key The key under which to cache the item.
|
||||
*
|
||||
* @throws \Doctrine\ORM\Cache\CacheException Indicates a problem accessing the region.
|
||||
*/
|
||||
public function evict(CacheKey $key);
|
||||
|
||||
/**
|
||||
* Remove all contents of this particular cache region.
|
||||
*
|
||||
* @throws \Doctrine\ORM\Cache\CacheException Indicates problem accessing the region.
|
||||
*/
|
||||
public function evictAll();
|
||||
}
|
||||
77
lib/Doctrine/ORM/Cache/Region/DefaultMultiGetRegion.php
Normal file
77
lib/Doctrine/ORM/Cache/Region/DefaultMultiGetRegion.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache\Region;
|
||||
|
||||
use Doctrine\Common\Cache\MultiGetCache;
|
||||
use Doctrine\ORM\Cache\Region;
|
||||
use Doctrine\ORM\Cache\CollectionCacheEntry;
|
||||
|
||||
/**
|
||||
* A cache region that enables the retrieval of multiple elements with one call
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Asmir Mustafic <goetas@gmail.com>
|
||||
*/
|
||||
class DefaultMultiGetRegion extends DefaultRegion
|
||||
{
|
||||
/**
|
||||
* Note that the multiple type is due to doctrine/cache not integrating the MultiGetCache interface
|
||||
* in its signature due to BC in 1.x
|
||||
*
|
||||
* @var MultiGetCache|\Doctrine\Common\Cache\Cache
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @param MultiGetCache $cache
|
||||
*/
|
||||
public function __construct($name, MultiGetCache $cache, $lifetime = 0)
|
||||
{
|
||||
/* @var $cache \Doctrine\Common\Cache\Cache */
|
||||
parent::__construct($name, $cache, $lifetime);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMultiple(CollectionCacheEntry $collection)
|
||||
{
|
||||
$keysToRetrieve = array();
|
||||
|
||||
foreach ($collection->identifiers as $index => $key) {
|
||||
$keysToRetrieve[$index] = $this->getCacheEntryKey($key);
|
||||
}
|
||||
|
||||
$items = $this->cache->fetchMultiple($keysToRetrieve);
|
||||
if (count($items) !== count($keysToRetrieve)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$returnableItems = array();
|
||||
foreach ($keysToRetrieve as $index => $key) {
|
||||
$returnableItems[$index] = $items[$key];
|
||||
}
|
||||
|
||||
return $returnableItems;
|
||||
}
|
||||
}
|
||||
160
lib/Doctrine/ORM/Cache/Region/DefaultRegion.php
Normal file
160
lib/Doctrine/ORM/Cache/Region/DefaultRegion.php
Normal file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache\Region;
|
||||
|
||||
use Doctrine\Common\Cache\Cache as CacheAdapter;
|
||||
use Doctrine\Common\Cache\ClearableCache;
|
||||
use Doctrine\ORM\Cache\CacheEntry;
|
||||
use Doctrine\ORM\Cache\CacheKey;
|
||||
use Doctrine\ORM\Cache\CollectionCacheEntry;
|
||||
use Doctrine\ORM\Cache\Lock;
|
||||
use Doctrine\ORM\Cache\Region;
|
||||
|
||||
/**
|
||||
* The simplest cache region compatible with all doctrine-cache drivers.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class DefaultRegion implements Region
|
||||
{
|
||||
const REGION_KEY_SEPARATOR = '_';
|
||||
|
||||
/**
|
||||
* @var CacheAdapter
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
protected $lifetime = 0;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param CacheAdapter $cache
|
||||
* @param integer $lifetime
|
||||
*/
|
||||
public function __construct($name, CacheAdapter $cache, $lifetime = 0)
|
||||
{
|
||||
$this->cache = $cache;
|
||||
$this->name = (string) $name;
|
||||
$this->lifetime = (integer) $lifetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Doctrine\Common\Cache\CacheProvider
|
||||
*/
|
||||
public function getCache()
|
||||
{
|
||||
return $this->cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function contains(CacheKey $key)
|
||||
{
|
||||
return $this->cache->contains($this->getCacheEntryKey($key));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get(CacheKey $key)
|
||||
{
|
||||
return $this->cache->fetch($this->getCacheEntryKey($key)) ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMultiple(CollectionCacheEntry $collection)
|
||||
{
|
||||
$result = array();
|
||||
|
||||
foreach ($collection->identifiers as $key) {
|
||||
$entryKey = $this->getCacheEntryKey($key);
|
||||
$entryValue = $this->cache->fetch($entryKey);
|
||||
|
||||
if ($entryValue === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$result[] = $entryValue;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CacheKey $key
|
||||
* @return string
|
||||
*/
|
||||
protected function getCacheEntryKey(CacheKey $key)
|
||||
{
|
||||
return $this->name . self::REGION_KEY_SEPARATOR . $key->hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null)
|
||||
{
|
||||
return $this->cache->save($this->getCacheEntryKey($key), $entry, $this->lifetime);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function evict(CacheKey $key)
|
||||
{
|
||||
return $this->cache->delete($this->getCacheEntryKey($key));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function evictAll()
|
||||
{
|
||||
if (! $this->cache instanceof ClearableCache) {
|
||||
throw new \BadMethodCallException(sprintf(
|
||||
'Clearing all cache entries is not supported by the supplied cache adapter of type %s',
|
||||
get_class($this->cache)
|
||||
));
|
||||
}
|
||||
|
||||
return $this->cache->deleteAll();
|
||||
}
|
||||
}
|
||||
265
lib/Doctrine/ORM/Cache/Region/FileLockRegion.php
Normal file
265
lib/Doctrine/ORM/Cache/Region/FileLockRegion.php
Normal file
@@ -0,0 +1,265 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache\Region;
|
||||
|
||||
use Doctrine\ORM\Cache\CollectionCacheEntry;
|
||||
use Doctrine\ORM\Cache\Lock;
|
||||
use Doctrine\ORM\Cache\Region;
|
||||
use Doctrine\ORM\Cache\CacheKey;
|
||||
use Doctrine\ORM\Cache\CacheEntry;
|
||||
use Doctrine\ORM\Cache\ConcurrentRegion;
|
||||
|
||||
/**
|
||||
* Very naive concurrent region, based on file locks.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silvagmail.com>
|
||||
*/
|
||||
class FileLockRegion implements ConcurrentRegion
|
||||
{
|
||||
const LOCK_EXTENSION = 'lock';
|
||||
|
||||
/**
|
||||
* var \Doctrine\ORM\Cache\Region
|
||||
*/
|
||||
private $region;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $directory;
|
||||
|
||||
/**
|
||||
* var integer
|
||||
*/
|
||||
private $lockLifetime;
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Cache\Region $region
|
||||
* @param string $directory
|
||||
* @param string $lockLifetime
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct(Region $region, $directory, $lockLifetime)
|
||||
{
|
||||
if ( ! is_dir($directory) && ! @mkdir($directory, 0775, true)) {
|
||||
throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist and could not be created.', $directory));
|
||||
}
|
||||
|
||||
if ( ! is_writable($directory)) {
|
||||
throw new \InvalidArgumentException(sprintf('The directory "%s" is not writable.', $directory));
|
||||
}
|
||||
|
||||
$this->region = $region;
|
||||
$this->directory = $directory;
|
||||
$this->lockLifetime = $lockLifetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Cache\CacheKey $key
|
||||
* @param \Doctrine\ORM\Cache\Lock $lock
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function isLocked(CacheKey $key, Lock $lock = null)
|
||||
{
|
||||
$filename = $this->getLockFileName($key);
|
||||
|
||||
if ( ! is_file($filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$time = $this->getLockTime($filename);
|
||||
$content = $this->getLockContent($filename);
|
||||
|
||||
if ( ! $content || ! $time) {
|
||||
@unlink($filename);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($lock && $content === $lock->value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// outdated lock
|
||||
if (($time + $this->lockLifetime) <= time()) {
|
||||
@unlink($filename);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Cache\CacheKey $key
|
||||
*
|
||||
* return string
|
||||
*/
|
||||
private function getLockFileName(CacheKey $key)
|
||||
{
|
||||
return $this->directory . DIRECTORY_SEPARATOR . $key->hash . '.' . self::LOCK_EXTENSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
*
|
||||
* return string
|
||||
*/
|
||||
private function getLockContent($filename)
|
||||
{
|
||||
return @file_get_contents($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
*
|
||||
* return integer
|
||||
*/
|
||||
private function getLockTime($filename)
|
||||
{
|
||||
return @fileatime($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->region->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
*/
|
||||
public function contains(CacheKey $key)
|
||||
{
|
||||
if ($this->isLocked($key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->region->contains($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
*/
|
||||
public function get(CacheKey $key)
|
||||
{
|
||||
if ($this->isLocked($key)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->region->get($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMultiple(CollectionCacheEntry $collection)
|
||||
{
|
||||
if (array_filter(array_map([$this, 'isLocked'], $collection->identifiers))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->region->getMultiple($collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
*/
|
||||
public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null)
|
||||
{
|
||||
if ($this->isLocked($key, $lock)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->region->put($key, $entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
*/
|
||||
public function evict(CacheKey $key)
|
||||
{
|
||||
if ($this->isLocked($key)) {
|
||||
@unlink($this->getLockFileName($key));
|
||||
}
|
||||
|
||||
return $this->region->evict($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
*/
|
||||
public function evictAll()
|
||||
{
|
||||
// The check below is necessary because on some platforms glob returns false
|
||||
// when nothing matched (even though no errors occurred)
|
||||
$filenames = glob(sprintf("%s/*.%s" , $this->directory, self::LOCK_EXTENSION));
|
||||
|
||||
if ($filenames) {
|
||||
foreach ($filenames as $filename) {
|
||||
@unlink($filename);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->region->evictAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
*/
|
||||
public function lock(CacheKey $key)
|
||||
{
|
||||
if ($this->isLocked($key)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$lock = Lock::createLockRead();
|
||||
$filename = $this->getLockFileName($key);
|
||||
|
||||
if ( ! @file_put_contents($filename, $lock->value, LOCK_EX)) {
|
||||
return null;
|
||||
}
|
||||
chmod($filename, 0664);
|
||||
|
||||
return $lock;
|
||||
}
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
*/
|
||||
public function unlock(CacheKey $key, Lock $lock)
|
||||
{
|
||||
if ($this->isLocked($key, $lock)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! @unlink($this->getLockFileName($key))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
42
lib/Doctrine/ORM/Cache/Region/UpdateTimestampCache.php
Normal file
42
lib/Doctrine/ORM/Cache/Region/UpdateTimestampCache.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Cache\Region;
|
||||
|
||||
use Doctrine\ORM\Cache\TimestampCacheEntry;
|
||||
use Doctrine\ORM\Cache\TimestampRegion;
|
||||
use Doctrine\ORM\Cache\CacheKey;
|
||||
|
||||
/**
|
||||
* Tracks the timestamps of the most recent updates to particular keys.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class UpdateTimestampCache extends DefaultRegion implements TimestampRegion
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function update(CacheKey $key)
|
||||
{
|
||||
$this->put($key, new TimestampCacheEntry);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user