mirror of
https://github.com/doctrine/orm.git
synced 2026-03-24 15:02:22 +01:00
Compare commits
1315 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84373d05a4 | ||
|
|
e82e7147fa | ||
|
|
e23ed2250d | ||
|
|
192bb6fd21 | ||
|
|
0f3679f034 | ||
|
|
1d2cd82706 | ||
|
|
b983d86612 | ||
|
|
b11f01643c | ||
|
|
b58fb8f5d4 | ||
|
|
925a22b71d | ||
|
|
0f0d8abd67 | ||
|
|
470c15ce05 | ||
|
|
3cc5fc0252 | ||
|
|
fd0657089a | ||
|
|
de3b237292 | ||
|
|
1221cc3a2a | ||
|
|
9efbc1fa71 | ||
|
|
57705e0d78 | ||
|
|
82bb6b78cd | ||
|
|
64c56b21aa | ||
|
|
b04e2e6364 | ||
|
|
a70f9b7f49 | ||
|
|
c88a7c1ffe | ||
|
|
c206728c96 | ||
|
|
e8d420c641 | ||
|
|
fdcab7eae8 | ||
|
|
45d7d5234f | ||
|
|
159ca79b81 | ||
|
|
2b148a27e0 | ||
|
|
0aef57f60c | ||
|
|
fef1e0286c | ||
|
|
4a38534150 | ||
|
|
1de22adb16 | ||
|
|
62b4160887 | ||
|
|
dbb7c4d2bf | ||
|
|
e8978ee365 | ||
|
|
c095b88804 | ||
|
|
efe4208ba6 | ||
|
|
453a56670d | ||
|
|
ec36e2c866 | ||
|
|
e250572cb4 | ||
|
|
758955e183 | ||
|
|
5b8d6a1486 | ||
|
|
3f1003fee9 | ||
|
|
7e241e89b8 | ||
|
|
67c1e1d2b1 | ||
|
|
261eacdbfc | ||
|
|
43df821691 | ||
|
|
11d09702da | ||
|
|
f9f14139cf | ||
|
|
39f4d46d36 | ||
|
|
1dae8d318f | ||
|
|
a361a7c1cb | ||
|
|
6a73608baf | ||
|
|
f9955152b2 | ||
|
|
5aad1df149 | ||
|
|
243832555b | ||
|
|
ae12fa6b5b | ||
|
|
edaf9b6813 | ||
|
|
b324a21abf | ||
|
|
ff34aaaa2c | ||
|
|
9767a814a6 | ||
|
|
e6007575e1 | ||
|
|
29d6da0fa0 | ||
|
|
69fe5c48f4 | ||
|
|
8e1111c8d3 | ||
|
|
e4bccdc7b3 | ||
|
|
06ed21e883 | ||
|
|
5635fa60a4 | ||
|
|
4d93a4950b | ||
|
|
a91050e7f4 | ||
|
|
20e5d98b7b | ||
|
|
2f6e914d64 | ||
|
|
457036aacb | ||
|
|
2ce72f38a2 | ||
|
|
1cff8b4d98 | ||
|
|
a165f63c8c | ||
|
|
eaf8fd3c34 | ||
|
|
70427871ce | ||
|
|
2879162015 | ||
|
|
3b92cfac5a | ||
|
|
53c9ffda30 | ||
|
|
647c5e2cad | ||
|
|
3555007f08 | ||
|
|
523697d0b6 | ||
|
|
1382d766b0 | ||
|
|
c743bb938b | ||
|
|
3340234785 | ||
|
|
a39ceb3159 | ||
|
|
6ff5043ce8 | ||
|
|
1a958f70fd | ||
|
|
184e8eb26c | ||
|
|
7903a2b513 | ||
|
|
52b3fc1fc3 | ||
|
|
09d67b10b0 | ||
|
|
37d7df6ac4 | ||
|
|
3488049c18 | ||
|
|
a66fc03441 | ||
|
|
37e7e841c3 | ||
|
|
f2f1d8986c | ||
|
|
7eb744126b | ||
|
|
f16c8e3efe | ||
|
|
6ef48561ba | ||
|
|
0a90279a99 | ||
|
|
a1355d0bb9 | ||
|
|
6937061b23 | ||
|
|
c1e688fc81 | ||
|
|
d961028b14 | ||
|
|
d685f592fe | ||
|
|
b15758bb42 | ||
|
|
3d86c82a7f | ||
|
|
0d834d0bd4 | ||
|
|
0248f743ba | ||
|
|
ed7a4bdcf3 | ||
|
|
529064aff2 | ||
|
|
4e99c5c127 | ||
|
|
462173ad71 | ||
|
|
710d0d1109 | ||
|
|
4ef043fc3b | ||
|
|
afb9c829e2 | ||
|
|
9bea612d74 | ||
|
|
77b905eaa8 | ||
|
|
5c7b98b2a9 | ||
|
|
424793c263 | ||
|
|
753d63c2d4 | ||
|
|
27511374ec | ||
|
|
3d6436c2f3 | ||
|
|
a986fe013e | ||
|
|
4e8b787d07 | ||
|
|
c64c149ebf | ||
|
|
f269ecc3ac | ||
|
|
6bc18402e2 | ||
|
|
3d0ac62059 | ||
|
|
0e29fe871a | ||
|
|
e5de0dad7e | ||
|
|
66a842c143 | ||
|
|
6548947df5 | ||
|
|
20b5ab26e7 | ||
|
|
a36f84eeb5 | ||
|
|
e8b3598751 | ||
|
|
1f4a65f3e0 | ||
|
|
384dfa87ab | ||
|
|
6bb3184dbf | ||
|
|
4c1869dca0 | ||
|
|
ec57306efe | ||
|
|
dca0881d94 | ||
|
|
7430320bac | ||
|
|
c9d9b68fa9 | ||
|
|
f92214997f | ||
|
|
65886fdfea | ||
|
|
1e95110b08 | ||
|
|
1d7c72cc06 | ||
|
|
4d6cef1ff6 | ||
|
|
bef5b585cb | ||
|
|
b147c472be | ||
|
|
eb1a162cbc | ||
|
|
bf9673203c | ||
|
|
fa75856d5f | ||
|
|
22c9f6ebec | ||
|
|
07c207081e | ||
|
|
762f43c3dc | ||
|
|
b53f4fd4cc | ||
|
|
665efad039 | ||
|
|
540af2fd2a | ||
|
|
1a0adecf29 | ||
|
|
97622b57bd | ||
|
|
b9a0a19607 | ||
|
|
f8efd85ae6 | ||
|
|
dc674f809f | ||
|
|
30f90a6f49 | ||
|
|
6d5afb18bc | ||
|
|
d3cd10d926 | ||
|
|
7220c3c125 | ||
|
|
86277def7e | ||
|
|
abe8ef6778 | ||
|
|
e3b8ce7737 | ||
|
|
acbafd081b | ||
|
|
3997d0df87 | ||
|
|
33888f1b08 | ||
|
|
c8bcdb4b61 | ||
|
|
8e8560b276 | ||
|
|
937473329f | ||
|
|
5e19e1bed3 | ||
|
|
3c74491720 | ||
|
|
9adc45767d | ||
|
|
59fff4ddef | ||
|
|
6d02c7e1a5 | ||
|
|
d33e0a3488 | ||
|
|
0864ab8ada | ||
|
|
7d53cb2aeb | ||
|
|
131164b7f6 | ||
|
|
6c889eee64 | ||
|
|
640a8e58c7 | ||
|
|
6505c96ec4 | ||
|
|
760aaa67c4 | ||
|
|
325387a6f2 | ||
|
|
1f08acb576 | ||
|
|
7abf46af70 | ||
|
|
2ca24375e4 | ||
|
|
34adb16ee8 | ||
|
|
5cdc73e13b | ||
|
|
d513e0f084 | ||
|
|
7c2da2d5b8 | ||
|
|
cc24ac496d | ||
|
|
408eef1356 | ||
|
|
9791b2eb00 | ||
|
|
99ec4dc72c | ||
|
|
3aae50cb59 | ||
|
|
a0a133b02c | ||
|
|
c2967b35ff | ||
|
|
92b41e017a | ||
|
|
52b2e066c5 | ||
|
|
e835175865 | ||
|
|
27e23faa5a | ||
|
|
4bafcc5b31 | ||
|
|
b8b7afe576 | ||
|
|
142c20aad1 | ||
|
|
e561f47cb2 | ||
|
|
d1f8e18d02 | ||
|
|
34374db56e | ||
|
|
b2e29eaf97 | ||
|
|
2ad6565632 | ||
|
|
cef20890dc | ||
|
|
8109db02b5 | ||
|
|
3fef8d7285 | ||
|
|
fe238d03c8 | ||
|
|
3c4a9c8efa | ||
|
|
30b050b44c | ||
|
|
64b2ecfefc | ||
|
|
edca8c88ea | ||
|
|
1278b79c79 | ||
|
|
7af84e79e5 | ||
|
|
e54c11e3bb | ||
|
|
786d904328 | ||
|
|
3e8796f781 | ||
|
|
3196b0c05a | ||
|
|
6fc18e330d | ||
|
|
9b0f252aff | ||
|
|
2e8272e18f | ||
|
|
484d03a5bc | ||
|
|
51bcda51c5 | ||
|
|
a4db7c8b42 | ||
|
|
3e8dd1e45c | ||
|
|
5ea9cf418a | ||
|
|
b7e09ecf98 | ||
|
|
7f26e9ac27 | ||
|
|
827f1f84cb | ||
|
|
7afe5af73a | ||
|
|
57020322cb | ||
|
|
1f89d8ce2f | ||
|
|
99a15377be | ||
|
|
d4061ff41b | ||
|
|
10c48bad7b | ||
|
|
94ceb0e410 | ||
|
|
24c1b00963 | ||
|
|
3866472459 | ||
|
|
204c1afe9a | ||
|
|
220f367658 | ||
|
|
60b8bc63a1 | ||
|
|
39ea24675d | ||
|
|
acae1aeaaa | ||
|
|
c399dcfe58 | ||
|
|
ea19a0063f | ||
|
|
4ef8e8c7aa | ||
|
|
bd964411e8 | ||
|
|
5941d0267d | ||
|
|
a7d764f6c0 | ||
|
|
bdfe6098a4 | ||
|
|
e0d706219b | ||
|
|
685c96a1b9 | ||
|
|
1effd38043 | ||
|
|
6ee9e2284a | ||
|
|
559303430a | ||
|
|
d0419782bd | ||
|
|
4982e2b6b0 | ||
|
|
b53fffe252 | ||
|
|
622ddd8d05 | ||
|
|
e128728105 | ||
|
|
cf2cd549c8 | ||
|
|
057e86eb27 | ||
|
|
eca468b9d1 | ||
|
|
dba63c5a61 | ||
|
|
4841a068be | ||
|
|
fc86a31c10 | ||
|
|
ba4705176e | ||
|
|
610e1a96f0 | ||
|
|
0c4f48766a | ||
|
|
8b8d1a5aaa | ||
|
|
ce0dd1c4f4 | ||
|
|
3ba0562006 | ||
|
|
6a69b4700c | ||
|
|
b6c3fc5b1a | ||
|
|
d937d1fc82 | ||
|
|
ca268c9da6 | ||
|
|
905c0b9d91 | ||
|
|
363df46006 | ||
|
|
d808c5d895 | ||
|
|
87e06993d6 | ||
|
|
6d85779f4d | ||
|
|
a2cd0f5804 | ||
|
|
9d5e7eb6e9 | ||
|
|
760623346c | ||
|
|
acc8b61cd1 | ||
|
|
9ce4f9806e | ||
|
|
399584db4c | ||
|
|
a07c9dfbbd | ||
|
|
e809f9c266 | ||
|
|
b30d6dfd8e | ||
|
|
2eff096ddd | ||
|
|
159daa9985 | ||
|
|
1846f5845c | ||
|
|
32dd7f1a0e | ||
|
|
20b46fe17f | ||
|
|
2372a85d9f | ||
|
|
30fd22a260 | ||
|
|
f9519479fc | ||
|
|
e5779a0756 | ||
|
|
61e4413541 | ||
|
|
4f46a08d65 | ||
|
|
f001c31342 | ||
|
|
6b85d5b5ac | ||
|
|
d5dd7d6f8a | ||
|
|
32c220497c | ||
|
|
5206566707 | ||
|
|
350fa4f15b | ||
|
|
be24439e2f | ||
|
|
97ff197198 | ||
|
|
16b407f535 | ||
|
|
8cfbe0c032 | ||
|
|
b55d78e119 | ||
|
|
04b216426a | ||
|
|
8fce78fbfb | ||
|
|
a5ece5063a | ||
|
|
d8dd5129e7 | ||
|
|
2980d76adb | ||
|
|
3f16ec0d22 | ||
|
|
805bb5ff9f | ||
|
|
6b928600ba | ||
|
|
19da1933a6 | ||
|
|
afee16e56b | ||
|
|
a58d4ae462 | ||
|
|
271f5cf033 | ||
|
|
8272ffd23f | ||
|
|
35fda90473 | ||
|
|
ce594fb152 | ||
|
|
3d9cb9460a | ||
|
|
719031f2ef | ||
|
|
39374b7235 | ||
|
|
35562d3a4d | ||
|
|
cba1c8295c | ||
|
|
d08c010ae1 | ||
|
|
e86419cfa5 | ||
|
|
14bc7f75be | ||
|
|
1a163cd48d | ||
|
|
9bf501dd25 | ||
|
|
4c90d0cedc | ||
|
|
07616094d2 | ||
|
|
3b0a242ab3 | ||
|
|
e2ac064914 | ||
|
|
673323fc67 | ||
|
|
a928ce48da | ||
|
|
f281dbbf54 | ||
|
|
3ebed101fd | ||
|
|
f0674ea034 | ||
|
|
114827a4b3 | ||
|
|
d3cbdfcafa | ||
|
|
ef1ed588b5 | ||
|
|
ec1b47a3e8 | ||
|
|
2bfbe03e37 | ||
|
|
4b58c6fc41 | ||
|
|
f834c37f8a | ||
|
|
71a68a5c6f | ||
|
|
dea37ed9e8 | ||
|
|
abc3ba0c7e | ||
|
|
4651d92d63 | ||
|
|
1627fc9596 | ||
|
|
452e6912b1 | ||
|
|
c5aecd43c8 | ||
|
|
7764ed9a8b | ||
|
|
819e5896bf | ||
|
|
d65ba04c5c | ||
|
|
76c4be1b74 | ||
|
|
7177306536 | ||
|
|
ec2d5af2c7 | ||
|
|
e6d9d1de47 | ||
|
|
46fea51622 | ||
|
|
e9c89cafb9 | ||
|
|
3f9a4c82b0 | ||
|
|
6d7b3863b5 | ||
|
|
7b0f59ed7c | ||
|
|
0d0fc320b4 | ||
|
|
0d0f91a807 | ||
|
|
c60e3e4ba4 | ||
|
|
ffc8d032c7 | ||
|
|
195b639344 | ||
|
|
6b7e588da5 | ||
|
|
4be25cb330 | ||
|
|
8495eca1a4 | ||
|
|
a01d6583d3 | ||
|
|
27745bb87b | ||
|
|
a265511368 | ||
|
|
46474bf457 | ||
|
|
69bfc71b6a | ||
|
|
256cecbefa | ||
|
|
fd6f592430 | ||
|
|
7021f002f2 | ||
|
|
415c2a95f2 | ||
|
|
f0b04375de | ||
|
|
917aa70c97 | ||
|
|
7e54ae3702 | ||
|
|
6be7a03b72 | ||
|
|
4cfe2294e3 | ||
|
|
c6adcda567 | ||
|
|
dbd0697c2c | ||
|
|
315f7ba43b | ||
|
|
ccc0a2a94f | ||
|
|
c5d59ab4c7 | ||
|
|
3c223a59c4 | ||
|
|
0f081d7c45 | ||
|
|
368cf73f89 | ||
|
|
3d1956d260 | ||
|
|
206c251090 | ||
|
|
dc190a297d | ||
|
|
8eee325db4 | ||
|
|
6662096ed3 | ||
|
|
5e6bc0847f | ||
|
|
61634950f3 | ||
|
|
46f4b00f8d | ||
|
|
92ada246b5 | ||
|
|
935594578a | ||
|
|
d7f82221d1 | ||
|
|
1949ff8602 | ||
|
|
23e0bb7345 | ||
|
|
f5f583d1cc | ||
|
|
cbcc693e36 | ||
|
|
8fcf2d4501 | ||
|
|
94526ab602 | ||
|
|
a512c7f89a | ||
|
|
f5ba83cae5 | ||
|
|
271bd37ad3 | ||
|
|
eedf85cbdb | ||
|
|
3c157eafd5 | ||
|
|
5298c03fce | ||
|
|
916424af49 | ||
|
|
d0810c7c19 | ||
|
|
3a4331db89 | ||
|
|
93fba518a6 | ||
|
|
1d42a5385b | ||
|
|
4dcd5a1286 | ||
|
|
c4b0bc5adc | ||
|
|
ec23961c7d | ||
|
|
27ac88fe28 | ||
|
|
b369158dc6 | ||
|
|
16533299b0 | ||
|
|
b30b06852b | ||
|
|
6074755b91 | ||
|
|
8230b270e3 | ||
|
|
66774f8fe0 | ||
|
|
6a23e6be96 | ||
|
|
939ca1b85f | ||
|
|
47043a54a5 | ||
|
|
6f572a61c7 | ||
|
|
3ccbbcb0b5 | ||
|
|
71efe2109a | ||
|
|
cfd1b07ffe | ||
|
|
6032e3efd8 | ||
|
|
dc925cc9c5 | ||
|
|
151192ae37 | ||
|
|
0b2d3d4f5d | ||
|
|
c20cfed6ae | ||
|
|
512a001e8c | ||
|
|
1e669132c2 | ||
|
|
9322ca7052 | ||
|
|
ce290bc99b | ||
|
|
7dfe0cae08 | ||
|
|
4210969087 | ||
|
|
32f4be83b1 | ||
|
|
fbd1e7bc45 | ||
|
|
b0a24c8baa | ||
|
|
1b5d4316fe | ||
|
|
2eb4849a69 | ||
|
|
9354e70fd3 | ||
|
|
0577f73ef5 | ||
|
|
90b6d5e293 | ||
|
|
59ffbd5f8d | ||
|
|
e2c1ff1a48 | ||
|
|
904effcf4e | ||
|
|
7cf26950cc | ||
|
|
8443eee628 | ||
|
|
e319e34783 | ||
|
|
d8b94b0527 | ||
|
|
3d99711ac8 | ||
|
|
99ab58febd | ||
|
|
2e90cd9924 | ||
|
|
6f5948746e | ||
|
|
015771f10b | ||
|
|
0b21046fce | ||
|
|
bc6921504a | ||
|
|
b6b493f450 | ||
|
|
eda43c77bb | ||
|
|
a8340cc980 | ||
|
|
8b5e4a9a52 | ||
|
|
2104ae9935 | ||
|
|
5627993827 | ||
|
|
e17fc6c474 | ||
|
|
86c33d78d0 | ||
|
|
fb055ca75d | ||
|
|
4bbfe0ce8a | ||
|
|
b6fd203355 | ||
|
|
7c337748b6 | ||
|
|
774bb3fec4 | ||
|
|
aadce3c747 | ||
|
|
c405f6d3f6 | ||
|
|
76f2ba50eb | ||
|
|
8e2c060fc7 | ||
|
|
ad967e8e22 | ||
|
|
2524c878b6 | ||
|
|
0122d8d36c | ||
|
|
71e78014e5 | ||
|
|
e72445b836 | ||
|
|
7869ec714d | ||
|
|
4c1759eecb | ||
|
|
ba16789843 | ||
|
|
28966e2087 | ||
|
|
d4357801a2 | ||
|
|
98ac6b5fec | ||
|
|
f743da0e02 | ||
|
|
42e83a2716 | ||
|
|
ca1c6498ec | ||
|
|
df1336dd26 | ||
|
|
657a54da84 | ||
|
|
df2bfbb636 | ||
|
|
43b301f22b | ||
|
|
159ece8943 | ||
|
|
46e6ada4f9 | ||
|
|
4b4126dfa6 | ||
|
|
4714a53c32 | ||
|
|
8717088ed1 | ||
|
|
fad22d1e60 | ||
|
|
56b230a1f0 | ||
|
|
dacdd6cd89 | ||
|
|
4bb6ff637c | ||
|
|
26d6f5ce4e | ||
|
|
5edc287848 | ||
|
|
95334e9764 | ||
|
|
29f0b678cf | ||
|
|
f4c0fd1744 | ||
|
|
bac92f4d3e | ||
|
|
d1dc72b65a | ||
|
|
41b907606f | ||
|
|
46e6753649 | ||
|
|
5b3f54429a | ||
|
|
9b78100378 | ||
|
|
05e5ae8bfa | ||
|
|
173333c861 | ||
|
|
072d8a8a13 | ||
|
|
8457a9e77e | ||
|
|
390fbd8493 | ||
|
|
1f2050f20d | ||
|
|
7f0ff6a079 | ||
|
|
bc9c7d1c07 | ||
|
|
9f86cda681 | ||
|
|
52bc9a82ef | ||
|
|
d84462923c | ||
|
|
106620e6dd | ||
|
|
0cfc7dd402 | ||
|
|
ced44e1916 | ||
|
|
9873c9fc04 | ||
|
|
07e6a3ec68 | ||
|
|
24228711fd | ||
|
|
4a01d2904c | ||
|
|
697e7b1ca5 | ||
|
|
c84099508f | ||
|
|
9c59ed5891 | ||
|
|
f7e9a91b5c | ||
|
|
afdb92ff9b | ||
|
|
08a3423ce2 | ||
|
|
7b1d84cbdb | ||
|
|
2c99ecf586 | ||
|
|
f221faff28 | ||
|
|
f25b098029 | ||
|
|
941670aa9d | ||
|
|
17bb564534 | ||
|
|
727647902c | ||
|
|
9ea40edb48 | ||
|
|
3666d1485a | ||
|
|
2a69d2d1f9 | ||
|
|
bb3eeffe78 | ||
|
|
1d04902326 | ||
|
|
30bddbd254 | ||
|
|
072a65bd26 | ||
|
|
0782b8a682 | ||
|
|
da6236f830 | ||
|
|
6821e633fd | ||
|
|
d0428df9bd | ||
|
|
77827303d2 | ||
|
|
c1de4c5fda | ||
|
|
a07c63dde6 | ||
|
|
0d58e6627a | ||
|
|
f453d6c85b | ||
|
|
918ea1cdd8 | ||
|
|
935842845b | ||
|
|
624ef309f0 | ||
|
|
5e2a433828 | ||
|
|
482da95352 | ||
|
|
b1c69ebab9 | ||
|
|
8fe9fa0dc7 | ||
|
|
c7a75f477f | ||
|
|
4e04daaed4 | ||
|
|
b6b75d3a27 | ||
|
|
8a29d91d15 | ||
|
|
88d0933a6e | ||
|
|
a0e8ca128c | ||
|
|
79315cb7de | ||
|
|
ccfd2adf5b | ||
|
|
7520a745c2 | ||
|
|
055d7f261c | ||
|
|
3cee83be2b | ||
|
|
fa3f1e088d | ||
|
|
2edccbfbc8 | ||
|
|
56eec8979b | ||
|
|
182ed07a41 | ||
|
|
af39687f45 | ||
|
|
9f898b0b90 | ||
|
|
cfee331006 | ||
|
|
09d53f0e58 | ||
|
|
c2d9197900 | ||
|
|
e15cf324c3 | ||
|
|
122569eee0 | ||
|
|
0fa89647d2 | ||
|
|
ecaa3fd03e | ||
|
|
c8899c2b3b | ||
|
|
a295525501 | ||
|
|
827153624c | ||
|
|
04e1838c92 | ||
|
|
14a2b61671 | ||
|
|
9a041c8fdb | ||
|
|
2b1aaebe18 | ||
|
|
07492bda9d | ||
|
|
3156c1549d | ||
|
|
308b54a8f3 | ||
|
|
7e348b7815 | ||
|
|
e6f08f0b92 | ||
|
|
b998a522b0 | ||
|
|
d6d5c341e2 | ||
|
|
262c3eea6b | ||
|
|
62f43e6ea2 | ||
|
|
c4e6a04676 | ||
|
|
a09a5b9b7b | ||
|
|
57e5fa9873 | ||
|
|
a44579303c | ||
|
|
863d14a61a | ||
|
|
7a895209e3 | ||
|
|
283ed55824 | ||
|
|
6949a95782 | ||
|
|
2f7e970c5f | ||
|
|
88b29a4e59 | ||
|
|
9705ee89d9 | ||
|
|
a27be2fab6 | ||
|
|
9e916a2893 | ||
|
|
ff80e99cc9 | ||
|
|
26dd533662 | ||
|
|
84477440b6 | ||
|
|
e402a0c078 | ||
|
|
3a8ea7260c | ||
|
|
fc40c437cb | ||
|
|
1b01a074dc | ||
|
|
1d3fe87215 | ||
|
|
5a6c398ea0 | ||
|
|
73e6164096 | ||
|
|
7d7287a1ba | ||
|
|
ec1950d3ca | ||
|
|
515847bece | ||
|
|
409516e86c | ||
|
|
b5ac85d19a | ||
|
|
1b7ca67fdb | ||
|
|
129d6efd85 | ||
|
|
ae30ce4596 | ||
|
|
185a0fb19c | ||
|
|
a65996f74c | ||
|
|
0a2ba38e58 | ||
|
|
0f92944edd | ||
|
|
98c5b34f2b | ||
|
|
7f33143502 | ||
|
|
f5c1b38e2d | ||
|
|
a55b46b4bf | ||
|
|
95b8e27bc5 | ||
|
|
e9c7deae4f | ||
|
|
f6cf8f2f0c | ||
|
|
9d0b254407 | ||
|
|
38d8c7f0d9 | ||
|
|
a16a935bff | ||
|
|
cd7ef6e7a7 | ||
|
|
814f2f9e03 | ||
|
|
2c0feb2a46 | ||
|
|
b03388293f | ||
|
|
fb467a1196 | ||
|
|
86fddfed9a | ||
|
|
1a17b1670b | ||
|
|
f4cdded06c | ||
|
|
e3bbd058f2 | ||
|
|
0cfc37d757 | ||
|
|
54941d1f09 | ||
|
|
05b170fe99 | ||
|
|
d3c58d83a5 | ||
|
|
ca82a4720b | ||
|
|
5657e199bd | ||
|
|
56e96793c0 | ||
|
|
deb6327b56 | ||
|
|
b7b49203aa | ||
|
|
d4a6c488ca | ||
|
|
ec5ad7136f | ||
|
|
a4b85c49c9 | ||
|
|
90bbb35655 | ||
|
|
8cc24f4cf2 | ||
|
|
66efd65e64 | ||
|
|
7f8af83b5b | ||
|
|
c604adc804 | ||
|
|
65fabc20c9 | ||
|
|
bf54c22cd9 | ||
|
|
1e1f34f9cb | ||
|
|
6ccf7a7ac7 | ||
|
|
d344407636 | ||
|
|
adc3d21385 | ||
|
|
235ad8e553 | ||
|
|
a67a6aa685 | ||
|
|
a5e043e6e6 | ||
|
|
f7220ae416 | ||
|
|
44c0ca4d3c | ||
|
|
fd28624120 | ||
|
|
a0440b63bb | ||
|
|
0c8be37ca9 | ||
|
|
13762f20c9 | ||
|
|
a47359e3f5 | ||
|
|
91caff1d89 | ||
|
|
913377e31b | ||
|
|
079beb957e | ||
|
|
45eef4a03c | ||
|
|
3ecce5251b | ||
|
|
742590d1d7 | ||
|
|
919cf8558b | ||
|
|
3b27216c51 | ||
|
|
5cb4466f7c | ||
|
|
4510f5a5b8 | ||
|
|
cd37ec47d5 | ||
|
|
c97eff94f5 | ||
|
|
dd984c7319 | ||
|
|
5f89fa4190 | ||
|
|
7c754e495e | ||
|
|
1bd6e841bf | ||
|
|
de93983dff | ||
|
|
3aa8d3fdac | ||
|
|
91efe10855 | ||
|
|
4dca27962e | ||
|
|
6844116b94 | ||
|
|
f0403a5394 | ||
|
|
e5e45a3a5c | ||
|
|
ddb2651691 | ||
|
|
af2f556fd3 | ||
|
|
b19e4a6440 | ||
|
|
88f04b5ebd | ||
|
|
2b403b7dad | ||
|
|
b29d47a682 | ||
|
|
0fbb78e61a | ||
|
|
ed89695a8c | ||
|
|
0e60c50c5e | ||
|
|
0c1a8cd43f | ||
|
|
ee7b5da64a | ||
|
|
95971a6180 | ||
|
|
380f4fbac7 | ||
|
|
831f0acdc5 | ||
|
|
34d8843fd6 | ||
|
|
a61afedcf9 | ||
|
|
06ad3389e0 | ||
|
|
fa29d36d09 | ||
|
|
c463121da8 | ||
|
|
a9517b1b17 | ||
|
|
a256c43871 | ||
|
|
bc277c6e28 | ||
|
|
f86dcfc288 | ||
|
|
68f543b8bb | ||
|
|
de5b20d0bf | ||
|
|
8193397d4f | ||
|
|
dacb51fba2 | ||
|
|
9a14b40495 | ||
|
|
bb8dd6cb11 | ||
|
|
4b3ecfe674 | ||
|
|
7acdcd6952 | ||
|
|
7beffb5a5f | ||
|
|
0ff1f418bd | ||
|
|
ab990cfba6 | ||
|
|
2c99f97c8b | ||
|
|
a9b4debe37 | ||
|
|
959c4f026f | ||
|
|
e0d16331a4 | ||
|
|
a6b6b25267 | ||
|
|
c7f5d9d77d | ||
|
|
783c53d57c | ||
|
|
d47b872292 | ||
|
|
7c7118d883 | ||
|
|
4e4cf33342 | ||
|
|
bc2476f342 | ||
|
|
9c682efb2f | ||
|
|
267daa5fc1 | ||
|
|
ac98f15cfa | ||
|
|
e68807ad4f | ||
|
|
a162f00ecc | ||
|
|
5b8ead9db8 | ||
|
|
e873351624 | ||
|
|
9ae1f804e1 | ||
|
|
46be81115a | ||
|
|
2aba7fb374 | ||
|
|
1c2f2b5c13 | ||
|
|
433d208572 | ||
|
|
d3ab948d88 | ||
|
|
148789600a | ||
|
|
c6b3899c2d | ||
|
|
b6ddb58634 | ||
|
|
a9dfe1e10e | ||
|
|
711844296d | ||
|
|
f3dea276e7 | ||
|
|
2f72219d6e | ||
|
|
221ab3b695 | ||
|
|
eaeda36bdb | ||
|
|
83b509e033 | ||
|
|
71d3f5852d | ||
|
|
5e9255dda8 | ||
|
|
6c932af0a0 | ||
|
|
5d0082471f | ||
|
|
26608fed5a | ||
|
|
8742377c3b | ||
|
|
8a86242a5d | ||
|
|
7c79985460 | ||
|
|
1502d9b552 | ||
|
|
f0cc192d7b | ||
|
|
a673018508 | ||
|
|
9b2f8dca64 | ||
|
|
b62ef939bf | ||
|
|
366bc21ab8 | ||
|
|
b3119c0a5f | ||
|
|
610295f875 | ||
|
|
15c9f10bc1 | ||
|
|
6943244107 | ||
|
|
d75569abab | ||
|
|
b5e11259e1 | ||
|
|
86884a33f5 | ||
|
|
de9f053cfb | ||
|
|
bc76f33092 | ||
|
|
b4a9b1550c | ||
|
|
458b0df39e | ||
|
|
b292adceb0 | ||
|
|
6e78973eec | ||
|
|
edb7950be8 | ||
|
|
1a9443b55a | ||
|
|
f854a99e0a | ||
|
|
50879db001 | ||
|
|
63ebaea25a | ||
|
|
a3883eb306 | ||
|
|
65e2f60b40 | ||
|
|
e5bac27fcc | ||
|
|
feeef689f3 | ||
|
|
47febcd7f4 | ||
|
|
138ec8411c | ||
|
|
24d488b5f1 | ||
|
|
672b39fb84 | ||
|
|
9f575aad5b | ||
|
|
988d0001d3 | ||
|
|
2205045ca9 | ||
|
|
e41704b211 | ||
|
|
b25548414b | ||
|
|
34f7ccb5fa | ||
|
|
671177e162 | ||
|
|
8b4e08d694 | ||
|
|
4627c8b3ee | ||
|
|
b5b569afd4 | ||
|
|
b184772349 | ||
|
|
f61bd43621 | ||
|
|
da21917dad | ||
|
|
e65dbcf2b5 | ||
|
|
a1e7389e71 | ||
|
|
ecd6e1d510 | ||
|
|
0222981161 | ||
|
|
488914a4ac | ||
|
|
7d9738e8c0 | ||
|
|
05a188da38 | ||
|
|
03a74a250a | ||
|
|
6a80ebf985 | ||
|
|
4debe46d1f | ||
|
|
58d4b2a617 | ||
|
|
2007f1ab95 | ||
|
|
987834a2dd | ||
|
|
3076e2a1f7 | ||
|
|
543a3ddb03 | ||
|
|
301f4d0346 | ||
|
|
429ac54a34 | ||
|
|
e168b4e543 | ||
|
|
01381fae1f | ||
|
|
7782f91131 | ||
|
|
579774f505 | ||
|
|
e8fbafd154 | ||
|
|
35ded56fdd | ||
|
|
361c88d6ea | ||
|
|
9b484192c4 | ||
|
|
86010bdb0d | ||
|
|
138b67db86 | ||
|
|
ea95bd57ef | ||
|
|
3def848422 | ||
|
|
6fa7580d10 | ||
|
|
984c8f7db1 | ||
|
|
53947bf2e9 | ||
|
|
bd30a04d0d | ||
|
|
18bdc53907 | ||
|
|
6f56dbe395 | ||
|
|
ff9e7ef64b | ||
|
|
cfbfac6a51 | ||
|
|
7c8a9c0f9a | ||
|
|
d378b5aec8 | ||
|
|
c613351f39 | ||
|
|
9bd51f2062 | ||
|
|
cfd5eefa7a | ||
|
|
781f83f648 | ||
|
|
02e7dcdc87 | ||
|
|
8a39a66057 | ||
|
|
5e3e48c8dd | ||
|
|
0b7d5f2813 | ||
|
|
ccabbf328f | ||
|
|
022c08d947 | ||
|
|
f9523aa419 | ||
|
|
b0ec3dfb47 | ||
|
|
19b7d4d0d4 | ||
|
|
d5a97c0c59 | ||
|
|
3b9aac21c4 | ||
|
|
3880ec6839 | ||
|
|
9602e6785e | ||
|
|
7918a42a0e | ||
|
|
81ba71e8d5 | ||
|
|
23529e839d | ||
|
|
34a696c3d6 | ||
|
|
cad694e469 | ||
|
|
17d91d173b | ||
|
|
1b23b4bc47 | ||
|
|
d44d82b694 | ||
|
|
649d29414f | ||
|
|
7b4349a9ce | ||
|
|
1d5597917b | ||
|
|
ce0873d589 | ||
|
|
bc91e5c0fd | ||
|
|
6d1f716f8b | ||
|
|
442227fc89 | ||
|
|
ae1c171392 | ||
|
|
65c64f52c8 | ||
|
|
1c31603e17 | ||
|
|
894dfd1a6b | ||
|
|
d23b3bb056 | ||
|
|
0b819ca3b0 | ||
|
|
041a4e0b43 | ||
|
|
ca87ddd540 | ||
|
|
e29dfabb74 | ||
|
|
43e4e1c389 | ||
|
|
e42d70a2b0 | ||
|
|
48acbf75cd | ||
|
|
22ac3a3099 | ||
|
|
373090f223 | ||
|
|
5ea8861bf3 | ||
|
|
e85ce5c02f | ||
|
|
220c6c4e0e | ||
|
|
f057587457 | ||
|
|
69295ba076 | ||
|
|
76254d693d | ||
|
|
cbefb7c543 | ||
|
|
228d8517c7 | ||
|
|
bece5f0f91 | ||
|
|
1300758499 | ||
|
|
129b9d0945 | ||
|
|
b9b05fc3eb | ||
|
|
4c8ab82f9c | ||
|
|
2200c1f7e1 | ||
|
|
59849f73bd | ||
|
|
51211980a4 | ||
|
|
c53e2772a0 | ||
|
|
650a80a61a | ||
|
|
cfe5424cf9 | ||
|
|
59b4641d29 | ||
|
|
64200c405e | ||
|
|
0eed56fae9 | ||
|
|
c0f86e796d | ||
|
|
0f11b0c61d | ||
|
|
981a2d4341 | ||
|
|
299642083b | ||
|
|
c21aaebbc4 | ||
|
|
0317f43987 | ||
|
|
78ef07f630 | ||
|
|
e98cb4f145 | ||
|
|
cf44745d08 | ||
|
|
196fd79d5f | ||
|
|
8dc77a7083 | ||
|
|
a5688f60d3 | ||
|
|
288ec8aa5c | ||
|
|
c7658d6285 | ||
|
|
1bc0efba43 | ||
|
|
de75ea88b6 | ||
|
|
0a989e63d2 | ||
|
|
237c20c9b6 | ||
|
|
6d4337fc71 | ||
|
|
b88ef8b1a5 | ||
|
|
9c389a49c7 | ||
|
|
da4948944d | ||
|
|
1a1d36c73f | ||
|
|
d3f6ffb09e | ||
|
|
f32a780459 | ||
|
|
3ec55d0cdd | ||
|
|
a61c7e59d6 | ||
|
|
8084b6cbf0 | ||
|
|
79c113b532 | ||
|
|
e6e1243852 | ||
|
|
5fc0ede5bf | ||
|
|
c90f3cf275 | ||
|
|
ffa2a545fa | ||
|
|
35cb7b97db | ||
|
|
a5ac76b192 | ||
|
|
26c56dd0d2 | ||
|
|
31f34e95cc | ||
|
|
189c729f15 | ||
|
|
60ed7769cd | ||
|
|
5ee8861350 | ||
|
|
01caa0d06e | ||
|
|
1de9b906fd | ||
|
|
255cf347ec | ||
|
|
6cd7d21db1 | ||
|
|
67818ea2cc | ||
|
|
bfe5bea68d | ||
|
|
3a70ee6662 | ||
|
|
c0860a6018 | ||
|
|
da60e86993 | ||
|
|
ecb13a87dc | ||
|
|
d8125768a3 | ||
|
|
91b2c82c58 | ||
|
|
660ead4b0e | ||
|
|
c876f9edb2 | ||
|
|
f4074dbe1d | ||
|
|
175faeb5f2 | ||
|
|
82419813df | ||
|
|
609e3b8c92 | ||
|
|
fac1a517df | ||
|
|
653add64f9 | ||
|
|
89b807177e | ||
|
|
09d5169a8b | ||
|
|
8af162711e | ||
|
|
503b6c1716 | ||
|
|
ad012a63cb | ||
|
|
36e500682a | ||
|
|
de5952f597 | ||
|
|
f6aa387e6f | ||
|
|
e750b27edb | ||
|
|
3417f3716e | ||
|
|
5c5feab38e | ||
|
|
97c305452e | ||
|
|
41600667c2 | ||
|
|
c187862e4f | ||
|
|
d8f9c5380a | ||
|
|
b5827ea83f | ||
|
|
6816816101 | ||
|
|
06e45f7587 | ||
|
|
71c47f69ec | ||
|
|
6f909b6e69 | ||
|
|
a5cfd2321f | ||
|
|
26c2690536 | ||
|
|
729ad9e5c9 | ||
|
|
48cf91a3d0 | ||
|
|
457abbacef | ||
|
|
7adbf5698a | ||
|
|
f58dcbaa8a | ||
|
|
6d457d9927 | ||
|
|
e665d9f6df | ||
|
|
d436ea19df | ||
|
|
6cd778f940 | ||
|
|
e9d096e411 | ||
|
|
894b2614ed | ||
|
|
c242ab4371 | ||
|
|
a51f182cc0 | ||
|
|
302199938f | ||
|
|
8d66809d0b | ||
|
|
16f132bff0 | ||
|
|
3cac853dd4 | ||
|
|
511268ddd7 | ||
|
|
01bfc4f2f1 | ||
|
|
3a80896173 | ||
|
|
f76728818b | ||
|
|
a0b41feb72 | ||
|
|
2e777f99b9 | ||
|
|
a259ac9bcf | ||
|
|
04527a8bcb | ||
|
|
63c431f01d | ||
|
|
7639f1e99b | ||
|
|
abc8f29800 | ||
|
|
22ab3d1256 | ||
|
|
126e592758 | ||
|
|
cca642b094 | ||
|
|
c551192b6b | ||
|
|
d0ae95604f | ||
|
|
e94d15ec11 | ||
|
|
0252368af2 | ||
|
|
e1c2084eeb | ||
|
|
aab2303c37 | ||
|
|
c8c4c4ed46 | ||
|
|
654dbb1b10 | ||
|
|
9a289e2982 | ||
|
|
51fe83b7be | ||
|
|
755f0b3dbb | ||
|
|
35dae4e1f4 | ||
|
|
1d24cb4828 | ||
|
|
24defe7100 | ||
|
|
6309710ccc | ||
|
|
b794643735 | ||
|
|
a592dc116d | ||
|
|
eea5b71da5 | ||
|
|
93207c081e | ||
|
|
94244683b0 | ||
|
|
609176a18f | ||
|
|
c0cefa8749 | ||
|
|
0f974c562c | ||
|
|
af60471b62 | ||
|
|
8fc7eb295b | ||
|
|
2a38e5f408 | ||
|
|
be8d34dc21 | ||
|
|
fc6cec9074 | ||
|
|
0411b1e75d | ||
|
|
d81dca3e90 | ||
|
|
c89290e507 | ||
|
|
754ebc052e | ||
|
|
7d42497e09 | ||
|
|
7093e37b45 | ||
|
|
51bdd6499a | ||
|
|
0e5b30902c | ||
|
|
721915d61b | ||
|
|
f4e93e7550 | ||
|
|
4a231a34f2 | ||
|
|
72081ff7ba | ||
|
|
ef8689c400 | ||
|
|
6bf2f22315 | ||
|
|
34057c5e4b | ||
|
|
53ebc683c9 | ||
|
|
2eab525077 | ||
|
|
6a0f3f2d7b | ||
|
|
65fe9b86c5 | ||
|
|
76743431a4 | ||
|
|
2e00e9d715 | ||
|
|
c8e7ff51c4 | ||
|
|
6e7f47bf9e | ||
|
|
34dc2e6506 | ||
|
|
29b47d3d44 | ||
|
|
0618944f8a | ||
|
|
d37120bb15 | ||
|
|
5d58d9171e | ||
|
|
9c6d3dbecd | ||
|
|
eb0fd4d066 | ||
|
|
01c2a09991 | ||
|
|
46983465fd | ||
|
|
6789f2c8d1 | ||
|
|
1b9e9c019d | ||
|
|
49ecdff016 | ||
|
|
f2b20e5949 | ||
|
|
54a61bbbc2 | ||
|
|
69d4e185d8 | ||
|
|
87c1c50bfa | ||
|
|
de1d72f348 | ||
|
|
ee042bc642 | ||
|
|
c22bddc9a7 | ||
|
|
a5a0dfa96e | ||
|
|
e7e1f62f72 | ||
|
|
aa25b7cc0a | ||
|
|
1bfeaf3eaf | ||
|
|
6985b671d8 | ||
|
|
468c878c92 | ||
|
|
05f5ae1519 | ||
|
|
61640ef7ce | ||
|
|
833a4a9319 | ||
|
|
3182f150c1 | ||
|
|
62aef84205 | ||
|
|
9c63363ccc | ||
|
|
e4b1357a22 | ||
|
|
f8ba66bb0c | ||
|
|
61c1f0e1ed | ||
|
|
8ac6d29c74 | ||
|
|
6b01986c48 | ||
|
|
efe2a18189 | ||
|
|
e9617f1e50 | ||
|
|
4f18aaaf6e | ||
|
|
d2efb5bbc8 | ||
|
|
e8eed2ebae | ||
|
|
7a8b69edbb | ||
|
|
bebc8cf871 | ||
|
|
453341c754 | ||
|
|
13a99c30ea | ||
|
|
e342b4536a | ||
|
|
3d7eb3bac8 | ||
|
|
d8dd44aa16 | ||
|
|
d4489d1bdc | ||
|
|
a6cdafe85c | ||
|
|
088ccf58fc | ||
|
|
d2d32e5439 | ||
|
|
4232509427 | ||
|
|
82874181b0 | ||
|
|
9fa64adbda | ||
|
|
70d12fc57a | ||
|
|
732dee92ad | ||
|
|
fa5768b14d | ||
|
|
1fe35e518c | ||
|
|
d859a77b52 | ||
|
|
f89646eabe | ||
|
|
ec307a6046 | ||
|
|
437f14aa4e | ||
|
|
c5512b933e | ||
|
|
a6b5ee3cd3 | ||
|
|
93c061b7a5 | ||
|
|
a34bab7f1e | ||
|
|
96538ae2fe | ||
|
|
fcea78a6cb | ||
|
|
bf23503000 | ||
|
|
84c1cc8865 | ||
|
|
c6e423024e | ||
|
|
9c28cc5b19 | ||
|
|
23efc790c1 | ||
|
|
99533afd11 | ||
|
|
f309190486 | ||
|
|
3b76ea9ffe | ||
|
|
e0c702d068 | ||
|
|
b5e5cb1c65 | ||
|
|
31b8141fee | ||
|
|
62c4f3e6fb | ||
|
|
298773b2f8 | ||
|
|
0a066533de | ||
|
|
f8c22abaa6 | ||
|
|
974e31a307 | ||
|
|
d5afa35cb8 | ||
|
|
439cccbc0d | ||
|
|
b1f6fe2528 | ||
|
|
a851e08109 | ||
|
|
e7066d6f36 | ||
|
|
29cb77b964 | ||
|
|
4c813f60f9 | ||
|
|
0c8e0450de | ||
|
|
1017e2c6c7 | ||
|
|
0428774ec8 | ||
|
|
59aafe100b | ||
|
|
400be2a3e6 | ||
|
|
8d2da96480 | ||
|
|
80fd691880 | ||
|
|
8e7385ccc2 | ||
|
|
16740b9695 | ||
|
|
823db6071b | ||
|
|
27233af0d2 | ||
|
|
e71f00296e | ||
|
|
c7a5a695d3 | ||
|
|
cc2d84c992 | ||
|
|
53ec0700bb | ||
|
|
88e5babb56 | ||
|
|
50e35e6bdc | ||
|
|
d872d7f0b1 | ||
|
|
51729cbaa7 | ||
|
|
854a966a22 | ||
|
|
9d2f7f0686 | ||
|
|
37124c4e0a | ||
|
|
30b9cfce3d | ||
|
|
e127274aa5 | ||
|
|
c34b5ad1a7 | ||
|
|
10b676aa2b | ||
|
|
0698a8c455 | ||
|
|
1193c530c9 | ||
|
|
18b9c6cd69 | ||
|
|
9231f54313 | ||
|
|
72c2432586 | ||
|
|
5c79ca25aa | ||
|
|
a59b62e3ba | ||
|
|
f99a096d01 | ||
|
|
a3b42392b4 | ||
|
|
5a79963bd2 | ||
|
|
184a402f39 | ||
|
|
7d79777bfb | ||
|
|
84c1d34028 | ||
|
|
21bbbb1f1f | ||
|
|
177a9ce4d2 | ||
|
|
150bca0498 | ||
|
|
0c60efc03b | ||
|
|
b857655992 | ||
|
|
dd6eafbb6c | ||
|
|
0799a5b5e9 | ||
|
|
a1079e692c | ||
|
|
f9b77f9854 | ||
|
|
bc0f853b52 | ||
|
|
a3feeef88c | ||
|
|
c18e23b817 | ||
|
|
4121c1ca72 | ||
|
|
7ffdd80bb2 | ||
|
|
51f9fb35c6 | ||
|
|
e155e86517 | ||
|
|
d835372e93 | ||
|
|
b4bc4b029d | ||
|
|
c0dc0112d3 | ||
|
|
e01510953e | ||
|
|
74ecfca43e | ||
|
|
6d34b91425 | ||
|
|
605ee881c5 | ||
|
|
fc290404e1 | ||
|
|
d837a2110b | ||
|
|
8426a3da11 | ||
|
|
2a1f5e121b | ||
|
|
997ef97521 | ||
|
|
8aed5c72df | ||
|
|
d1fbaae91f | ||
|
|
ee34c42697 | ||
|
|
2adf9378e4 | ||
|
|
1e7193134a | ||
|
|
33ff1df4ec | ||
|
|
69158cd4f3 |
4
.coveralls.yml
Normal file
4
.coveralls.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
# for php-coveralls
|
||||
service_name: travis-ci
|
||||
src_dir: lib
|
||||
coverage_clover: build/logs/clover.xml
|
||||
12
.gitattributes
vendored
Normal file
12
.gitattributes
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
/tests export-ignore
|
||||
/tools export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
.gitmodules export-ignore
|
||||
.travis.yml export-ignore
|
||||
build.properties export-ignore
|
||||
build.properties.dev export-ignore
|
||||
build.xml export-ignore
|
||||
CONTRIBUTING.md export-ignore
|
||||
phpunit.xml.dist export-ignore
|
||||
run-all.sh export-ignore
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -8,4 +8,7 @@ lib/Doctrine/Common
|
||||
lib/Doctrine/DBAL
|
||||
/.settings/
|
||||
.buildpath
|
||||
.project
|
||||
.project
|
||||
.idea
|
||||
vendor/
|
||||
composer.lock
|
||||
|
||||
19
.gitmodules
vendored
19
.gitmodules
vendored
@@ -1,15 +1,6 @@
|
||||
[submodule "lib/vendor/doctrine-common"]
|
||||
path = lib/vendor/doctrine-common
|
||||
url = git://github.com/doctrine/common.git
|
||||
[submodule "lib/vendor/doctrine-dbal"]
|
||||
path = lib/vendor/doctrine-dbal
|
||||
url = git://github.com/doctrine/dbal.git
|
||||
[submodule "lib/vendor/Symfony/Component/Console"]
|
||||
path = lib/vendor/Symfony/Component/Console
|
||||
url = git://github.com/symfony/Console.git
|
||||
[submodule "lib/vendor/Symfony/Component/Yaml"]
|
||||
path = lib/vendor/Symfony/Component/Yaml
|
||||
url = git://github.com/symfony/Yaml.git
|
||||
[submodule "docs/en/_theme"]
|
||||
path = docs/en/_theme
|
||||
url = git://github.com/doctrine/doctrine-sphinx-theme.git
|
||||
[submodule "lib/vendor/doctrine-build-common"]
|
||||
path = lib/vendor/doctrine-build-common
|
||||
url = git://github.com/doctrine/doctrine-build-common.git
|
||||
path = lib/vendor/doctrine-build-common
|
||||
url = git://github.com/doctrine/doctrine-build-common.git
|
||||
|
||||
@@ -3,6 +3,8 @@ language: php
|
||||
php:
|
||||
- 5.3
|
||||
- 5.4
|
||||
- 5.5
|
||||
|
||||
env:
|
||||
- DB=mysql
|
||||
- DB=pgsql
|
||||
@@ -14,6 +16,9 @@ before_script:
|
||||
- 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"
|
||||
- git submodule update --init
|
||||
- composer install --prefer-dist --dev
|
||||
|
||||
script: phpunit --configuration tests/travis/$DB.travis.xml
|
||||
script: phpunit --configuration tests/travis/$DB.travis.xml
|
||||
|
||||
after_script:
|
||||
- php vendor/bin/coveralls -v
|
||||
|
||||
72
CONTRIBUTING.md
Normal file
72
CONTRIBUTING.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Contribute to Doctrine
|
||||
|
||||
Thank you for contributing to Doctrine!
|
||||
|
||||
Before we can merge your Pull-Request here are some guidelines that you need to follow.
|
||||
These guidelines exist not to annoy you, but to keep the code base clean,
|
||||
unified and future proof.
|
||||
|
||||
## We only accept PRs to "master"
|
||||
|
||||
Our branching strategy is summed up with "everything to master first", even
|
||||
bugfixes and we then merge them into the stable branches. You should only
|
||||
open pull requests against the master branch. Otherwise we cannot accept the PR.
|
||||
|
||||
There is one exception to the rule, when we merged a bug into some stable branches
|
||||
we do occasionally accept pull requests that merge the same bug fix into earlier
|
||||
branches.
|
||||
|
||||
## Coding Standard
|
||||
|
||||
We use PSR-1 and PSR-2:
|
||||
|
||||
* https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md
|
||||
* https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md
|
||||
|
||||
with some exceptions/differences:
|
||||
|
||||
* Keep the nesting of control structures per method as small as possible
|
||||
* Align equals (=) signs
|
||||
* Add spaces between assignment, control and return statements
|
||||
* Prefer early exit over nesting conditions
|
||||
* Add spaces around a negation if condition ``if ( ! $cond)``
|
||||
|
||||
## Unit-Tests
|
||||
|
||||
Always 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,
|
||||
``DDC1234Test.php`` for example.
|
||||
* If you want to contribute new functionality add unit- or functional tests
|
||||
depending on the scope of the feature.
|
||||
|
||||
You can run the unit-tests by calling ``phpunit`` from the root of the project.
|
||||
It will run all the tests with an in memory SQLite database.
|
||||
|
||||
To run the testsuite against another database, copy the ``phpunit.xml.dist``
|
||||
to for example ``mysql.phpunit.xml`` and edit the parameters. You can
|
||||
take a look at the ``tests/travis`` folder for some examples. Then run:
|
||||
|
||||
phpunit -c mysql.phpunit.xml
|
||||
|
||||
## Travis
|
||||
|
||||
We automatically run your pull request through [Travis CI](http://www.travis-ci.org)
|
||||
against SQLite, MySQL and PostgreSQL. If you break the tests, we cannot merge your code,
|
||||
so please make sure that your code is working before opening up a Pull-Request.
|
||||
|
||||
## DoctrineBot, Tickets and Jira
|
||||
|
||||
DoctrineBot will synchronize your Pull-Request into our [Jira](http://www.doctrine-project.org).
|
||||
Make sure to add any existing Jira ticket into the Pull-Request Title, for example:
|
||||
|
||||
"[DDC-123] My Pull Request"
|
||||
|
||||
## Getting merged
|
||||
|
||||
Please allow us time to review your pull requests. We will give our best to review
|
||||
everything as fast as possible, but cannot always live up to our own expectations.
|
||||
|
||||
Thank you very much again for your contribution!
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
# Doctrine 2 ORM
|
||||
|
||||
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
|
||||
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),
|
||||
|
||||
66
UPGRADE.md
66
UPGRADE.md
@@ -1,5 +1,57 @@
|
||||
# Upgrade to 2.4
|
||||
|
||||
## BC BREAK: Compatibility Bugfix in PersistentCollection#matching()
|
||||
|
||||
In Doctrine 2.3 it was possible to use the new ``matching($criteria)``
|
||||
functionality by adding constraints for assocations based on ID:
|
||||
|
||||
Criteria::expr()->eq('association', $assocation->getId());
|
||||
|
||||
This functionality does not work on InMemory collections however, because
|
||||
in memory criteria compares object values based on reference.
|
||||
As of 2.4 the above code will throw an exception. You need to change
|
||||
offending code to pass the ``$assocation`` reference directly:
|
||||
|
||||
Criteria::expr()->eq('association', $assocation);
|
||||
|
||||
## Composer is now the default autoloader
|
||||
|
||||
The test suite now runs with composer autoloading. Support for PEAR, and tarball autoloading is deprecated.
|
||||
Support for GIT submodules is removed.
|
||||
|
||||
## OnFlush and PostFlush event always called
|
||||
|
||||
Before 2.4 the postFlush and onFlush events were only called when there were
|
||||
actually entities that changed. Now these events are called no matter if there
|
||||
are entities in the UoW or changes are found.
|
||||
|
||||
## Parenthesis are now considered in arithmetic expression
|
||||
|
||||
Before 2.4 parenthesis are not considered in arithmetic primary expression.
|
||||
That's conceptually wrong, since it might result in wrong values. For example:
|
||||
|
||||
The DQL:
|
||||
|
||||
SELECT 100 / ( 2 * 2 ) FROM MyEntity
|
||||
|
||||
Before 2.4 it generates the SQL:
|
||||
|
||||
SELECT 100 / 2 * 2 FROM my_entity
|
||||
|
||||
Now parenthesis are considered, the previous DQL will generate:
|
||||
|
||||
SELECT 100 / (2 * 2) FROM my_entity
|
||||
|
||||
# Upgrade to 2.3
|
||||
|
||||
## EntityManager#find() not calls EntityRepository#find() anymore
|
||||
|
||||
Previous to 2.3, calling ``EntityManager#find()`` would be delegated to
|
||||
``EntityRepository#find()``. This has lead to some unexpected behavior in the
|
||||
core of Doctrine when people have overwritten the find method in their
|
||||
repositories. That is why this behavior has been reversed in 2.3, and
|
||||
``EntityRepository#find()`` calls ``EntityManager#find()`` instead.
|
||||
|
||||
## EntityGenerator add*() method generation
|
||||
|
||||
When generating an add*() method for a collection the EntityGenerator will now not
|
||||
@@ -33,6 +85,12 @@ Also, related functions were affected:
|
||||
Internal changes were made to DQL and SQL generation. If you have implemented your own TreeWalker,
|
||||
you probably need to update it. The method walkJoinVariableDeclaration is now named walkJoin.
|
||||
|
||||
## New methods in TreeWalker interface *BC break*
|
||||
|
||||
Two methods getQueryComponents() and setQueryComponent() were added to the TreeWalker interface and all its implementations
|
||||
including TreeWalkerAdapter, TreeWalkerChain and SqlWalker. If you have your own implementation not inheriting from one of the
|
||||
above you must implement these new methods.
|
||||
|
||||
## Metadata Drivers
|
||||
|
||||
Metadata drivers have been rewritten to reuse code from Doctrine\Common. Anyone who is using the
|
||||
@@ -99,7 +157,7 @@ from 2.0 have to configure the annotation driver if they don't use `Configuratio
|
||||
$config->setMetadataDriverImpl($driver);
|
||||
|
||||
|
||||
## Scalar mappings can now be ommitted from DQL result
|
||||
## Scalar mappings can now be omitted from DQL result
|
||||
|
||||
You are now allowed to mark scalar SELECT expressions as HIDDEN an they are not hydrated anymore.
|
||||
Example:
|
||||
@@ -280,7 +338,7 @@ them for batch updates like SchemaTool and other commands. However the
|
||||
annotations driver being a default driver does not really help that much
|
||||
anyways.
|
||||
|
||||
Therefore we decided to break backwards compability in this issue and drop
|
||||
Therefore we decided to break backwards compatibility in this issue and drop
|
||||
the support for Annotations as Default Driver and require our users to
|
||||
specify the driver explicitly (which allows us to ask for the path to all
|
||||
entities).
|
||||
@@ -339,7 +397,7 @@ apologize for the inconvenience.
|
||||
## Default Property for Field Mappings
|
||||
|
||||
The "default" option for database column defaults has been removed. If desired, database column defaults can
|
||||
be implemented by using the columnDefinition attribute of the @Column annotation (or the approriate XML and YAML equivalents).
|
||||
be implemented by using the columnDefinition attribute of the @Column annotation (or the appropriate XML and YAML equivalents).
|
||||
Prefer PHP default values, if possible.
|
||||
|
||||
## Selecting Partial Objects
|
||||
@@ -424,7 +482,7 @@ With new required method AbstractTask::buildDocumentation, its implementation de
|
||||
|
||||
* "doctrine schema-tool --drop" now always drops the complete database instead of
|
||||
only those tables defined by the current database model. The previous method had
|
||||
problems when foreign keys of orphaned tables pointed to tables that were schedulded
|
||||
problems when foreign keys of orphaned tables pointed to tables that were scheduled
|
||||
for deletion.
|
||||
* Use "doctrine schema-tool --update" to get a save incremental update for your
|
||||
database schema without deleting any unused tables, sequences or foreign keys.
|
||||
|
||||
@@ -13,31 +13,47 @@
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the LGPL. For more information, see
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
use Symfony\Component\Console\Helper\HelperSet;
|
||||
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||
|
||||
(@include_once __DIR__ . '/../vendor/autoload.php') || @include_once __DIR__ . '/../../../autoload.php';
|
||||
$configFile = getcwd() . DIRECTORY_SEPARATOR . 'cli-config.php';
|
||||
|
||||
$helperSet = null;
|
||||
if (file_exists($configFile)) {
|
||||
if ( ! is_readable($configFile)) {
|
||||
trigger_error(
|
||||
'Configuration file [' . $configFile . '] does not have read permission.', E_ERROR
|
||||
);
|
||||
$directories = array(getcwd(), getcwd() . DIRECTORY_SEPARATOR . 'config');
|
||||
|
||||
$configFile = null;
|
||||
foreach ($directories as $directory) {
|
||||
$configFile = $directory . DIRECTORY_SEPARATOR . 'cli-config.php';
|
||||
|
||||
if (file_exists($configFile)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
require $configFile;
|
||||
if ( ! file_exists($configFile)) {
|
||||
ConsoleRunner::printCliConfigTemplate();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ( ! is_readable($configFile)) {
|
||||
echo 'Configuration file [' . $configFile . '] does not have read permission.' . "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$commands = array();
|
||||
|
||||
$helperSet = require $configFile;
|
||||
|
||||
if ( ! ($helperSet instanceof HelperSet)) {
|
||||
foreach ($GLOBALS as $helperSetCandidate) {
|
||||
if ($helperSetCandidate instanceof \Symfony\Component\Console\Helper\HelperSet) {
|
||||
if ($helperSetCandidate instanceof HelperSet) {
|
||||
$helperSet = $helperSetCandidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$helperSet = ($helperSet) ?: new \Symfony\Component\Console\Helper\HelperSet();
|
||||
|
||||
\Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet);
|
||||
\Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet, $commands);
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
# Project Name
|
||||
project.name=DoctrineORM
|
||||
|
||||
# Dependency minimum versions
|
||||
dependencies.common=2.2.0beta1
|
||||
dependencies.dbal=2.2.0beta1
|
||||
dependencies.sfconsole=2.0.0
|
||||
|
||||
# Version class and file
|
||||
project.version_class = Doctrine\ORM\Version
|
||||
project.version_class = Doctrine\\ORM\\Version
|
||||
project.version_file = lib/Doctrine/ORM/Version.php
|
||||
|
||||
199
build.xml
199
build.xml
@@ -1,114 +1,101 @@
|
||||
<?xml version="1.0"?>
|
||||
<project name="DoctrineORM" default="build" basedir=".">
|
||||
<taskdef classname="phing.tasks.ext.d51PearPkg2Task" name="d51pearpkg2" />
|
||||
<import file="${project.basedir}/lib/vendor/doctrine-build-common/packaging.xml" />
|
||||
|
||||
<property file="build.properties" />
|
||||
|
||||
<!--
|
||||
Fileset for artifacts shared across all distributed packages.
|
||||
-->
|
||||
<fileset id="shared-artifacts" dir=".">
|
||||
<include name="LICENSE"/>
|
||||
<include name="UPGRADE*" />
|
||||
<include name="doctrine-mapping.xsd" />
|
||||
</fileset>
|
||||
|
||||
<!--
|
||||
Fileset for command line scripts
|
||||
-->
|
||||
<fileset id="bin-scripts" dir="./bin">
|
||||
<include name="doctrine"/>
|
||||
<include name="doctrine-pear.php"/>
|
||||
<include name="doctrine.bat"/>
|
||||
</fileset>
|
||||
|
||||
<!--
|
||||
Fileset for the sources of the Doctrine Common dependency.
|
||||
-->
|
||||
<fileset id="common-sources" dir="./lib/vendor/doctrine-common/lib">
|
||||
<include name="Doctrine/Common/**"/>
|
||||
</fileset>
|
||||
|
||||
<!--
|
||||
Fileset for the sources of the Doctrine DBAL dependency.
|
||||
-->
|
||||
<fileset id="dbal-sources" dir="./lib/vendor/doctrine-dbal/lib">
|
||||
<include name="Doctrine/DBAL/**"/>
|
||||
</fileset>
|
||||
|
||||
<!--
|
||||
Fileset for the sources of the Doctrine ORM.
|
||||
-->
|
||||
<fileset id="orm-sources" dir="./lib">
|
||||
<include name="Doctrine/ORM/**"/>
|
||||
</fileset>
|
||||
|
||||
<!--
|
||||
Fileset for source of the Symfony YAML and Console components.
|
||||
-->
|
||||
<fileset id="symfony-sources" dir="./lib/vendor">
|
||||
<include name="Symfony/Component/**"/>
|
||||
<exclude name="**/.git/**" />
|
||||
</fileset>
|
||||
|
||||
<!--
|
||||
Builds ORM package, preparing it for distribution.
|
||||
-->
|
||||
<target name="copy-files" depends="prepare">
|
||||
<copy todir="${build.dir}/${project.name}-${version}">
|
||||
<fileset refid="shared-artifacts"/>
|
||||
</copy>
|
||||
<copy todir="${build.dir}/${project.name}-${version}">
|
||||
<fileset refid="common-sources"/>
|
||||
<fileset refid="dbal-sources"/>
|
||||
<fileset refid="orm-sources"/>
|
||||
</copy>
|
||||
<copy todir="${build.dir}/${project.name}-${version}/Doctrine">
|
||||
<fileset refid="symfony-sources"/>
|
||||
</copy>
|
||||
<copy todir="${build.dir}/${project.name}-${version}/bin">
|
||||
<fileset refid="bin-scripts"/>
|
||||
</copy>
|
||||
<target name="php">
|
||||
<exec executable="which" outputproperty="php_executable">
|
||||
<arg value="php" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<!--
|
||||
Builds distributable PEAR packages.
|
||||
-->
|
||||
<target name="define-pear-package" depends="copy-files">
|
||||
<d51pearpkg2 baseinstalldir="/" dir="${build.dir}/${project.name}-${version}">
|
||||
<name>DoctrineORM</name>
|
||||
<summary>Doctrine Object Relational Mapper</summary>
|
||||
<channel>pear.doctrine-project.org</channel>
|
||||
<description>The Doctrine ORM package is the primary package containing the object relational mapper.</description>
|
||||
<lead user="jwage" name="Jonathan H. Wage" email="jonwage@gmail.com" />
|
||||
<lead user="guilhermeblanco" name="Guilherme Blanco" email="guilhermeblanco@gmail.com" />
|
||||
<lead user="romanb" name="Roman Borschel" email="roman@code-factory.org" />
|
||||
<lead user="beberlei" name="Benjamin Eberlei" email="kontakt@beberlei.de" />
|
||||
<license>LGPL</license>
|
||||
<version release="${pear.version}" api="${pear.version}" />
|
||||
<stability release="${pear.stability}" api="${pear.stability}" />
|
||||
<notes>-</notes>
|
||||
<dependencies>
|
||||
<php minimum_version="5.3.0" />
|
||||
<pear minimum_version="1.6.0" recommended_version="1.6.1" />
|
||||
<package name="DoctrineCommon" channel="pear.doctrine-project.org" minimum_version="${dependencies.common}" />
|
||||
<package name="DoctrineDBAL" channel="pear.doctrine-project.org" minimum_version="${dependencies.dbal}" />
|
||||
<package name="Console" channel="pear.symfony.com" minimum_version="2.0.0" />
|
||||
<package name="Yaml" channel="pear.symfony.com" minimum_version="2.0.0" />
|
||||
</dependencies>
|
||||
<dirroles key="bin">script</dirroles>
|
||||
<ignore>Doctrine/Common/</ignore>
|
||||
<ignore>Doctrine/DBAL/</ignore>
|
||||
<ignore>Symfony/Component/Yaml/</ignore>
|
||||
<ignore>Symfony/Component/Console/</ignore>
|
||||
<release>
|
||||
<install as="doctrine" name="bin/doctrine" />
|
||||
<install as="doctrine.php" name="bin/doctrine-pear.php" />
|
||||
<install as="doctrine.bat" name="bin/doctrine.bat" />
|
||||
</release>
|
||||
<replacement path="bin/doctrine" type="pear-config" from="@php_bin@" to="php_bin" />
|
||||
<replacement path="bin/doctrine.bat" type="pear-config" from="@bin_dir@" to="bin_dir" />
|
||||
</d51pearpkg2>
|
||||
<target name="prepare">
|
||||
<mkdir dir="build" />
|
||||
</target>
|
||||
|
||||
<target name="build" depends="check-git-checkout-clean,prepare,php,composer">
|
||||
<exec executable="${php_executable}">
|
||||
<arg value="build/composer.phar" />
|
||||
<arg value="archive" />
|
||||
<arg value="--dir=build" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="composer" depends="php,composer-check,composer-download">
|
||||
<exec executable="${php_executable}">
|
||||
<arg value="build/composer.phar" />
|
||||
<arg value="install" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="composer-check" depends="prepare">
|
||||
<available file="build/composer.phar" property="composer.present"/>
|
||||
</target>
|
||||
|
||||
<target name="composer-download" unless="composer.present">
|
||||
<exec executable="wget">
|
||||
<arg value="-Obuild/composer.phar" />
|
||||
<arg value="http://getcomposer.org/composer.phar" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="make-release" depends="check-git-checkout-clean,prepare,php">
|
||||
<replace file="${project.version_file}" token="-DEV" value="" failOnNoReplacements="true" />
|
||||
<exec executable="${php_executable}" outputproperty="doctrine.current_version" failonerror="true">
|
||||
<arg value="-r" />
|
||||
<arg value="require_once '${project.version_file}';echo ${project.version_class}::VERSION;" />
|
||||
</exec>
|
||||
<exec executable="${php_executable}" outputproperty="doctrine.next_version" failonerror="true">
|
||||
<arg value="-r" />
|
||||
<arg value="$parts = explode('.', str_ireplace(array('-DEV', '-ALPHA', '-BETA'), '', '${doctrine.current_version}'));
|
||||
if (count($parts) != 3) {
|
||||
throw new \InvalidArgumentException('Version is assumed in format x.y.z, ${doctrine.current_version} given');
|
||||
}
|
||||
$parts[2]++;
|
||||
echo implode('.', $parts);
|
||||
" />
|
||||
</exec>
|
||||
|
||||
<git-commit file="${project.version_file}" message="Release ${doctrine.current_version}" />
|
||||
<git-tag version="${doctrine.current_version}" />
|
||||
<replace file="${project.version_file}" token="${doctrine.current_version}" value="${doctrine.next_version}-DEV" />
|
||||
<git-commit file="${project.version_file}" message="Bump version to ${doctrine.next_version}" />
|
||||
</target>
|
||||
|
||||
<target name="check-git-checkout-clean">
|
||||
<exec executable="git" failonerror="true">
|
||||
<arg value="diff-index" />
|
||||
<arg value="--quiet" />
|
||||
<arg value="HEAD" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<macrodef name="git-commit">
|
||||
<attribute name="file" default="NOT SET"/>
|
||||
<attribute name="message" default="NOT SET"/>
|
||||
|
||||
<sequential>
|
||||
<exec executable="git">
|
||||
<arg value="add" />
|
||||
<arg value="@{file}" />
|
||||
</exec>
|
||||
<exec executable="git">
|
||||
<arg value="commit" />
|
||||
<arg value="-m" />
|
||||
<arg value="@{message}" />
|
||||
</exec>
|
||||
</sequential>
|
||||
</macrodef>
|
||||
|
||||
<macrodef name="git-tag">
|
||||
<attribute name="version" default="NOT SET" />
|
||||
|
||||
<sequential>
|
||||
<exec executable="git">
|
||||
<arg value="tag" />
|
||||
<arg value="-m" />
|
||||
<arg value="v@{version}" />
|
||||
<arg value="v@{version}" />
|
||||
</exec>
|
||||
</sequential>
|
||||
</macrodef>
|
||||
</project>
|
||||
|
||||
@@ -14,19 +14,27 @@
|
||||
"require": {
|
||||
"php": ">=5.3.2",
|
||||
"ext-pdo": "*",
|
||||
"doctrine/dbal": ">=2.3-dev,<2.5-dev",
|
||||
"symfony/console": "2.*"
|
||||
"doctrine/collections": "~1.1",
|
||||
"doctrine/dbal": "~2.4",
|
||||
"symfony/console": "~2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/yaml": "~2.1",
|
||||
"satooshi/php-coveralls": "dev-master"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/yaml": "If you want to use YAML Metadata Mapping Driver"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": { "Doctrine\\ORM": "lib/" }
|
||||
"psr-0": { "Doctrine\\ORM\\": "lib/" }
|
||||
},
|
||||
"bin": ["bin/doctrine", "bin/doctrine.php"],
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.4.x-dev"
|
||||
}
|
||||
},
|
||||
"archive": {
|
||||
"exclude": ["!vendor", "tests", "*phpunit.xml", ".travis.yml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp", "*coveralls.yml"]
|
||||
}
|
||||
}
|
||||
|
||||
4
docs/.gitignore
vendored
Normal file
4
docs/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
en/_exts/configurationblock.pyc
|
||||
build
|
||||
en/_build
|
||||
.idea
|
||||
3
docs/.gitmodules
vendored
Normal file
3
docs/.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "en/_theme"]
|
||||
path = en/_theme
|
||||
url = https://github.com/doctrine/doctrine-sphinx-theme.git
|
||||
8
docs/README.md
Normal file
8
docs/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Doctrine ORM Documentation
|
||||
|
||||
## How to Generate
|
||||
|
||||
1. Run ./bin/install-dependencies.sh
|
||||
2. Run ./bin/generate-docs.sh
|
||||
|
||||
It will generate the documentation into the build directory of the checkout.
|
||||
10
docs/bin/generate-docs.sh
Executable file
10
docs/bin/generate-docs.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
EXECPATH=`dirname $0`
|
||||
cd $EXECPATH
|
||||
cd ..
|
||||
|
||||
rm build -Rf
|
||||
sphinx-build en build
|
||||
|
||||
sphinx-build -b latex en build/pdf
|
||||
rubber --into build/pdf --pdf build/pdf/Doctrine2ORM.tex
|
||||
4
docs/bin/install-dependencies.sh
Normal file
4
docs/bin/install-dependencies.sh
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
sudo apt-get install python25 python25-dev texlive-full rubber
|
||||
sudo easy_install pygments
|
||||
sudo easy_install sphinx
|
||||
89
docs/en/Makefile
Normal file
89
docs/en/Makefile
Normal file
@@ -0,0 +1,89 @@
|
||||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Doctrine2ORM.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Doctrine2ORM.qhc"
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
|
||||
"run these through (pdf)latex."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
93
docs/en/_exts/configurationblock.py
Normal file
93
docs/en/_exts/configurationblock.py
Normal file
@@ -0,0 +1,93 @@
|
||||
#Copyright (c) 2010 Fabien Potencier
|
||||
#
|
||||
#Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
#of this software and associated documentation files (the "Software"), to deal
|
||||
#in the Software without restriction, including without limitation the rights
|
||||
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
#copies of the Software, and to permit persons to whom the Software is furnished
|
||||
#to do so, subject to the following conditions:
|
||||
#
|
||||
#The above copyright notice and this permission notice shall be included in all
|
||||
#copies or substantial portions of the Software.
|
||||
#
|
||||
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
#THE SOFTWARE.
|
||||
|
||||
from docutils.parsers.rst import Directive, directives
|
||||
from docutils import nodes
|
||||
from string import upper
|
||||
|
||||
class configurationblock(nodes.General, nodes.Element):
|
||||
pass
|
||||
|
||||
class ConfigurationBlock(Directive):
|
||||
has_content = True
|
||||
required_arguments = 0
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = True
|
||||
option_spec = {}
|
||||
formats = {
|
||||
'html': 'HTML',
|
||||
'xml': 'XML',
|
||||
'php': 'PHP',
|
||||
'yaml': 'YAML',
|
||||
'jinja': 'Twig',
|
||||
'html+jinja': 'Twig',
|
||||
'jinja+html': 'Twig',
|
||||
'php+html': 'PHP',
|
||||
'html+php': 'PHP',
|
||||
'ini': 'INI',
|
||||
'php-annotations': 'Annotations',
|
||||
}
|
||||
|
||||
def run(self):
|
||||
env = self.state.document.settings.env
|
||||
|
||||
node = nodes.Element()
|
||||
node.document = self.state.document
|
||||
self.state.nested_parse(self.content, self.content_offset, node)
|
||||
|
||||
entries = []
|
||||
for i, child in enumerate(node):
|
||||
if isinstance(child, nodes.literal_block):
|
||||
# add a title (the language name) before each block
|
||||
#targetid = "configuration-block-%d" % env.new_serialno('configuration-block')
|
||||
#targetnode = nodes.target('', '', ids=[targetid])
|
||||
#targetnode.append(child)
|
||||
|
||||
innernode = nodes.emphasis(self.formats[child['language']], self.formats[child['language']])
|
||||
|
||||
para = nodes.paragraph()
|
||||
para += [innernode, child]
|
||||
|
||||
entry = nodes.list_item('')
|
||||
entry.append(para)
|
||||
entries.append(entry)
|
||||
|
||||
resultnode = configurationblock()
|
||||
resultnode.append(nodes.bullet_list('', *entries))
|
||||
|
||||
return [resultnode]
|
||||
|
||||
def visit_configurationblock_html(self, node):
|
||||
self.body.append(self.starttag(node, 'div', CLASS='configuration-block'))
|
||||
|
||||
def depart_configurationblock_html(self, node):
|
||||
self.body.append('</div>\n')
|
||||
|
||||
def visit_configurationblock_latex(self, node):
|
||||
pass
|
||||
|
||||
def depart_configurationblock_latex(self, node):
|
||||
pass
|
||||
|
||||
def setup(app):
|
||||
app.add_node(configurationblock,
|
||||
html=(visit_configurationblock_html, depart_configurationblock_html),
|
||||
latex=(visit_configurationblock_latex, depart_configurationblock_latex))
|
||||
app.add_directive('configuration-block', ConfigurationBlock)
|
||||
1
docs/en/_theme
Submodule
1
docs/en/_theme
Submodule
Submodule docs/en/_theme added at 68795c5888
201
docs/en/conf.py
Normal file
201
docs/en/conf.py
Normal file
@@ -0,0 +1,201 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Doctrine 2 ORM documentation build configuration file, created by
|
||||
# sphinx-quickstart on Fri Dec 3 18:10:24 2010.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.append(os.path.abspath('_exts'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['configurationblock']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Doctrine 2 ORM'
|
||||
copyright = u'2010-12, Doctrine Project Team'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '2'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '2'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
language = 'en'
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of documents that shouldn't be included in the build.
|
||||
#unused_docs = []
|
||||
|
||||
# List of directories, relative to source directory, that shouldn't be searched
|
||||
# for source files.
|
||||
exclude_trees = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
show_authors = True
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||
html_theme = 'doctrine'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
html_theme_path = ['_theme']
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_use_modindex = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = ''
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Doctrine2ORMdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
# The paper size ('letter' or 'a4').
|
||||
#latex_paper_size = 'letter'
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#latex_font_size = '10pt'
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'Doctrine2ORM.tex', u'Doctrine 2 ORM Documentation',
|
||||
u'Doctrine Project Team', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#latex_preamble = ''
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_use_modindex = True
|
||||
|
||||
primary_domain = "dcorm"
|
||||
|
||||
def linkcode_resolve(domain, info):
|
||||
if domain == 'dcorm':
|
||||
return 'http://'
|
||||
return None
|
||||
@@ -0,0 +1,256 @@
|
||||
Advanced field value conversion using custom mapping types
|
||||
==========================================================
|
||||
|
||||
.. sectionauthor:: Jan Sorgalla <jsorgalla@googlemail.com>
|
||||
|
||||
When creating entities, you sometimes have the need to transform field values
|
||||
before they are saved to the database. In Doctrine you can use Custom Mapping
|
||||
Types to solve this (see: :ref:`reference-basic-mapping-custom-mapping-types`).
|
||||
|
||||
There are several ways to achieve this: converting the value inside the Type
|
||||
class, converting the value on the database-level or a combination of both.
|
||||
|
||||
This article describes the third way by implementing the MySQL specific column
|
||||
type `Point <http://dev.mysql.com/doc/refman/5.5/en/gis-class-point.html>`_.
|
||||
|
||||
The ``Point`` type is part of the `Spatial extension <http://dev.mysql.com/doc/refman/5.5/en/spatial-extensions.html>`_
|
||||
of MySQL and enables you to store a single location in a coordinate space by
|
||||
using x and y coordinates. You can use the Point type to store a
|
||||
longitude/latitude pair to represent a geographic location.
|
||||
|
||||
The entity
|
||||
----------
|
||||
|
||||
We create a simple entity with a field ``$point`` which holds a value object
|
||||
``Point`` representing the latitude and longitude of the position.
|
||||
|
||||
The entity class:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
namespace Geo\Entity;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class Location
|
||||
{
|
||||
/**
|
||||
* @Column(type="point")
|
||||
*
|
||||
* @var \Geo\ValueObject\Point
|
||||
*/
|
||||
private $point;
|
||||
|
||||
/**
|
||||
* @Column(type="string")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $address;
|
||||
|
||||
/**
|
||||
* @param \Geo\ValueObject\Point $point
|
||||
*/
|
||||
public function setPoint(\Geo\ValueObject\Point $point)
|
||||
{
|
||||
$this->point = $point;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Geo\ValueObject\Point
|
||||
*/
|
||||
public function getPoint()
|
||||
{
|
||||
return $this->point;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $address
|
||||
*/
|
||||
public function setAddress($address)
|
||||
{
|
||||
$this->address = $address;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAddress()
|
||||
{
|
||||
return $this->address;
|
||||
}
|
||||
}
|
||||
|
||||
We use the custom type ``point`` in the ``@Column`` docblock annotation of the
|
||||
``$point`` field. We will create this custom mapping type in the next chapter.
|
||||
|
||||
The point class:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
namespace Geo\ValueObject;
|
||||
|
||||
class Point
|
||||
{
|
||||
|
||||
/**
|
||||
* @param float $latitude
|
||||
* @param float $longitude
|
||||
*/
|
||||
public function __construct($latitude, $longitude)
|
||||
{
|
||||
$this->latitude = $latitude;
|
||||
$this->longitude = $longitude;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getLatitude()
|
||||
{
|
||||
return $this->latitude;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getLongitude()
|
||||
{
|
||||
return $this->longitude;
|
||||
}
|
||||
}
|
||||
|
||||
The mapping type
|
||||
----------------
|
||||
|
||||
Now we're going to create the ``point`` type and implement all required methods.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
namespace Geo\Types;
|
||||
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
|
||||
use Geo\ValueObject\Point;
|
||||
|
||||
class PointType extends Type
|
||||
{
|
||||
const POINT = 'point';
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return self::POINT;
|
||||
}
|
||||
|
||||
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||
{
|
||||
return 'POINT';
|
||||
}
|
||||
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
list($longitude, $latitude) = sscanf($value, 'POINT(%f %f)');
|
||||
|
||||
return new Point($latitude, $longitude);
|
||||
}
|
||||
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
if ($value instanceof Point) {
|
||||
$value = sprintf('POINT(%F %F)', $value->getLongitude(), $value->getLatitude());
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function canRequireSQLConversion()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function convertToPHPValueSQL($sqlExpr, AbstractPlatform $platform)
|
||||
{
|
||||
return sprintf('AsText(%s)', $sqlExpr);
|
||||
}
|
||||
|
||||
public function convertToDatabaseValueSQL($sqlExpr, AbstractPlatform $platform)
|
||||
{
|
||||
return sprintf('PointFromText(%s)', $sqlExpr);
|
||||
}
|
||||
}
|
||||
|
||||
We do a 2-step conversion here. In the first step, we convert the ``Point``
|
||||
object into a string representation before saving to the database (in the
|
||||
``convertToDatabaseValue`` method) and back into an object after fetching the
|
||||
value from the database (in the ``convertToPHPValue`` method).
|
||||
|
||||
The format of the string representation format is called `Well-known text (WKT)
|
||||
<http://en.wikipedia.org/wiki/Well-known_text>`_. The advantage of this format
|
||||
is, that it is both human readable and parsable by MySQL.
|
||||
|
||||
Internally, MySQL stores geometry values in a binary format that is not
|
||||
identical to the WKT format. So, we need to let MySQL transform the WKT
|
||||
representation into its internal format.
|
||||
|
||||
This is where the ``convertToPHPValueSQL`` and ``convertToDatabaseValueSQL``
|
||||
methods come into play.
|
||||
|
||||
This methods wrap a sql expression (the WKT representation of the Point) into
|
||||
MySQL functions `PointFromText <http://dev.mysql.com/doc/refman/5.5/en/creating-spatial-values.html#function_pointfromtext>`_
|
||||
and `AsText <http://dev.mysql.com/doc/refman/5.5/en/functions-to-convert-geometries-between-formats.html#function_astext>`_
|
||||
which convert WKT strings to and from the internal format of MySQL.
|
||||
|
||||
.. note::
|
||||
|
||||
When using DQL queries, the ``convertToPHPValueSQL`` and
|
||||
``convertToDatabaseValueSQL`` methods only apply to identification variables
|
||||
and path expressions in SELECT clauses. Expressions in WHERE clauses are
|
||||
**not** wrapped!
|
||||
|
||||
If you want to use Point values in WHERE clauses, you have to implement a
|
||||
:doc:`user defined function <dql-user-defined-functions>` for
|
||||
``PointFromText``.
|
||||
|
||||
Example usage
|
||||
-------------
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
// Bootstrapping stuff...
|
||||
// $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config);
|
||||
|
||||
// Setup custom mapping type
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
|
||||
Type::addType('point', 'Geo\Types\Point');
|
||||
$em->getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping('point', 'point');
|
||||
|
||||
// Store a Location object
|
||||
use Geo\Entity\Location;
|
||||
use Geo\ValueObject\Point;
|
||||
|
||||
$location = new Location();
|
||||
|
||||
$location->setAddress('1600 Amphitheatre Parkway, Mountain View, CA');
|
||||
$location->setPoint(new Point(37.4220761, -122.0845187));
|
||||
|
||||
$em->persist($location);
|
||||
$em->flush();
|
||||
$em->clear();
|
||||
|
||||
// Fetch the Location object
|
||||
$query = $em->createQuery("SELECT l FROM Geo\Entity\Location WHERE l.address = '1600 Amphitheatre Parkway, Mountain View, CA'");
|
||||
$location = $query->getSingleResult();
|
||||
|
||||
/* @var Geo\ValueObject\Point */
|
||||
$point = $location->getPoint();
|
||||
376
docs/en/cookbook/aggregate-fields.rst
Normal file
376
docs/en/cookbook/aggregate-fields.rst
Normal file
@@ -0,0 +1,376 @@
|
||||
Aggregate Fields
|
||||
================
|
||||
|
||||
.. sectionauthor:: Benjamin Eberlei <kontakt@beberlei.de>
|
||||
|
||||
You will often come across the requirement to display aggregate
|
||||
values of data that can be computed by using the MIN, MAX, COUNT or
|
||||
SUM SQL functions. For any ORM this is a tricky issue
|
||||
traditionally. Doctrine 2 offers several ways to get access to
|
||||
these values and this article will describe all of them from
|
||||
different perspectives.
|
||||
|
||||
You will see that aggregate fields can become very explicit
|
||||
features in your domain model and how this potentially complex
|
||||
business rules can be easily tested.
|
||||
|
||||
An example model
|
||||
----------------
|
||||
|
||||
Say you want to model a bank account and all their entries. Entries
|
||||
into the account can either be of positive or negative money
|
||||
values. Each account has a credit limit and the account is never
|
||||
allowed to have a balance below that value.
|
||||
|
||||
For simplicity we live in a world were money is composed of
|
||||
integers only. Also we omit the receiver/sender name, stated reason
|
||||
for transfer and the execution date. These all would have to be
|
||||
added on the ``Entry`` object.
|
||||
|
||||
Our entities look like:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace Bank\Entities;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class Account
|
||||
{
|
||||
/** @Id @GeneratedValue @Column(type="integer") */
|
||||
private $id;
|
||||
|
||||
/** @Column(type="string", unique=true) */
|
||||
private $no;
|
||||
|
||||
/**
|
||||
* @OneToMany(targetEntity="Entry", mappedBy="account", cascade={"persist"})
|
||||
*/
|
||||
private $entries;
|
||||
|
||||
/**
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
private $maxCredit = 0;
|
||||
|
||||
public function __construct($no, $maxCredit = 0)
|
||||
{
|
||||
$this->no = $no;
|
||||
$this->maxCredit = $maxCredit;
|
||||
$this->entries = new \Doctrine\Common\Collections\ArrayCollection();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class Entry
|
||||
{
|
||||
/** @Id @GeneratedValue @Column(type="integer") */
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @ManyToOne(targetEntity="Account", inversedBy="entries")
|
||||
*/
|
||||
private $account;
|
||||
|
||||
/**
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
private $amount;
|
||||
|
||||
public function __construct($account, $amount)
|
||||
{
|
||||
$this->account = $account;
|
||||
$this->amount = $amount;
|
||||
// more stuff here, from/to whom, stated reason, execution date and such
|
||||
}
|
||||
|
||||
public function getAmount()
|
||||
{
|
||||
return $this->amount;
|
||||
}
|
||||
}
|
||||
|
||||
Using DQL
|
||||
---------
|
||||
|
||||
The Doctrine Query Language allows you to select for aggregate
|
||||
values computed from fields of your Domain Model. You can select
|
||||
the current balance of your account by calling:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$dql = "SELECT SUM(e.amount) AS balance FROM Bank\Entities\Entry e " .
|
||||
"WHERE e.account = ?1";
|
||||
$balance = $em->createQuery($dql)
|
||||
->setParameter(1, $myAccountId)
|
||||
->getSingleScalarResult();
|
||||
|
||||
The ``$em`` variable in this (and forthcoming) example holds the
|
||||
Doctrine ``EntityManager``. We create a query for the SUM of all
|
||||
amounts (negative amounts are withdraws) and retrieve them as a
|
||||
single scalar result, essentially return only the first column of
|
||||
the first row.
|
||||
|
||||
This approach is simple and powerful, however it has a serious
|
||||
drawback. We have to execute a specific query for the balance
|
||||
whenever we need it.
|
||||
|
||||
To implement a powerful domain model we would rather have access to
|
||||
the balance from our ``Account`` entity during all times (even if
|
||||
the Account was not persisted in the database before!).
|
||||
|
||||
Also an additional requirement is the max credit per ``Account``
|
||||
rule.
|
||||
|
||||
We cannot reliably enforce this rule in our ``Account`` entity with
|
||||
the DQL retrieval of the balance. There are many different ways to
|
||||
retrieve accounts. We cannot guarantee that we can execute the
|
||||
aggregation query for all these use-cases, let alone that a
|
||||
userland programmer checks this balance against newly added
|
||||
entries.
|
||||
|
||||
Using your Domain Model
|
||||
-----------------------
|
||||
|
||||
``Account`` and all the ``Entry`` instances are connected through a
|
||||
collection, which means we can compute this value at runtime:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class Account
|
||||
{
|
||||
// .. previous code
|
||||
public function getBalance()
|
||||
{
|
||||
$balance = 0;
|
||||
foreach ($this->entries AS $entry) {
|
||||
$balance += $entry->getAmount();
|
||||
}
|
||||
return $balance;
|
||||
}
|
||||
}
|
||||
|
||||
Now we can always call ``Account::getBalance()`` to access the
|
||||
current account balance.
|
||||
|
||||
To enforce the max credit rule we have to implement the "Aggregate
|
||||
Root" pattern as described in Eric Evans book on Domain Driven
|
||||
Design. Described with one sentence, an aggregate root controls the
|
||||
instance creation, access and manipulation of its children.
|
||||
|
||||
In our case we want to enforce that new entries can only added to
|
||||
the ``Account`` by using a designated method. The ``Account`` is
|
||||
the aggregate root of this relation. We can also enforce the
|
||||
correctness of the bi-directional ``Account`` <-> ``Entry``
|
||||
relation with this method:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class Account
|
||||
{
|
||||
public function addEntry($amount)
|
||||
{
|
||||
$this->assertAcceptEntryAllowed($amount);
|
||||
|
||||
$e = new Entry($this, $amount);
|
||||
$this->entries[] = $e;
|
||||
return $e;
|
||||
}
|
||||
}
|
||||
|
||||
Now look at the following test-code for our entities:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class AccountTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testAddEntry()
|
||||
{
|
||||
$account = new Account("123456", $maxCredit = 200);
|
||||
$this->assertEquals(0, $account->getBalance());
|
||||
|
||||
$account->addEntry(500);
|
||||
$this->assertEquals(500, $account->getBalance());
|
||||
|
||||
$account->addEntry(-700);
|
||||
$this->assertEquals(-200, $account->getBalance());
|
||||
}
|
||||
|
||||
public function testExceedMaxLimit()
|
||||
{
|
||||
$account = new Account("123456", $maxCredit = 200);
|
||||
|
||||
$this->setExpectedException("Exception");
|
||||
$account->addEntry(-1000);
|
||||
}
|
||||
}
|
||||
|
||||
To enforce our rule we can now implement the assertion in
|
||||
``Account::addEntry``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class Account
|
||||
{
|
||||
private function assertAcceptEntryAllowed($amount)
|
||||
{
|
||||
$futureBalance = $this->getBalance() + $amount;
|
||||
$allowedMinimalBalance = ($this->maxCredit * -1);
|
||||
if ($futureBalance < $allowedMinimalBalance) {
|
||||
throw new Exception("Credit Limit exceeded, entry is not allowed!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
We haven't talked to the entity manager for persistence of our
|
||||
account example before. You can call
|
||||
``EntityManager::persist($account)`` and then
|
||||
``EntityManager::flush()`` at any point to save the account to the
|
||||
database. All the nested ``Entry`` objects are automatically
|
||||
flushed to the database also.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$account = new Account("123456", 200);
|
||||
$account->addEntry(500);
|
||||
$account->addEntry(-200);
|
||||
$em->persist($account);
|
||||
$em->flush();
|
||||
|
||||
The current implementation has a considerable drawback. To get the
|
||||
balance, we have to initialize the complete ``Account::$entries``
|
||||
collection, possibly a very large one. This can considerably hurt
|
||||
the performance of your application.
|
||||
|
||||
Using an Aggregate Field
|
||||
------------------------
|
||||
|
||||
To overcome the previously mentioned issue (initializing the whole
|
||||
entries collection) we want to add an aggregate field called
|
||||
"balance" on the Account and adjust the code in
|
||||
``Account::getBalance()`` and ``Account:addEntry()``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class Account
|
||||
{
|
||||
/**
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
private $balance = 0;
|
||||
|
||||
public function getBalance()
|
||||
{
|
||||
return $this->balance;
|
||||
}
|
||||
|
||||
public function addEntry($amount)
|
||||
{
|
||||
$this->assertAcceptEntryAllowed($amount);
|
||||
|
||||
$e = new Entry($this, $amount);
|
||||
$this->entries[] = $e;
|
||||
$this->balance += $amount;
|
||||
return $e;
|
||||
}
|
||||
}
|
||||
|
||||
This is a very simple change, but all the tests still pass. Our
|
||||
account entities return the correct balance. Now calling the
|
||||
``Account::getBalance()`` method will not occur the overhead of
|
||||
loading all entries anymore. Adding a new Entry to the
|
||||
``Account::$entities`` will also not initialize the collection
|
||||
internally.
|
||||
|
||||
Adding a new entry is therefore very performant and explicitly
|
||||
hooked into the domain model. It will only update the account with
|
||||
the current balance and insert the new entry into the database.
|
||||
|
||||
Tackling Race Conditions with Aggregate Fields
|
||||
----------------------------------------------
|
||||
|
||||
Whenever you denormalize your database schema race-conditions can
|
||||
potentially lead to inconsistent state. See this example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// The Account $accId has a balance of 0 and a max credit limit of 200:
|
||||
// request 1 account
|
||||
$account1 = $em->find('Bank\Entities\Account', $accId);
|
||||
|
||||
// request 2 account
|
||||
$account2 = $em->find('Bank\Entities\Account', $accId);
|
||||
|
||||
$account1->addEntry(-200);
|
||||
$account2->addEntry(-200);
|
||||
|
||||
// now request 1 and 2 both flush the changes.
|
||||
|
||||
The aggregate field ``Account::$balance`` is now -200, however the
|
||||
SUM over all entries amounts yields -400. A violation of our max
|
||||
credit rule.
|
||||
|
||||
You can use both optimistic or pessimistic locking to save-guard
|
||||
your aggregate fields against this kind of race-conditions. Reading
|
||||
Eric Evans DDD carefully he mentions that the "Aggregate Root"
|
||||
(Account in our example) needs a locking mechanism.
|
||||
|
||||
Optimistic locking is as easy as adding a version column:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class Amount
|
||||
{
|
||||
/** @Column(type="integer") @Version */
|
||||
private $version;
|
||||
}
|
||||
|
||||
The previous example would then throw an exception in the face of
|
||||
whatever request saves the entity last (and would create the
|
||||
inconsistent state).
|
||||
|
||||
Pessimistic locking requires an additional flag set on the
|
||||
``EntityManager::find()`` call, enabling write locking directly in
|
||||
the database using a FOR UPDATE.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\DBAL\LockMode;
|
||||
|
||||
$account = $em->find('Bank\Entities\Account', $accId, LockMode::PESSIMISTIC_READ);
|
||||
|
||||
Keeping Updates and Deletes in Sync
|
||||
-----------------------------------
|
||||
|
||||
The example shown in this article does not allow changes to the
|
||||
value in ``Entry``, which considerably simplifies the effort to
|
||||
keep ``Account::$balance`` in sync. If your use-case allows fields
|
||||
to be updated or related entities to be removed you have to
|
||||
encapsulate this logic in your "Aggregate Root" entity and adjust
|
||||
the aggregate field accordingly.
|
||||
|
||||
Conclusion
|
||||
----------
|
||||
|
||||
This article described how to obtain aggregate values using DQL or
|
||||
your domain model. It showed how you can easily add an aggregate
|
||||
field that offers serious performance benefits over iterating all
|
||||
the related objects that make up an aggregate value. Finally I
|
||||
showed how you can ensure that your aggregate fields do not get out
|
||||
of sync due to race-conditions and concurrent access.
|
||||
|
||||
|
||||
273
docs/en/cookbook/decorator-pattern.rst
Normal file
273
docs/en/cookbook/decorator-pattern.rst
Normal file
@@ -0,0 +1,273 @@
|
||||
Persisting the Decorator Pattern
|
||||
================================
|
||||
|
||||
.. sectionauthor:: Chris Woodford <chris.woodford@gmail.com>
|
||||
|
||||
This recipe will show you a simple example of how you can use
|
||||
Doctrine 2 to persist an implementation of the
|
||||
`Decorator Pattern <http://en.wikipedia.org/wiki/Decorator_pattern>`_
|
||||
|
||||
Component
|
||||
---------
|
||||
|
||||
The ``Component`` class needs to be persisted, so it's going to
|
||||
be an ``Entity``. As the top of the inheritance hierarchy, it's going
|
||||
to have to define the persistent inheritance. For this example, we
|
||||
will use Single Table Inheritance, but Class Table Inheritance
|
||||
would work as well. In the discriminator map, we will define two
|
||||
concrete subclasses, ``ConcreteComponent`` and ``ConcreteDecorator``.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
namespace Test;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @InheritanceType("SINGLE_TABLE")
|
||||
* @DiscriminatorColumn(name="discr", type="string")
|
||||
* @DiscriminatorMap({"cc" = "Test\Component\ConcreteComponent",
|
||||
"cd" = "Test\Decorator\ConcreteDecorator"})
|
||||
*/
|
||||
abstract class Component
|
||||
{
|
||||
|
||||
/**
|
||||
* @Id @Column(type="integer")
|
||||
* @GeneratedValue(strategy="AUTO")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/** @Column(type="string", nullable=true) */
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* Get id
|
||||
* @return integer $id
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set name
|
||||
* @param string $name
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name
|
||||
* @return string $name
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ConcreteComponent
|
||||
-----------------
|
||||
|
||||
The ``ConcreteComponent`` class is pretty simple and doesn't do much
|
||||
more than extend the abstract ``Component`` class (only for the
|
||||
purpose of keeping this example simple).
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
namespace Test\Component;
|
||||
|
||||
use Test\Component;
|
||||
|
||||
/** @Entity */
|
||||
class ConcreteComponent extends Component
|
||||
{}
|
||||
|
||||
Decorator
|
||||
---------
|
||||
|
||||
The ``Decorator`` class doesn't need to be persisted, but it does
|
||||
need to define an association with a persisted ``Entity``. We can
|
||||
use a ``MappedSuperclass`` for this.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
namespace Test;
|
||||
|
||||
/** @MappedSuperclass */
|
||||
abstract class Decorator extends Component
|
||||
{
|
||||
|
||||
/**
|
||||
* @OneToOne(targetEntity="Test\Component", cascade={"all"})
|
||||
* @JoinColumn(name="decorates", referencedColumnName="id")
|
||||
*/
|
||||
protected $decorates;
|
||||
|
||||
/**
|
||||
* initialize the decorator
|
||||
* @param Component $c
|
||||
*/
|
||||
public function __construct(Component $c)
|
||||
{
|
||||
$this->setDecorates($c);
|
||||
}
|
||||
|
||||
/**
|
||||
* (non-PHPdoc)
|
||||
* @see Test.Component::getName()
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'Decorated ' . $this->getDecorates()->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* the component being decorated
|
||||
* @return Component
|
||||
*/
|
||||
protected function getDecorates()
|
||||
{
|
||||
return $this->decorates;
|
||||
}
|
||||
|
||||
/**
|
||||
* sets the component being decorated
|
||||
* @param Component $c
|
||||
*/
|
||||
protected function setDecorates(Component $c)
|
||||
{
|
||||
$this->decorates = $c;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
All operations on the ``Decorator`` (i.e. persist, remove, etc) will
|
||||
cascade from the ``Decorator`` to the ``Component``. This means that
|
||||
when we persist a ``Decorator``, Doctrine will take care of
|
||||
persisting the chain of decorated objects for us. A ``Decorator`` can
|
||||
be treated exactly as a ``Component`` when it comes time to
|
||||
persisting it.
|
||||
|
||||
The ``Decorator's`` constructor accepts an instance of a
|
||||
``Component``, as defined by the ``Decorator`` pattern. The
|
||||
setDecorates/getDecorates methods have been defined as protected to
|
||||
hide the fact that a ``Decorator`` is decorating a ``Component`` and
|
||||
keeps the ``Component`` interface and the ``Decorator`` interface
|
||||
identical.
|
||||
|
||||
To illustrate the intended result of the ``Decorator`` pattern, the
|
||||
getName() method has been overridden to append a string to the
|
||||
``Component's`` getName() method.
|
||||
|
||||
ConcreteDecorator
|
||||
-----------------
|
||||
|
||||
The final class required to complete a simple implementation of the
|
||||
Decorator pattern is the ``ConcreteDecorator``. In order to further
|
||||
illustrate how the ``Decorator`` can alter data as it moves through
|
||||
the chain of decoration, a new field, "special", has been added to
|
||||
this class. The getName() has been overridden and appends the value
|
||||
of the getSpecial() method to its return value.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
namespace Test\Decorator;
|
||||
|
||||
use Test\Decorator;
|
||||
|
||||
/** @Entity */
|
||||
class ConcreteDecorator extends Decorator
|
||||
{
|
||||
|
||||
/** @Column(type="string", nullable=true) */
|
||||
protected $special;
|
||||
|
||||
/**
|
||||
* Set special
|
||||
* @param string $special
|
||||
*/
|
||||
public function setSpecial($special)
|
||||
{
|
||||
$this->special = $special;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get special
|
||||
* @return string $special
|
||||
*/
|
||||
public function getSpecial()
|
||||
{
|
||||
return $this->special;
|
||||
}
|
||||
|
||||
/**
|
||||
* (non-PHPdoc)
|
||||
* @see Test.Component::getName()
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return '[' . $this->getSpecial()
|
||||
. '] ' . parent::getName();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Here is an example of how to persist and retrieve your decorated
|
||||
objects
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use Test\Component\ConcreteComponent,
|
||||
Test\Decorator\ConcreteDecorator;
|
||||
|
||||
// assumes Doctrine 2 is configured and an instance of
|
||||
// an EntityManager is available as $em
|
||||
|
||||
// create a new concrete component
|
||||
$c = new ConcreteComponent();
|
||||
$c->setName('Test Component 1');
|
||||
$em->persist($c); // assigned unique ID = 1
|
||||
|
||||
// create a new concrete decorator
|
||||
$c = new ConcreteComponent();
|
||||
$c->setName('Test Component 2');
|
||||
|
||||
$d = new ConcreteDecorator($c);
|
||||
$d->setSpecial('Really');
|
||||
$em->persist($d);
|
||||
// assigns c as unique ID = 2, and d as unique ID = 3
|
||||
|
||||
$em->flush();
|
||||
|
||||
$c = $em->find('Test\Component', 1);
|
||||
$d = $em->find('Test\Component', 3);
|
||||
|
||||
echo get_class($c);
|
||||
// prints: Test\Component\ConcreteComponent
|
||||
|
||||
echo $c->getName();
|
||||
// prints: Test Component 1
|
||||
|
||||
echo get_class($d)
|
||||
// prints: Test\Component\ConcreteDecorator
|
||||
|
||||
echo $d->getName();
|
||||
// prints: [Really] Decorated Test Component 2
|
||||
|
||||
217
docs/en/cookbook/dql-custom-walkers.rst
Normal file
217
docs/en/cookbook/dql-custom-walkers.rst
Normal file
@@ -0,0 +1,217 @@
|
||||
Extending DQL in Doctrine 2: Custom AST Walkers
|
||||
===============================================
|
||||
|
||||
.. sectionauthor:: Benjamin Eberlei <kontakt@beberlei.de>
|
||||
|
||||
The Doctrine Query Language (DQL) is a proprietary sql-dialect that
|
||||
substitutes tables and columns for Entity names and their fields.
|
||||
Using DQL you write a query against the database using your
|
||||
entities. With the help of the metadata you can write very concise,
|
||||
compact and powerful queries that are then translated into SQL by
|
||||
the Doctrine ORM.
|
||||
|
||||
In Doctrine 1 the DQL language was not implemented using a real
|
||||
parser. This made modifications of the DQL by the user impossible.
|
||||
Doctrine 2 in contrast has a real parser for the DQL language,
|
||||
which transforms the DQL statement into an
|
||||
`Abstract Syntax Tree <http://en.wikipedia.org/wiki/Abstract_syntax_tree>`_
|
||||
and generates the appropriate SQL statement for it. Since this
|
||||
process is deterministic Doctrine heavily caches the SQL that is
|
||||
generated from any given DQL query, which reduces the performance
|
||||
overhead of the parsing process to zero.
|
||||
|
||||
You can modify the Abstract syntax tree by hooking into DQL parsing
|
||||
process by adding a Custom Tree Walker. A walker is an interface
|
||||
that walks each node of the Abstract syntax tree, thereby
|
||||
generating the SQL statement.
|
||||
|
||||
There are two types of custom tree walkers that you can hook into
|
||||
the DQL parser:
|
||||
|
||||
|
||||
- An output walker. This one actually generates the SQL, and there
|
||||
is only ever one of them. We implemented the default SqlWalker
|
||||
implementation for it.
|
||||
- A tree walker. There can be many tree walkers, they cannot
|
||||
generate the sql, however they can modify the AST before its
|
||||
rendered to sql.
|
||||
|
||||
Now this is all awfully technical, so let me come to some use-cases
|
||||
fast to keep you motivated. Using walker implementation you can for
|
||||
example:
|
||||
|
||||
|
||||
- Modify the AST to generate a Count Query to be used with a
|
||||
paginator for any given DQL query.
|
||||
- Modify the Output Walker to generate vendor-specific SQL
|
||||
(instead of ANSI).
|
||||
- Modify the AST to add additional where clauses for specific
|
||||
entities (example ACL, country-specific content...)
|
||||
- Modify the Output walker to pretty print the SQL for debugging
|
||||
purposes.
|
||||
|
||||
In this cookbook-entry I will show examples on the first two
|
||||
points. There are probably much more use-cases.
|
||||
|
||||
Generic count query for pagination
|
||||
----------------------------------
|
||||
|
||||
Say you have a blog and posts all with one category and one author.
|
||||
A query for the front-page or any archive page might look something
|
||||
like:
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
SELECT p, c, a FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ...
|
||||
|
||||
Now in this query the blog post is the root entity, meaning its the
|
||||
one that is hydrated directly from the query and returned as an
|
||||
array of blog posts. In contrast the comment and author are loaded
|
||||
for deeper use in the object tree.
|
||||
|
||||
A pagination for this query would want to approximate the number of
|
||||
posts that match the WHERE clause of this query to be able to
|
||||
predict the number of pages to show to the user. A draft of the DQL
|
||||
query for pagination would look like:
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
SELECT count(DISTINCT p.id) FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ...
|
||||
|
||||
Now you could go and write each of these queries by hand, or you
|
||||
can use a tree walker to modify the AST for you. Lets see how the
|
||||
API would look for this use-case:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$pageNum = 1;
|
||||
$query = $em->createQuery($dql);
|
||||
$query->setFirstResult( ($pageNum-1) * 20)->setMaxResults(20);
|
||||
|
||||
$totalResults = Paginate::count($query);
|
||||
$results = $query->getResult();
|
||||
|
||||
The ``Paginate::count(Query $query)`` looks like:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class Paginate
|
||||
{
|
||||
static public function count(Query $query)
|
||||
{
|
||||
/* @var $countQuery Query */
|
||||
$countQuery = clone $query;
|
||||
|
||||
$countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('DoctrineExtensions\Paginate\CountSqlWalker'));
|
||||
$countQuery->setFirstResult(null)->setMaxResults(null);
|
||||
|
||||
return $countQuery->getSingleScalarResult();
|
||||
}
|
||||
}
|
||||
|
||||
It clones the query, resets the limit clause first and max results
|
||||
and registers the ``CountSqlWalker`` customer tree walker which
|
||||
will modify the AST to execute a count query. The walkers
|
||||
implementation is:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class CountSqlWalker extends TreeWalkerAdapter
|
||||
{
|
||||
/**
|
||||
* Walks down a SelectStatement AST node, thereby generating the appropriate SQL.
|
||||
*
|
||||
* @return string The SQL.
|
||||
*/
|
||||
public function walkSelectStatement(SelectStatement $AST)
|
||||
{
|
||||
$parent = null;
|
||||
$parentName = null;
|
||||
foreach ($this->_getQueryComponents() AS $dqlAlias => $qComp) {
|
||||
if ($qComp['parent'] === null && $qComp['nestingLevel'] == 0) {
|
||||
$parent = $qComp;
|
||||
$parentName = $dqlAlias;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$pathExpression = new PathExpression(
|
||||
PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName,
|
||||
$parent['metadata']->getSingleIdentifierFieldName()
|
||||
);
|
||||
$pathExpression->type = PathExpression::TYPE_STATE_FIELD;
|
||||
|
||||
$AST->selectClause->selectExpressions = array(
|
||||
new SelectExpression(
|
||||
new AggregateExpression('count', $pathExpression, true), null
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
This will delete any given select expressions and replace them with
|
||||
a distinct count query for the root entities primary key. This will
|
||||
only work if your entity has only one identifier field (composite
|
||||
keys won't work).
|
||||
|
||||
Modify the Output Walker to generate Vendor specific SQL
|
||||
--------------------------------------------------------
|
||||
|
||||
Most RMDBS have vendor-specific features for optimizing select
|
||||
query execution plans. You can write your own output walker to
|
||||
introduce certain keywords using the Query Hint API. A query hint
|
||||
can be set via ``Query::setHint($name, $value)`` as shown in the
|
||||
previous example with the ``HINT_CUSTOM_TREE_WALKERS`` query hint.
|
||||
|
||||
We will implement a custom Output Walker that allows to specify the
|
||||
SQL\_NO\_CACHE query hint.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$dql = "SELECT p, c, a FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ...";
|
||||
$query = $m->createQuery($dql);
|
||||
$query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'DoctrineExtensions\Query\MysqlWalker');
|
||||
$query->setHint("mysqlWalker.sqlNoCache", true);
|
||||
$results = $query->getResult();
|
||||
|
||||
Our ``MysqlWalker`` will extend the default ``SqlWalker``. We will
|
||||
modify the generation of the SELECT clause, adding the
|
||||
SQL\_NO\_CACHE on those queries that need it:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class MysqlWalker extends SqlWalker
|
||||
{
|
||||
/**
|
||||
* Walks down a SelectClause AST node, thereby generating the appropriate SQL.
|
||||
*
|
||||
* @param $selectClause
|
||||
* @return string The SQL.
|
||||
*/
|
||||
public function walkSelectClause($selectClause)
|
||||
{
|
||||
$sql = parent::walkSelectClause($selectClause);
|
||||
|
||||
if ($this->getQuery()->getHint('mysqlWalker.sqlNoCache') === true) {
|
||||
if ($selectClause->isDistinct) {
|
||||
$sql = str_replace('SELECT DISTINCT', 'SELECT DISTINCT SQL_NO_CACHE', $sql);
|
||||
} else {
|
||||
$sql = str_replace('SELECT', 'SELECT SQL_NO_CACHE', $sql);
|
||||
}
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
}
|
||||
|
||||
Writing extensions to the Output Walker requires a very deep
|
||||
understanding of the DQL Parser and Walkers, but may offer your
|
||||
huge benefits with using vendor specific features. This would still
|
||||
allow you write DQL queries instead of NativeQueries to make use of
|
||||
vendor specific features.
|
||||
|
||||
240
docs/en/cookbook/dql-user-defined-functions.rst
Normal file
240
docs/en/cookbook/dql-user-defined-functions.rst
Normal file
@@ -0,0 +1,240 @@
|
||||
DQL User Defined Functions
|
||||
==========================
|
||||
|
||||
.. sectionauthor:: Benjamin Eberlei <kontakt@beberlei.de>
|
||||
|
||||
By default DQL supports a limited subset of all the vendor-specific
|
||||
SQL functions common between all the vendors. However in many cases
|
||||
once you have decided on a specific database vendor, you will never
|
||||
change it during the life of your project. This decision for a
|
||||
specific vendor potentially allows you to make use of powerful SQL
|
||||
features that are unique to the vendor.
|
||||
|
||||
It is worth to mention that Doctrine 2 also allows you to handwrite
|
||||
your SQL instead of extending the DQL parser. Extending DQL is sort of an
|
||||
advanced extension point. You can map arbitrary SQL to your objects
|
||||
and gain access to vendor specific functionalities using the
|
||||
``EntityManager#createNativeQuery()`` API as described in
|
||||
the :doc:`Native Query <../reference/native-sql>` chapter.
|
||||
|
||||
|
||||
The DQL Parser has hooks to register functions that can then be
|
||||
used in your DQL queries and transformed into SQL, allowing to
|
||||
extend Doctrines Query capabilities to the vendors strength. This
|
||||
post explains the Used-Defined Functions API (UDF) of the Dql
|
||||
Parser and shows some examples to give you some hints how you would
|
||||
extend DQL.
|
||||
|
||||
There are three types of functions in DQL, those that return a
|
||||
numerical value, those that return a string and those that return a
|
||||
Date. Your custom method has to be registered as either one of
|
||||
those. The return type information is used by the DQL parser to
|
||||
check possible syntax errors during the parsing process, for
|
||||
example using a string function return value in a math expression.
|
||||
|
||||
Registering your own DQL functions
|
||||
----------------------------------
|
||||
|
||||
You can register your functions adding them to the ORM
|
||||
configuration:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$config->addCustomStringFunction($name, $class);
|
||||
$config->addCustomNumericFunction($name, $class);
|
||||
$config->addCustomDatetimeFunction($name, $class);
|
||||
|
||||
$em = EntityManager::create($dbParams, $config);
|
||||
|
||||
The ``$name`` is the name the function will be referred to in the
|
||||
DQL query. ``$class`` is a string of a class-name which has to
|
||||
extend ``Doctrine\ORM\Query\Node\FunctionNode``. This is a class
|
||||
that offers all the necessary API and methods to implement a UDF.
|
||||
|
||||
In this post we will implement some MySql specific Date calculation
|
||||
methods, which are quite handy in my opinion:
|
||||
|
||||
Date Diff
|
||||
---------
|
||||
|
||||
`Mysql's DateDiff function <http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_datediff>`_
|
||||
takes two dates as argument and calculates the difference in days
|
||||
with ``date1-date2``.
|
||||
|
||||
The DQL parser is a top-down recursive descent parser to generate
|
||||
the Abstract-Syntax Tree (AST) and uses a TreeWalker approach to
|
||||
generate the appropriate SQL from the AST. This makes reading the
|
||||
Parser/TreeWalker code manageable in a finite amount of time.
|
||||
|
||||
The ``FunctionNode`` class I referred to earlier requires you to
|
||||
implement two methods, one for the parsing process (obviously)
|
||||
called ``parse`` and one for the TreeWalker process called
|
||||
``getSql()``. I show you the code for the DateDiff method and
|
||||
discuss it step by step:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* DateDiffFunction ::= "DATEDIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
|
||||
*/
|
||||
class DateDiff extends FunctionNode
|
||||
{
|
||||
// (1)
|
||||
public $firstDateExpression = null;
|
||||
public $secondDateExpression = null;
|
||||
|
||||
public function parse(\Doctrine\ORM\Query\Parser $parser)
|
||||
{
|
||||
$parser->match(Lexer::T_IDENTIFIER); // (2)
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS); // (3)
|
||||
$this->firstDateExpression = $parser->ArithmeticPrimary(); // (4)
|
||||
$parser->match(Lexer::T_COMMA); // (5)
|
||||
$this->secondDateExpression = $parser->ArithmeticPrimary(); // (6)
|
||||
$parser->match(Lexer::T_CLOSE_PARENTHESIS); // (3)
|
||||
}
|
||||
|
||||
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
|
||||
{
|
||||
return 'DATEDIFF(' .
|
||||
$this->firstDateExpression->dispatch($sqlWalker) . ', ' .
|
||||
$this->secondDateExpression->dispatch($sqlWalker) .
|
||||
')'; // (7)
|
||||
}
|
||||
}
|
||||
|
||||
The Parsing process of the DATEDIFF function is going to find two
|
||||
expressions the date1 and the date2 values, whose AST Node
|
||||
representations will be saved in the variables of the DateDiff
|
||||
FunctionNode instance at (1).
|
||||
|
||||
The parse() method has to cut the function call "DATEDIFF" and its
|
||||
argument into pieces. Since the parser detects the function using a
|
||||
lookahead the T\_IDENTIFIER of the function name has to be taken
|
||||
from the stack (2), followed by a detection of the arguments in
|
||||
(4)-(6). The opening and closing parenthesis have to be detected
|
||||
also. This happens during the Parsing process and leads to the
|
||||
generation of a DateDiff FunctionNode somewhere in the AST of the
|
||||
dql statement.
|
||||
|
||||
The ``ArithmeticPrimary`` method call is the most common
|
||||
denominator of valid EBNF tokens taken from the
|
||||
`DQL EBNF grammar <http://www.doctrine-project.org/documentation/manual/2_0/en/dql-doctrine-query-language#ebnf>`_
|
||||
that matches our requirements for valid input into the DateDiff Dql
|
||||
function. Picking the right tokens for your methods is a tricky
|
||||
business, but the EBNF grammar is pretty helpful finding it, as is
|
||||
looking at the Parser source code.
|
||||
|
||||
Now in the TreeWalker process we have to pick up this node and
|
||||
generate SQL from it, which apparently is quite easy looking at the
|
||||
code in (7). Since we don't know which type of AST Node the first
|
||||
and second Date expression are we are just dispatching them back to
|
||||
the SQL Walker to generate SQL from and then wrap our DATEDIFF
|
||||
function call around this output.
|
||||
|
||||
Now registering this DateDiff FunctionNode with the ORM using:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$config->addCustomStringFunction('DATEDIFF', 'DoctrineExtensions\Query\MySql\DateDiff');
|
||||
|
||||
We can do fancy stuff like:
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATEDIFF(CURRENT_TIME(), p.created) < 7
|
||||
|
||||
Date Add
|
||||
--------
|
||||
|
||||
Often useful it the ability to do some simple date calculations in
|
||||
your DQL query using
|
||||
`MySql's DATE\_ADD function <http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date-add>`_.
|
||||
|
||||
I'll skip the blah and show the code for this function:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* DateAddFunction ::=
|
||||
* "DATE_ADD" "(" ArithmeticPrimary ", INTERVAL" ArithmeticPrimary Identifier ")"
|
||||
*/
|
||||
class DateAdd extends FunctionNode
|
||||
{
|
||||
public $firstDateExpression = null;
|
||||
public $intervalExpression = null;
|
||||
public $unit = null;
|
||||
|
||||
public function parse(\Doctrine\ORM\Query\Parser $parser)
|
||||
{
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
$this->firstDateExpression = $parser->ArithmeticPrimary();
|
||||
|
||||
$parser->match(Lexer::T_COMMA);
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
|
||||
$this->intervalExpression = $parser->ArithmeticPrimary();
|
||||
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
|
||||
/* @var $lexer Lexer */
|
||||
$lexer = $parser->getLexer();
|
||||
$this->unit = $lexer->token['value'];
|
||||
|
||||
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
}
|
||||
|
||||
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
|
||||
{
|
||||
return 'DATE_ADD(' .
|
||||
$this->firstDateExpression->dispatch($sqlWalker) . ', INTERVAL ' .
|
||||
$this->intervalExpression->dispatch($sqlWalker) . ' ' . $this->unit .
|
||||
')';
|
||||
}
|
||||
}
|
||||
|
||||
The only difference compared to the DATEDIFF here is, we
|
||||
additionally need the ``Lexer`` to access the value of the
|
||||
``T_IDENTIFIER`` token for the Date Interval unit, for example the
|
||||
MONTH in:
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATE_ADD(CURRENT_TIME(), INTERVAL 4 MONTH) > p.created
|
||||
|
||||
The above method now only supports the specification using
|
||||
``INTERVAL``, to also allow a real date in DATE\_ADD we need to add
|
||||
some decision logic to the parsing process (makes up for a nice
|
||||
exercise).
|
||||
|
||||
Now as you see, the Parsing process doesn't catch all the possible
|
||||
SQL errors, here we don't match for all the valid inputs for the
|
||||
interval unit. However where necessary we rely on the database
|
||||
vendors SQL parser to show us further errors in the parsing
|
||||
process, for example if the Unit would not be one of the supported
|
||||
values by MySql.
|
||||
|
||||
Conclusion
|
||||
----------
|
||||
|
||||
Now that you all know how you can implement vendor specific SQL
|
||||
functionalities in DQL, we would be excited to see user extensions
|
||||
that add vendor specific function packages, for example more math
|
||||
functions, XML + GIS Support, Hashing functions and so on.
|
||||
|
||||
For 2.0 we will come with the current set of functions, however for
|
||||
a future version we will re-evaluate if we can abstract even more
|
||||
vendor sql functions and extend the DQL languages scope.
|
||||
|
||||
Code for this Extension to DQL and other Doctrine Extensions can be
|
||||
found
|
||||
`in my Github DoctrineExtensions repository <http://github.com/beberlei/DoctrineExtensions>`_.
|
||||
|
||||
|
||||
68
docs/en/cookbook/entities-in-session.rst
Normal file
68
docs/en/cookbook/entities-in-session.rst
Normal file
@@ -0,0 +1,68 @@
|
||||
Entities in the Session
|
||||
=======================
|
||||
|
||||
There are several use-cases to save entities in the session, for example:
|
||||
|
||||
1. User object
|
||||
2. Multi-step forms
|
||||
|
||||
To achieve this with Doctrine you have to pay attention to some details to get
|
||||
this working.
|
||||
|
||||
Merging entity into an EntityManager
|
||||
------------------------------------
|
||||
|
||||
In Doctrine an entity objects has to be "managed" by an EntityManager to be
|
||||
updateable. Entities saved into the session are not managed in the next request
|
||||
anymore. This means that you have to register these entities with an
|
||||
EntityManager again if you want to change them or use them as part of
|
||||
references between other entities. You can achieve this by calling
|
||||
``EntityManager#merge()``.
|
||||
|
||||
For a representative User object the code to get turn an instance from
|
||||
the session into a managed Doctrine object looks like this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
require_once 'bootstrap.php';
|
||||
$em = GetEntityManager(); // creates an EntityManager
|
||||
|
||||
session_start();
|
||||
if (isset($_SESSION['user']) && $_SESSION['user'] instanceof User) {
|
||||
$user = $_SESSION['user'];
|
||||
$user = $em->merge($user);
|
||||
}
|
||||
|
||||
.. note::
|
||||
|
||||
A frequent mistake is not to get the merged user object from the return
|
||||
value of ``EntityManager#merge()``. The entity object passed to merge is
|
||||
not necessarily the same object that is returned from the method.
|
||||
|
||||
Serializing entity into the session
|
||||
-----------------------------------
|
||||
|
||||
Entities that are serialized into the session normally contain references to
|
||||
other entities as well. Think of the user entity has a reference to his
|
||||
articles, groups, photos or many other different entities. If you serialize
|
||||
this object into the session then you don't want to serialize the related
|
||||
entities as well. This is why you should call ``EntityManager#detach()`` on this
|
||||
object or implement the __sleep() magic method on your entity.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
require_once 'bootstrap.php';
|
||||
$em = GetEntityManager(); // creates an EntityManager
|
||||
|
||||
$user = $em->find("User", 1);
|
||||
$em->detach($user);
|
||||
$_SESSION['user'] = $user;
|
||||
|
||||
.. note::
|
||||
|
||||
When you called detach on your objects they get "unmanaged" with that
|
||||
entity manager. This means you cannot use them as part of write operations
|
||||
during ``EntityManager#flush()`` anymore in this request.
|
||||
|
||||
112
docs/en/cookbook/implementing-arrayaccess-for-domain-objects.rst
Normal file
112
docs/en/cookbook/implementing-arrayaccess-for-domain-objects.rst
Normal file
@@ -0,0 +1,112 @@
|
||||
Implementing ArrayAccess for Domain Objects
|
||||
===========================================
|
||||
|
||||
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
|
||||
|
||||
This recipe will show you how to implement ArrayAccess for your
|
||||
domain objects in order to allow more uniform access, for example
|
||||
in templates. In these examples we will implement ArrayAccess on a
|
||||
`Layer Supertype <http://martinfowler.com/eaaCatalog/layerSupertype.html>`_
|
||||
for all our domain objects.
|
||||
|
||||
Option 1
|
||||
--------
|
||||
|
||||
In this implementation we will make use of PHPs highly dynamic
|
||||
nature to dynamically access properties of a subtype in a supertype
|
||||
at runtime. Note that this implementation has 2 main caveats:
|
||||
|
||||
|
||||
- It will not work with private fields
|
||||
- It will not go through any getters/setters
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
abstract class DomainObject implements ArrayAccess
|
||||
{
|
||||
public function offsetExists($offset) {
|
||||
return isset($this->$offset);
|
||||
}
|
||||
|
||||
public function offsetSet($offset, $value) {
|
||||
$this->$offset = $value;
|
||||
}
|
||||
|
||||
public function offsetGet($offset) {
|
||||
return $this->$offset;
|
||||
}
|
||||
|
||||
public function offsetUnset($offset) {
|
||||
$this->$offset = null;
|
||||
}
|
||||
}
|
||||
|
||||
Option 2
|
||||
--------
|
||||
|
||||
In this implementation we will dynamically invoke getters/setters.
|
||||
Again we use PHPs dynamic nature to invoke methods on a subtype
|
||||
from a supertype at runtime. This implementation has the following
|
||||
caveats:
|
||||
|
||||
|
||||
- It relies on a naming convention
|
||||
- The semantics of offsetExists can differ
|
||||
- offsetUnset will not work with typehinted setters
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
abstract class DomainObject implements ArrayAccess
|
||||
{
|
||||
public function offsetExists($offset) {
|
||||
// In this example we say that exists means it is not null
|
||||
$value = $this->{"get$offset"}();
|
||||
return $value !== null;
|
||||
}
|
||||
|
||||
public function offsetSet($offset, $value) {
|
||||
$this->{"set$offset"}($value);
|
||||
}
|
||||
|
||||
public function offsetGet($offset) {
|
||||
return $this->{"get$offset"}();
|
||||
}
|
||||
|
||||
public function offsetUnset($offset) {
|
||||
$this->{"set$offset"}(null);
|
||||
}
|
||||
}
|
||||
|
||||
Read-only
|
||||
---------
|
||||
|
||||
You can slightly tweak option 1 or option 2 in order to make array
|
||||
access read-only. This will also circumvent some of the caveats of
|
||||
each option. Simply make offsetSet and offsetUnset throw an
|
||||
exception (i.e. BadMethodCallException).
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
abstract class DomainObject implements ArrayAccess
|
||||
{
|
||||
public function offsetExists($offset) {
|
||||
// option 1 or option 2
|
||||
}
|
||||
|
||||
public function offsetSet($offset, $value) {
|
||||
throw new BadMethodCallException("Array access of class " . get_class($this) . " is read-only!");
|
||||
}
|
||||
|
||||
public function offsetGet($offset) {
|
||||
// option 1 or option 2
|
||||
}
|
||||
|
||||
public function offsetUnset($offset) {
|
||||
throw new BadMethodCallException("Array access of class " . get_class($this) . " is read-only!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
Implementing the Notify ChangeTracking Policy
|
||||
=============================================
|
||||
|
||||
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
|
||||
|
||||
The NOTIFY change-tracking policy is the most effective
|
||||
change-tracking policy provided by Doctrine but it requires some
|
||||
boilerplate code. This recipe will show you how this boilerplate
|
||||
code should look like. We will implement it on a
|
||||
`Layer Supertype <http://martinfowler.com/eaaCatalog/layerSupertype.html>`_
|
||||
for all our domain objects.
|
||||
|
||||
Implementing NotifyPropertyChanged
|
||||
----------------------------------
|
||||
|
||||
The NOTIFY policy is based on the assumption that the entities
|
||||
notify interested listeners of changes to their properties. For
|
||||
that purpose, a class that wants to use this policy needs to
|
||||
implement the ``NotifyPropertyChanged`` interface from the
|
||||
``Doctrine\Common`` namespace.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\Common\NotifyPropertyChanged;
|
||||
use Doctrine\Common\PropertyChangedListener;
|
||||
|
||||
abstract class DomainObject implements NotifyPropertyChanged
|
||||
{
|
||||
private $listeners = array();
|
||||
|
||||
public function addPropertyChangedListener(PropertyChangedListener $listener) {
|
||||
$this->listeners[] = $listener;
|
||||
}
|
||||
|
||||
/** Notifies listeners of a change. */
|
||||
protected function onPropertyChanged($propName, $oldValue, $newValue) {
|
||||
if ($this->listeners) {
|
||||
foreach ($this->listeners as $listener) {
|
||||
$listener->propertyChanged($this, $propName, $oldValue, $newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Then, in each property setter of concrete, derived domain classes,
|
||||
you need to invoke onPropertyChanged as follows to notify
|
||||
listeners:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// Mapping not shown, either in annotations, xml or yaml as usual
|
||||
class MyEntity extends DomainObject
|
||||
{
|
||||
private $data;
|
||||
// ... other fields as usual
|
||||
|
||||
public function setData($data) {
|
||||
if ($data != $this->data) { // check: is it actually modified?
|
||||
$this->onPropertyChanged('data', $this->data, $data);
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
The check whether the new value is different from the old one is
|
||||
not mandatory but recommended. That way you can avoid unnecessary
|
||||
updates and also have full control over when you consider a
|
||||
property changed.
|
||||
|
||||
|
||||
78
docs/en/cookbook/implementing-wakeup-or-clone.rst
Normal file
78
docs/en/cookbook/implementing-wakeup-or-clone.rst
Normal file
@@ -0,0 +1,78 @@
|
||||
Implementing Wakeup or Clone
|
||||
============================
|
||||
|
||||
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
|
||||
|
||||
As explained in the
|
||||
`restrictions for entity classes in the manual <http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities>`_,
|
||||
it is usually not allowed for an entity to implement ``__wakeup``
|
||||
or ``__clone``, because Doctrine makes special use of them.
|
||||
However, it is quite easy to make use of these methods in a safe
|
||||
way by guarding the custom wakeup or clone code with an entity
|
||||
identity check, as demonstrated in the following sections.
|
||||
|
||||
Safely implementing \_\_wakeup
|
||||
------------------------------
|
||||
|
||||
To safely implement ``__wakeup``, simply enclose your
|
||||
implementation code in an identity check as follows:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class MyEntity
|
||||
{
|
||||
private $id; // This is the identifier of the entity.
|
||||
//...
|
||||
|
||||
public function __wakeup()
|
||||
{
|
||||
// If the entity has an identity, proceed as normal.
|
||||
if ($this->id) {
|
||||
// ... Your code here as normal ...
|
||||
}
|
||||
// otherwise do nothing, do NOT throw an exception!
|
||||
}
|
||||
|
||||
//...
|
||||
}
|
||||
|
||||
Safely implementing \_\_clone
|
||||
-----------------------------
|
||||
|
||||
Safely implementing ``__clone`` is pretty much the same:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class MyEntity
|
||||
{
|
||||
private $id; // This is the identifier of the entity.
|
||||
//...
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
// If the entity has an identity, proceed as normal.
|
||||
if ($this->id) {
|
||||
// ... Your code here as normal ...
|
||||
}
|
||||
// otherwise do nothing, do NOT throw an exception!
|
||||
}
|
||||
|
||||
//...
|
||||
}
|
||||
|
||||
Summary
|
||||
-------
|
||||
|
||||
As you have seen, it is quite easy to safely make use of
|
||||
``__wakeup`` and ``__clone`` in your entities without adding any
|
||||
really Doctrine-specific or Doctrine-dependant code.
|
||||
|
||||
These implementations are possible and safe because when Doctrine
|
||||
invokes these methods, the entities never have an identity (yet).
|
||||
Furthermore, it is possibly a good idea to check for the identity
|
||||
in your code anyway, since it's rarely the case that you want to
|
||||
unserialize or clone an entity with no identity.
|
||||
|
||||
|
||||
140
docs/en/cookbook/integrating-with-codeigniter.rst
Normal file
140
docs/en/cookbook/integrating-with-codeigniter.rst
Normal file
@@ -0,0 +1,140 @@
|
||||
Integrating with CodeIgniter
|
||||
============================
|
||||
|
||||
This is recipe for using Doctrine 2 in your
|
||||
`CodeIgniter <http://www.codeigniter.com>`_ framework.
|
||||
|
||||
.. note::
|
||||
|
||||
This might not work for all CodeIgniter versions and may require
|
||||
slight adjustments.
|
||||
|
||||
|
||||
Here is how to set it up:
|
||||
|
||||
Make a CodeIgniter library that is both a wrapper and a bootstrap
|
||||
for Doctrine 2.
|
||||
|
||||
Setting up the file structure
|
||||
-----------------------------
|
||||
|
||||
Here are the steps:
|
||||
|
||||
|
||||
- Add a php file to your system/application/libraries folder
|
||||
called Doctrine.php. This is going to be your wrapper/bootstrap for
|
||||
the D2 entity manager.
|
||||
- Put the Doctrine folder (the one that contains Common, DBAL, and
|
||||
ORM) inside that same libraries folder.
|
||||
- Your system/application/libraries folder now looks like this:
|
||||
|
||||
system/applications/libraries -Doctrine -Doctrine.php -index.html
|
||||
|
||||
- If you want, open your config/autoload.php file and autoload
|
||||
your Doctrine library.
|
||||
|
||||
<?php $autoload['libraries'] = array('doctrine');
|
||||
|
||||
|
||||
Creating your Doctrine CodeIgniter library
|
||||
------------------------------------------
|
||||
|
||||
Now, here is what your Doctrine.php file should look like.
|
||||
Customize it to your needs.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\Common\ClassLoader,
|
||||
Doctrine\ORM\Configuration,
|
||||
Doctrine\ORM\EntityManager,
|
||||
Doctrine\Common\Cache\ArrayCache,
|
||||
Doctrine\DBAL\Logging\EchoSQLLogger;
|
||||
|
||||
class Doctrine {
|
||||
|
||||
public $em = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// load database configuration from CodeIgniter
|
||||
require_once APPPATH.'config/database.php';
|
||||
|
||||
// Set up class loading. You could use different autoloaders, provided by your favorite framework,
|
||||
// if you want to.
|
||||
require_once APPPATH.'libraries/Doctrine/Common/ClassLoader.php';
|
||||
|
||||
$doctrineClassLoader = new ClassLoader('Doctrine', APPPATH.'libraries');
|
||||
$doctrineClassLoader->register();
|
||||
$entitiesClassLoader = new ClassLoader('models', rtrim(APPPATH, "/" ));
|
||||
$entitiesClassLoader->register();
|
||||
$proxiesClassLoader = new ClassLoader('Proxies', APPPATH.'models/proxies');
|
||||
$proxiesClassLoader->register();
|
||||
|
||||
// Set up caches
|
||||
$config = new Configuration;
|
||||
$cache = new ArrayCache;
|
||||
$config->setMetadataCacheImpl($cache);
|
||||
$driverImpl = $config->newDefaultAnnotationDriver(array(APPPATH.'models/Entities'));
|
||||
$config->setMetadataDriverImpl($driverImpl);
|
||||
$config->setQueryCacheImpl($cache);
|
||||
|
||||
$config->setQueryCacheImpl($cache);
|
||||
|
||||
// Proxy configuration
|
||||
$config->setProxyDir(APPPATH.'/models/proxies');
|
||||
$config->setProxyNamespace('Proxies');
|
||||
|
||||
// Set up logger
|
||||
$logger = new EchoSQLLogger;
|
||||
$config->setSQLLogger($logger);
|
||||
|
||||
$config->setAutoGenerateProxyClasses( TRUE );
|
||||
|
||||
// Database connection information
|
||||
$connectionOptions = array(
|
||||
'driver' => 'pdo_mysql',
|
||||
'user' => $db['default']['username'],
|
||||
'password' => $db['default']['password'],
|
||||
'host' => $db['default']['hostname'],
|
||||
'dbname' => $db['default']['database']
|
||||
);
|
||||
|
||||
// Create EntityManager
|
||||
$this->em = EntityManager::create($connectionOptions, $config);
|
||||
}
|
||||
}
|
||||
|
||||
Please note that this is a development configuration; for a
|
||||
production system you'll want to use a real caching system like
|
||||
APC, get rid of EchoSqlLogger, and turn off
|
||||
autoGenerateProxyClasses.
|
||||
|
||||
For more details, consult the
|
||||
`Doctrine 2 Configuration documentation <http://www.doctrine-project.org/documentation/manual/2_0/en/configuration#configuration-options>`_.
|
||||
|
||||
Now to use it
|
||||
-------------
|
||||
|
||||
Whenever you need a reference to the entity manager inside one of
|
||||
your controllers, views, or models you can do this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$em = $this->doctrine->em;
|
||||
|
||||
That's all there is to it. Once you get the reference to your
|
||||
EntityManager do your Doctrine 2.0 voodoo as normal.
|
||||
|
||||
Note: If you do not choose to autoload the Doctrine library, you
|
||||
will need to put this line before you get a reference to it:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$this->load->library('doctrine');
|
||||
|
||||
Good luck!
|
||||
|
||||
|
||||
189
docs/en/cookbook/mysql-enums.rst
Normal file
189
docs/en/cookbook/mysql-enums.rst
Normal file
@@ -0,0 +1,189 @@
|
||||
Mysql Enums
|
||||
===========
|
||||
|
||||
The type system of Doctrine 2 consists of flyweights, which means there is only
|
||||
one instance of any given type. Additionally types do not contain state. Both
|
||||
assumptions make it rather complicated to work with the Enum Type of MySQL that
|
||||
is used quite a lot by developers.
|
||||
|
||||
When using Enums with a non-tweaked Doctrine 2 application you will get
|
||||
errors from the Schema-Tool commands due to the unknown database type "enum".
|
||||
By default Doctrine does not map the MySQL enum type to a Doctrine type.
|
||||
This is because Enums contain state (their allowed values) and Doctrine
|
||||
types don't.
|
||||
|
||||
This cookbook entry shows two possible solutions to work with MySQL enums.
|
||||
But first a word of warning. The MySQL Enum type has considerable downsides:
|
||||
|
||||
- Adding new values requires to rebuild the whole table, which can take hours
|
||||
depending on the size.
|
||||
- Enums are ordered in the way the values are specified, not in their "natural" order.
|
||||
- Enums validation mechanism for allowed values is not necessarily good,
|
||||
specifying invalid values leads to an empty enum for the default MySQL error
|
||||
settings. You can easily replicate the "allow only some values" requirement
|
||||
in your Doctrine entities.
|
||||
|
||||
Solution 1: Mapping to Varchars
|
||||
-------------------------------
|
||||
|
||||
You can map ENUMs to varchars. You can register MySQL ENUMs to map to Doctrine
|
||||
varchars. This way Doctrine always resolves ENUMs to Doctrine varchars. It
|
||||
will even detect this match correctly when using SchemaTool update commands.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$conn = $em->getConnection();
|
||||
$conn->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string');
|
||||
|
||||
In this case you have to ensure that each varchar field that is an enum in the
|
||||
database only gets passed the allowed values. You can easily enforce this in your
|
||||
entities:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
class Article
|
||||
{
|
||||
const STATUS_VISIBLE = 'visible';
|
||||
const STATUS_INVISIBLE = 'invisible';
|
||||
|
||||
/** @Column(type="string") */
|
||||
private $status;
|
||||
|
||||
public function setStatus($status)
|
||||
{
|
||||
if (!in_array($status, array(self::STATUS_VISIBLE, self::STATUS_INVISIBLE))) {
|
||||
throw new \InvalidArgumentException("Invalid status");
|
||||
}
|
||||
$this->status = $status;
|
||||
}
|
||||
}
|
||||
|
||||
If you want to actively create enums through the Doctrine Schema-Tool by using
|
||||
the **columnDefinition** attribute.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
class Article
|
||||
{
|
||||
/** @Column(type="string", columnDefinition="ENUM('visible', 'invisible')") */
|
||||
private $status;
|
||||
}
|
||||
|
||||
In this case however Schema-Tool update will have a hard time not to request changes for this column on each call.
|
||||
|
||||
Solution 2: Defining a Type
|
||||
---------------------------
|
||||
|
||||
You can make a stateless ENUM type by creating a type class for each unique set of ENUM values.
|
||||
For example for the previous enum type:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace MyProject\DBAL;
|
||||
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
|
||||
class EnumVisibilityType extends Type
|
||||
{
|
||||
const ENUM_VISIBILITY = 'enumvisibility';
|
||||
const STATUS_VISIBLE = 'visible';
|
||||
const STATUS_INVISIBLE = 'invisible';
|
||||
|
||||
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||
{
|
||||
return "ENUM('visible', 'invisible') COMMENT '(DC2Type:enumvisibility)'";
|
||||
}
|
||||
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
if (!in_array($value, array(self::STATUS_VISIBLE, self::STATUS_INVISIBLE))) {
|
||||
throw new \InvalidArgumentException("Invalid status");
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return self::ENUM_VISIBILITY;
|
||||
}
|
||||
}
|
||||
|
||||
You can register this type with ``Type::addType('enumvisibility', 'MyProject\DBAL\EnumVisibilityType');``.
|
||||
Then in your entity you can just use this type:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
class Article
|
||||
{
|
||||
/** @Column(type="enumvisibility") */
|
||||
private $status;
|
||||
}
|
||||
|
||||
You can generalize this approach easily to create a base class for enums:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace MyProject\DBAL;
|
||||
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
|
||||
abstract class EnumType extends Type
|
||||
{
|
||||
protected $name;
|
||||
protected $values = array();
|
||||
|
||||
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||
{
|
||||
$values = array_map(function($val) { return "'".$val."'"; }, $this->values);
|
||||
|
||||
return "ENUM(".implode(", ", $values).") COMMENT '(DC2Type:".$this->name.")'";
|
||||
}
|
||||
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
if (!in_array($value, $this->values)) {
|
||||
throw new \InvalidArgumentException("Invalid '".$this->name."' value.");
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
|
||||
With this base class you can define an enum as easily as:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace MyProject\DBAL;
|
||||
|
||||
class EnumVisibilityType extends EnumType
|
||||
{
|
||||
protected $name = 'enumvisibility';
|
||||
protected $values = array('visible', 'invisible');
|
||||
}
|
||||
|
||||
137
docs/en/cookbook/resolve-target-entity-listener.rst
Normal file
137
docs/en/cookbook/resolve-target-entity-listener.rst
Normal file
@@ -0,0 +1,137 @@
|
||||
Keeping your Modules independent
|
||||
=================================
|
||||
|
||||
.. versionadded:: 2.2
|
||||
|
||||
One of the goals of using modules is to create discreet units of functionality
|
||||
that do not have many (if any) dependencies, allowing you to use that
|
||||
functionality in other applications without including unnecessary items.
|
||||
|
||||
Doctrine 2.2 includes a new utility called the ``ResolveTargetEntityListener``,
|
||||
that functions by intercepting certain calls inside Doctrine and rewrite
|
||||
targetEntity parameters in your metadata mapping at runtime. It means that
|
||||
in your bundle you are able to use an interface or abstract class in your
|
||||
mappings and expect correct mapping to a concrete entity at runtime.
|
||||
|
||||
This functionality allows you to define relationships between different entities
|
||||
but not making them hard dependencies.
|
||||
|
||||
Background
|
||||
----------
|
||||
|
||||
In the following example, the situation is we have an `InvoiceModule`
|
||||
which provides invoicing functionality, and a `CustomerModule` that
|
||||
contains customer management tools. We want to keep these separated,
|
||||
because they can be used in other systems without each other, but for
|
||||
our application we want to use them together.
|
||||
|
||||
In this case, we have an ``Invoice`` entity with a relationship to a
|
||||
non-existent object, an ``InvoiceSubjectInterface``. The goal is to get
|
||||
the ``ResolveTargetEntityListener`` to replace any mention of the interface
|
||||
with a real object that implements that interface.
|
||||
|
||||
Set up
|
||||
------
|
||||
|
||||
We're going to use the following basic entities (which are incomplete
|
||||
for brevity) to explain how to set up and use the RTEL.
|
||||
|
||||
A Customer entity
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
// src/Acme/AppModule/Entity/Customer.php
|
||||
|
||||
namespace Acme\AppModule\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Acme\CustomerModule\Entity\Customer as BaseCustomer;
|
||||
use Acme\InvoiceModule\Model\InvoiceSubjectInterface;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="customer")
|
||||
*/
|
||||
class Customer extends BaseCustomer implements InvoiceSubjectInterface
|
||||
{
|
||||
// In our example, any methods defined in the InvoiceSubjectInterface
|
||||
// are already implemented in the BaseCustomer
|
||||
}
|
||||
|
||||
An Invoice entity
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
// src/Acme/InvoiceModule/Entity/Invoice.php
|
||||
|
||||
namespace Acme\InvoiceModule\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping AS ORM;
|
||||
use Acme\InvoiceModule\Model\InvoiceSubjectInterface;
|
||||
|
||||
/**
|
||||
* Represents an Invoice.
|
||||
*
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="invoice")
|
||||
*/
|
||||
class Invoice
|
||||
{
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity="Acme\InvoiceModule\Model\InvoiceSubjectInterface")
|
||||
* @var InvoiceSubjectInterface
|
||||
*/
|
||||
protected $subject;
|
||||
}
|
||||
|
||||
An InvoiceSubjectInterface
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
// src/Acme/InvoiceModule/Model/InvoiceSubjectInterface.php
|
||||
|
||||
namespace Acme\InvoiceModule\Model;
|
||||
|
||||
/**
|
||||
* An interface that the invoice Subject object should implement.
|
||||
* In most circumstances, only a single object should implement
|
||||
* this interface as the ResolveTargetEntityListener can only
|
||||
* change the target to a single object.
|
||||
*/
|
||||
interface InvoiceSubjectInterface
|
||||
{
|
||||
// List any additional methods that your InvoiceModule
|
||||
// will need to access on the subject so that you can
|
||||
// be sure that you have access to those methods.
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName();
|
||||
}
|
||||
|
||||
Next, we need to configure the listener. Add this to the area you set up Doctrine. You
|
||||
must set this up in the way outlined below, otherwise you can not be guaranteed that
|
||||
the targetEntity resolution will occur reliably:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$evm = new \Doctrine\Common\EventManager;
|
||||
|
||||
$rtel = new \Doctrine\ORM\Tools\ResolveTargetEntityListener;
|
||||
$rtel->addResolveTargetEntity('Acme\\InvoiceModule\\Model\\InvoiceSubjectInterface', 'Acme\\CustomerModule\\Entity\\Customer', array());
|
||||
|
||||
// Add the ResolveTargetEntityListener
|
||||
$evm->addEventSubscriber($rtel);
|
||||
|
||||
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);
|
||||
|
||||
Final Thoughts
|
||||
--------------
|
||||
|
||||
With the ``ResolveTargetEntityListener``, we are able to decouple our
|
||||
bundles, keeping them usable by themselves, but still being able to
|
||||
define relationships between different objects. By using this method,
|
||||
I've found my bundles end up being easier to maintain independently.
|
||||
|
||||
|
||||
80
docs/en/cookbook/sql-table-prefixes.rst
Normal file
80
docs/en/cookbook/sql-table-prefixes.rst
Normal file
@@ -0,0 +1,80 @@
|
||||
SQL-Table Prefixes
|
||||
==================
|
||||
|
||||
This recipe is intended as an example of implementing a
|
||||
loadClassMetadata listener to provide a Table Prefix option for
|
||||
your application. The method used below is not a hack, but fully
|
||||
integrates into the Doctrine system, all SQL generated will include
|
||||
the appropriate table prefix.
|
||||
|
||||
In most circumstances it is desirable to separate different
|
||||
applications into individual databases, but in certain cases, it
|
||||
may be beneficial to have a table prefix for your Entities to
|
||||
separate them from other vendor products in the same database.
|
||||
|
||||
Implementing the listener
|
||||
-------------------------
|
||||
|
||||
The listener in this example has been set up with the
|
||||
DoctrineExtensions namespace. You create this file in your
|
||||
library/DoctrineExtensions directory, but will need to set up
|
||||
appropriate autoloaders.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
namespace DoctrineExtensions;
|
||||
use \Doctrine\ORM\Event\LoadClassMetadataEventArgs;
|
||||
|
||||
class TablePrefix
|
||||
{
|
||||
protected $prefix = '';
|
||||
|
||||
public function __construct($prefix)
|
||||
{
|
||||
$this->prefix = (string) $prefix;
|
||||
}
|
||||
|
||||
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
|
||||
{
|
||||
$classMetadata = $eventArgs->getClassMetadata();
|
||||
$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'];
|
||||
$classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix . $mappedTableName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Telling the EntityManager about our listener
|
||||
--------------------------------------------
|
||||
|
||||
A listener of this type must be set up before the EntityManager has
|
||||
been initialised, otherwise an Entity might be created or cached
|
||||
before the prefix has been set.
|
||||
|
||||
.. note::
|
||||
|
||||
If you set this listener up, be aware that you will need
|
||||
to clear your caches and drop then recreate your database schema.
|
||||
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
// $connectionOptions and $config set earlier
|
||||
|
||||
$evm = new \Doctrine\Common\EventManager;
|
||||
|
||||
// Table Prefix
|
||||
$tablePrefix = new \DoctrineExtensions\TablePrefix('prefix_');
|
||||
$evm->addEventListener(\Doctrine\ORM\Events::loadClassMetadata, $tablePrefix);
|
||||
|
||||
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);
|
||||
|
||||
|
||||
254
docs/en/cookbook/strategy-cookbook-introduction.rst
Normal file
254
docs/en/cookbook/strategy-cookbook-introduction.rst
Normal file
@@ -0,0 +1,254 @@
|
||||
Strategy-Pattern
|
||||
================
|
||||
|
||||
This recipe will give you a short introduction on how to design
|
||||
similar entities without using expensive (i.e. slow) inheritance
|
||||
but with not more than \* the well-known strategy pattern \* event
|
||||
listeners
|
||||
|
||||
Scenario / Problem
|
||||
------------------
|
||||
|
||||
Given a Content-Management-System, we probably want to add / edit
|
||||
some so-called "blocks" and "panels". What are they for?
|
||||
|
||||
|
||||
- A block might be a registration form, some text content, a table
|
||||
with information. A good example might also be a small calendar.
|
||||
- A panel is by definition a block that can itself contain blocks.
|
||||
A good example for a panel might be a sidebar box: You could easily
|
||||
add a small calendar into it.
|
||||
|
||||
So, in this scenario, when building your CMS, you will surely add
|
||||
lots of blocks and panels to your pages and you will find yourself
|
||||
highly uncomfortable because of the following:
|
||||
|
||||
|
||||
- Every existing page needs to know about the panels it contains -
|
||||
therefore, you'll have an association to your panels. But if you've
|
||||
got several types of panels - what do you do? Add an association to
|
||||
every panel-type? This wouldn't be flexible. You might be tempted
|
||||
to add an AbstractPanelEntity and an AbstractBlockEntity that use
|
||||
class inheritance. Your page could then only confer to the
|
||||
AbstractPanelType and Doctrine 2 would do the rest for you, i.e.
|
||||
load the right entities. But - you'll for sure have lots of panels
|
||||
and blocks, and even worse, you'd have to edit the discriminator
|
||||
map *manually* every time you or another developer implements a new
|
||||
block / entity. This would tear down any effort of modular
|
||||
programming.
|
||||
|
||||
Therefore, we need something that's far more flexible.
|
||||
|
||||
Solution
|
||||
--------
|
||||
|
||||
The solution itself is pretty easy. We will have one base class
|
||||
that will be loaded via the page and that has specific behaviour -
|
||||
a Block class might render the front-end and even the backend, for
|
||||
example. Now, every block that you'll write might look different or
|
||||
need different data - therefore, we'll offer an API to these
|
||||
methods but internally, we use a strategy that exactly knows what
|
||||
to do.
|
||||
|
||||
First of all, we need to make sure that we have an interface that
|
||||
contains every needed action. Such actions would be rendering the
|
||||
front-end or the backend, solving dependencies (blocks that are
|
||||
supposed to be placed in the sidebar could refuse to be placed in
|
||||
the middle of your page, for example).
|
||||
|
||||
Such an interface could look like this:
|
||||
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* This interface defines the basic actions that a block / panel needs to support.
|
||||
*
|
||||
* Every blockstrategy is *only* responsible for rendering a block and declaring some basic
|
||||
* support, but *not* for updating its configuration etc. For this purpose, use controllers
|
||||
* and models.
|
||||
*/
|
||||
interface BlockStrategyInterface {
|
||||
/**
|
||||
* This could configure your entity
|
||||
*/
|
||||
public function setConfig(Config\EntityConfig $config);
|
||||
|
||||
/**
|
||||
* Returns the config this strategy is configured with.
|
||||
* @return Core\Model\Config\EntityConfig
|
||||
*/
|
||||
public function getConfig();
|
||||
|
||||
/**
|
||||
* Set the view object.
|
||||
* @param \Zend_View_Interface $view
|
||||
* @return \Zend_View_Helper_Interface
|
||||
*/
|
||||
public function setView(\Zend_View_Interface $view);
|
||||
|
||||
/**
|
||||
* @return \Zend_View_Interface
|
||||
*/
|
||||
public function getView();
|
||||
|
||||
/**
|
||||
* Renders this strategy. This method will be called when the user
|
||||
* displays the site.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function renderFrontend();
|
||||
|
||||
/**
|
||||
* Renders the backend of this block. This method will be called when
|
||||
* a user tries to reconfigure this block instance.
|
||||
*
|
||||
* Most of the time, this method will return / output a simple form which in turn
|
||||
* calls some controllers.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function renderBackend();
|
||||
|
||||
/**
|
||||
* Returns all possible types of panels this block can be stacked onto
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getRequiredPanelTypes();
|
||||
|
||||
/**
|
||||
* Determines whether a Block is able to use a given type or not
|
||||
* @param string $typeName The typename
|
||||
* @return boolean
|
||||
*/
|
||||
public function canUsePanelType($typeName);
|
||||
|
||||
public function setBlockEntity(AbstractBlock $block);
|
||||
|
||||
public function getBlockEntity();
|
||||
}
|
||||
|
||||
As you can see, we have a method "setBlockEntity" which ties a potential strategy to an object of type AbstractBlock. This type will simply define the basic behaviour of our blocks and could potentially look something like this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* This is the base class for both Panels and Blocks.
|
||||
* It shouldn't be extended by your own blocks - simply write a strategy!
|
||||
*/
|
||||
abstract class AbstractBlock {
|
||||
/**
|
||||
* The id of the block item instance
|
||||
* This is a doctrine field, so you need to setup generation for it
|
||||
* @var integer
|
||||
*/
|
||||
private $id;
|
||||
|
||||
// Add code for relation to the parent panel, configuration objects, ....
|
||||
|
||||
/**
|
||||
* This var contains the classname of the strategy
|
||||
* that is used for this blockitem. (This string (!) value will be persisted by Doctrine 2)
|
||||
*
|
||||
* This is a doctrine field, so make sure that you use an @column annotation or setup your
|
||||
* yaml or xml files correctly
|
||||
* @var string
|
||||
*/
|
||||
protected $strategyClassName;
|
||||
|
||||
/**
|
||||
* This var contains an instance of $this->blockStrategy. Will not be persisted by Doctrine 2.
|
||||
*
|
||||
* @var BlockStrategyInterface
|
||||
*/
|
||||
protected $strategyInstance;
|
||||
|
||||
/**
|
||||
* Returns the strategy that is used for this blockitem.
|
||||
*
|
||||
* The strategy itself defines how this block can be rendered etc.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getStrategyClassName() {
|
||||
return $this->strategyClassName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the instantiated strategy
|
||||
*
|
||||
* @return BlockStrategyInterface
|
||||
*/
|
||||
public function getStrategyInstance() {
|
||||
return $this->strategyInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the strategy this block / panel should work as. Make sure that you've used
|
||||
* this method before persisting the block!
|
||||
*
|
||||
* @param BlockStrategyInterface $strategy
|
||||
*/
|
||||
public function setStrategy(BlockStrategyInterface $strategy) {
|
||||
$this->strategyInstance = $strategy;
|
||||
$this->strategyClassName = get_class($strategy);
|
||||
$strategy->setBlockEntity($this);
|
||||
}
|
||||
|
||||
Now, the important point is that $strategyClassName is a Doctrine 2
|
||||
field, i.e. Doctrine will persist this value. This is only the
|
||||
class name of your strategy and not an instance!
|
||||
|
||||
Finishing your strategy pattern, we hook into the Doctrine postLoad
|
||||
event and check whether a block has been loaded. If so, you will
|
||||
initialize it - i.e. get the strategies classname, create an
|
||||
instance of it and set it via setStrategyBlock().
|
||||
|
||||
This might look like this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use \Doctrine\ORM,
|
||||
\Doctrine\Common;
|
||||
|
||||
/**
|
||||
* The BlockStrategyEventListener will initialize a strategy after the
|
||||
* block itself was loaded.
|
||||
*/
|
||||
class BlockStrategyEventListener implements Common\EventSubscriber {
|
||||
|
||||
protected $view;
|
||||
|
||||
public function __construct(\Zend_View_Interface $view) {
|
||||
$this->view = $view;
|
||||
}
|
||||
|
||||
public function getSubscribedEvents() {
|
||||
return array(ORM\Events::postLoad);
|
||||
}
|
||||
|
||||
public function postLoad(ORM\Event\LifecycleEventArgs $args) {
|
||||
$blockItem = $args->getEntity();
|
||||
|
||||
// Both blocks and panels are instances of Block\AbstractBlock
|
||||
if ($blockItem instanceof Block\AbstractBlock) {
|
||||
$strategy = $blockItem->getStrategyClassName();
|
||||
$strategyInstance = new $strategy();
|
||||
if (null !== $blockItem->getConfig()) {
|
||||
$strategyInstance->setConfig($blockItem->getConfig());
|
||||
}
|
||||
$strategyInstance->setView($this->view);
|
||||
$blockItem->setStrategy($strategyInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
In this example, even some variables are set - like a view object
|
||||
or a specific configuration object.
|
||||
|
||||
|
||||
137
docs/en/cookbook/validation-of-entities.rst
Normal file
137
docs/en/cookbook/validation-of-entities.rst
Normal file
@@ -0,0 +1,137 @@
|
||||
Validation of Entities
|
||||
======================
|
||||
|
||||
.. sectionauthor:: Benjamin Eberlei <kontakt@beberlei.de>
|
||||
|
||||
Doctrine 2 does not ship with any internal validators, the reason
|
||||
being that we think all the frameworks out there already ship with
|
||||
quite decent ones that can be integrated into your Domain easily.
|
||||
What we offer are hooks to execute any kind of validation.
|
||||
|
||||
.. note::
|
||||
|
||||
You don't need to validate your entities in the lifecycle
|
||||
events. Its only one of many options. Of course you can also
|
||||
perform validations in value setters or any other method of your
|
||||
entities that are used in your code.
|
||||
|
||||
|
||||
Entities can register lifecycle event methods with Doctrine that
|
||||
are called on different occasions. For validation we would need to
|
||||
hook into the events called before persisting and updating. Even
|
||||
though we don't support validation out of the box, the
|
||||
implementation is even simpler than in Doctrine 1 and you will get
|
||||
the additional benefit of being able to re-use your validation in
|
||||
any other part of your domain.
|
||||
|
||||
Say we have an ``Order`` with several ``OrderLine`` instances. We
|
||||
never want to allow any customer to order for a larger sum than he
|
||||
is allowed to:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class Order
|
||||
{
|
||||
public function assertCustomerAllowedBuying()
|
||||
{
|
||||
$orderLimit = $this->customer->getOrderLimit();
|
||||
|
||||
$amount = 0;
|
||||
foreach ($this->orderLines AS $line) {
|
||||
$amount += $line->getAmount();
|
||||
}
|
||||
|
||||
if ($amount > $orderLimit) {
|
||||
throw new CustomerOrderLimitExceededException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Now this is some pretty important piece of business logic in your
|
||||
code, enforcing it at any time is important so that customers with
|
||||
a unknown reputation don't owe your business too much money.
|
||||
|
||||
We can enforce this constraint in any of the metadata drivers.
|
||||
First Annotations:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
* @HasLifecycleCallbacks
|
||||
*/
|
||||
class Order
|
||||
{
|
||||
/**
|
||||
* @PrePersist @PreUpdate
|
||||
*/
|
||||
public function assertCustomerAllowedBuying() {}
|
||||
}
|
||||
|
||||
In XML Mappings:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="Order">
|
||||
<lifecycle-callbacks>
|
||||
<lifecycle-callback type="prePersist" method="assertCustomerallowedBuying" />
|
||||
<lifecycle-callback type="preUpdate" method="assertCustomerallowedBuying" />
|
||||
</lifecycle-callbacks>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
YAML needs some little change yet, to allow multiple lifecycle
|
||||
events for one method, this will happen before Beta 1 though.
|
||||
|
||||
Now validation is performed whenever you call
|
||||
``EntityManager#persist($order)`` or when you call
|
||||
``EntityManager#flush()`` and an order is about to be updated. Any
|
||||
Exception that happens in the lifecycle callbacks will be cached by
|
||||
the EntityManager and the current transaction is rolled back.
|
||||
|
||||
Of course you can do any type of primitive checks, not null,
|
||||
email-validation, string size, integer and date ranges in your
|
||||
validation callbacks.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class Order
|
||||
{
|
||||
/**
|
||||
* @PrePersist @PreUpdate
|
||||
*/
|
||||
public function validate()
|
||||
{
|
||||
if (!($this->plannedShipDate instanceof DateTime)) {
|
||||
throw new ValidateException();
|
||||
}
|
||||
|
||||
if ($this->plannedShipDate->format('U') < time()) {
|
||||
throw new ValidateException();
|
||||
}
|
||||
|
||||
if ($this->customer == null) {
|
||||
throw new OrderRequiresCustomerException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
What is nice about lifecycle events is, you can also re-use the
|
||||
methods at other places in your domain, for example in combination
|
||||
with your form library. Additionally there is no limitation in the
|
||||
number of methods you register on one particular event, i.e. you
|
||||
can register multiple methods for validation in "PrePersist" or
|
||||
"PreUpdate" or mix and share them in any combinations between those
|
||||
two events.
|
||||
|
||||
There is no limit to what you can and can't validate in
|
||||
"PrePersist" and "PreUpdate" as long as you don't create new entity
|
||||
instances. This was already discussed in the previous blog post on
|
||||
the Versionable extension, which requires another type of event
|
||||
called "onFlush".
|
||||
|
||||
Further readings: :doc:`Lifecycle Events <../reference/events>`
|
||||
168
docs/en/cookbook/working-with-datetime.rst
Normal file
168
docs/en/cookbook/working-with-datetime.rst
Normal file
@@ -0,0 +1,168 @@
|
||||
Working with DateTime Instances
|
||||
===============================
|
||||
|
||||
There are many nitty gritty details when working with PHPs DateTime instances. You have know their inner
|
||||
workings pretty well not to make mistakes with date handling. This cookbook entry holds several
|
||||
interesting pieces of information on how to work with PHP DateTime instances in Doctrine 2.
|
||||
|
||||
DateTime changes are detected by Reference
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When calling ``EntityManager#flush()`` Doctrine computes the changesets of all the currently managed entities
|
||||
and saves the differences to the database. In case of object properties (@Column(type="datetime") or @Column(type="object"))
|
||||
these comparisons are always made **BY REFERENCE**. That means the following change will **NOT** be saved into the database:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
class Article
|
||||
{
|
||||
/** @Column(type="datetime") */
|
||||
private $updated;
|
||||
|
||||
public function setUpdated()
|
||||
{
|
||||
// will NOT be saved in the database
|
||||
$this->updated->modify("now");
|
||||
}
|
||||
}
|
||||
|
||||
The way to go would be:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class Article
|
||||
{
|
||||
public function setUpdated()
|
||||
{
|
||||
// WILL be saved in the database
|
||||
$this->updated = new \DateTime("now");
|
||||
}
|
||||
}
|
||||
|
||||
Default Timezone Gotcha
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
By default Doctrine assumes that you are working with a default timezone. Each DateTime instance that
|
||||
is created by Doctrine will be assigned the timezone that is currently the default, either through
|
||||
the ``date.timezone`` ini setting or by calling ``date_default_timezone_set()``.
|
||||
|
||||
This is very important to handle correctly if your application runs on different serves or is moved from one to another server
|
||||
(with different timezone settings). You have to make sure that the timezone is the correct one
|
||||
on all this systems.
|
||||
|
||||
Handling different Timezones with the DateTime Type
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you first come across the requirement to save different you are still optimistic to manage this mess,
|
||||
however let me crush your expectations fast. There is not a single database out there (supported by Doctrine 2)
|
||||
that supports timezones correctly. Correctly here means that you can cover all the use-cases that
|
||||
can come up with timezones. If you don't believe me you should read up on `Storing DateTime
|
||||
in Databases <http://derickrethans.nl/storing-date-time-in-database.html>`_.
|
||||
|
||||
The problem is simple. Not a single database vendor saves the timezone, only the differences to UTC.
|
||||
However with frequent daylight saving and political timezone changes you can have a UTC offset that moves
|
||||
in different offset directions depending on the real location.
|
||||
|
||||
The solution for this dilemma is simple. Don't use timezones with DateTime and Doctrine 2. However there is a workaround
|
||||
that even allows correct date-time handling with timezones:
|
||||
|
||||
1. Always convert any DateTime instance to UTC.
|
||||
2. Only set Timezones for displaying purposes
|
||||
3. Save the Timezone in the Entity for persistence.
|
||||
|
||||
Say we have an application for an international postal company and employees insert events regarding postal-package
|
||||
around the world, in their current timezones. To determine the exact time an event occurred means to save both
|
||||
the UTC time at the time of the booking and the timezone the event happened in.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
namespace DoctrineExtensions\DBAL\Types;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\ConversionException;
|
||||
|
||||
class UTCDateTimeType extends DateTimeType
|
||||
{
|
||||
static private $utc = null;
|
||||
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
return $value->format($platform->getDateTimeFormatString(),
|
||||
(self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone('UTC'))
|
||||
);
|
||||
}
|
||||
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$val = \DateTime::createFromFormat(
|
||||
$platform->getDateTimeFormatString(),
|
||||
$value,
|
||||
(self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone('UTC'))
|
||||
);
|
||||
if (!$val) {
|
||||
throw ConversionException::conversionFailed($value, $this->getName());
|
||||
}
|
||||
return $val;
|
||||
}
|
||||
}
|
||||
|
||||
This database type makes sure that every DateTime instance is always saved in UTC, relative
|
||||
to the current timezone that the passed DateTime instance has. To be able to transform these values
|
||||
back into their real timezone you have to save the timezone in a separate field of the entity
|
||||
requiring timezoned datetimes:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace Shipping;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class Event
|
||||
{
|
||||
/** @Column(type="datetime") */
|
||||
private $created;
|
||||
|
||||
/** @Column(type="string") */
|
||||
private $timezone;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $localized = false;
|
||||
|
||||
public function __construct(\DateTime $createDate)
|
||||
{
|
||||
$this->localized = true;
|
||||
$this->created = $createDate;
|
||||
$this->timezone = $createDate->getTimeZone()->getName();
|
||||
}
|
||||
|
||||
public function getCreated()
|
||||
{
|
||||
if (!$this->localized) {
|
||||
$this->created->setTimeZone(new \DateTimeZone($this->timezone));
|
||||
}
|
||||
return $this->created;
|
||||
}
|
||||
}
|
||||
|
||||
This snippet makes use of the previously discussed "changeset by reference only" property of
|
||||
objects. That means a new DateTime will only be used during updating if the reference
|
||||
changes between retrieval and flush operation. This means we can easily go and modify
|
||||
the instance by setting the previous local timezone.
|
||||
122
docs/en/index.rst
Normal file
122
docs/en/index.rst
Normal file
@@ -0,0 +1,122 @@
|
||||
Welcome to Doctrine 2 ORM's documentation!
|
||||
==========================================
|
||||
|
||||
The Doctrine documentation is comprised of tutorials, a reference section and
|
||||
cookbook articles that explain different parts of the Object Relational mapper.
|
||||
|
||||
Doctrine DBAL and Doctrine Common both have their own documentation.
|
||||
|
||||
Getting Help
|
||||
------------
|
||||
|
||||
If this documentation is not helping to answer questions you have about
|
||||
Doctrine ORM don't panic. You can get help from different sources:
|
||||
|
||||
- There is a :doc:`FAQ <reference/faq>` with answers to frequent questions.
|
||||
- The `Doctrine Mailing List <http://groups.google.com/group/doctrine-user>`_
|
||||
- Internet Relay Chat (IRC) in `#doctrine on Freenode <irc://irc.freenode.net/doctrine>`_
|
||||
- Report a bug on `JIRA <http://www.doctrine-project.org/jira>`_.
|
||||
- On `Twitter <https://twitter.com/search/%23doctrine2>`_ with ``#doctrine2``
|
||||
- On `StackOverflow <http://stackoverflow.com/questions/tagged/doctrine2>`_
|
||||
|
||||
If you need more structure over the different topics you can browse the :doc:`table
|
||||
of contents <toc>`.
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
* **Tutorial**:
|
||||
:doc:`Getting Started with Doctrine <tutorials/getting-started>`
|
||||
|
||||
* **Setup**:
|
||||
:doc:`Installation & Configuration <reference/configuration>`
|
||||
|
||||
Mapping Objects onto a Database
|
||||
-------------------------------
|
||||
|
||||
* **Mapping**:
|
||||
:doc:`Objects <reference/basic-mapping>` |
|
||||
:doc:`Associations <reference/association-mapping>` |
|
||||
:doc:`Inheritance <reference/inheritance-mapping>`
|
||||
|
||||
* **Drivers**:
|
||||
:doc:`Docblock Annotations <reference/annotations-reference>` |
|
||||
:doc:`XML <reference/xml-mapping>` |
|
||||
:doc:`YAML <reference/yaml-mapping>` |
|
||||
:doc:`PHP <reference/php-mapping>`
|
||||
|
||||
Working with Objects
|
||||
--------------------
|
||||
|
||||
* **Basic Reference**:
|
||||
:doc:`Entities <reference/working-with-objects>` |
|
||||
:doc:`Associations <reference/working-with-associations>` |
|
||||
:doc:`Events <reference/events>`
|
||||
|
||||
* **Query Reference**:
|
||||
:doc:`DQL <reference/dql-doctrine-query-language>` |
|
||||
:doc:`QueryBuilder <reference/query-builder>` |
|
||||
:doc:`Native SQL <reference/native-sql>`
|
||||
|
||||
* **Internals**:
|
||||
:doc:`Internals explained <reference/unitofwork>` |
|
||||
:doc:`Associations <reference/unitofwork-associations>`
|
||||
|
||||
Advanced Topics
|
||||
---------------
|
||||
|
||||
* :doc:`Architecture <reference/architecture>`
|
||||
* :doc:`Advanced Configuration <reference/advanced-configuration>`
|
||||
* :doc:`Limitations and 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>`
|
||||
|
||||
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>`
|
||||
|
||||
Cookbook
|
||||
--------
|
||||
|
||||
* **Patterns**:
|
||||
:doc:`Aggregate Fields <cookbook/aggregate-fields>` |
|
||||
:doc:`Decorator Pattern <cookbook/decorator-pattern>` |
|
||||
:doc:`Strategy Pattern <cookbook/strategy-cookbook-introduction>`
|
||||
|
||||
* **DQL Extension Points**:
|
||||
:doc:`DQL Custom Walkers <cookbook/dql-custom-walkers>` |
|
||||
:doc:`DQL User-Defined-Functions <cookbook/dql-user-defined-functions>`
|
||||
|
||||
* **Implementation**:
|
||||
:doc:`Array Access <cookbook/implementing-arrayaccess-for-domain-objects>` |
|
||||
:doc:`Notify ChangeTracking Example <cookbook/implementing-the-notify-changetracking-policy>` |
|
||||
:doc:`Using Wakeup Or Clone <cookbook/implementing-wakeup-or-clone>` |
|
||||
:doc:`Working with DateTime <cookbook/working-with-datetime>` |
|
||||
:doc:`Validation <cookbook/validation-of-entities>` |
|
||||
:doc:`Entities in the Session <cookbook/entities-in-session>` |
|
||||
:doc:`Keeping your Modules independent <cookbook/resolve-target-entity-listener>`
|
||||
|
||||
* **Integration into Frameworks/Libraries**
|
||||
:doc:`CodeIgniter <cookbook/integrating-with-codeigniter>`
|
||||
|
||||
* **Hidden Gems**
|
||||
:doc:`Prefixing Table Name <cookbook/sql-table-prefixes>`
|
||||
|
||||
* **Custom Datatypes**
|
||||
:doc:`MySQL Enums <cookbook/mysql-enums>`
|
||||
:doc:`Advanced Field Value Conversion <cookbook/advanced-field-value-conversion-using-custom-mapping-types>`
|
||||
|
||||
113
docs/en/make.bat
Normal file
113
docs/en/make.bat
Normal file
@@ -0,0 +1,113 @@
|
||||
@ECHO OFF
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
set SPHINXBUILD=sphinx-build
|
||||
set BUILDDIR=_build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "help" (
|
||||
:help
|
||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||
echo. html to make standalone HTML files
|
||||
echo. dirhtml to make HTML files named index.html in directories
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and a HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||
del /q /s %BUILDDIR%\*
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Doctrine2ORM.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Doctrine2ORM.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||
echo.
|
||||
echo.The overview file is in %BUILDDIR%/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in %BUILDDIR%/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
:end
|
||||
432
docs/en/reference/advanced-configuration.rst
Normal file
432
docs/en/reference/advanced-configuration.rst
Normal file
@@ -0,0 +1,432 @@
|
||||
Advanced Configuration
|
||||
======================
|
||||
|
||||
The configuration of the EntityManager requires a
|
||||
``Doctrine\ORM\Configuration`` instance as well as some database
|
||||
connection parameters. This example shows all the potential
|
||||
steps of configuration.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\EntityManager,
|
||||
Doctrine\ORM\Configuration;
|
||||
|
||||
// ...
|
||||
|
||||
if ($applicationMode == "development") {
|
||||
$cache = new \Doctrine\Common\Cache\ArrayCache;
|
||||
} else {
|
||||
$cache = new \Doctrine\Common\Cache\ApcCache;
|
||||
}
|
||||
|
||||
$config = new Configuration;
|
||||
$config->setMetadataCacheImpl($cache);
|
||||
$driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
|
||||
$config->setMetadataDriverImpl($driverImpl);
|
||||
$config->setQueryCacheImpl($cache);
|
||||
$config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies');
|
||||
$config->setProxyNamespace('MyProject\Proxies');
|
||||
|
||||
if ($applicationMode == "development") {
|
||||
$config->setAutoGenerateProxyClasses(true);
|
||||
} else {
|
||||
$config->setAutoGenerateProxyClasses(false);
|
||||
}
|
||||
|
||||
$connectionOptions = array(
|
||||
'driver' => 'pdo_sqlite',
|
||||
'path' => 'database.sqlite'
|
||||
);
|
||||
|
||||
$em = EntityManager::create($connectionOptions, $config);
|
||||
|
||||
.. note::
|
||||
|
||||
Do not use Doctrine without a metadata and query cache!
|
||||
Doctrine is optimized for working with caches. The main
|
||||
parts in Doctrine that are optimized for caching are the metadata
|
||||
mapping information with the metadata cache and the DQL to SQL
|
||||
conversions with the query cache. These 2 caches require only an
|
||||
absolute minimum of memory yet they heavily improve the runtime
|
||||
performance of Doctrine. The recommended cache driver to use with
|
||||
Doctrine is `APC <http://www.php.net/apc>`_. APC provides you with
|
||||
an opcode-cache (which is highly recommended anyway) and a very
|
||||
fast in-memory cache storage that you can use for the metadata and
|
||||
query caches as seen in the previous code snippet.
|
||||
|
||||
Configuration Options
|
||||
---------------------
|
||||
|
||||
The following sections describe all the configuration options
|
||||
available on a ``Doctrine\ORM\Configuration`` instance.
|
||||
|
||||
Proxy Directory (***REQUIRED***)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config->setProxyDir($dir);
|
||||
$config->getProxyDir();
|
||||
|
||||
Gets or sets the directory where Doctrine generates any proxy
|
||||
classes. For a detailed explanation on proxy classes and how they
|
||||
are used in Doctrine, refer to the "Proxy Objects" section further
|
||||
down.
|
||||
|
||||
Proxy Namespace (***REQUIRED***)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config->setProxyNamespace($namespace);
|
||||
$config->getProxyNamespace();
|
||||
|
||||
Gets or sets the namespace to use for generated proxy classes. For
|
||||
a detailed explanation on proxy classes and how they are used in
|
||||
Doctrine, refer to the "Proxy Objects" section further down.
|
||||
|
||||
Metadata Driver (***REQUIRED***)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config->setMetadataDriverImpl($driver);
|
||||
$config->getMetadataDriverImpl();
|
||||
|
||||
Gets or sets the metadata driver implementation that is used by
|
||||
Doctrine to acquire the object-relational metadata for your
|
||||
classes.
|
||||
|
||||
There are currently 4 available implementations:
|
||||
|
||||
|
||||
- ``Doctrine\ORM\Mapping\Driver\AnnotationDriver``
|
||||
- ``Doctrine\ORM\Mapping\Driver\XmlDriver``
|
||||
- ``Doctrine\ORM\Mapping\Driver\YamlDriver``
|
||||
- ``Doctrine\ORM\Mapping\Driver\DriverChain``
|
||||
|
||||
Throughout the most part of this manual the AnnotationDriver is
|
||||
used in the examples. For information on the usage of the XmlDriver
|
||||
or YamlDriver please refer to the dedicated chapters
|
||||
``XML Mapping`` and ``YAML Mapping``.
|
||||
|
||||
The annotation driver can be configured with a factory method on
|
||||
the ``Doctrine\ORM\Configuration``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
|
||||
$config->setMetadataDriverImpl($driverImpl);
|
||||
|
||||
The path information to the entities is required for the annotation
|
||||
driver, because otherwise mass-operations on all entities through
|
||||
the console could not work correctly. All of metadata drivers
|
||||
accept either a single directory as a string or an array of
|
||||
directories. With this feature a single driver can support multiple
|
||||
directories of Entities.
|
||||
|
||||
Metadata Cache (***RECOMMENDED***)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config->setMetadataCacheImpl($cache);
|
||||
$config->getMetadataCacheImpl();
|
||||
|
||||
Gets or sets the cache implementation to use for caching metadata
|
||||
information, that is, all the information you supply via
|
||||
annotations, xml or yaml, so that they do not need to be parsed and
|
||||
loaded from scratch on every single request which is a waste of
|
||||
resources. The cache implementation must implement the
|
||||
``Doctrine\Common\Cache\Cache`` interface.
|
||||
|
||||
Usage of a metadata cache is highly recommended.
|
||||
|
||||
The recommended implementations for production are:
|
||||
|
||||
|
||||
- ``Doctrine\Common\Cache\ApcCache``
|
||||
- ``Doctrine\Common\Cache\MemcacheCache``
|
||||
- ``Doctrine\Common\Cache\XcacheCache``
|
||||
- ``Doctrine\Common\Cache\RedisCache``
|
||||
|
||||
For development you should use the
|
||||
``Doctrine\Common\Cache\ArrayCache`` which only caches data on a
|
||||
per-request basis.
|
||||
|
||||
Query Cache (***RECOMMENDED***)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config->setQueryCacheImpl($cache);
|
||||
$config->getQueryCacheImpl();
|
||||
|
||||
Gets or sets the cache implementation to use for caching DQL
|
||||
queries, that is, the result of a DQL parsing process that includes
|
||||
the final SQL as well as meta information about how to process the
|
||||
SQL result set of a query. Note that the query cache does not
|
||||
affect query results. You do not get stale data. This is a pure
|
||||
optimization cache without any negative side-effects (except some
|
||||
minimal memory usage in your cache).
|
||||
|
||||
Usage of a query cache is highly recommended.
|
||||
|
||||
The recommended implementations for production are:
|
||||
|
||||
|
||||
- ``Doctrine\Common\Cache\ApcCache``
|
||||
- ``Doctrine\Common\Cache\MemcacheCache``
|
||||
- ``Doctrine\Common\Cache\XcacheCache``
|
||||
- ``Doctrine\Common\Cache\RedisCache``
|
||||
|
||||
For development you should use the
|
||||
``Doctrine\Common\Cache\ArrayCache`` which only caches data on a
|
||||
per-request basis.
|
||||
|
||||
SQL Logger (***Optional***)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config->setSQLLogger($logger);
|
||||
$config->getSQLLogger();
|
||||
|
||||
Gets or sets the logger to use for logging all SQL statements
|
||||
executed by Doctrine. The logger class must implement the
|
||||
``Doctrine\DBAL\Logging\SQLLogger`` interface. A simple default
|
||||
implementation that logs to the standard output using ``echo`` and
|
||||
``var_dump`` can be found at
|
||||
``Doctrine\DBAL\Logging\EchoSQLLogger``.
|
||||
|
||||
Auto-generating Proxy Classes (***OPTIONAL***)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config->setAutoGenerateProxyClasses($bool);
|
||||
$config->getAutoGenerateProxyClasses();
|
||||
|
||||
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``.
|
||||
|
||||
Development vs Production Configuration
|
||||
---------------------------------------
|
||||
|
||||
You should code your Doctrine2 bootstrapping with two different
|
||||
runtime models in mind. There are some serious benefits of using
|
||||
APC or Memcache in production. In development however this will
|
||||
frequently give you fatal errors, when you change your entities and
|
||||
the cache still keeps the outdated metadata. That is why we
|
||||
recommend the ``ArrayCache`` for development.
|
||||
|
||||
Furthermore you should have the Auto-generating Proxy Classes
|
||||
option to true in development and to false in production. If this
|
||||
option is set to ``TRUE`` it can seriously hurt your script
|
||||
performance if several proxy classes are re-generated during script
|
||||
execution. Filesystem calls of that magnitude can even slower than
|
||||
all the database queries Doctrine issues. Additionally writing a
|
||||
proxy sets an exclusive file lock which can cause serious
|
||||
performance bottlenecks in systems with regular concurrent
|
||||
requests.
|
||||
|
||||
Connection Options
|
||||
------------------
|
||||
|
||||
The ``$connectionOptions`` passed as the first argument to
|
||||
``EntityManager::create()`` has to be either an array or an
|
||||
instance of ``Doctrine\DBAL\Connection``. If an array is passed it
|
||||
is directly passed along to the DBAL Factory
|
||||
``Doctrine\DBAL\DriverManager::getConnection()``. The DBAL
|
||||
configuration is explained in the
|
||||
`DBAL section <./../../../../../dbal/2.0/docs/reference/configuration/en>`_.
|
||||
|
||||
Proxy Objects
|
||||
-------------
|
||||
|
||||
A proxy object is an object that is put in place or used instead of
|
||||
the "real" object. A proxy object can add behavior to the object
|
||||
being proxied without that object being aware of it. In Doctrine 2,
|
||||
proxy objects are used to realize several features but mainly for
|
||||
transparent lazy-loading.
|
||||
|
||||
Proxy objects with their lazy-loading facilities help to keep the
|
||||
subset of objects that are already in memory connected to the rest
|
||||
of the objects. This is an essential property as without it there
|
||||
would always be fragile partial objects at the outer edges of your
|
||||
object graph.
|
||||
|
||||
Doctrine 2 implements a variant of the proxy pattern where it
|
||||
generates classes that extend your entity classes and adds
|
||||
lazy-loading capabilities to them. Doctrine can then give you an
|
||||
instance of such a proxy class whenever you request an object of
|
||||
the class being proxied. This happens in two situations:
|
||||
|
||||
Reference Proxies
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The method ``EntityManager#getReference($entityName, $identifier)``
|
||||
lets you obtain a reference to an entity for which the identifier
|
||||
is known, without loading that entity from the database. This is
|
||||
useful, for example, as a performance enhancement, when you want to
|
||||
establish an association to an entity for which you have the
|
||||
identifier. You could simply do this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// $em instanceof EntityManager, $cart instanceof MyProject\Model\Cart
|
||||
// $itemId comes from somewhere, probably a request parameter
|
||||
$item = $em->getReference('MyProject\Model\Item', $itemId);
|
||||
$cart->addItem($item);
|
||||
|
||||
Here, we added an Item to a Cart without loading the Item from the
|
||||
database. If you invoke any method on the Item instance, it would
|
||||
fully initialize its state transparently from the database. Here
|
||||
$item is actually an instance of the proxy class that was generated
|
||||
for the Item class but your code does not need to care. In fact it
|
||||
**should not care**. Proxy objects should be transparent to your
|
||||
code.
|
||||
|
||||
Association proxies
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The second most important situation where Doctrine uses proxy
|
||||
objects is when querying for objects. Whenever you query for an
|
||||
object that has a single-valued association to another object that
|
||||
is configured LAZY, without joining that association in the same
|
||||
query, Doctrine puts proxy objects in place where normally the
|
||||
associated object would be. Just like other proxies it will
|
||||
transparently initialize itself on first access.
|
||||
|
||||
.. note::
|
||||
|
||||
Joining an association in a DQL or native query
|
||||
essentially means eager loading of that association in that query.
|
||||
This will override the 'fetch' option specified in the mapping for
|
||||
that association, but only for that query.
|
||||
|
||||
|
||||
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:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ ./doctrine orm:generate-proxies
|
||||
|
||||
Autoloading Proxies
|
||||
-------------------
|
||||
|
||||
When you deserialize proxy objects from the session or any other storage
|
||||
it is necessary to have an autoloading mechanism in place for these classes.
|
||||
For implementation reasons Proxy class names are not PSR-0 compliant. This
|
||||
means that you have to register a special autoloader for these classes:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Proxy\Autoloader;
|
||||
|
||||
$proxyDir = "/path/to/proxies";
|
||||
$proxyNamespace = "MyProxies";
|
||||
|
||||
Autoloader::register($proxyDir, $proxyNamespace);
|
||||
|
||||
If you want to execute additional logic to intercept the proxy file not found
|
||||
state you can pass a closure as the third argument. It will be called with
|
||||
the arguments proxydir, namespace and className when the proxy file could not
|
||||
be found.
|
||||
|
||||
Multiple Metadata Sources
|
||||
-------------------------
|
||||
|
||||
When using different components using Doctrine 2 you may end up
|
||||
with them using two different metadata drivers, for example XML and
|
||||
YAML. You can use the DriverChain Metadata implementations to
|
||||
aggregate these drivers based on namespaces:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Mapping\Driver\DriverChain;
|
||||
|
||||
$chain = new DriverChain();
|
||||
$chain->addDriver($xmlDriver, 'Doctrine\Tests\Models\Company');
|
||||
$chain->addDriver($yamlDriver, 'Doctrine\Tests\ORM\Mapping');
|
||||
|
||||
Based on the namespace of the entity the loading of entities is
|
||||
delegated to the appropriate driver. The chain semantics come from
|
||||
the fact that the driver loops through all namespaces and matches
|
||||
the entity class name against the namespace using a
|
||||
``strpos() === 0`` call. This means you need to order the drivers
|
||||
correctly if sub-namespaces use different metadata driver
|
||||
implementations.
|
||||
|
||||
|
||||
Default Repository (***OPTIONAL***)
|
||||
-----------------------------------
|
||||
|
||||
Specifies the FQCN of a subclass of the EntityRepository.
|
||||
That will be available for all entities without a custom repository class.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config->setDefaultRepositoryClassName($fqcn);
|
||||
$config->getDefaultRepositoryClassName();
|
||||
|
||||
The default value is ``Doctrine\ORM\EntityRepository``.
|
||||
Any repository class must be a subclass of EntityRepository otherwise you got an ORMException
|
||||
|
||||
Setting up the Console
|
||||
----------------------
|
||||
|
||||
Doctrine uses the Symfony Console component for generating the command
|
||||
line interface. You can take a look at the ``vendor/bin/doctrine.php``
|
||||
script and the ``Doctrine\ORM\Tools\Console\ConsoleRunner`` command
|
||||
for inspiration how to setup the cli.
|
||||
|
||||
In general the required code looks like this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cli = new Application('Doctrine Command Line Interface', \Doctrine\ORM\Version::VERSION);
|
||||
$cli->setCatchExceptions(true);
|
||||
$cli->setHelperSet($helperSet);
|
||||
Doctrine\ORM\Tools\Console\ConsoleRunner::addCommands($cli);
|
||||
$cli->run();
|
||||
|
||||
1114
docs/en/reference/annotations-reference.rst
Normal file
1114
docs/en/reference/annotations-reference.rst
Normal file
File diff suppressed because it is too large
Load Diff
197
docs/en/reference/architecture.rst
Normal file
197
docs/en/reference/architecture.rst
Normal file
@@ -0,0 +1,197 @@
|
||||
Architecture
|
||||
============
|
||||
|
||||
This chapter gives an overview of the overall architecture,
|
||||
terminology and constraints of Doctrine 2. It is recommended to
|
||||
read this chapter carefully.
|
||||
|
||||
Using an Object-Relational Mapper
|
||||
---------------------------------
|
||||
|
||||
As the term ORM already hints at, Doctrine 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
|
||||
well.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
Doctrine 2 requires a minimum of PHP 5.3.0. For greatly improved
|
||||
performance it is also recommended that you use APC with PHP.
|
||||
|
||||
Doctrine 2 Packages
|
||||
-------------------
|
||||
|
||||
Doctrine 2 is divided into three main packages.
|
||||
|
||||
- Common
|
||||
- DBAL (includes Common)
|
||||
- ORM (includes DBAL+Common)
|
||||
|
||||
This manual mainly covers the ORM package, sometimes touching parts
|
||||
of the underlying DBAL and Common packages. The Doctrine code base
|
||||
is split in to these packages for a few reasons and they are to...
|
||||
|
||||
|
||||
- ...make things more maintainable and decoupled
|
||||
- ...allow you to use the code in Doctrine Common without the ORM
|
||||
or DBAL
|
||||
- ...allow you to use the DBAL without the ORM
|
||||
|
||||
The Common Package
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The Common package contains highly reusable components that have no
|
||||
dependencies beyond the package itself (and PHP, of course). The
|
||||
root namespace of the Common package is ``Doctrine\Common``.
|
||||
|
||||
The DBAL Package
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
The DBAL package contains an enhanced database abstraction layer on
|
||||
top of PDO but is not strongly bound to PDO. The purpose of this
|
||||
layer is to provide a single API that bridges most of the
|
||||
differences between the different RDBMS vendors. The root namespace
|
||||
of the DBAL package is ``Doctrine\DBAL``.
|
||||
|
||||
The ORM Package
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
The ORM package contains the object-relational mapping toolkit that
|
||||
provides transparent relational persistence for plain PHP objects.
|
||||
The root namespace of the ORM package is ``Doctrine\ORM``.
|
||||
|
||||
Terminology
|
||||
-----------
|
||||
|
||||
Entities
|
||||
~~~~~~~~
|
||||
|
||||
An entity is a lightweight, persistent domain object. An entity can
|
||||
be any regular PHP class observing the following restrictions:
|
||||
|
||||
|
||||
- An entity class must not be final or contain final methods.
|
||||
- All persistent properties/field of any entity class should
|
||||
always be private or protected, otherwise lazy-loading might not
|
||||
work as expected. In case you serialize entities (for example Session)
|
||||
properties should be protected (See Serialize section below).
|
||||
- An entity class must not implement ``__clone`` or
|
||||
:doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
|
||||
- An entity class must not implement ``__wakeup`` or
|
||||
:doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
|
||||
Also consider implementing
|
||||
`Serializable <http://de3.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
|
||||
property with the same name. That is, if B inherits from A then B
|
||||
must not have a mapped field with the same name as an already
|
||||
mapped field that is inherited from A.
|
||||
- An entity cannot make use of func_get_args() to implement variable parameters.
|
||||
Generated proxies do not support this for performance reasons and your code might
|
||||
actually fail to work when violating this restriction.
|
||||
|
||||
Entities support inheritance, polymorphic associations, and
|
||||
polymorphic queries. Both abstract and concrete classes can be
|
||||
entities. Entities may extend non-entity classes as well as entity
|
||||
classes, and non-entity classes may extend entity classes.
|
||||
|
||||
.. note::
|
||||
|
||||
The constructor of an entity is only ever invoked when
|
||||
*you* construct a new instance with the *new* keyword. Doctrine
|
||||
never calls entity constructors, thus you are free to use them as
|
||||
you wish and even have it require arguments of any type.
|
||||
|
||||
|
||||
Entity states
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
An entity instance can be characterized as being NEW, MANAGED,
|
||||
DETACHED or REMOVED.
|
||||
|
||||
|
||||
- A NEW entity instance has no persistent identity, and is not yet
|
||||
associated with an EntityManager and a UnitOfWork (i.e. those just
|
||||
created with the "new" operator).
|
||||
- A MANAGED entity instance is an instance with a persistent
|
||||
identity that is associated with an EntityManager and whose
|
||||
persistence is thus managed.
|
||||
- A DETACHED entity instance is an instance with a persistent
|
||||
identity that is not (or no longer) associated with an
|
||||
EntityManager and a UnitOfWork.
|
||||
- A REMOVED entity instance is an instance with a persistent
|
||||
identity, associated with an EntityManager, that will be removed
|
||||
from the database upon transaction commit.
|
||||
|
||||
.. _architecture_persistent_fields:
|
||||
|
||||
Persistent fields
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The persistent state of an entity is represented by instance
|
||||
variables. An instance variable must be directly accessed only from
|
||||
within the methods of the entity by the entity instance itself.
|
||||
Instance variables must not be accessed by clients of the entity.
|
||||
The state of the entity is available to clients only through the
|
||||
entity’s methods, i.e. accessor methods (getter/setter methods) or
|
||||
other business methods.
|
||||
|
||||
Collection-valued persistent fields and properties must be defined
|
||||
in terms of the ``Doctrine\Common\Collections\Collection``
|
||||
interface. The collection implementation type may be used by the
|
||||
application to initialize fields or properties before the entity is
|
||||
made persistent. Once the entity becomes managed (or detached),
|
||||
subsequent access must be through the interface type.
|
||||
|
||||
Serializing entities
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Serializing entities can be problematic and is not really
|
||||
recommended, at least not as long as an entity instance still holds
|
||||
references to proxy objects or is still managed by an
|
||||
EntityManager. If you intend to serialize (and unserialize) entity
|
||||
instances that still hold references to proxy objects you may run
|
||||
into problems with private properties because of technical
|
||||
limitations. Proxy objects implement ``__sleep`` and it is not
|
||||
possible for ``__sleep`` to return names of private properties in
|
||||
parent classes. On the other hand it is not a solution for proxy
|
||||
objects to implement ``Serializable`` because Serializable does not
|
||||
work well with any potential cyclic object references (at least we
|
||||
did not find a way yet, if you did, please contact us).
|
||||
|
||||
The EntityManager
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``EntityManager`` class is a central access point to the ORM
|
||||
functionality provided by Doctrine 2. The ``EntityManager`` API is
|
||||
used to manage the persistence of your objects and to query for
|
||||
persistent objects.
|
||||
|
||||
Transactional write-behind
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
An ``EntityManager`` and the underlying ``UnitOfWork`` employ a
|
||||
strategy called "transactional write-behind" that delays the
|
||||
execution of SQL statements in order to execute them in the most
|
||||
efficient way and to execute them at the end of a transaction so
|
||||
that all write locks are quickly released. You should see Doctrine
|
||||
as a tool to synchronize your in-memory objects with the database
|
||||
in well defined units of work. Work with your objects and modify
|
||||
them as usual and when you're done call ``EntityManager#flush()``
|
||||
to make your changes persistent.
|
||||
|
||||
The Unit of Work
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Internally an ``EntityManager`` uses a ``UnitOfWork``, which is a
|
||||
typical implementation of the
|
||||
`Unit of Work pattern <http://martinfowler.com/eaaCatalog/unitOfWork.html>`_,
|
||||
to keep track of all the things that need to be done the next time
|
||||
``flush`` is invoked. You usually do not directly interact with a
|
||||
``UnitOfWork`` but with the ``EntityManager`` instead.
|
||||
|
||||
|
||||
1145
docs/en/reference/association-mapping.rst
Normal file
1145
docs/en/reference/association-mapping.rst
Normal file
File diff suppressed because it is too large
Load Diff
683
docs/en/reference/basic-mapping.rst
Normal file
683
docs/en/reference/basic-mapping.rst
Normal file
@@ -0,0 +1,683 @@
|
||||
Basic Mapping
|
||||
=============
|
||||
|
||||
This chapter explains the basic mapping of objects and properties.
|
||||
Mapping of associations will be covered in the next chapter
|
||||
"Association Mapping".
|
||||
|
||||
Mapping Drivers
|
||||
---------------
|
||||
|
||||
Doctrine provides several different ways for specifying
|
||||
object-relational mapping metadata:
|
||||
|
||||
|
||||
- Docblock Annotations
|
||||
- XML
|
||||
- YAML
|
||||
|
||||
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.
|
||||
|
||||
.. 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.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
class MyPersistentClass
|
||||
{
|
||||
//...
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="MyPersistentClass">
|
||||
<!-- ... -->
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
MyPersistentClass:
|
||||
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:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
* @Table(name="my_persistent_class")
|
||||
*/
|
||||
class MyPersistentClass
|
||||
{
|
||||
//...
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="MyPersistentClass" table="my_persistent_class">
|
||||
<!-- ... -->
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
MyPersistentClass:
|
||||
type: entity
|
||||
table: my_persistent_class
|
||||
# ...
|
||||
|
||||
Now instances of MyPersistentClass will be persisted into a table
|
||||
named ``my_persistent_class``.
|
||||
|
||||
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.
|
||||
|
||||
For 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.
|
||||
- ``decimal``: Type that maps a SQL DECIMAL to a PHP string.
|
||||
- ``date``: Type that maps a SQL DATETIME to a PHP DateTime
|
||||
object.
|
||||
- ``time``: Type that maps a SQL TIME to a PHP DateTime object.
|
||||
- ``datetime``: Type that maps a SQL DATETIME/TIMESTAMP to a PHP
|
||||
DateTime object.
|
||||
- ``datetimetz``: Type that maps a SQL DATETIME/TIMESTAMP to a PHP
|
||||
DateTime object with timezone.
|
||||
- ``text``: Type that maps a SQL CLOB to a PHP string.
|
||||
- ``object``: Type that maps a SQL CLOB to a PHP object using
|
||||
``serialize()`` and ``unserialize()``
|
||||
- ``array``: Type that maps a SQL CLOB to a PHP array using
|
||||
``serialize()`` and ``unserialize()``
|
||||
- ``simple_array``: Type that maps a SQL CLOB to a PHP array using
|
||||
``implode()`` and ``explode()``, with a comma as delimiter. *IMPORTANT*
|
||||
Only use this type if you are sure that your values cannot contain a ",".
|
||||
- ``json_array``: Type that maps a SQL CLOB to a PHP array using
|
||||
``json_encode()`` and ``json_decode()``
|
||||
- ``float``: Type that maps a SQL Float (Double Precision) to a
|
||||
PHP double. *IMPORTANT*: Works only with locale settings that use
|
||||
decimal points as separator.
|
||||
- ``guid``: Type that maps a database GUID/UUID to a PHP string. Defaults to
|
||||
varchar but uses a specific type if the platform supports it.
|
||||
- ``blob``: Type that maps 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.
|
||||
|
||||
.. 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.
|
||||
|
||||
.. warning::
|
||||
|
||||
All Date types assume that you are exclusively using the default timezone
|
||||
set by `date_default_timezone_set() <http://docs.php.net/manual/en/function.date-default-timezone-set.php>`_
|
||||
or by the php.ini configuration ``date.timezone``. Working with
|
||||
different timezones will cause troubles and unexpected behavior.
|
||||
|
||||
If you need specific timezone handling you have to handle this
|
||||
in your domain, converting all the values back and forth from UTC.
|
||||
There is also a :doc:`cookbook entry <../cookbook/working-with-datetime>`
|
||||
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:
|
||||
|
||||
.. 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
|
||||
{
|
||||
/**
|
||||
* @Id @Column(type="integer")
|
||||
* @GeneratedValue
|
||||
*/
|
||||
private $id;
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="MyPersistentClass">
|
||||
<id name="id" type="integer">
|
||||
<generator strategy="AUTO" />
|
||||
</id>
|
||||
<field name="name" length="50" />
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
MyPersistentClass:
|
||||
type: entity
|
||||
id:
|
||||
id:
|
||||
type: integer
|
||||
generator:
|
||||
strategy: AUTO
|
||||
fields:
|
||||
name:
|
||||
length: 50
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
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.
|
||||
- ``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.
|
||||
- ``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
|
||||
(AUTO\_INCREMENT), MSSQL (IDENTITY) and PostgreSQL (SERIAL).
|
||||
- ``TABLE``: Tells Doctrine to use a separate table for ID
|
||||
generation. This strategy provides full portability.
|
||||
***This strategy is not yet implemented!***
|
||||
- ``NONE``: Tells Doctrine that the identifiers are assigned (and
|
||||
thus generated) by your code. The assignment must take place before
|
||||
a new entity is passed to ``EntityManager#persist``. NONE is the
|
||||
same as leaving off the @GeneratedValue entirely.
|
||||
|
||||
Sequence Generator
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The Sequence Generator can currently be used in conjunction with
|
||||
Oracle or Postgres and allows some additional configuration options
|
||||
besides specifying the sequence's name:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class User
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @GeneratedValue(strategy="SEQUENCE")
|
||||
* @SequenceGenerator(sequenceName="tablename_seq", initialValue=1, allocationSize=100)
|
||||
*/
|
||||
protected $id = null;
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="User">
|
||||
<id name="id" type="integer">
|
||||
<generator strategy="SEQUENCE" />
|
||||
<sequence-generator sequence-name="tablename_seq" allocation-size="100" initial-value="1" />
|
||||
</id>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
MyPersistentClass:
|
||||
type: entity
|
||||
id:
|
||||
id:
|
||||
type: integer
|
||||
generator:
|
||||
strategy: SEQUENCE
|
||||
sequenceGenerator:
|
||||
sequenceName: tablename_seq
|
||||
allocationSize: 100
|
||||
initialValue: 1
|
||||
|
||||
The initial value specifies at which value the sequence should
|
||||
start.
|
||||
|
||||
The allocationSize is a powerful feature to optimize INSERT
|
||||
performance of Doctrine. The allocationSize specifies by how much
|
||||
values the sequence is incremented whenever the next value is
|
||||
retrieved. If this is larger than 1 (one) Doctrine can generate
|
||||
identifier values for the allocationSizes amount of entities. In
|
||||
the above example with ``allocationSize=100`` Doctrine 2 would only
|
||||
need to access the sequence once to generate the identifiers for
|
||||
100 new entities.
|
||||
|
||||
*The default allocationSize for a @SequenceGenerator is currently 10.*
|
||||
|
||||
.. caution::
|
||||
|
||||
The allocationSize is detected by SchemaTool and
|
||||
transformed into an "INCREMENT BY " clause in the CREATE SEQUENCE
|
||||
statement. For a database schema created manually (and not
|
||||
SchemaTool) you have to make sure that the allocationSize
|
||||
configuration option is never larger than the actual sequences
|
||||
INCREMENT BY value, otherwise you may get duplicate keys.
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
It is possible to use strategy="AUTO" and at the same time
|
||||
specifying a @SequenceGenerator. In such a case, your custom
|
||||
sequence settings are used in the case where the preferred strategy
|
||||
of the underlying platform is SEQUENCE, such as for Oracle and
|
||||
PostgreSQL.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
To designate a composite primary key / identifier, simply put the
|
||||
@Id marker annotation on all fields that make up the primary key.
|
||||
|
||||
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:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Column(name="`number`", type="integer") */
|
||||
private $number;
|
||||
|
||||
Doctrine will then quote this column name in all SQL statements
|
||||
according to the used database platform.
|
||||
|
||||
.. warning::
|
||||
|
||||
Identifier Quoting is not supported for join column
|
||||
names or discriminator column names.
|
||||
|
||||
.. warning::
|
||||
|
||||
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).
|
||||
|
||||
|
||||
|
||||
179
docs/en/reference/batch-processing.rst
Normal file
179
docs/en/reference/batch-processing.rst
Normal file
@@ -0,0 +1,179 @@
|
||||
Batch Processing
|
||||
================
|
||||
|
||||
This chapter shows you how to accomplish bulk inserts, updates and
|
||||
deletes with Doctrine in an efficient way. The main problem with
|
||||
bulk operations is usually not to run out of memory and this is
|
||||
especially what the strategies presented here provide help with.
|
||||
|
||||
.. warning::
|
||||
|
||||
An ORM tool is not primarily well-suited for mass
|
||||
inserts, updates or deletions. Every RDBMS has its own, most
|
||||
effective way of dealing with such operations and if the options
|
||||
outlined below are not sufficient for your purposes we recommend
|
||||
you use the tools for your particular RDBMS for these bulk
|
||||
operations.
|
||||
|
||||
|
||||
Bulk Inserts
|
||||
------------
|
||||
|
||||
Bulk inserts in Doctrine are best performed in batches, taking
|
||||
advantage of the transactional write-behind behavior of an
|
||||
``EntityManager``. The following code shows an example for
|
||||
inserting 10000 objects with a batch size of 20. You may need to
|
||||
experiment with the batch size to find the size that works best for
|
||||
you. Larger batch sizes mean more prepared statement reuse
|
||||
internally but also mean more work during ``flush``.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$batchSize = 20;
|
||||
for ($i = 1; $i <= 10000; ++$i) {
|
||||
$user = new CmsUser;
|
||||
$user->setStatus('user');
|
||||
$user->setUsername('user' . $i);
|
||||
$user->setName('Mr.Smith-' . $i);
|
||||
$em->persist($user);
|
||||
if (($i % $batchSize) === 0) {
|
||||
$em->flush();
|
||||
$em->clear(); // Detaches all objects from Doctrine!
|
||||
}
|
||||
}
|
||||
|
||||
Bulk Updates
|
||||
------------
|
||||
|
||||
There are 2 possibilities for bulk updates with Doctrine.
|
||||
|
||||
DQL UPDATE
|
||||
~~~~~~~~~~
|
||||
|
||||
The by far most efficient way for bulk updates is to use a DQL
|
||||
UPDATE query. Example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$q = $em->createQuery('update MyProject\Model\Manager m set m.salary = m.salary * 0.9');
|
||||
$numUpdated = $q->execute();
|
||||
|
||||
Iterating results
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
An alternative solution for bulk updates is to use the
|
||||
``Query#iterate()`` facility to iterate over the query results step
|
||||
by step instead of loading the whole result into memory at once.
|
||||
The following example shows how to do this, combining the iteration
|
||||
with the batching strategy that was already used for bulk inserts:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$batchSize = 20;
|
||||
$i = 0;
|
||||
$q = $em->createQuery('select u from MyProject\Model\User u');
|
||||
$iterableResult = $q->iterate();
|
||||
foreach($iterableResult AS $row) {
|
||||
$user = $row[0];
|
||||
$user->increaseCredit();
|
||||
$user->calculateNewBonuses();
|
||||
if (($i % $batchSize) === 0) {
|
||||
$em->flush(); // Executes all updates.
|
||||
$em->clear(); // Detaches all objects from Doctrine!
|
||||
}
|
||||
++$i;
|
||||
}
|
||||
$em->flush();
|
||||
|
||||
.. note::
|
||||
|
||||
Iterating results is not possible with queries that
|
||||
fetch-join a collection-valued association. The nature of such SQL
|
||||
result sets is not suitable for incremental hydration.
|
||||
|
||||
|
||||
Bulk Deletes
|
||||
------------
|
||||
|
||||
There are two possibilities for bulk deletes with Doctrine. You can
|
||||
either issue a single DQL DELETE query or you can iterate over
|
||||
results removing them one at a time.
|
||||
|
||||
DQL DELETE
|
||||
~~~~~~~~~~
|
||||
|
||||
The by far most efficient way for bulk deletes is to use a DQL
|
||||
DELETE query.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$q = $em->createQuery('delete from MyProject\Model\Manager m where m.salary > 100000');
|
||||
$numDeleted = $q->execute();
|
||||
|
||||
Iterating results
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
An alternative solution for bulk deletes is to use the
|
||||
``Query#iterate()`` facility to iterate over the query results step
|
||||
by step instead of loading the whole result into memory at once.
|
||||
The following example shows how to do this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$batchSize = 20;
|
||||
$i = 0;
|
||||
$q = $em->createQuery('select u from MyProject\Model\User u');
|
||||
$iterableResult = $q->iterate();
|
||||
while (($row = $iterableResult->next()) !== false) {
|
||||
$em->remove($row[0]);
|
||||
if (($i % $batchSize) === 0) {
|
||||
$em->flush(); // Executes all deletions.
|
||||
$em->clear(); // Detaches all objects from Doctrine!
|
||||
}
|
||||
++$i;
|
||||
}
|
||||
$em->flush();
|
||||
|
||||
.. note::
|
||||
|
||||
Iterating results is not possible with queries that
|
||||
fetch-join a collection-valued association. The nature of such SQL
|
||||
result sets is not suitable for incremental hydration.
|
||||
|
||||
|
||||
Iterating Large Results for Data-Processing
|
||||
-------------------------------------------
|
||||
|
||||
You can use the ``iterate()`` method just to iterate over a large
|
||||
result and no UPDATE or DELETE intention. The ``IterableResult``
|
||||
instance returned from ``$query->iterate()`` implements the
|
||||
Iterator interface so you can process a large result without memory
|
||||
problems using the following approach:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$q = $this->_em->createQuery('select u from MyProject\Model\User u');
|
||||
$iterableResult = $q->iterate();
|
||||
foreach ($iterableResult AS $row) {
|
||||
// do stuff with the data in the row, $row[0] is always the object
|
||||
|
||||
// detach from Doctrine, so that it can be Garbage-Collected immediately
|
||||
$this->_em->detach($row[0]);
|
||||
}
|
||||
|
||||
.. note::
|
||||
|
||||
Iterating results is not possible with queries that
|
||||
fetch-join a collection-valued association. The nature of such SQL
|
||||
result sets is not suitable for incremental hydration.
|
||||
|
||||
|
||||
|
||||
127
docs/en/reference/best-practices.rst
Normal file
127
docs/en/reference/best-practices.rst
Normal file
@@ -0,0 +1,127 @@
|
||||
Best Practices
|
||||
==============
|
||||
|
||||
The best practices mentioned here that affect database
|
||||
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
|
||||
-------------------------------------------
|
||||
|
||||
It is important to constrain relationships as much as possible.
|
||||
This means:
|
||||
|
||||
|
||||
- Impose a traversal direction (avoid bidirectional associations
|
||||
if possible)
|
||||
- Eliminate nonessential associations
|
||||
|
||||
This has several benefits:
|
||||
|
||||
|
||||
- Reduced coupling in your domain model
|
||||
- Simpler code in your domain model (no need to maintain
|
||||
bidirectionality properly)
|
||||
- Less work for Doctrine
|
||||
|
||||
Avoid composite keys
|
||||
--------------------
|
||||
|
||||
Even though Doctrine fully supports composite keys it is best not
|
||||
to use them if possible. Composite keys require additional work by
|
||||
Doctrine and thus have a higher probability of errors.
|
||||
|
||||
Use events judiciously
|
||||
----------------------
|
||||
|
||||
The event system of Doctrine is great and fast. Even though making
|
||||
heavy use of events, especially lifecycle events, can have a
|
||||
negative impact on the performance of your application. Thus you
|
||||
should use events judiciously.
|
||||
|
||||
Use cascades judiciously
|
||||
------------------------
|
||||
|
||||
Automatic cascades of the persist/remove/merge/etc. operations are
|
||||
very handy but should be used wisely. Do NOT simply add all
|
||||
cascades to all associations. Think about which cascades actually
|
||||
do make sense for you for a particular association, given the
|
||||
scenarios it is most likely used in.
|
||||
|
||||
Don't use special characters
|
||||
----------------------------
|
||||
|
||||
Avoid using any non-ASCII characters in class, field, table or
|
||||
column names. Doctrine itself is not unicode-safe in many places
|
||||
and will not be until PHP itself is fully unicode-aware (PHP6).
|
||||
|
||||
Don't use identifier quoting
|
||||
----------------------------
|
||||
|
||||
Identifier quoting is a workaround for using reserved words that
|
||||
often causes problems in edge cases. Do not use identifier quoting
|
||||
and avoid using reserved words as table or column names.
|
||||
|
||||
Initialize collections in the constructor
|
||||
-----------------------------------------
|
||||
|
||||
It is recommended best practice to initialize any business
|
||||
collections in entities in the constructor. Example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace MyProject\Model;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
class User {
|
||||
private $addresses;
|
||||
private $articles;
|
||||
|
||||
public function __construct() {
|
||||
$this->addresses = new ArrayCollection;
|
||||
$this->articles = new ArrayCollection;
|
||||
}
|
||||
}
|
||||
|
||||
Don't map foreign keys to fields in an entity
|
||||
---------------------------------------------
|
||||
|
||||
Foreign keys have no meaning whatsoever in an object model. Foreign
|
||||
keys are how a relational database establishes relationships. Your
|
||||
object model establishes relationships through object references.
|
||||
Thus mapping foreign keys to object fields heavily leaks details of
|
||||
the relational model into the object model, something you really
|
||||
should not do.
|
||||
|
||||
Use explicit transaction demarcation
|
||||
------------------------------------
|
||||
|
||||
While Doctrine will automatically wrap all DML operations in a
|
||||
transaction on flush(), it is considered best practice to
|
||||
explicitly set the transaction boundaries yourself. Otherwise every
|
||||
single query is wrapped in a small transaction (Yes, SELECT
|
||||
queries, too) since you can not talk to your database outside of a
|
||||
transaction. While such short transactions for read-only (SELECT)
|
||||
queries generally don't have any noticeable performance impact, it
|
||||
is still preferable to use fewer, well-defined transactions that
|
||||
are established through explicit transaction boundaries.
|
||||
|
||||
|
||||
439
docs/en/reference/caching.rst
Normal file
439
docs/en/reference/caching.rst
Normal file
@@ -0,0 +1,439 @@
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
The interface defines the following methods for you to publicly
|
||||
use.
|
||||
|
||||
|
||||
- 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.
|
||||
|
||||
|
||||
- \_doFetch($id)
|
||||
- \_doContains($id)
|
||||
- \_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
|
||||
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
|
||||
these methods.
|
||||
|
||||
APC
|
||||
~~~
|
||||
|
||||
In order to use the APC cache driver you must have it compiled and
|
||||
enabled in your php.ini. You can read about APC
|
||||
`in the PHP Documentation <http://us2.php.net/apc>`_. It will give
|
||||
you a little background information about what it is and how you
|
||||
can use it as well as how to install it.
|
||||
|
||||
Below is a simple example of how you could use the APC cache driver
|
||||
by itself.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cacheDriver = new \Doctrine\Common\Cache\ApcCache();
|
||||
$cacheDriver->save('cache_id', 'my_data');
|
||||
|
||||
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
|
||||
give you a little background information about what it is and how
|
||||
you can use it as well as how to install it.
|
||||
|
||||
Below is a simple example of how you could use the Memcache cache
|
||||
driver by itself.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$memcache = new Memcache();
|
||||
$memcache->connect('memcache_host', 11211);
|
||||
|
||||
$cacheDriver = new \Doctrine\Common\Cache\MemcacheCache();
|
||||
$cacheDriver->setMemcache($memcache);
|
||||
$cacheDriver->save('cache_id', 'my_data');
|
||||
|
||||
Xcache
|
||||
~~~~~~
|
||||
|
||||
In order to use the Xcache cache driver you must have it compiled
|
||||
and enabled in your php.ini. You can read about Xcache
|
||||
`here <http://xcache.lighttpd.net/>`_. It will give you a little
|
||||
background information about what it is and how you can use it as
|
||||
well as how to install it.
|
||||
|
||||
Below is a simple example of how you could use the Xcache cache
|
||||
driver by itself.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cacheDriver = new \Doctrine\Common\Cache\XcacheCache();
|
||||
$cacheDriver->save('cache_id', 'my_data');
|
||||
|
||||
Redis
|
||||
~~~~~
|
||||
|
||||
In order to use the Redis cache driver you must have it compiled
|
||||
and enabled in your php.ini. You can read about what is Redis
|
||||
`from here <http://redis.io/>`_. Also check
|
||||
`here <https://github.com/nicolasff/phpredis/>`_ for how you can use
|
||||
and install Redis PHP extension.
|
||||
|
||||
Below is a simple example of how you could use the Redis cache
|
||||
driver by itself.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$redis = new Redis();
|
||||
$redis->connect('redis_host', 6379);
|
||||
|
||||
$cacheDriver = new \Doctrine\Common\Cache\RedisCache();
|
||||
$cacheDriver->setRedis($redis);
|
||||
$cacheDriver->save('cache_id', 'my_data');
|
||||
|
||||
Using Cache Drivers
|
||||
-------------------
|
||||
|
||||
In this section we'll describe how you can fully utilize the API of
|
||||
the cache drivers to save cache, check if some cache exists, fetch
|
||||
the cached data and delete the cached data. We'll use the
|
||||
``ArrayCache`` implementation as our example here.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cacheDriver = new \Doctrine\Common\Cache\ArrayCache();
|
||||
|
||||
Saving
|
||||
~~~~~~
|
||||
|
||||
To save some data to the cache driver it is as simple as using the
|
||||
``save()`` method.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cacheDriver->save('cache_id', 'my_data');
|
||||
|
||||
The ``save()`` method accepts three arguments which are described
|
||||
below.
|
||||
|
||||
|
||||
- ``$id`` - The cache id
|
||||
- ``$data`` - The cache entry/data.
|
||||
- ``$lifeTime`` - The lifetime. If != false, sets a specific
|
||||
lifetime for this cache entry (null => infinite lifeTime).
|
||||
|
||||
You can save any type of data whether it be a string, array,
|
||||
object, etc.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$array = array(
|
||||
'key1' => 'value1',
|
||||
'key2' => 'value2'
|
||||
);
|
||||
$cacheDriver->save('my_array', $array);
|
||||
|
||||
Checking
|
||||
~~~~~~~~
|
||||
|
||||
Checking whether some cache exists is very simple, just use the
|
||||
``contains()`` method. It accepts a single argument which is the ID
|
||||
of the cache entry.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
if ($cacheDriver->contains('cache_id')) {
|
||||
echo 'cache exists';
|
||||
} else {
|
||||
echo 'cache does not exist';
|
||||
}
|
||||
|
||||
Fetching
|
||||
~~~~~~~~
|
||||
|
||||
Now if you want to retrieve some cache entry you can use the
|
||||
``fetch()`` method. It also accepts a single argument just like
|
||||
``contains()`` which is the ID of the cache entry.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$array = $cacheDriver->fetch('my_array');
|
||||
|
||||
Deleting
|
||||
~~~~~~~~
|
||||
|
||||
As you might guess, deleting is just as easy as saving, checking
|
||||
and fetching. 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.
|
||||
|
||||
By Cache ID
|
||||
^^^^^^^^^^^
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cacheDriver->delete('my_array');
|
||||
|
||||
All
|
||||
^^^
|
||||
|
||||
If you simply want to delete all cache entries you can do so with
|
||||
the ``deleteAll()`` method.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$deleted = $cacheDriver->deleteAll();
|
||||
|
||||
Namespaces
|
||||
~~~~~~~~~~
|
||||
|
||||
If you heavily use caching in your application and utilize it in
|
||||
multiple parts of your application, or use it in different
|
||||
applications on the same server you may have issues with cache
|
||||
naming collisions. This can be worked around by using namespaces.
|
||||
You can set the namespace a cache driver should use by using the
|
||||
``setNamespace()`` method.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cacheDriver->setNamespace('my_namespace_');
|
||||
|
||||
Integrating with the ORM
|
||||
------------------------
|
||||
|
||||
The Doctrine ORM package is tightly integrated with the cache
|
||||
drivers to allow you to improve performance of various aspects of
|
||||
Doctrine by just simply making some additional configurations and
|
||||
method calls.
|
||||
|
||||
Query Cache
|
||||
~~~~~~~~~~~
|
||||
|
||||
It is highly recommended that in a production environment you cache
|
||||
the transformation of a DQL query to its SQL counterpart. It
|
||||
doesn't make sense to do this parsing multiple times as it doesn't
|
||||
change unless you alter the DQL query.
|
||||
|
||||
This can be done by configuring the query cache implementation to
|
||||
use on your ORM configuration.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$config->setQueryCacheImpl(new \Doctrine\Common\Cache\ApcCache());
|
||||
|
||||
Result Cache
|
||||
~~~~~~~~~~~~
|
||||
|
||||
The result cache can be used to cache the results of your queries
|
||||
so that we don't have to query the database or hydrate the data
|
||||
again after the first time. You just need to configure the result
|
||||
cache implementation.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config->setResultCacheImpl(new \Doctrine\Common\Cache\ApcCache());
|
||||
|
||||
Now when you're executing DQL queries you can configure them to use
|
||||
the result cache.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('select u from \Entities\User u');
|
||||
$query->useResultCache(true);
|
||||
|
||||
You can also configure an individual query to use a different
|
||||
result cache driver.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query->setResultCacheDriver(new \Doctrine\Common\Cache\ApcCache());
|
||||
|
||||
.. note::
|
||||
|
||||
Setting the result cache driver on the query will
|
||||
automatically enable the result cache for the query. If you want to
|
||||
disable it pass false to ``useResultCache()``.
|
||||
|
||||
::
|
||||
|
||||
<?php
|
||||
$query->useResultCache(false);
|
||||
|
||||
|
||||
If you want to set the time the cache has to live you can use the
|
||||
``setResultCacheLifetime()`` method.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query->setResultCacheLifetime(3600);
|
||||
|
||||
The ID used to store the result set cache is a hash which is
|
||||
automatically generated for you if you don't set a custom ID
|
||||
yourself with the ``setResultCacheId()`` method.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query->setResultCacheId('my_custom_id');
|
||||
|
||||
You can also set the lifetime and cache ID by passing the values as
|
||||
the second and third argument to ``useResultCache()``.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query->useResultCache(true, 3600, 'my_custom_id');
|
||||
|
||||
Metadata Cache
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Your class metadata can be parsed from a few different sources like
|
||||
YAML, XML, Annotations, etc. Instead of parsing this information on
|
||||
each request we should cache it using one of the cache drivers.
|
||||
|
||||
Just like the query and result cache we need to configure it
|
||||
first.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcCache());
|
||||
|
||||
Now the metadata information will only be parsed once and stored in
|
||||
the cache driver.
|
||||
|
||||
Clearing the Cache
|
||||
------------------
|
||||
|
||||
We've already shown you previously 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
|
||||
clearing the query, result and metadata cache.
|
||||
|
||||
From the Doctrine command line you can run the following command.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ ./doctrine clear-cache
|
||||
|
||||
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.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ ./doctrine clear-cache --query
|
||||
|
||||
To clear the metadata cache use the ``--metadata`` option.
|
||||
|
||||
.. 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.
|
||||
|
||||
|
||||
Cache Slams
|
||||
-----------
|
||||
|
||||
Something to be careful of when utilizing the cache drivers is
|
||||
cache slams. If 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
|
||||
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.
|
||||
|
||||
You can read more about cache slams
|
||||
`in this blog post <http://notmysock.org/blog/php/user-cache-timebomb.html>`_.
|
||||
|
||||
|
||||
151
docs/en/reference/change-tracking-policies.rst
Normal file
151
docs/en/reference/change-tracking-policies.rst
Normal file
@@ -0,0 +1,151 @@
|
||||
Change Tracking Policies
|
||||
========================
|
||||
|
||||
Change tracking is the process of determining what has changed in
|
||||
managed entities since the last time they were synchronized with
|
||||
the database.
|
||||
|
||||
Doctrine provides 3 different change tracking policies, each having
|
||||
its particular advantages and disadvantages. The change tracking
|
||||
policy can be defined on a per-class basis (or more precisely,
|
||||
per-hierarchy).
|
||||
|
||||
Deferred Implicit
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The deferred implicit policy is the default change tracking policy
|
||||
and the most convenient one. With this policy, Doctrine detects the
|
||||
changes by a property-by-property comparison at commit time and
|
||||
also detects changes to entities or new entities that are
|
||||
referenced by other managed entities ("persistence by
|
||||
reachability"). Although the most convenient policy, it can have
|
||||
negative effects on performance if you are dealing with large units
|
||||
of work (see "Understanding the Unit of Work"). Since Doctrine
|
||||
can't know what has changed, it needs to check all managed entities
|
||||
for changes every time you invoke EntityManager#flush(), making
|
||||
this operation rather costly.
|
||||
|
||||
Deferred Explicit
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The deferred explicit policy is similar to the deferred implicit
|
||||
policy in that it detects changes through a property-by-property
|
||||
comparison at commit time. The difference is that Doctrine 2 only
|
||||
considers entities that have been explicitly marked for change detection
|
||||
through a call to EntityManager#persist(entity) or through a save
|
||||
cascade. All other entities are skipped. This policy therefore
|
||||
gives improved performance for larger units of work while
|
||||
sacrificing the behavior of "automatic dirty checking".
|
||||
|
||||
Therefore, flush() operations are potentially cheaper with this
|
||||
policy. The negative aspect this has is that if you have a rather
|
||||
large application and you pass your objects through several layers
|
||||
for processing purposes and business tasks you may need to track
|
||||
yourself which entities have changed on the way so you can pass
|
||||
them to EntityManager#persist().
|
||||
|
||||
This policy can be configured as follows:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
* @ChangeTrackingPolicy("DEFERRED_EXPLICIT")
|
||||
*/
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
Notify
|
||||
~~~~~~
|
||||
|
||||
This policy is based on the assumption that the entities notify
|
||||
interested listeners of changes to their properties. For that
|
||||
purpose, a class that wants to use this policy needs to implement
|
||||
the ``NotifyPropertyChanged`` interface from the Doctrine
|
||||
namespace. As a guideline, such an implementation can look as
|
||||
follows:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\Common\NotifyPropertyChanged,
|
||||
Doctrine\Common\PropertyChangedListener;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @ChangeTrackingPolicy("NOTIFY")
|
||||
*/
|
||||
class MyEntity implements NotifyPropertyChanged
|
||||
{
|
||||
// ...
|
||||
|
||||
private $_listeners = array();
|
||||
|
||||
public function addPropertyChangedListener(PropertyChangedListener $listener)
|
||||
{
|
||||
$this->_listeners[] = $listener;
|
||||
}
|
||||
}
|
||||
|
||||
Then, in each property setter of this class or derived classes, you
|
||||
need to notify all the ``PropertyChangedListener`` instances. As an
|
||||
example we add a convenience method on ``MyEntity`` that shows this
|
||||
behaviour:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// ...
|
||||
|
||||
class MyEntity implements NotifyPropertyChanged
|
||||
{
|
||||
// ...
|
||||
|
||||
protected function _onPropertyChanged($propName, $oldValue, $newValue)
|
||||
{
|
||||
if ($this->_listeners) {
|
||||
foreach ($this->_listeners as $listener) {
|
||||
$listener->propertyChanged($this, $propName, $oldValue, $newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function setData($data)
|
||||
{
|
||||
if ($data != $this->data) {
|
||||
$this->_onPropertyChanged('data', $this->data, $data);
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
You have to invoke ``_onPropertyChanged`` inside every method that
|
||||
changes the persistent state of ``MyEntity``.
|
||||
|
||||
The check whether the new value is different from the old one is
|
||||
not mandatory but recommended. That way you also have full control
|
||||
over when you consider a property changed.
|
||||
|
||||
The negative point of this policy is obvious: You need implement an
|
||||
interface and write some plumbing code. But also note that we tried
|
||||
hard to keep this notification functionality abstract. Strictly
|
||||
speaking, it has nothing to do with the persistence layer and the
|
||||
Doctrine ORM or DBAL. You may find that property notification
|
||||
events come in handy in many other scenarios as well. As mentioned
|
||||
earlier, the ``Doctrine\Common`` namespace is not that evil and
|
||||
consists solely of very small classes and interfaces that have
|
||||
almost no external dependencies (none to the DBAL and none to the
|
||||
ORM) and that you can easily take with you should you want to swap
|
||||
out the persistence layer. This change tracking policy does not
|
||||
introduce a dependency on the Doctrine DBAL/ORM or the persistence
|
||||
layer.
|
||||
|
||||
The positive point and main advantage of this policy is its
|
||||
effectiveness. It has the best performance characteristics of the 3
|
||||
policies with larger units of work and a flush() operation is very
|
||||
cheap when nothing has changed.
|
||||
|
||||
|
||||
140
docs/en/reference/configuration.rst
Normal file
140
docs/en/reference/configuration.rst
Normal file
@@ -0,0 +1,140 @@
|
||||
Installation and Configuration
|
||||
==============================
|
||||
|
||||
Doctrine can be installed with `Composer <http://www.getcomposer.org>`_. For
|
||||
older versions we still have `PEAR packages
|
||||
<http://pear.doctrine-project.org>`_.
|
||||
|
||||
Define the following requirement in your ``composer.json`` file:
|
||||
|
||||
::
|
||||
|
||||
{
|
||||
"require": {
|
||||
"doctrine/orm": "*"
|
||||
}
|
||||
}
|
||||
|
||||
Then call ``composer install`` from your command line. If you don't know
|
||||
how Composer works, check out their `Getting Started
|
||||
<http://getcomposer.org/doc/00-intro.md>`_ to set up.
|
||||
|
||||
Class loading
|
||||
-------------
|
||||
|
||||
Autoloading is taken care of by Composer. You just have to include the composer autoload file in your project:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// bootstrap.php
|
||||
// Include Composer Autoload (relative to project root).
|
||||
require_once "vendor/autoload.php";
|
||||
|
||||
Obtaining an EntityManager
|
||||
--------------------------
|
||||
|
||||
Once you have prepared the class loading, you acquire an
|
||||
*EntityManager* instance. The EntityManager class is the primary
|
||||
access point to ORM functionality provided by Doctrine.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// bootstrap.php
|
||||
require_once "vendor/autoload.php";
|
||||
|
||||
use Doctrine\ORM\Tools\Setup;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
|
||||
$paths = array("/path/to/entities-or-mapping-files");
|
||||
$isDevMode = false;
|
||||
|
||||
// the connection configuration
|
||||
$dbParams = array(
|
||||
'driver' => 'pdo_mysql',
|
||||
'user' => 'root',
|
||||
'password' => '',
|
||||
'dbname' => 'foo',
|
||||
);
|
||||
|
||||
$config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode);
|
||||
$entityManager = EntityManager::create($dbParams, $config);
|
||||
|
||||
Or if you prefer XML:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config = Setup::createXMLMetadataConfiguration($paths, $isDevMode);
|
||||
$entityManager = EntityManager::create($dbParams, $config);
|
||||
|
||||
Or if you prefer YAML:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$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 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
|
||||
Configuration <reference/advanced-configuration>` section.
|
||||
|
||||
.. note::
|
||||
|
||||
You can learn more about the database connection configuration in the
|
||||
`Doctrine DBAL connection configuration reference <http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html>`_.
|
||||
|
||||
Setting up the Commandline Tool
|
||||
-------------------------------
|
||||
|
||||
Doctrine ships with a number of command line tools that are very helpful
|
||||
during development. You can call this command from the Composer binary
|
||||
directory:
|
||||
|
||||
.. code-block::
|
||||
|
||||
$ php vendor/bin/doctrine
|
||||
|
||||
You need to register your applications EntityManager to the console tool
|
||||
to make use of the tasks by creating a ``cli-config.php`` file with the
|
||||
following content:
|
||||
|
||||
On Doctrine 2.4 and above:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||
|
||||
// replace with file to your own project bootstrap
|
||||
require_once 'bootstrap.php';
|
||||
|
||||
// replace with mechanism to retrieve EntityManager in your app
|
||||
$entityManager = GetEntityManager();
|
||||
|
||||
return ConsoleRunner::createHelperSet($entityManager);
|
||||
|
||||
On Doctrine 2.3 and below:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// cli-config.php
|
||||
require_once 'my_bootstrap.php';
|
||||
|
||||
// Any way to access the EntityManager from your application
|
||||
$em = GetMyEntityManager();
|
||||
|
||||
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
|
||||
'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()),
|
||||
'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em)
|
||||
));
|
||||
1672
docs/en/reference/dql-doctrine-query-language.rst
Normal file
1672
docs/en/reference/dql-doctrine-query-language.rst
Normal file
File diff suppressed because it is too large
Load Diff
955
docs/en/reference/events.rst
Normal file
955
docs/en/reference/events.rst
Normal file
@@ -0,0 +1,955 @@
|
||||
Events
|
||||
======
|
||||
|
||||
Doctrine 2 features a lightweight event system that is part of the
|
||||
Common package. Doctrine uses it to dispatch system events, mainly
|
||||
:ref:`lifecycle events <reference-events-lifecycle-events>`.
|
||||
You can also use it for your own custom events.
|
||||
|
||||
The Event System
|
||||
----------------
|
||||
|
||||
The event system is controlled by the ``EventManager``. It is the
|
||||
central point of Doctrine's event listener system. Listeners are
|
||||
registered on the manager and events are dispatched through the
|
||||
manager.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$evm = new EventManager();
|
||||
|
||||
Now we can add some event listeners to the ``$evm``. Let's create a
|
||||
``EventTest`` class to play around with.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class EventTest
|
||||
{
|
||||
const preFoo = 'preFoo';
|
||||
const postFoo = 'postFoo';
|
||||
|
||||
private $_evm;
|
||||
|
||||
public $preFooInvoked = false;
|
||||
public $postFooInvoked = false;
|
||||
|
||||
public function __construct($evm)
|
||||
{
|
||||
$evm->addEventListener(array(self::preFoo, self::postFoo), $this);
|
||||
}
|
||||
|
||||
public function preFoo(EventArgs $e)
|
||||
{
|
||||
$this->preFooInvoked = true;
|
||||
}
|
||||
|
||||
public function postFoo(EventArgs $e)
|
||||
{
|
||||
$this->postFooInvoked = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new instance
|
||||
$test = new EventTest($evm);
|
||||
|
||||
Events can be dispatched by using the ``dispatchEvent()`` method.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$evm->dispatchEvent(EventTest::preFoo);
|
||||
$evm->dispatchEvent(EventTest::postFoo);
|
||||
|
||||
You can easily remove a listener with the ``removeEventListener()``
|
||||
method.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$evm->removeEventListener(array(self::preFoo, self::postFoo), $this);
|
||||
|
||||
The Doctrine 2 event system also has a simple concept of event
|
||||
subscribers. We can define a simple ``TestEventSubscriber`` class
|
||||
which implements the ``\Doctrine\Common\EventSubscriber`` interface
|
||||
and implements a ``getSubscribedEvents()`` method which returns an
|
||||
array of events it should be subscribed to.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class TestEventSubscriber implements \Doctrine\Common\EventSubscriber
|
||||
{
|
||||
public $preFooInvoked = false;
|
||||
|
||||
public function preFoo()
|
||||
{
|
||||
$this->preFooInvoked = true;
|
||||
}
|
||||
|
||||
public function getSubscribedEvents()
|
||||
{
|
||||
return array(TestEvent::preFoo);
|
||||
}
|
||||
}
|
||||
|
||||
$eventSubscriber = new TestEventSubscriber();
|
||||
$evm->addEventSubscriber($eventSubscriber);
|
||||
|
||||
.. note::
|
||||
|
||||
The array to return in the ``getSubscribedEvents`` method is a simple array
|
||||
with the values being the event names. The subscriber must have a method
|
||||
that is named exactly like the event.
|
||||
|
||||
Now when you dispatch an event, any event subscribers will be
|
||||
notified for that event.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$evm->dispatchEvent(TestEvent::preFoo);
|
||||
|
||||
Now you can test the ``$eventSubscriber`` instance to see if the
|
||||
``preFoo()`` method was invoked.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
if ($eventSubscriber->preFooInvoked) {
|
||||
echo 'pre foo invoked!';
|
||||
}
|
||||
|
||||
Naming convention
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Events being used with the Doctrine 2 EventManager are best named
|
||||
with camelcase and the value of the corresponding constant should
|
||||
be the name of the constant itself, even with spelling. This has
|
||||
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.
|
||||
|
||||
An example for a correct notation can be found in the example
|
||||
``EventTest`` above.
|
||||
|
||||
.. _reference-events-lifecycle-events:
|
||||
|
||||
Lifecycle Events
|
||||
----------------
|
||||
|
||||
The EntityManager and UnitOfWork trigger a bunch of events during
|
||||
the life-time of their registered entities.
|
||||
|
||||
|
||||
- preRemove - The preRemove event occurs for a given entity before
|
||||
the respective EntityManager remove operation for that entity is
|
||||
executed. It is not called for a DQL DELETE statement.
|
||||
- postRemove - The postRemove event occurs for an entity after the
|
||||
entity has been deleted. It will be invoked after the database
|
||||
delete operations. It is not called for a DQL DELETE statement.
|
||||
- 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
|
||||
- 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
|
||||
available in the postPersist event.
|
||||
- preUpdate - The preUpdate event occurs before the database
|
||||
update operations to entity data. It is not called for a DQL UPDATE statement.
|
||||
- postUpdate - The postUpdate event occurs after the database
|
||||
update operations to entity data. It is not called for a DQL UPDATE statement.
|
||||
- postLoad - The postLoad event occurs for an entity after the
|
||||
entity has been loaded into the current EntityManager from the
|
||||
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).
|
||||
- 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
|
||||
managed entities are computed. This event is not a lifecycle
|
||||
callback.
|
||||
- postFlush - The postFlush event occurs at the end of a flush operation. This
|
||||
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.
|
||||
|
||||
.. 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.
|
||||
|
||||
|
||||
You can access the Event constants from the ``Events`` class in the
|
||||
ORM package.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Events;
|
||||
echo Events::preUpdate;
|
||||
|
||||
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. They receives some kind of ``EventArgs``.
|
||||
- Lifecycle Event Listeners and Subscribers are classes with specific callback
|
||||
methods that receives some kind of ``EventArgs`` instance which
|
||||
give access to the entity, EntityManager or other relevant data.
|
||||
|
||||
.. note::
|
||||
|
||||
All Lifecycle events that happen during the ``flush()`` of
|
||||
an EntityManager have very specific constraints on the allowed
|
||||
operations that can be executed. Please read the
|
||||
:ref:`reference-events-implementing-listeners` section very carefully
|
||||
to understand which operations are allowed in which lifecycle event.
|
||||
|
||||
|
||||
Lifecycle Callbacks
|
||||
-------------------
|
||||
|
||||
A lifecycle event is a regular event with the additional feature of
|
||||
providing a mechanism to register direct callbacks inside the
|
||||
corresponding entity classes that are executed when the lifecycle
|
||||
event occurs.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
/** @Entity @HasLifecycleCallbacks */
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* @Column(type="string", length=255)
|
||||
*/
|
||||
public $value;
|
||||
|
||||
/** @Column(name="created_at", type="string", length=255) */
|
||||
private $createdAt;
|
||||
|
||||
/** @PrePersist */
|
||||
public function doStuffOnPrePersist()
|
||||
{
|
||||
$this->createdAt = date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
/** @PrePersist */
|
||||
public function doOtherStuffOnPrePersist()
|
||||
{
|
||||
$this->value = 'changed from prePersist callback!';
|
||||
}
|
||||
|
||||
/** @PostPersist */
|
||||
public function doStuffOnPostPersist()
|
||||
{
|
||||
$this->value = 'changed from postPersist callback!';
|
||||
}
|
||||
|
||||
/** @PostLoad */
|
||||
public function doStuffOnPostLoad()
|
||||
{
|
||||
$this->value = 'changed from postLoad callback!';
|
||||
}
|
||||
|
||||
/** @PreUpdate */
|
||||
public function doStuffOnPreUpdate()
|
||||
{
|
||||
$this->value = 'changed from preUpdate callback!';
|
||||
}
|
||||
}
|
||||
|
||||
Note that when using annotations you have to apply the
|
||||
@HasLifecycleCallbacks marker annotation on the entity class.
|
||||
|
||||
If you want to register lifecycle callbacks from YAML or XML you
|
||||
can do it with the following.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
User:
|
||||
type: entity
|
||||
fields:
|
||||
# ...
|
||||
name:
|
||||
type: string(50)
|
||||
lifecycleCallbacks:
|
||||
prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ]
|
||||
postPersist: [ doStuffOnPostPersist ]
|
||||
|
||||
XML would look something like this:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
/Users/robo/dev/php/Doctrine/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="User">
|
||||
|
||||
<lifecycle-callbacks>
|
||||
<lifecycle-callback type="prePersist" method="doStuffOnPrePersist"/>
|
||||
<lifecycle-callback type="postPersist" method="doStuffOnPostPersist"/>
|
||||
</lifecycle-callbacks>
|
||||
|
||||
</entity>
|
||||
|
||||
</doctrine-mapping>
|
||||
|
||||
You just need to make sure a public ``doStuffOnPrePersist()`` and
|
||||
``doStuffOnPostPersist()`` method is defined on your ``User``
|
||||
model.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// ...
|
||||
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
|
||||
public function doStuffOnPrePersist()
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
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
|
||||
-----------------------------------
|
||||
|
||||
.. versionadded:: 2.4
|
||||
|
||||
Since 2.4 the triggered event is given to the lifecycle-callback.
|
||||
|
||||
With the additional argument you have access to the
|
||||
``EntityManager`` and ``UnitOfWork`` APIs inside these callback methods.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// ...
|
||||
|
||||
class User
|
||||
{
|
||||
public function preUpdate(PreUpdateEventArgs $event)
|
||||
{
|
||||
if ($event->hasChangedField('username')) {
|
||||
// Do something when the username is changed.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Listening and subscribing to Lifecycle Events
|
||||
---------------------------------------------
|
||||
|
||||
Lifecycle event listeners are much more powerful than the simple
|
||||
lifecycle callbacks that are defined on the entity classes. They
|
||||
allow to implement re-usable behaviors between different entity
|
||||
classes, yet require much more detailed knowledge about the inner
|
||||
workings of the EntityManager and UnitOfWork. Please read the
|
||||
*Implementing Event Listeners* section carefully if you are trying
|
||||
to write your own listener.
|
||||
|
||||
For event subscribers, there are no surprises. They declare the
|
||||
lifecycle events in their ``getSubscribedEvents`` method and provide
|
||||
public methods that expect the relevant arguments.
|
||||
|
||||
A lifecycle event listener looks like the following:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
|
||||
|
||||
class MyEventListener
|
||||
{
|
||||
public function preUpdate(LifecycleEventArgs $args)
|
||||
{
|
||||
$entity = $args->getObject();
|
||||
$entityManager = $args->getObjectManager();
|
||||
|
||||
// perhaps you only want to act on some "Product" entity
|
||||
if ($entity instanceof Product) {
|
||||
// do something with the Product
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
A lifecycle event subscriber may looks like this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Events;
|
||||
use Doctrine\Common\EventSubscriber;
|
||||
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
|
||||
|
||||
class MyEventSubscriber implements EventSubscriber
|
||||
{
|
||||
public function getSubscribedEvents()
|
||||
{
|
||||
return array(
|
||||
Events::postUpdate,
|
||||
);
|
||||
}
|
||||
|
||||
public function postUpdate(LifecycleEventArgs $args)
|
||||
{
|
||||
$entity = $args->getObject();
|
||||
$entityManager = $args->getObjectManager();
|
||||
|
||||
// perhaps you only want to act on some "Product" entity
|
||||
if ($entity instanceof Product) {
|
||||
// do something with the Product
|
||||
}
|
||||
}
|
||||
|
||||
.. note::
|
||||
|
||||
Lifecycle events are triggered for all entities. It is the responsibility
|
||||
of the listeners and subscribers to check if the entity is of a type
|
||||
it wants to handle.
|
||||
|
||||
To register an event listener or subscriber, you have to hook it into the
|
||||
EventManager that is passed to the EntityManager factory:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$eventManager = new EventManager();
|
||||
$eventManager->addEventListener(array(Events::preUpdate), new MyEventListener());
|
||||
$eventManager->addEventSubscriber(new MyEventSubscriber());
|
||||
|
||||
$entityManager = EntityManager::create($dbOpts, $config, $eventManager);
|
||||
|
||||
You can also retrieve the event manager instance after the
|
||||
EntityManager was created:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$entityManager->getEventManager()->addEventListener(array(Events::preUpdate), new MyEventListener());
|
||||
$entityManager->getEventManager()->addEventSubscriber(new MyEventSubscriber());
|
||||
|
||||
.. _reference-events-implementing-listeners:
|
||||
|
||||
Implementing Event Listeners
|
||||
----------------------------
|
||||
|
||||
This section explains what is and what is not allowed during
|
||||
specific lifecycle events of the UnitOfWork. Although you get
|
||||
passed the EntityManager in all of these events, you have to follow
|
||||
these restrictions very carefully since operations in the wrong
|
||||
event may produce lots of different errors, such as inconsistent
|
||||
data and lost updates/persists/removes.
|
||||
|
||||
For the described events that are also lifecycle callback events
|
||||
the restrictions apply as well, with the additional restriction
|
||||
that you do not have access to the EntityManager or UnitOfWork APIs
|
||||
inside these events.
|
||||
|
||||
prePersist
|
||||
~~~~~~~~~~
|
||||
|
||||
There are two ways for the ``prePersist`` event to be triggered.
|
||||
One is obviously when you call ``EntityManager#persist()``. The
|
||||
event is also called for all cascaded associations.
|
||||
|
||||
There is another way for ``prePersist`` to be called, inside the
|
||||
``flush()`` method when changes to associations are computed and
|
||||
this association is marked as cascade persist. Any new entity found
|
||||
during this operation is also persisted and ``prePersist`` called
|
||||
on it. This is called "persistence by reachability".
|
||||
|
||||
In both cases you get passed a ``LifecycleEventArgs`` instance
|
||||
which has access to the entity and the entity manager.
|
||||
|
||||
The following restrictions apply to ``prePersist``:
|
||||
|
||||
|
||||
- If you are using a PrePersist Identity Generator such as
|
||||
sequences the ID value will *NOT* be available within any
|
||||
PrePersist events.
|
||||
- Doctrine will not recognize changes made to relations in a pre
|
||||
persist event called by "reachability" through a cascade persist
|
||||
unless you use the internal ``UnitOfWork`` API. We do not recommend
|
||||
such operations in the persistence by reachability context, so do
|
||||
this at your own risk and possibly supported by unit-tests.
|
||||
|
||||
preRemove
|
||||
~~~~~~~~~
|
||||
|
||||
The ``preRemove`` event is called on every entity when its passed
|
||||
to the ``EntityManager#remove()`` method. It is cascaded for all
|
||||
associations that are marked as cascade delete.
|
||||
|
||||
There are no restrictions to what methods can be called inside the
|
||||
``preRemove`` event, except when the remove method itself was
|
||||
called during a flush operation.
|
||||
|
||||
preFlush
|
||||
~~~~~~~~
|
||||
|
||||
``preFlush`` is called at ``EntityManager#flush()`` before
|
||||
anything else. ``EntityManager#flush()`` can be called safely
|
||||
inside its listeners.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\ORM\Event\PreFlushEventArgs;
|
||||
|
||||
class PreFlushExampleListener
|
||||
{
|
||||
public function preFlush(PreFlushEventArgs $args)
|
||||
{
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
onFlush
|
||||
~~~~~~~
|
||||
|
||||
OnFlush is a very powerful event. It is called inside
|
||||
``EntityManager#flush()`` after the changes to all the managed
|
||||
entities and their associations have been computed. This means, the
|
||||
``onFlush`` event has access to the sets of:
|
||||
|
||||
|
||||
- Entities scheduled for insert
|
||||
- Entities scheduled for update
|
||||
- Entities scheduled for removal
|
||||
- Collections scheduled for update
|
||||
- Collections scheduled for removal
|
||||
|
||||
To make use of the onFlush event you have to be familiar with the
|
||||
internal UnitOfWork API, which grants you access to the previously
|
||||
mentioned sets. See this example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class FlushExampleListener
|
||||
{
|
||||
public function onFlush(OnFlushEventArgs $eventArgs)
|
||||
{
|
||||
$em = $eventArgs->getEntityManager();
|
||||
$uow = $em->getUnitOfWork();
|
||||
|
||||
foreach ($uow->getScheduledEntityInsertions() AS $entity) {
|
||||
|
||||
}
|
||||
|
||||
foreach ($uow->getScheduledEntityUpdates() AS $entity) {
|
||||
|
||||
}
|
||||
|
||||
foreach ($uow->getScheduledEntityDeletions() AS $entity) {
|
||||
|
||||
}
|
||||
|
||||
foreach ($uow->getScheduledCollectionDeletions() AS $col) {
|
||||
|
||||
}
|
||||
|
||||
foreach ($uow->getScheduledCollectionUpdates() AS $col) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
The following restrictions apply to the onFlush event:
|
||||
|
||||
|
||||
- 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
|
||||
``$unitOfWork->recomputeSingleEntityChangeSet($classMetadata, $entity)``.
|
||||
|
||||
postFlush
|
||||
~~~~~~~~~
|
||||
|
||||
``postFlush`` is called at the end of ``EntityManager#flush()``.
|
||||
``EntityManager#flush()`` can **NOT** be called safely inside its listeners.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\ORM\Event\PostFlushEventArgs;
|
||||
|
||||
class PostFlushExampleListener
|
||||
{
|
||||
public function postFlush(PostFlushEventArgs $args)
|
||||
{
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
preUpdate
|
||||
~~~~~~~~~
|
||||
|
||||
PreUpdate is the most restrictive to use event, since it is called
|
||||
right before an update statement is called for an entity inside the
|
||||
``EntityManager#flush()`` method.
|
||||
|
||||
Changes to associations of the updated entity are never allowed in
|
||||
this event, since Doctrine cannot guarantee to correctly handle
|
||||
referential integrity at this point of the flush operation. This
|
||||
event has a powerful feature however, it is executed with a
|
||||
``PreUpdateEventArgs`` instance, which contains a reference to the
|
||||
computed change-set of this entity.
|
||||
|
||||
This means you have access to all the fields that have changed for
|
||||
this entity with their old and new value. The following methods are
|
||||
available on the ``PreUpdateEventArgs``:
|
||||
|
||||
|
||||
- ``getEntity()`` to get access to the actual entity.
|
||||
- ``getEntityChangeSet()`` to get a copy of the changeset array.
|
||||
Changes to this returned array do not affect updating.
|
||||
- ``hasChangedField($fieldName)`` to check if the given field name
|
||||
of the current entity changed.
|
||||
- ``getOldValue($fieldName)`` and ``getNewValue($fieldName)`` to
|
||||
access the values of a field.
|
||||
- ``setNewValue($fieldName, $value)`` to change the value of a
|
||||
field to be updated.
|
||||
|
||||
A simple example for this event looks like:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class NeverAliceOnlyBobListener
|
||||
{
|
||||
public function preUpdate(PreUpdateEventArgs $eventArgs)
|
||||
{
|
||||
if ($eventArgs->getEntity() instanceof User) {
|
||||
if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue('name') == 'Alice') {
|
||||
$eventArgs->setNewValue('name', 'Bob');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
You could also use this listener to implement validation of all the
|
||||
fields that have changed. This is more efficient than using a
|
||||
lifecycle callback when there are expensive validations to call:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class ValidCreditCardListener
|
||||
{
|
||||
public function preUpdate(PreUpdateEventArgs $eventArgs)
|
||||
{
|
||||
if ($eventArgs->getEntity() instanceof Account) {
|
||||
if ($eventArgs->hasChangedField('creditCard')) {
|
||||
$this->validateCreditCard($eventArgs->getNewValue('creditCard'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function validateCreditCard($no)
|
||||
{
|
||||
// throw an exception to interrupt flush event. Transaction will be rolled back.
|
||||
}
|
||||
}
|
||||
|
||||
Restrictions for this event:
|
||||
|
||||
|
||||
- Changes to associations of the passed entities are not
|
||||
recognized by the flush operation anymore.
|
||||
- Changes to fields of the passed entities are not recognized by
|
||||
the flush operation anymore, use the computed change-set passed to
|
||||
the event to modify primitive field values.
|
||||
- Any calls to ``EntityManager#persist()`` or
|
||||
``EntityManager#remove()``, even in combination with the UnitOfWork
|
||||
API are strongly discouraged and don't work as expected outside the
|
||||
flush operation.
|
||||
|
||||
postUpdate, postRemove, postPersist
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The three post events are called inside ``EntityManager#flush()``.
|
||||
Changes in here are not relevant to the persistence in the
|
||||
database, but you can use these events to alter non-persistable items,
|
||||
like non-mapped fields, logging or even associated classes that are
|
||||
directly mapped by Doctrine.
|
||||
|
||||
postLoad
|
||||
~~~~~~~~
|
||||
|
||||
This event is called after an entity is constructed by the
|
||||
EntityManager.
|
||||
|
||||
Entity listeners
|
||||
----------------
|
||||
|
||||
.. versionadded:: 2.4
|
||||
|
||||
An entity listeners is a lifecycle listener classes used for an entity.
|
||||
|
||||
- The entity listeners 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::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace MyProject\Entity;
|
||||
|
||||
/** @Entity @EntityListeners({"UserListener"}) */
|
||||
class User
|
||||
{
|
||||
// ....
|
||||
}
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="MyProject\Entity\User">
|
||||
<entity-listeners>
|
||||
<entity-listener class="UserListener"/>
|
||||
</entity-listeners>
|
||||
<!-- .... -->
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
.. code-block:: yaml
|
||||
|
||||
MyProject\Entity\User:
|
||||
type: entity
|
||||
entityListeners:
|
||||
UserListener:
|
||||
# ....
|
||||
|
||||
.. _reference-entity-listeners:
|
||||
|
||||
Entity listeners class
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
An ``Entity Listener`` could be any class, by default it should be a class with a no-arg constructor.
|
||||
|
||||
- Different from :ref:`reference-events-implementing-listeners` an ``Entity Listener`` is invoked just to the specified entity
|
||||
- An entity listener method receives two arguments, the entity instance and the lifecycle event.
|
||||
- A callback method could be defined by naming convention or specifying a method mapping.
|
||||
- When the listener mapping is not given the parser will lookup for methods that match with the naming convention.
|
||||
- When the listener mapping is given the parser won't lookup for any naming convention.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class UserListener
|
||||
{
|
||||
public function preUpdate(User $user, PreUpdateEventArgs $event)
|
||||
{
|
||||
// Do something on pre update.
|
||||
}
|
||||
}
|
||||
|
||||
To define a specific event listener method
|
||||
you should map the listener method using the event type mapping.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class UserListener
|
||||
{
|
||||
/** @PrePersist */
|
||||
public function prePersistHandler(User $user, LifecycleEventArgs $event) { // ... }
|
||||
|
||||
/** @PostPersist */
|
||||
public function postPersistHandler(User $user, LifecycleEventArgs $event) { // ... }
|
||||
|
||||
/** @PreUpdate */
|
||||
public function preUpdateHandler(User $user, PreUpdateEventArgs $event) { // ... }
|
||||
|
||||
/** @PostUpdate */
|
||||
public function postUpdateHandler(User $user, LifecycleEventArgs $event) { // ... }
|
||||
|
||||
/** @PostRemove */
|
||||
public function postRemoveHandler(User $user, LifecycleEventArgs $event) { // ... }
|
||||
|
||||
/** @PreRemove */
|
||||
public function preRemoveHandler(User $user, LifecycleEventArgs $event) { // ... }
|
||||
|
||||
/** @PreFlush */
|
||||
public function preFlushHandler(User $user, PreFlushEventArgs $event) { // ... }
|
||||
|
||||
/** @PostLoad */
|
||||
public function postLoadHandler(User $user, LifecycleEventArgs $event) { // ... }
|
||||
}
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="MyProject\Entity\User">
|
||||
<entity-listeners>
|
||||
<entity-listener class="UserListener">
|
||||
<lifecycle-callback type="preFlush" method="preFlushHandler"/>
|
||||
<lifecycle-callback type="postLoad" method="postLoadHandler"/>
|
||||
|
||||
<lifecycle-callback type="postPersist" method="postPersistHandler"/>
|
||||
<lifecycle-callback type="prePersist" method="prePersistHandler"/>
|
||||
|
||||
<lifecycle-callback type="postUpdate" method="postUpdateHandler"/>
|
||||
<lifecycle-callback type="preUpdate" method="preUpdateHandler"/>
|
||||
|
||||
<lifecycle-callback type="postRemove" method="postRemoveHandler"/>
|
||||
<lifecycle-callback type="preRemove" method="preRemoveHandler"/>
|
||||
</entity-listener>
|
||||
</entity-listeners>
|
||||
<!-- .... -->
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
.. code-block:: yaml
|
||||
|
||||
MyProject\Entity\User:
|
||||
type: entity
|
||||
entityListeners:
|
||||
UserListener:
|
||||
preFlush: [preFlushHandler]
|
||||
postLoad: [postLoadHandler]
|
||||
|
||||
postPersist: [postPersistHandler]
|
||||
prePersist: [prePersistHandler]
|
||||
|
||||
postUpdate: [postUpdateHandler]
|
||||
preUpdate: [preUpdateHandler]
|
||||
|
||||
postRemove: [postRemoveHandler]
|
||||
preRemove: [preRemoveHandler]
|
||||
# ....
|
||||
|
||||
|
||||
|
||||
Entity listeners resolver
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Doctrine invoke the listener resolver to get the listener instance.
|
||||
|
||||
- An 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 :
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// User.php
|
||||
|
||||
/** @Entity @EntityListeners({"UserListener"}) */
|
||||
class User
|
||||
{
|
||||
// ....
|
||||
}
|
||||
|
||||
// UserListener.php
|
||||
class UserListener
|
||||
{
|
||||
public function __construct(MyService $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
public function preUpdate(User $user, PreUpdateEventArgs $event)
|
||||
{
|
||||
$this->service->doSomething($user);
|
||||
}
|
||||
}
|
||||
|
||||
// register a entity listener.
|
||||
$listener = $container->get('user_listener');
|
||||
$em->getConfiguration()->getEntityListenerResolver()->register($listener);
|
||||
|
||||
Implementing your own resolver :
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class MyEntityListenerResolver extends \Doctrine\ORM\Mapping\DefaultEntityListenerResolver
|
||||
{
|
||||
public function __construct($container)
|
||||
{
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
public function resolve($className)
|
||||
{
|
||||
// resolve the service id by the given class name;
|
||||
$id = 'user_listener';
|
||||
|
||||
return $this->container->get($id);
|
||||
}
|
||||
}
|
||||
|
||||
// configure the listener resolver.
|
||||
$em->getConfiguration()->setEntityListenerResolver($container->get('my_resolver'));
|
||||
|
||||
Load ClassMetadata Event
|
||||
------------------------
|
||||
|
||||
When the mapping information for an entity is read, it is populated
|
||||
in to a ``ClassMetadataInfo`` instance. You can hook in to this
|
||||
process and manipulate the instance.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$test = new EventTest();
|
||||
$metadataFactory = $em->getMetadataFactory();
|
||||
$evm = $em->getEventManager();
|
||||
$evm->addEventListener(Events::loadClassMetadata, $test);
|
||||
|
||||
class EventTest
|
||||
{
|
||||
public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs)
|
||||
{
|
||||
$classMetadata = $eventArgs->getClassMetadata();
|
||||
$fieldMapping = array(
|
||||
'fieldName' => 'about',
|
||||
'type' => 'string',
|
||||
'length' => 255
|
||||
);
|
||||
$classMetadata->mapField($fieldMapping);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
224
docs/en/reference/faq.rst
Normal file
224
docs/en/reference/faq.rst
Normal file
@@ -0,0 +1,224 @@
|
||||
Frequently Asked Questions
|
||||
==========================
|
||||
|
||||
.. note::
|
||||
|
||||
This FAQ is a work in progress. We will add lots of questions and not answer them right away just to remember
|
||||
what is often asked. If you stumble across an unanswered question please write a mail to the mailing-list or
|
||||
join the #doctrine channel on Freenode IRC.
|
||||
|
||||
Database Schema
|
||||
---------------
|
||||
|
||||
How do I set the charset and collation for MySQL tables?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can't set these values inside the annotations, yml or xml mapping files. To make a database
|
||||
work with the default charset and collation you should configure MySQL to use it as default charset,
|
||||
or create the database with charset and collation details. This way they get inherited to all newly
|
||||
created database tables and columns.
|
||||
|
||||
Entity Classes
|
||||
--------------
|
||||
|
||||
I access a variable and its null, what is wrong?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If this variable is a public variable then you are violating one of the criteria for entities.
|
||||
All properties have to be protected or private for the proxy object pattern to work.
|
||||
|
||||
How can I add default values to a column?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Doctrine does not support to set the default values in columns through the "DEFAULT" keyword in SQL.
|
||||
This is not necessary however, you can just use your class properties as default values. These are then used
|
||||
upon insert:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
class User
|
||||
{
|
||||
const STATUS_DISABLED = 0;
|
||||
const STATUS_ENABLED = 1;
|
||||
|
||||
private $algorithm = "sha1";
|
||||
private $status = self:STATUS_DISABLED;
|
||||
}
|
||||
|
||||
.
|
||||
|
||||
Mapping
|
||||
-------
|
||||
|
||||
Why do I get exceptions about unique constraint failures during ``$em->flush()``?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Doctrine does not check if you are re-adding entities with a primary key that already exists
|
||||
or adding entities to a collection twice. You have to check for both conditions yourself
|
||||
in the code before calling ``$em->flush()`` if you know that unique constraint failures
|
||||
can occur.
|
||||
|
||||
In `Symfony2 <http://www.symfony.com>`_ for example there is a Unique Entity Validator
|
||||
to achieve this task.
|
||||
|
||||
For collections you can check with ``$collection->contains($entity)`` if an entity is already
|
||||
part of this collection. For a FETCH=LAZY collection this will initialize the collection,
|
||||
however for FETCH=EXTRA_LAZY this method will use SQL to determine if this entity is already
|
||||
part of the collection.
|
||||
|
||||
Associations
|
||||
------------
|
||||
|
||||
What is wrong when I get an InvalidArgumentException "A new entity was found through the relationship.."?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This exception is thrown during ``EntityManager#flush()`` when there exists an object in the identity map
|
||||
that contains a reference to an object that Doctrine does not know about. Say for example you grab
|
||||
a "User"-entity from the database with a specific id and set a completely new object into one of the associations
|
||||
of the User object. If you then call ``EntityManager#flush()`` without letting Doctrine know about
|
||||
this new object using ``EntityManager#persist($newObject)`` you will see this exception.
|
||||
|
||||
You can solve this exception by:
|
||||
|
||||
* Calling ``EntityManager#persist($newObject)`` on the new object
|
||||
* Using cascade=persist on the association that contains the new object
|
||||
|
||||
How can I filter an association?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Natively you can't filter associations in 2.0 and 2.1. You should use DQL queries to query for the filtered set of entities.
|
||||
|
||||
I call clear() on a One-To-Many collection but the entities are not deleted
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This is an expected behavior that has to do with the inverse/owning side handling of Doctrine.
|
||||
By definition a One-To-Many association is on the inverse side, that means changes to it
|
||||
will not be recognized by Doctrine.
|
||||
|
||||
If you want to perform the equivalent of the clear operation you have to iterate the
|
||||
collection and set the owning side many-to-one reference to NULL as well to detach all entities
|
||||
from the collection. This will trigger the appropriate UPDATE statements on the database.
|
||||
|
||||
How can I add columns to a many-to-many table?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The many-to-many association is only supporting foreign keys in the table definition
|
||||
To work with many-to-many tables containing extra columns you have to use the
|
||||
foreign keys as primary keys feature of Doctrine introduced in version 2.1.
|
||||
|
||||
See :doc:`the tutorial on composite primary keys for more information<../tutorials/composite-primary-keys>`.
|
||||
|
||||
|
||||
How can i paginate fetch-joined collections?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you are issuing a DQL statement that fetches a collection as well you cannot easily iterate
|
||||
over this collection using a LIMIT statement (or vendor equivalent).
|
||||
|
||||
Doctrine does not offer a solution for this out of the box but there are several extensions
|
||||
that do:
|
||||
|
||||
* `DoctrineExtensions <http://github.com/beberlei/DoctrineExtensions>`_
|
||||
* `Pagerfanta <http://github.com/whiteoctober/pagerfanta>`_
|
||||
|
||||
Why does pagination not work correctly with fetch joins?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Pagination in Doctrine uses a LIMIT clause (or vendor equivalent) to restrict the results.
|
||||
However when fetch-joining this is not returning the correct number of results since joining
|
||||
with a one-to-many or many-to-many association multiplies the number of rows by the number
|
||||
of associated entities.
|
||||
|
||||
See the previous question for a solution to this task.
|
||||
|
||||
Inheritance
|
||||
-----------
|
||||
|
||||
Can I use Inheritance with Doctrine 2?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Yes, you can use Single- or Joined-Table Inheritance in Doctrine 2.
|
||||
|
||||
See the documentation chapter on :doc:`inheritance mapping <inheritance-mapping>` for
|
||||
the details.
|
||||
|
||||
Why does Doctrine not create proxy objects for my inheritance hierarchy?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you set a many-to-one or one-to-one association target-entity to any parent class of
|
||||
an inheritance hierarchy Doctrine does not know what PHP class the foreign is actually of.
|
||||
To find this out it has to execute a SQL query to look this information up in the database.
|
||||
|
||||
EntityGenerator
|
||||
---------------
|
||||
|
||||
Why does the EntityGenerator not do X?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The EntityGenerator is not a full fledged code-generator that solves all tasks. Code-Generation
|
||||
is not a first-class priority in Doctrine 2 anymore (compared to Doctrine 1). The EntityGenerator
|
||||
is supposed to kick-start you, but not towards 100%.
|
||||
|
||||
Why does the EntityGenerator not generate inheritance correctly?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Just from the details of the discriminator map the EntityGenerator cannot guess the inheritance hierarchy.
|
||||
This is why the generation of inherited entities does not fully work. You have to adjust some additional
|
||||
code to get this one working correctly.
|
||||
|
||||
Performance
|
||||
-----------
|
||||
|
||||
Why is an extra SQL query executed every time I fetch an entity with a one-to-one relation?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If Doctrine detects that you are fetching an inverse side one-to-one association
|
||||
it has to execute an additional query to load this object, because it cannot know
|
||||
if there is no such object (setting null) or if it should set a proxy and which id this proxy has.
|
||||
|
||||
To solve this problem currently a query has to be executed to find out this information.
|
||||
|
||||
Doctrine Query Language
|
||||
-----------------------
|
||||
|
||||
What is DQL?
|
||||
~~~~~~~~~~~~
|
||||
|
||||
DQL stands for Doctrine Query Language, a query language that very much looks like SQL
|
||||
but has some important benefits when using Doctrine:
|
||||
|
||||
- It uses class names and fields instead of tables and columns, separating concerns between backend and your object model.
|
||||
- It utilizes the metadata defined to offer a range of shortcuts when writing. For example you do not have to specify the ON clause of joins, since Doctrine already knows about them.
|
||||
- It adds some functionality that is related to object management and transforms them into SQL.
|
||||
|
||||
It also has some drawbacks of course:
|
||||
|
||||
- The syntax is slightly different to SQL so you have to learn and remember the differences.
|
||||
- To be vendor independent it can only implement a subset of all the existing SQL dialects. Vendor specific functionality and optimizations cannot be used through DQL unless implemented by you explicitly.
|
||||
- For some DQL constructs subselects are used which are known to be slow in MySQL.
|
||||
|
||||
Can I sort by a function (for example ORDER BY RAND()) in DQL?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
No, it is not supported to sort by function in DQL. If you need this functionality you should either
|
||||
use a native-query or come up with another solution. As a side note: Sorting with ORDER BY RAND() is painfully slow
|
||||
starting with 1000 rows.
|
||||
|
||||
A Query fails, how can I debug it?
|
||||
----------------------------------
|
||||
|
||||
First, if you are using the QueryBuilder you can use
|
||||
``$queryBuilder->getDQL()`` to get the DQL string of this query. The
|
||||
corresponding SQL you can get from the Query instance by calling
|
||||
``$query->getSQL()``.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$dql = "SELECT u FROM User u";
|
||||
$query = $entityManager->createQuery($dql);
|
||||
var_dump($query->getSQL());
|
||||
|
||||
$qb = $entityManager->createQueryBuilder();
|
||||
$qb->select('u')->from('User', 'u');
|
||||
var_dump($qb->getDQL());
|
||||
93
docs/en/reference/filters.rst
Normal file
93
docs/en/reference/filters.rst
Normal file
@@ -0,0 +1,93 @@
|
||||
Filters
|
||||
=======
|
||||
|
||||
.. versionadded:: 2.2
|
||||
|
||||
Doctrine 2.2 features a filter system that allows the developer to add SQL to
|
||||
the conditional clauses of queries, regardless the place where the SQL is
|
||||
generated (e.g. from a DQL query, or by loading associated entities).
|
||||
|
||||
The filter functionality works on SQL level. Whether a SQL query is generated
|
||||
in a Persister, during lazy loading, in extra lazy collections or from DQL.
|
||||
Each time the system iterates over all the enabled filters, adding a new SQL
|
||||
part as a filter returns.
|
||||
|
||||
By adding SQL to the conditional clauses of queries, the filter system filters
|
||||
out rows belonging to the entities at the level of the SQL result set. This
|
||||
means that the filtered entities are never hydrated (which can be expensive).
|
||||
|
||||
|
||||
Example filter class
|
||||
--------------------
|
||||
Throughout this document the example ``MyLocaleFilter`` class will be used to
|
||||
illustrate how the filter feature works. A filter class must extend the base
|
||||
``Doctrine\ORM\Query\Filter\SQLFilter`` class and implement the ``addFilterConstraint``
|
||||
method. The method receives the ``ClassMetadata`` of the filtered entity and the
|
||||
table alias of the SQL table of the entity.
|
||||
|
||||
.. note::
|
||||
|
||||
In the case of joined or single table inheritance, you always get passed the ClassMetadata of the
|
||||
inheritance root. This is necessary to avoid edge cases that would break the SQL when applying the filters.
|
||||
|
||||
Parameters for the query should be set on the filter object by
|
||||
``SQLFilter#setParameter()``. Only parameters set via this function can be used
|
||||
in filters. The ``SQLFilter#getParameter()`` function takes care of the
|
||||
proper quoting of parameters.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace Example;
|
||||
use Doctrine\ORM\Mapping\ClassMetaData,
|
||||
Doctrine\ORM\Query\Filter\SQLFilter;
|
||||
|
||||
class MyLocaleFilter extends SQLFilter
|
||||
{
|
||||
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
|
||||
{
|
||||
// Check if the entity implements the LocalAware interface
|
||||
if (!$targetEntity->reflClass->implementsInterface('LocaleAware')) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return $targetTableAlias.'.locale = ' . $this->getParameter('locale'); // getParameter applies quoting automatically
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
Filter classes are added to the configuration as following:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config->addFilter("locale", "\Doctrine\Tests\ORM\Functional\MyLocaleFilter");
|
||||
|
||||
|
||||
The ``Configuration#addFilter()`` method takes a name for the filter and the name of the
|
||||
class responsible for the actual filtering.
|
||||
|
||||
|
||||
Disabling/Enabling Filters and Setting Parameters
|
||||
---------------------------------------------------
|
||||
Filters can be disabled and enabled via the ``FilterCollection`` which is
|
||||
stored in the ``EntityManager``. The ``FilterCollection#enable($name)`` method
|
||||
will retrieve the filter object. You can set the filter parameters on that
|
||||
object.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$filter = $em->getFilters()->enable("locale");
|
||||
$filter->setParameter('locale', 'en');
|
||||
|
||||
// Disable it
|
||||
$filter = $em->getFilters()->disable("locale");
|
||||
|
||||
.. warning::
|
||||
Disabling and enabling filters has no effect on managed entities. If you
|
||||
want to refresh or reload an object after having modified a filter or the
|
||||
FilterCollection, then you should clear the EntityManager and re-fetch your
|
||||
entities, having the new rules for filtering applied.
|
||||
64
docs/en/reference/improving-performance.rst
Normal file
64
docs/en/reference/improving-performance.rst
Normal file
@@ -0,0 +1,64 @@
|
||||
Improving Performance
|
||||
=====================
|
||||
|
||||
Bytecode Cache
|
||||
--------------
|
||||
|
||||
It is highly recommended to make use of a bytecode cache like APC.
|
||||
A bytecode cache removes the need for parsing PHP code on every
|
||||
request and can greatly improve performance.
|
||||
|
||||
"If you care about performance and don't use a bytecode
|
||||
cache then you don't really care about performance. Please get one
|
||||
and start using it."
|
||||
|
||||
*Stas Malyshev, Core Contributor to PHP and Zend Employee*
|
||||
|
||||
|
||||
Metadata and Query caches
|
||||
-------------------------
|
||||
|
||||
As already mentioned earlier in the chapter about configuring
|
||||
Doctrine, it is strongly discouraged to use Doctrine without a
|
||||
Metadata and Query cache (preferably with APC or Memcache as the
|
||||
cache driver). Operating Doctrine without these caches means
|
||||
Doctrine will need to load your mapping information on every single
|
||||
request and has to parse each DQL query on every single request.
|
||||
This is a waste of resources.
|
||||
|
||||
Alternative Query Result Formats
|
||||
--------------------------------
|
||||
|
||||
Make effective use of the available alternative query result
|
||||
formats like nested array graphs or pure scalar results, especially
|
||||
in scenarios where data is loaded for read-only purposes.
|
||||
|
||||
Read-Only Entities
|
||||
------------------
|
||||
|
||||
Starting with Doctrine 2.1 you can mark entities as read only (See metadata mapping
|
||||
references for details). This means that the entity marked as read only is never considered
|
||||
for updates, which means when you call flush on the EntityManager these entities are skipped
|
||||
even if properties changed. Read-Only allows to persist new entities of a kind and remove existing
|
||||
ones, they are just not considered for updates.
|
||||
|
||||
Extra-Lazy Collections
|
||||
----------------------
|
||||
|
||||
If entities hold references to large collections you will get performance and memory problems initializing them.
|
||||
To solve this issue you can use the EXTRA_LAZY fetch-mode feature for collections. See the :doc:`tutorial <../tutorials/extra-lazy-associations>`
|
||||
for more information on how this fetch mode works.
|
||||
|
||||
Temporarily change fetch mode in DQL
|
||||
------------------------------------
|
||||
|
||||
See :ref:`Doctrine Query Language chapter <dql-temporarily-change-fetch-mode>`
|
||||
|
||||
|
||||
Apply Best Practices
|
||||
--------------------
|
||||
|
||||
A lot of the points mentioned in the Best Practices chapter will
|
||||
also positively affect the performance of Doctrine.
|
||||
|
||||
|
||||
559
docs/en/reference/inheritance-mapping.rst
Normal file
559
docs/en/reference/inheritance-mapping.rst
Normal file
@@ -0,0 +1,559 @@
|
||||
Inheritance Mapping
|
||||
===================
|
||||
|
||||
Mapped Superclasses
|
||||
-------------------
|
||||
|
||||
A mapped superclass is an abstract or concrete class that provides
|
||||
persistent entity state and mapping information for its subclasses,
|
||||
but which is not itself an entity. Typically, the purpose of such a
|
||||
mapped superclass is to define state and mapping information that
|
||||
is common to multiple entity classes.
|
||||
|
||||
Mapped superclasses, just as regular, non-mapped classes, can
|
||||
appear in the middle of an otherwise mapped inheritance hierarchy
|
||||
(through Single Table Inheritance or Class Table Inheritance).
|
||||
|
||||
.. note::
|
||||
|
||||
A mapped superclass cannot be an entity, it is not query-able and
|
||||
persistent relationships defined by a mapped superclass must be
|
||||
unidirectional (with an owning side only). This means that One-To-Many
|
||||
associations are not possible on a mapped superclass at all.
|
||||
Furthermore Many-To-Many associations are only possible if the
|
||||
mapped superclass is only used in exactly one entity at the moment.
|
||||
For further support of inheritance, the single or
|
||||
joined table inheritance features have to be used.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @MappedSuperclass */
|
||||
class MappedSuperclassBase
|
||||
{
|
||||
/** @Column(type="integer") */
|
||||
protected $mapped1;
|
||||
/** @Column(type="string") */
|
||||
protected $mapped2;
|
||||
/**
|
||||
* @OneToOne(targetEntity="MappedSuperclassRelated1")
|
||||
* @JoinColumn(name="related1_id", referencedColumnName="id")
|
||||
*/
|
||||
protected $mappedRelated1;
|
||||
|
||||
// ... more fields and methods
|
||||
}
|
||||
|
||||
/** @Entity */
|
||||
class EntitySubClass extends MappedSuperclassBase
|
||||
{
|
||||
/** @Id @Column(type="integer") */
|
||||
private $id;
|
||||
/** @Column(type="string") */
|
||||
private $name;
|
||||
|
||||
// ... more fields and methods
|
||||
}
|
||||
|
||||
The DDL for the corresponding database schema would look something
|
||||
like this (this is for SQLite):
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
CREATE TABLE EntitySubClass (mapped1 INTEGER NOT NULL, mapped2 TEXT NOT NULL, id INTEGER NOT NULL, name TEXT NOT NULL, related1_id INTEGER DEFAULT NULL, PRIMARY KEY(id))
|
||||
|
||||
As you can see from this DDL snippet, there is only a single table
|
||||
for the entity subclass. All the mappings from the mapped
|
||||
superclass were inherited to the subclass as if they had been
|
||||
defined on that class directly.
|
||||
|
||||
Single Table Inheritance
|
||||
------------------------
|
||||
|
||||
`Single Table Inheritance <http://martinfowler.com/eaaCatalog/singleTableInheritance.html>`_
|
||||
is an inheritance mapping strategy where all classes of a hierarchy
|
||||
are mapped to a single database table. In order to distinguish
|
||||
which row represents which type in the hierarchy a so-called
|
||||
discriminator column is used.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?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
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
Things to note:
|
||||
|
||||
|
||||
- The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap
|
||||
must be specified on the topmost class that is part of the mapped
|
||||
entity hierarchy.
|
||||
- The @DiscriminatorMap specifies which values of the
|
||||
discriminator column identify a row as being of a certain type. In
|
||||
the case above a value of "person" identifies a row as being of
|
||||
type ``Person`` and "employee" identifies a row as being of type
|
||||
``Employee``.
|
||||
- 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.
|
||||
|
||||
Design-time considerations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This mapping approach works well when the type hierarchy is fairly
|
||||
simple and stable. Adding a new type to the hierarchy and adding
|
||||
fields to existing supertypes simply involves adding new columns to
|
||||
the table, though in large deployments this may have an adverse
|
||||
impact on the index and column layout inside the database.
|
||||
|
||||
Performance impact
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This strategy is very efficient for querying across all types in
|
||||
the hierarchy or for specific types. No table joins are required,
|
||||
only a WHERE clause listing the type identifiers. In particular,
|
||||
relationships involving types that employ this mapping strategy are
|
||||
very performant.
|
||||
|
||||
There is a general performance consideration with Single Table
|
||||
Inheritance: If the target-entity of a many-to-one or one-to-one
|
||||
association is an STI entity, it is preferable for performance reasons that it
|
||||
be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
|
||||
Otherwise Doctrine *CANNOT* create proxy instances
|
||||
of this entity and will *ALWAYS* load the entity eagerly.
|
||||
|
||||
SQL Schema considerations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
For Single-Table-Inheritance to work in scenarios where you are
|
||||
using either a legacy database schema or a self-written database
|
||||
schema you have to make sure that all columns that are not in the
|
||||
root entity but in any of the different sub-entities has to allows
|
||||
null values. Columns that have NOT NULL constraints have to be on
|
||||
the root entity of the single-table inheritance hierarchy.
|
||||
|
||||
Class Table Inheritance
|
||||
-----------------------
|
||||
|
||||
`Class Table Inheritance <http://martinfowler.com/eaaCatalog/classTableInheritance.html>`_
|
||||
is an inheritance mapping strategy where each class in a hierarchy
|
||||
is mapped to several tables: its own table and the tables of all
|
||||
parent classes. The table of a child class is linked to the table
|
||||
of a parent class through a foreign key constraint. Doctrine 2
|
||||
implements this strategy through the use of a discriminator column
|
||||
in the topmost table of the hierarchy because this is the easiest
|
||||
way to achieve polymorphic queries with Class Table Inheritance.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace MyProject\Model;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @InheritanceType("JOINED")
|
||||
* @DiscriminatorColumn(name="discr", type="string")
|
||||
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
|
||||
*/
|
||||
class Person
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
/** @Entity */
|
||||
class Employee extends Person
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
Things to note:
|
||||
|
||||
|
||||
- The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap
|
||||
must be specified on the topmost class that is part of the mapped
|
||||
entity hierarchy.
|
||||
- The @DiscriminatorMap specifies which values of the
|
||||
discriminator column identify a row as being of which type. In the
|
||||
case above a value of "person" identifies a row as being of type
|
||||
``Person`` and "employee" identifies a row as being of type
|
||||
``Employee``.
|
||||
- The names of the classes in the discriminator map do not need to
|
||||
be fully qualified if the classes are contained in the same
|
||||
namespace as the entity class on which the discriminator map is
|
||||
applied.
|
||||
|
||||
.. note::
|
||||
|
||||
When you do not use the SchemaTool to generate the
|
||||
required SQL you should know that deleting a class table
|
||||
inheritance makes use of the foreign key property
|
||||
``ON DELETE CASCADE`` in all database implementations. A failure to
|
||||
implement this yourself will lead to dead rows in the database.
|
||||
|
||||
|
||||
Design-time considerations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Introducing a new type to the hierarchy, at any level, simply
|
||||
involves interjecting a new table into the schema. Subtypes of that
|
||||
type will automatically join with that new type at runtime.
|
||||
Similarly, modifying any entity type in the hierarchy by adding,
|
||||
modifying or removing fields affects only the immediate table
|
||||
mapped to that type. This mapping strategy provides the greatest
|
||||
flexibility at design time, since changes to any type are always
|
||||
limited to that type's dedicated table.
|
||||
|
||||
Performance impact
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This strategy inherently requires multiple JOIN operations to
|
||||
perform just about any query which can have a negative impact on
|
||||
performance, especially with large tables and/or large hierarchies.
|
||||
When partial objects are allowed, either globally or on the
|
||||
specific query, then querying for any type will not cause the
|
||||
tables of subtypes to be OUTER JOINed which can increase
|
||||
performance but the resulting partial objects will not fully load
|
||||
themselves on access of any subtype fields, so accessing fields of
|
||||
subtypes after such a query is not safe.
|
||||
|
||||
There is a general performance consideration with Class Table
|
||||
Inheritance: If the target-entity of a many-to-one or one-to-one
|
||||
association is a CTI entity, it is preferable for performance reasons that it
|
||||
be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
|
||||
Otherwise Doctrine *CANNOT* create proxy instances
|
||||
of this entity and will *ALWAYS* load the entity eagerly.
|
||||
|
||||
SQL Schema considerations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
For each entity in the Class-Table Inheritance hierarchy all the
|
||||
mapped fields have to be columns on the table of this entity.
|
||||
Additionally each child table has to have an id column that matches
|
||||
the id column definition on the root table (except for any sequence
|
||||
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.
|
||||
|
||||
|
||||
Overrides
|
||||
---------
|
||||
Used to override a mapping for an entity field or relationship.
|
||||
May be applied to an entity that extends a mapped superclass
|
||||
to override a relationship or field mapping defined by the mapped superclass.
|
||||
|
||||
|
||||
Association Override
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
Override a mapping for an entity relationship.
|
||||
|
||||
Could be used by an entity that extends a mapped superclass
|
||||
to override a relationship mapping defined by the mapped superclass.
|
||||
|
||||
Example:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// user mapping
|
||||
namespace MyProject\Model;
|
||||
/**
|
||||
* @MappedSuperclass
|
||||
*/
|
||||
class User
|
||||
{
|
||||
//other fields mapping
|
||||
|
||||
/**
|
||||
* @ManyToMany(targetEntity="Group", inversedBy="users")
|
||||
* @JoinTable(name="users_groups",
|
||||
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
|
||||
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
|
||||
* )
|
||||
*/
|
||||
protected $groups;
|
||||
|
||||
/**
|
||||
* @ManyToOne(targetEntity="Address")
|
||||
* @JoinColumn(name="address_id", referencedColumnName="id")
|
||||
*/
|
||||
protected $address;
|
||||
}
|
||||
|
||||
// admin mapping
|
||||
namespace MyProject\Model;
|
||||
/**
|
||||
* @Entity
|
||||
* @AssociationOverrides({
|
||||
* @AssociationOverride(name="groups",
|
||||
* joinTable=@JoinTable(
|
||||
* name="users_admingroups",
|
||||
* joinColumns=@JoinColumn(name="adminuser_id"),
|
||||
* inverseJoinColumns=@JoinColumn(name="admingroup_id")
|
||||
* )
|
||||
* ),
|
||||
* @AssociationOverride(name="address",
|
||||
* joinColumns=@JoinColumn(
|
||||
* name="adminaddress_id", referencedColumnName="id"
|
||||
* )
|
||||
* )
|
||||
* })
|
||||
*/
|
||||
class Admin extends User
|
||||
{
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<!-- user mapping -->
|
||||
<doctrine-mapping>
|
||||
<mapped-superclass name="MyProject\Model\User">
|
||||
<!-- other fields mapping -->
|
||||
<many-to-many field="groups" target-entity="Group" inversed-by="users">
|
||||
<cascade>
|
||||
<cascade-persist/>
|
||||
<cascade-merge/>
|
||||
<cascade-detach/>
|
||||
</cascade>
|
||||
<join-table name="users_groups">
|
||||
<join-columns>
|
||||
<join-column name="user_id" referenced-column-name="id" />
|
||||
</join-columns>
|
||||
<inverse-join-columns>
|
||||
<join-column name="group_id" referenced-column-name="id" />
|
||||
</inverse-join-columns>
|
||||
</join-table>
|
||||
</many-to-many>
|
||||
</mapped-superclass>
|
||||
</doctrine-mapping>
|
||||
|
||||
<!-- admin mapping -->
|
||||
<doctrine-mapping>
|
||||
<entity name="MyProject\Model\Admin">
|
||||
<association-overrides>
|
||||
<association-override name="groups">
|
||||
<join-table name="users_admingroups">
|
||||
<join-columns>
|
||||
<join-column name="adminuser_id"/>
|
||||
</join-columns>
|
||||
<inverse-join-columns>
|
||||
<join-column name="admingroup_id"/>
|
||||
</inverse-join-columns>
|
||||
</join-table>
|
||||
</association-override>
|
||||
<association-override name="address">
|
||||
<join-columns>
|
||||
<join-column name="adminaddress_id" referenced-column-name="id"/>
|
||||
</join-columns>
|
||||
</association-override>
|
||||
</association-overrides>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
.. code-block:: yaml
|
||||
|
||||
# user mapping
|
||||
MyProject\Model\User:
|
||||
type: mappedSuperclass
|
||||
# other fields mapping
|
||||
manyToOne:
|
||||
address:
|
||||
targetEntity: Address
|
||||
joinColumn:
|
||||
name: address_id
|
||||
referencedColumnName: id
|
||||
cascade: [ persist, merge ]
|
||||
manyToMany:
|
||||
groups:
|
||||
targetEntity: Group
|
||||
joinTable:
|
||||
name: users_groups
|
||||
joinColumns:
|
||||
user_id:
|
||||
referencedColumnName: id
|
||||
inverseJoinColumns:
|
||||
group_id:
|
||||
referencedColumnName: id
|
||||
cascade: [ persist, merge, detach ]
|
||||
|
||||
# admin mapping
|
||||
MyProject\Model\Admin:
|
||||
type: entity
|
||||
associationOverride:
|
||||
address:
|
||||
joinColumn:
|
||||
adminaddress_id:
|
||||
name: adminaddress_id
|
||||
referencedColumnName: id
|
||||
groups:
|
||||
joinTable:
|
||||
name: users_admingroups
|
||||
joinColumns:
|
||||
adminuser_id:
|
||||
referencedColumnName: id
|
||||
inverseJoinColumns:
|
||||
admingroup_id:
|
||||
referencedColumnName: id
|
||||
|
||||
|
||||
Things to note:
|
||||
|
||||
- The "association override" specifies the overrides base on the property name.
|
||||
- This feature is available for all kind of associations. (OneToOne, OneToMany, ManyToOne, ManyToMany)
|
||||
- The association type *CANNOT* be changed.
|
||||
- The override could redefine the joinTables or joinColumns depending on the association type.
|
||||
|
||||
Attribute Override
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
Override the mapping of a field.
|
||||
|
||||
Could be used by an entity that extends a mapped superclass to override a field mapping defined by the mapped superclass.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// user mapping
|
||||
namespace MyProject\Model;
|
||||
/**
|
||||
* @MappedSuperclass
|
||||
*/
|
||||
class User
|
||||
{
|
||||
/** @Id @GeneratedValue @Column(type="integer", name="user_id", length=150) */
|
||||
protected $id;
|
||||
|
||||
/** @Column(name="user_name", nullable=true, unique=false, length=250) */
|
||||
protected $name;
|
||||
|
||||
// other fields mapping
|
||||
}
|
||||
|
||||
// guest mapping
|
||||
namespace MyProject\Model;
|
||||
/**
|
||||
* @Entity
|
||||
* @AttributeOverrides({
|
||||
* @AttributeOverride(name="id",
|
||||
* column=@Column(
|
||||
* name = "guest_id",
|
||||
* type = "integer",
|
||||
length = 140
|
||||
* )
|
||||
* ),
|
||||
* @AttributeOverride(name="name",
|
||||
* column=@Column(
|
||||
* name = "guest_name",
|
||||
* nullable = false,
|
||||
* unique = true,
|
||||
length = 240
|
||||
* )
|
||||
* )
|
||||
* })
|
||||
*/
|
||||
class Guest extends User
|
||||
{
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<!-- user mapping -->
|
||||
<doctrine-mapping>
|
||||
<mapped-superclass name="MyProject\Model\User">
|
||||
<id name="id" type="integer" column="user_id" length="150">
|
||||
<generator strategy="AUTO"/>
|
||||
</id>
|
||||
<field name="name" column="user_name" type="string" length="250" nullable="true" unique="false" />
|
||||
<many-to-one field="address" target-entity="Address">
|
||||
<cascade>
|
||||
<cascade-persist/>
|
||||
<cascade-merge/>
|
||||
</cascade>
|
||||
<join-column name="address_id" referenced-column-name="id"/>
|
||||
</many-to-one>
|
||||
<!-- other fields mapping -->
|
||||
</mapped-superclass>
|
||||
</doctrine-mapping>
|
||||
|
||||
<!-- admin mapping -->
|
||||
<doctrine-mapping>
|
||||
<entity name="MyProject\Model\Guest">
|
||||
<attribute-overrides>
|
||||
<attribute-override name="id">
|
||||
<field column="guest_id" length="140"/>
|
||||
</attribute-override>
|
||||
<attribute-override name="name">
|
||||
<field column="guest_name" type="string" length="240" nullable="false" unique="true" />
|
||||
</attribute-override>
|
||||
</attribute-overrides>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
.. code-block:: yaml
|
||||
|
||||
# user mapping
|
||||
MyProject\Model\User:
|
||||
type: mappedSuperclass
|
||||
id:
|
||||
id:
|
||||
type: integer
|
||||
column: user_id
|
||||
length: 150
|
||||
generator:
|
||||
strategy: AUTO
|
||||
fields:
|
||||
name:
|
||||
type: string
|
||||
column: user_name
|
||||
length: 250
|
||||
nullable: true
|
||||
unique: false
|
||||
#other fields mapping
|
||||
|
||||
|
||||
# guest mapping
|
||||
MyProject\Model\Guest:
|
||||
type: entity
|
||||
attributeOverride:
|
||||
id:
|
||||
column: guest_id
|
||||
type: integer
|
||||
length: 140
|
||||
name:
|
||||
column: guest_name
|
||||
type: string
|
||||
length: 240
|
||||
nullable: false
|
||||
unique: true
|
||||
|
||||
Things to note:
|
||||
|
||||
- The "attribute override" specifies the overrides 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.
|
||||
5
docs/en/reference/installation.rst
Normal file
5
docs/en/reference/installation.rst
Normal file
@@ -0,0 +1,5 @@
|
||||
Installation
|
||||
============
|
||||
|
||||
The installation chapter has moved to `Installation and Configuration
|
||||
<reference/configuration>`_.
|
||||
189
docs/en/reference/limitations-and-known-issues.rst
Normal file
189
docs/en/reference/limitations-and-known-issues.rst
Normal file
@@ -0,0 +1,189 @@
|
||||
Limitations and Known Issues
|
||||
============================
|
||||
|
||||
We try to make using Doctrine2 a very pleasant experience.
|
||||
Therefore we think it is very important to be honest about the
|
||||
current limitations to our users. Much like every other piece of
|
||||
software Doctrine2 is not perfect and far from feature complete.
|
||||
This section should give you an overview of current limitations of
|
||||
Doctrine 2 as well as critical known issues that you should know
|
||||
about.
|
||||
|
||||
Current Limitations
|
||||
-------------------
|
||||
|
||||
There is a set of limitations that exist currently which might be
|
||||
solved in the future. Any of this limitations now stated has at
|
||||
least one ticket in the Tracker and is discussed for future
|
||||
releases.
|
||||
|
||||
Join-Columns with non-primary keys
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
It is not possible to use join columns pointing to non-primary keys. Doctrine will think these are the primary
|
||||
keys and create lazy-loading proxies with the data, which can lead to unexpected results. Doctrine can for performance
|
||||
reasons not validate the correctness of this settings at runtime but only through the Validate Schema command.
|
||||
|
||||
Mapping Arrays to a Join Table
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Related to the previous limitation with "Foreign Keys as
|
||||
Identifier" you might be interested in mapping the same table
|
||||
structure as given above to an array. However this is not yet
|
||||
possible either. See the following example:
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
CREATE TABLE product (
|
||||
id INTEGER,
|
||||
name VARCHAR,
|
||||
PRIMARY KEY(id)
|
||||
);
|
||||
|
||||
CREATE TABLE product_attributes (
|
||||
product_id INTEGER,
|
||||
attribute_name VARCHAR,
|
||||
attribute_value VARCHAR,
|
||||
PRIMARY KEY (product_id, attribute_name)
|
||||
);
|
||||
|
||||
This schema should be mapped to a Product Entity as follows:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
class Product
|
||||
{
|
||||
private $id;
|
||||
private $name;
|
||||
private $attributes = array();
|
||||
}
|
||||
|
||||
Where the ``attribute_name`` column contains the key and
|
||||
``attribute_value`` contains the value of each array element in
|
||||
``$attributes``.
|
||||
|
||||
The feature request for persistence of primitive value arrays
|
||||
`is described in the DDC-298 ticket <http://www.doctrine-project.org/jira/browse/DDC-298>`_.
|
||||
|
||||
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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
There are two bugs now that concern the use of cascade merge in combination with bi-directional associations.
|
||||
Make sure to study the behavior of cascade merge if you are using it:
|
||||
|
||||
- `DDC-875 <http://www.doctrine-project.org/jira/browse/DDC-875>`_ Merge can sometimes add the same entity twice into a collection
|
||||
- `DDC-763 <http://www.doctrine-project.org/jira/browse/DDC-763>`_ Cascade merge on associated entities can insert too many rows through "Persistence by Reachability"
|
||||
|
||||
Custom Persisters
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
A Persister in Doctrine is an object that is responsible for the
|
||||
hydration and write operations of an entity against the database.
|
||||
Currently there is no way to overwrite the persister implementation
|
||||
for a given entity, however there are several use-cases that can
|
||||
benefit from custom persister implementations:
|
||||
|
||||
|
||||
- `Add Upsert Support <http://www.doctrine-project.org/jira/browse/DDC-668>`_
|
||||
- `Evaluate possible ways in which stored-procedures can be used <http://www.doctrine-project.org/jira/browse/DDC-445>`_
|
||||
- The previous Filter Rules Feature Request
|
||||
|
||||
Persist Keys of Collections
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
PHP Arrays are ordered hash-maps and so should be the
|
||||
``Doctrine\Common\Collections\Collection`` interface. We plan to
|
||||
evaluate a feature that optionally persists and hydrates the keys
|
||||
of a Collection instance.
|
||||
|
||||
`Ticket DDC-213 <http://www.doctrine-project.org/jira/browse/DDC-213>`_
|
||||
|
||||
Mapping many tables to one entity
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
It is not possible to map several equally looking tables onto one
|
||||
entity. For example if you have a production and an archive table
|
||||
of a certain business concept then you cannot have both tables map
|
||||
to the same entity.
|
||||
|
||||
Behaviors
|
||||
~~~~~~~~~
|
||||
|
||||
Doctrine 2 will **never** include a behavior system like Doctrine 1
|
||||
in the core library. We don't think behaviors add more value than
|
||||
they cost pain and debugging hell. Please see the many different
|
||||
blog posts we have written on this topics:
|
||||
|
||||
- `Doctrine2 "Behaviors" in a Nutshell <http://www.doctrine-project.org/blog/doctrine2-behaviours-nutshell>`_
|
||||
- `A re-usable Versionable behavior for Doctrine2 <http://www.doctrine-project.org/blog/doctrine2-versionable>`_
|
||||
- `Write your own ORM on top of Doctrine2 <http://www.doctrine-project.org/blog/your-own-orm-doctrine2>`_
|
||||
- `Doctrine 2 Behavioral Extensions <http://www.doctrine-project.org/blog/doctrine2-behavioral-extensions>`_
|
||||
- `Doctrator <https://github.com/pablodip/doctrator`>_
|
||||
|
||||
Doctrine 2 has enough hooks and extension points so that **you** can
|
||||
add whatever you want on top of it. None of this will ever become
|
||||
core functionality of Doctrine2 however, you will have to rely on
|
||||
third party extensions for magical behaviors.
|
||||
|
||||
Nested Set
|
||||
~~~~~~~~~~
|
||||
|
||||
NestedSet was offered as a behavior in Doctrine 1 and will not be
|
||||
included in the core of Doctrine 2. However there are already two
|
||||
extensions out there that offer support for Nested Set with
|
||||
Doctrine 2:
|
||||
|
||||
|
||||
- `Doctrine2 Hierarchical-Structural Behavior <http://github.com/guilhermeblanco/Doctrine2-Hierarchical-Structural-Behavior>`_
|
||||
- `Doctrine2 NestedSet <http://github.com/blt04/doctrine2-nestedset>`_
|
||||
|
||||
Known Issues
|
||||
------------
|
||||
|
||||
The Known Issues section describes critical/blocker bugs and other
|
||||
issues that are either complicated to fix, not fixable due to
|
||||
backwards compatibility issues or where no simple fix exists (yet).
|
||||
We don't plan to add every bug in the tracker there, just those
|
||||
issues that can potentially cause nightmares or pain of any sort.
|
||||
|
||||
See the Open Bugs on Jira for more details on `bugs, improvement and feature
|
||||
requests
|
||||
<http://www.doctrine-project.org/jira/secure/IssueNavigator.jspa?reset=true&mode=hide&pid=10032&resolution=-1&sorter/field=updated&sorter/order=DESC>`_.
|
||||
|
||||
Identifier Quoting and Legacy Databases
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
For compatibility reasons between all the supported vendors and
|
||||
edge case problems Doctrine 2 does **NOT** do automatic identifier
|
||||
quoting. This can lead to problems when trying to get
|
||||
legacy-databases to work with Doctrine 2.
|
||||
|
||||
|
||||
- You can quote column-names as described in the
|
||||
:doc:`Basic-Mapping <basic-mapping>` section.
|
||||
- You cannot quote join column names.
|
||||
- You cannot use non [a-zA-Z0-9\_]+ characters, they will break
|
||||
several SQL statements.
|
||||
|
||||
Having problems with these kind of column names? Many databases
|
||||
support all CRUD operations on views that semantically map to
|
||||
certain tables. You can create views for all your problematic
|
||||
tables and column names to avoid the legacy quoting nightmare.
|
||||
|
||||
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).
|
||||
194
docs/en/reference/metadata-drivers.rst
Normal file
194
docs/en/reference/metadata-drivers.rst
Normal file
@@ -0,0 +1,194 @@
|
||||
Metadata Drivers
|
||||
================
|
||||
|
||||
The heart of an object relational mapper is the mapping information
|
||||
that glues everything together. It instructs the EntityManager how
|
||||
it should behave when dealing with the different entities.
|
||||
|
||||
Core Metadata Drivers
|
||||
---------------------
|
||||
|
||||
Doctrine provides a few different ways for you to specify your
|
||||
metadata:
|
||||
|
||||
|
||||
- **XML files** (XmlDriver)
|
||||
- **Class DocBlock Annotations** (AnnotationDriver)
|
||||
- **YAML files** (YamlDriver)
|
||||
- **PHP Code in files or static functions** (PhpDriver)
|
||||
|
||||
Something important to note about the above drivers is they are all
|
||||
an intermediate step to the same end result. The mapping
|
||||
information is populated to ``Doctrine\ORM\Mapping\ClassMetadata``
|
||||
instances. So in the end, Doctrine only ever has to work with the
|
||||
API of the ``ClassMetadata`` class to get mapping information for
|
||||
an entity.
|
||||
|
||||
.. note::
|
||||
|
||||
The populated ``ClassMetadata`` instances are also cached
|
||||
so in a production environment the parsing and populating only ever
|
||||
happens once. You can configure the metadata cache implementation
|
||||
using the ``setMetadataCacheImpl()`` method on the
|
||||
``Doctrine\ORM\Configuration`` class:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$em->getConfiguration()->setMetadataCacheImpl(new ApcCache());
|
||||
|
||||
|
||||
If you want to use one of the included core metadata drivers you
|
||||
just need to configure it. All the drivers are in the
|
||||
``Doctrine\ORM\Mapping\Driver`` namespace:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$driver = new \Doctrine\ORM\Mapping\Driver\XmlDriver('/path/to/mapping/files');
|
||||
$em->getConfiguration()->setMetadataDriverImpl($driver);
|
||||
|
||||
Implementing Metadata Drivers
|
||||
-----------------------------
|
||||
|
||||
In addition to the included metadata drivers you can very easily
|
||||
implement your own. All you need to do is define a class which
|
||||
implements the ``Driver`` interface:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace Doctrine\ORM\Mapping\Driver;
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||
|
||||
interface Driver
|
||||
{
|
||||
/**
|
||||
* Loads the metadata for the specified class into the provided container.
|
||||
*
|
||||
* @param string $className
|
||||
* @param ClassMetadataInfo $metadata
|
||||
*/
|
||||
function loadMetadataForClass($className, ClassMetadataInfo $metadata);
|
||||
|
||||
/**
|
||||
* Gets the names of all mapped classes known to this driver.
|
||||
*
|
||||
* @return array The names of all mapped classes known to this driver.
|
||||
*/
|
||||
function getAllClassNames();
|
||||
|
||||
/**
|
||||
* Whether the class with the specified name should have its metadata loaded.
|
||||
* This is only the case if it is either mapped as an Entity or a
|
||||
* MappedSuperclass.
|
||||
*
|
||||
* @param string $className
|
||||
* @return boolean
|
||||
*/
|
||||
function isTransient($className);
|
||||
}
|
||||
|
||||
If you want to write a metadata driver to parse information from
|
||||
some file format we've made your life a little easier by providing
|
||||
the ``AbstractFileDriver`` implementation for you to extend from:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class MyMetadataDriver extends AbstractFileDriver
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $_fileExtension = '.dcm.ext';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadMetadataForClass($className, ClassMetadataInfo $metadata)
|
||||
{
|
||||
$data = $this->_loadMappingFile($file);
|
||||
|
||||
// populate ClassMetadataInfo instance from $data
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function _loadMappingFile($file)
|
||||
{
|
||||
// parse contents of $file and return php data structure
|
||||
}
|
||||
}
|
||||
|
||||
.. note::
|
||||
|
||||
When using the ``AbstractFileDriver`` it requires that you
|
||||
only have one entity defined per file and the file named after the
|
||||
class described inside where namespace separators are replaced by
|
||||
periods. So if you have an entity named ``Entities\User`` and you
|
||||
wanted to write a mapping file for your driver above you would need
|
||||
to name the file ``Entities.User.dcm.ext`` for it to be
|
||||
recognized.
|
||||
|
||||
|
||||
Now you can use your ``MyMetadataDriver`` implementation by setting
|
||||
it with the ``setMetadataDriverImpl()`` method:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$driver = new MyMetadataDriver('/path/to/mapping/files');
|
||||
$em->getConfiguration()->setMetadataDriverImpl($driver);
|
||||
|
||||
ClassMetadata
|
||||
-------------
|
||||
|
||||
The last piece you need to know and understand about metadata in
|
||||
Doctrine 2 is the API of the ``ClassMetadata`` classes. You need to
|
||||
be familiar with them in order to implement your own drivers but
|
||||
more importantly to retrieve mapping information for a certain
|
||||
entity when needed.
|
||||
|
||||
You have all the methods you need to manually specify the mapping
|
||||
information instead of using some mapping file to populate it from.
|
||||
The base ``ClassMetadataInfo`` class is responsible for only data
|
||||
storage and is not meant for runtime use. It does not require that
|
||||
the class actually exists yet so it is useful for describing some
|
||||
entity before it exists and using that information to generate for
|
||||
example the entities themselves. The class ``ClassMetadata``
|
||||
extends ``ClassMetadataInfo`` and adds some functionality required
|
||||
for runtime usage and requires that the PHP class is present and
|
||||
can be autoloaded.
|
||||
|
||||
You can read more about the API of the ``ClassMetadata`` classes in
|
||||
the PHP Mapping chapter.
|
||||
|
||||
Getting ClassMetadata Instances
|
||||
-------------------------------
|
||||
|
||||
If you want to get the ``ClassMetadata`` instance for an entity in
|
||||
your project to programmatically use some mapping information to
|
||||
generate some HTML or something similar you can retrieve it through
|
||||
the ``ClassMetadataFactory``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cmf = $em->getMetadataFactory();
|
||||
$class = $cmf->getMetadataFor('MyEntityName');
|
||||
|
||||
Now you can learn about the entity and use the data stored in the
|
||||
``ClassMetadata`` instance to get all mapped fields for example and
|
||||
iterate over them:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
foreach ($class->fieldMappings as $fieldMapping) {
|
||||
echo $fieldMapping['fieldName'] . "\n";
|
||||
}
|
||||
|
||||
|
||||
150
docs/en/reference/namingstrategy.rst
Normal file
150
docs/en/reference/namingstrategy.rst
Normal file
@@ -0,0 +1,150 @@
|
||||
Implementing a NamingStrategy
|
||||
==============================
|
||||
|
||||
.. versionadded:: 2.3
|
||||
|
||||
Using a naming strategy you can provide rules for automatically generating
|
||||
database identifiers, columns and tables names
|
||||
when the table/column name is not given.
|
||||
This feature helps reduce the verbosity of the mapping document,
|
||||
eliminating repetitive noise (eg: ``TABLE_``).
|
||||
|
||||
|
||||
Configuring a naming strategy
|
||||
-----------------------------
|
||||
The default strategy used by Doctrine is quite minimal.
|
||||
|
||||
By default the ``Doctrine\ORM\Mapping\DefaultNamingStrategy``
|
||||
uses the simple class name and the attributes names to generate tables and columns
|
||||
|
||||
You can specify a different strategy by calling ``Doctrine\ORM\Configuration#setNamingStrategy()`` :
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$namingStrategy = new MyNamingStrategy();
|
||||
$configuration()->setNamingStrategy($namingStrategy);
|
||||
|
||||
Underscore naming strategy
|
||||
---------------------------
|
||||
|
||||
``\Doctrine\ORM\Mapping\UnderscoreNamingStrategy`` is a built-in strategy
|
||||
that might be a useful if you want to use a underlying convention.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$namingStrategy = new \Doctrine\ORM\Mapping\UnderscoreNamingStrategy(CASE_UPPER);
|
||||
$configuration()->setNamingStrategy($namingStrategy);
|
||||
|
||||
Then SomeEntityName will generate the table SOME_ENTITY_NAME when CASE_UPPER
|
||||
or some_entity_name using CASE_LOWER is given.
|
||||
|
||||
|
||||
Naming strategy interface
|
||||
-------------------------
|
||||
The interface ``Doctrine\ORM\Mapping\NamingStrategy`` allows you to specify
|
||||
a "naming standard" for database tables and columns.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Return a table name for an entity class
|
||||
*
|
||||
* @param string $className The fully-qualified class name
|
||||
* @return string A table name
|
||||
*/
|
||||
function classToTableName($className);
|
||||
|
||||
/**
|
||||
* Return a column name for a property
|
||||
*
|
||||
* @param string $propertyName A property
|
||||
* @return string A column name
|
||||
*/
|
||||
function propertyToColumnName($propertyName);
|
||||
|
||||
/**
|
||||
* Return the default reference column name
|
||||
*
|
||||
* @return string A column name
|
||||
*/
|
||||
function referenceColumnName();
|
||||
|
||||
/**
|
||||
* Return a join column name for a property
|
||||
*
|
||||
* @param string $propertyName A property
|
||||
* @return string A join column name
|
||||
*/
|
||||
function joinColumnName($propertyName);
|
||||
|
||||
/**
|
||||
* Return a join table name
|
||||
*
|
||||
* @param string $sourceEntity The source entity
|
||||
* @param string $targetEntity The target entity
|
||||
* @param string $propertyName A property
|
||||
* @return string A join table name
|
||||
*/
|
||||
function joinTableName($sourceEntity, $targetEntity, $propertyName = null);
|
||||
|
||||
/**
|
||||
* Return the foreign key column name for the given parameters
|
||||
*
|
||||
* @param string $entityName A entity
|
||||
* @param string $referencedColumnName A property
|
||||
* @return string A join column name
|
||||
*/
|
||||
function joinKeyColumnName($entityName, $referencedColumnName = null);
|
||||
|
||||
Implementing a naming strategy
|
||||
-------------------------------
|
||||
If you have database naming standards like all tables names should be prefixed
|
||||
by the application prefix, all column names should be upper case,
|
||||
you can easily achieve such standards by implementing a naming strategy.
|
||||
You need to implements NamingStrategy first. Following is an example
|
||||
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class MyAppNamingStrategy implements NamingStrategy
|
||||
{
|
||||
public function classToTableName($className)
|
||||
{
|
||||
return 'MyApp_' . substr($className, strrpos($className, '\\') + 1);
|
||||
}
|
||||
public function propertyToColumnName($propertyName)
|
||||
{
|
||||
return $propertyName;
|
||||
}
|
||||
public function referenceColumnName()
|
||||
{
|
||||
return 'id';
|
||||
}
|
||||
public function joinColumnName($propertyName)
|
||||
{
|
||||
return $propertyName . '_' . $this->referenceColumnName();
|
||||
}
|
||||
public function joinTableName($sourceEntity, $targetEntity, $propertyName = null)
|
||||
{
|
||||
return strtolower($this->classToTableName($sourceEntity) . '_' .
|
||||
$this->classToTableName($targetEntity));
|
||||
}
|
||||
public function joinKeyColumnName($entityName, $referencedColumnName = null)
|
||||
{
|
||||
return strtolower($this->classToTableName($entityName) . '_' .
|
||||
($referencedColumnName ?: $this->referenceColumnName()));
|
||||
}
|
||||
}
|
||||
|
||||
Configuring the namingstrategy is easy if.
|
||||
Just set your naming strategy calling ``Doctrine\ORM\Configuration#setNamingStrategy()`` :.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$namingStrategy = new MyAppNamingStrategy();
|
||||
$configuration()->setNamingStrategy($namingStrategy);
|
||||
905
docs/en/reference/native-sql.rst
Normal file
905
docs/en/reference/native-sql.rst
Normal file
@@ -0,0 +1,905 @@
|
||||
Native SQL
|
||||
==========
|
||||
|
||||
With ``NativeQuery`` you can execute native SELECT SQL statements
|
||||
and map the results to Doctrine entities or any other result format
|
||||
supported by Doctrine.
|
||||
|
||||
In order to make this mapping possible, you need to describe
|
||||
to Doctrine what columns in the result map to which entity property.
|
||||
This description is represented by a ``ResultSetMapping`` object.
|
||||
|
||||
With this feature you can map arbitrary SQL code to objects, such as highly
|
||||
vendor-optimized SQL or stored-procedures.
|
||||
|
||||
Writing ``ResultSetMapping`` from scratch is complex, but there is a convenience
|
||||
wrapper around it called a ``ResultSetMappingBuilder``. It can generate
|
||||
the mappings for you based on Entities and even generates the ``SELECT``
|
||||
clause based on this information for you.
|
||||
|
||||
.. note::
|
||||
|
||||
If you want to execute DELETE, UPDATE or INSERT statements
|
||||
the Native SQL API cannot be used and will probably throw errors.
|
||||
Use ``EntityManager#getConnection()`` to access the native database
|
||||
connection and call the ``executeUpdate()`` method for these
|
||||
queries.
|
||||
|
||||
The NativeQuery class
|
||||
---------------------
|
||||
|
||||
To create a ``NativeQuery`` you use the method
|
||||
``EntityManager#createNativeQuery($sql, $resultSetMapping)``. As you can see in
|
||||
the signature of this method, it expects 2 ingredients: The SQL you want to
|
||||
execute and the ``ResultSetMapping`` that describes how the results will be
|
||||
mapped.
|
||||
|
||||
Once you obtained an instance of a ``NativeQuery``, you can bind parameters to
|
||||
it with the same API that ``Query`` has and execute it.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
|
||||
$rsm = new ResultSetMapping();
|
||||
// build rsm here
|
||||
|
||||
$query = $entityManager->createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm);
|
||||
$query->setParameter(1, 'romanb');
|
||||
|
||||
$users = $query->getResult();
|
||||
|
||||
ResultSetMappingBuilder
|
||||
-----------------------
|
||||
|
||||
An easy start into ResultSet mapping is the ``ResultSetMappingBuilder`` object.
|
||||
This has several benefits:
|
||||
|
||||
- The builder takes care of automatically updating your ``ResultSetMapping``
|
||||
when the fields or associations change on the metadata of an entity.
|
||||
- You can generate the required ``SELECT`` expression for a builder
|
||||
by converting it to a string.
|
||||
- The API is much simpler than the usual ``ResultSetMapping`` API.
|
||||
|
||||
One downside is that the builder API does not yet support entities
|
||||
with inheritance hierachies.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
||||
|
||||
$sql = "SELECT u.id, u.name, a.id AS address_id, a.street, a.city " .
|
||||
"FROM users u INNER JOIN address a ON u.address_id = a.id";
|
||||
|
||||
$rsm = new ResultSetMappingBuilder($entityManager);
|
||||
$rsm->addRootEntityFromClassMetadata('MyProject\User', 'u');
|
||||
$rsm->addJoinedEntityFromClassMetadata('MyProject\Address', 'a', 'u', 'address', array('id' => 'address_id'));
|
||||
|
||||
The builder extends the ``ResultSetMapping`` class and as such has all the functionality of it as well.
|
||||
|
||||
..versionadded:: 2.4
|
||||
|
||||
Starting with Doctrine ORM 2.4 you can generate the ``SELECT`` clause
|
||||
from a ``ResultSetMappingBuilder``. You can either cast the builder
|
||||
object to ``(string)`` and the DQL aliases are used as SQL table aliases
|
||||
or use the ``generateSelectClause($tableAliases)`` method and pass
|
||||
a mapping from DQL alias (key) to SQL alias (value)
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
$selectClause = $builder->generateSelectClause(array(
|
||||
'u' => 't1',
|
||||
'g' => 't2'
|
||||
));
|
||||
$sql = "SELECT " . $selectClause . " FROM users t1 JOIN groups t2 ON t1.group_id = t2.id";
|
||||
|
||||
|
||||
The ResultSetMapping
|
||||
--------------------
|
||||
|
||||
Understanding the ``ResultSetMapping`` is the key to using a
|
||||
``NativeQuery``. A Doctrine result can contain the following
|
||||
components:
|
||||
|
||||
|
||||
- Entity results. These represent root result elements.
|
||||
- Joined entity results. These represent joined entities in
|
||||
associations of root entity results.
|
||||
- Field results. These represent a column in the result set that
|
||||
maps to a field of an entity. A field result always belongs to an
|
||||
entity result or joined entity result.
|
||||
- Scalar results. These represent scalar values in the result set
|
||||
that will appear in each result row. Adding scalar results to a
|
||||
ResultSetMapping can also cause the overall result to become
|
||||
**mixed** (see DQL - Doctrine Query Language) if the same
|
||||
ResultSetMapping also contains entity results.
|
||||
- Meta results. These represent columns that contain
|
||||
meta-information, such as foreign keys and discriminator columns.
|
||||
When querying for objects (``getResult()``), all meta columns of
|
||||
root entities or joined entities must be present in the SQL query
|
||||
and mapped accordingly using ``ResultSetMapping#addMetaResult``.
|
||||
|
||||
.. note::
|
||||
|
||||
It might not surprise you that Doctrine uses
|
||||
``ResultSetMapping`` internally when you create DQL queries. As
|
||||
the query gets parsed and transformed to SQL, Doctrine fills a
|
||||
``ResultSetMapping`` that describes how the results should be
|
||||
processed by the hydration routines.
|
||||
|
||||
|
||||
We will now look at each of the result types that can appear in a
|
||||
ResultSetMapping in detail.
|
||||
|
||||
Entity results
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
An entity result describes an entity type that appears as a root
|
||||
element in the transformed result. You add an entity result through
|
||||
``ResultSetMapping#addEntityResult()``. Let's take a look at the
|
||||
method signature in detail:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Adds an entity result to this ResultSetMapping.
|
||||
*
|
||||
* @param string $class The class name of the entity.
|
||||
* @param string $alias The alias for the class. The alias must be unique among all entity
|
||||
* results or joined entity results within this ResultSetMapping.
|
||||
*/
|
||||
public function addEntityResult($class, $alias)
|
||||
|
||||
The first parameter is the fully qualified name of the entity
|
||||
class. The second parameter is some arbitrary alias for this entity
|
||||
result that must be unique within a ``ResultSetMapping``. You use
|
||||
this alias to attach field results to the entity result. It is very
|
||||
similar to an identification variable that you use in DQL to alias
|
||||
classes or relationships.
|
||||
|
||||
An entity result alone is not enough to form a valid
|
||||
``ResultSetMapping``. An entity result or joined entity result
|
||||
always needs a set of field results, which we will look at soon.
|
||||
|
||||
Joined entity results
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A joined entity result describes an entity type that appears as a
|
||||
joined relationship element in the transformed result, attached to
|
||||
a (root) entity result. You add a joined entity result through
|
||||
``ResultSetMapping#addJoinedEntityResult()``. Let's take a look at
|
||||
the method signature in detail:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Adds a joined entity result.
|
||||
*
|
||||
* @param string $class The class name of the joined entity.
|
||||
* @param string $alias The unique alias to use for the joined entity.
|
||||
* @param string $parentAlias The alias of the entity result that is the parent of this joined result.
|
||||
* @param object $relation The association field that connects the parent entity result with the joined entity result.
|
||||
*/
|
||||
public function addJoinedEntityResult($class, $alias, $parentAlias, $relation)
|
||||
|
||||
The first parameter is the class name of the joined entity. The
|
||||
second parameter is an arbitrary alias for the joined entity that
|
||||
must be unique within the ``ResultSetMapping``. You use this alias
|
||||
to attach field results to the entity result. The third parameter
|
||||
is the alias of the entity result that is the parent type of the
|
||||
joined relationship. The fourth and last parameter is the name of
|
||||
the field on the parent entity result that should contain the
|
||||
joined entity result.
|
||||
|
||||
Field results
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
A field result describes the mapping of a single column in a SQL
|
||||
result set to a field in an entity. As such, field results are
|
||||
inherently bound to entity results. You add a field result through
|
||||
``ResultSetMapping#addFieldResult()``. Again, let's examine the
|
||||
method signature in detail:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Adds a field result that is part of an entity result or joined entity result.
|
||||
*
|
||||
* @param string $alias The alias of the entity result or joined entity result.
|
||||
* @param string $columnName The name of the column in the SQL result set.
|
||||
* @param string $fieldName The name of the field on the (joined) entity.
|
||||
*/
|
||||
public function addFieldResult($alias, $columnName, $fieldName)
|
||||
|
||||
The first parameter is the alias of the entity result to which the
|
||||
field result will belong. The second parameter is the name of the
|
||||
column in the SQL result set. Note that this name is case
|
||||
sensitive, i.e. if you use a native query against Oracle it must be
|
||||
all uppercase. The third parameter is the name of the field on the
|
||||
entity result identified by ``$alias`` into which the value of the
|
||||
column should be set.
|
||||
|
||||
Scalar results
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
A scalar result describes the mapping of a single column in a SQL
|
||||
result set to a scalar value in the Doctrine result. Scalar results
|
||||
are typically used for aggregate values but any column in the SQL
|
||||
result set can be mapped as a scalar value. To add a scalar result
|
||||
use ``ResultSetMapping#addScalarResult()``. The method signature in
|
||||
detail:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Adds a scalar result mapping.
|
||||
*
|
||||
* @param string $columnName The name of the column in the SQL result set.
|
||||
* @param string $alias The result alias with which the scalar result should be placed in the result structure.
|
||||
*/
|
||||
public function addScalarResult($columnName, $alias)
|
||||
|
||||
The first parameter is the name of the column in the SQL result set
|
||||
and the second parameter is the result alias under which the value
|
||||
of the column will be placed in the transformed Doctrine result.
|
||||
|
||||
Meta results
|
||||
~~~~~~~~~~~~
|
||||
|
||||
A meta result describes a single column in a SQL result set that
|
||||
is either a foreign key or a discriminator column. These columns
|
||||
are essential for Doctrine to properly construct objects out of SQL
|
||||
result sets. To add a column as a meta result use
|
||||
``ResultSetMapping#addMetaResult()``. The method signature in
|
||||
detail:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Adds a meta column (foreign key or discriminator column) to the result set.
|
||||
*
|
||||
* @param string $alias
|
||||
* @param string $columnAlias
|
||||
* @param string $columnName
|
||||
* @param boolean $isIdentifierColumn
|
||||
*/
|
||||
public function addMetaResult($alias, $columnAlias, $columnName, $isIdentifierColumn = false)
|
||||
|
||||
The first parameter is the alias of the entity result to which the
|
||||
meta column belongs. A meta result column (foreign key or
|
||||
discriminator column) always belongs to an entity result. The
|
||||
second parameter is the column alias/name of the column in the SQL
|
||||
result set and the third parameter is the column name used in the
|
||||
mapping.
|
||||
The fourth parameter should be set to true in case the primary key
|
||||
of the entity is the foreign key you're adding.
|
||||
|
||||
Discriminator Column
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When joining an inheritance tree you have to give Doctrine a hint
|
||||
which meta-column is the discriminator column of this tree.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Sets a discriminator column for an entity result or joined entity result.
|
||||
* The discriminator column will be used to determine the concrete class name to
|
||||
* instantiate.
|
||||
*
|
||||
* @param string $alias The alias of the entity result or joined entity result the discriminator
|
||||
* column should be used for.
|
||||
* @param string $discrColumn The name of the discriminator column in the SQL result set.
|
||||
*/
|
||||
public function setDiscriminatorColumn($alias, $discrColumn)
|
||||
|
||||
Examples
|
||||
~~~~~~~~
|
||||
|
||||
Understanding a ResultSetMapping is probably easiest through
|
||||
looking at some examples.
|
||||
|
||||
First a basic example that describes the mapping of a single
|
||||
entity.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// Equivalent DQL query: "select u from User u where u.name=?1"
|
||||
// User owns no associations.
|
||||
$rsm = new ResultSetMapping;
|
||||
$rsm->addEntityResult('User', 'u');
|
||||
$rsm->addFieldResult('u', 'id', 'id');
|
||||
$rsm->addFieldResult('u', 'name', 'name');
|
||||
|
||||
$query = $this->_em->createNativeQuery('SELECT id, name FROM users WHERE name = ?', $rsm);
|
||||
$query->setParameter(1, 'romanb');
|
||||
|
||||
$users = $query->getResult();
|
||||
|
||||
The result would look like this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
array(
|
||||
[0] => User (Object)
|
||||
)
|
||||
|
||||
Note that this would be a partial object if the entity has more
|
||||
fields than just id and name. In the example above the column and
|
||||
field names are identical but that is not necessary, of course.
|
||||
Also note that the query string passed to createNativeQuery is
|
||||
**real native SQL**. Doctrine does not touch this SQL in any way.
|
||||
|
||||
In the previous basic example, a User had no relations and the
|
||||
table the class is mapped to owns no foreign keys. The next example
|
||||
assumes User has a unidirectional or bidirectional one-to-one
|
||||
association to a CmsAddress, where the User is the owning side and
|
||||
thus owns the foreign key.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// Equivalent DQL query: "select u from User u where u.name=?1"
|
||||
// User owns an association to an Address but the Address is not loaded in the query.
|
||||
$rsm = new ResultSetMapping;
|
||||
$rsm->addEntityResult('User', 'u');
|
||||
$rsm->addFieldResult('u', 'id', 'id');
|
||||
$rsm->addFieldResult('u', 'name', 'name');
|
||||
$rsm->addMetaResult('u', 'address_id', 'address_id');
|
||||
|
||||
$query = $this->_em->createNativeQuery('SELECT id, name, address_id FROM users WHERE name = ?', $rsm);
|
||||
$query->setParameter(1, 'romanb');
|
||||
|
||||
$users = $query->getResult();
|
||||
|
||||
Foreign keys are used by Doctrine for lazy-loading purposes when
|
||||
querying for objects. In the previous example, each user object in
|
||||
the result will have a proxy (a "ghost") in place of the address
|
||||
that contains the address\_id. When the ghost proxy is accessed, it
|
||||
loads itself based on this key.
|
||||
|
||||
Consequently, associations that are *fetch-joined* do not require
|
||||
the foreign keys to be present in the SQL result set, only
|
||||
associations that are lazy.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// Equivalent DQL query: "select u from User u join u.address a WHERE u.name = ?1"
|
||||
// User owns association to an Address and the Address is loaded in the query.
|
||||
$rsm = new ResultSetMapping;
|
||||
$rsm->addEntityResult('User', 'u');
|
||||
$rsm->addFieldResult('u', 'id', 'id');
|
||||
$rsm->addFieldResult('u', 'name', 'name');
|
||||
$rsm->addJoinedEntityResult('Address' , 'a', 'u', 'address');
|
||||
$rsm->addFieldResult('a', 'address_id', 'id');
|
||||
$rsm->addFieldResult('a', 'street', 'street');
|
||||
$rsm->addFieldResult('a', 'city', 'city');
|
||||
|
||||
$sql = 'SELECT u.id, u.name, a.id AS address_id, a.street, a.city FROM users u ' .
|
||||
'INNER JOIN address a ON u.address_id = a.id WHERE u.name = ?';
|
||||
$query = $this->_em->createNativeQuery($sql, $rsm);
|
||||
$query->setParameter(1, 'romanb');
|
||||
|
||||
$users = $query->getResult();
|
||||
|
||||
In this case the nested entity ``Address`` is registered with the
|
||||
``ResultSetMapping#addJoinedEntityResult`` method, which notifies
|
||||
Doctrine that this entity is not hydrated at the root level, but as
|
||||
a joined entity somewhere inside the object graph. In this case we
|
||||
specify the alias 'u' as third parameter and ``address`` as fourth
|
||||
parameter, which means the ``Address`` is hydrated into the
|
||||
``User::$address`` property.
|
||||
|
||||
If a fetched entity is part of a mapped hierarchy that requires a
|
||||
discriminator column, this column must be present in the result set
|
||||
as a meta column so that Doctrine can create the appropriate
|
||||
concrete type. This is shown in the following example where we
|
||||
assume that there are one or more subclasses that extend User and
|
||||
either Class Table Inheritance or Single Table Inheritance is used
|
||||
to map the hierarchy (both use a discriminator column).
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// Equivalent DQL query: "select u from User u where u.name=?1"
|
||||
// User is a mapped base class for other classes. User owns no associations.
|
||||
$rsm = new ResultSetMapping;
|
||||
$rsm->addEntityResult('User', 'u');
|
||||
$rsm->addFieldResult('u', 'id', 'id');
|
||||
$rsm->addFieldResult('u', 'name', 'name');
|
||||
$rsm->addMetaResult('u', 'discr', 'discr'); // discriminator column
|
||||
$rsm->setDiscriminatorColumn('u', 'discr');
|
||||
|
||||
$query = $this->_em->createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm);
|
||||
$query->setParameter(1, 'romanb');
|
||||
|
||||
$users = $query->getResult();
|
||||
|
||||
Note that in the case of Class Table Inheritance, an example as
|
||||
above would result in partial objects if any objects in the result
|
||||
are actually a subtype of User. When using DQL, Doctrine
|
||||
automatically includes the necessary joins for this mapping
|
||||
strategy but with native SQL it is your responsibility.
|
||||
|
||||
Named Native Query
|
||||
------------------
|
||||
|
||||
You can also map a native query using a named native query mapping.
|
||||
|
||||
To achieve that, you must describe the SQL resultset structure
|
||||
using named native query (and sql resultset mappings if is a several resultset mappings).
|
||||
|
||||
Like named query, a named native query can be defined at class level or in a XML or YAML file.
|
||||
|
||||
|
||||
A resultSetMapping parameter is defined in @NamedNativeQuery,
|
||||
it represents the name of a defined @SqlResultSetMapping.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace MyProject\Model;
|
||||
/**
|
||||
* @NamedNativeQueries({
|
||||
* @NamedNativeQuery(
|
||||
* name = "fetchMultipleJoinsEntityResults",
|
||||
* resultSetMapping= "mappingMultipleJoinsEntityResults",
|
||||
* query = "SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM users u INNER JOIN addresses a ON u.id = a.user_id INNER JOIN phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username"
|
||||
* ),
|
||||
* })
|
||||
* @SqlResultSetMappings({
|
||||
* @SqlResultSetMapping(
|
||||
* name = "mappingMultipleJoinsEntityResults",
|
||||
* entities= {
|
||||
* @EntityResult(
|
||||
* entityClass = "__CLASS__",
|
||||
* fields = {
|
||||
* @FieldResult(name = "id", column="u_id"),
|
||||
* @FieldResult(name = "name", column="u_name"),
|
||||
* @FieldResult(name = "status", column="u_status"),
|
||||
* }
|
||||
* ),
|
||||
* @EntityResult(
|
||||
* entityClass = "Address",
|
||||
* fields = {
|
||||
* @FieldResult(name = "id", column="a_id"),
|
||||
* @FieldResult(name = "zip", column="a_zip"),
|
||||
* @FieldResult(name = "country", column="a_country"),
|
||||
* }
|
||||
* )
|
||||
* },
|
||||
* columns = {
|
||||
* @ColumnResult("numphones")
|
||||
* }
|
||||
* )
|
||||
*})
|
||||
*/
|
||||
class User
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
public $id;
|
||||
|
||||
/** @Column(type="string", length=50, nullable=true) */
|
||||
public $status;
|
||||
|
||||
/** @Column(type="string", length=255, unique=true) */
|
||||
public $username;
|
||||
|
||||
/** @Column(type="string", length=255) */
|
||||
public $name;
|
||||
|
||||
/** @OneToMany(targetEntity="Phonenumber") */
|
||||
public $phonenumbers;
|
||||
|
||||
/** @OneToOne(targetEntity="Address") */
|
||||
public $address;
|
||||
|
||||
// ....
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="MyProject\Model\User">
|
||||
<named-native-queries>
|
||||
<named-native-query name="fetchMultipleJoinsEntityResults" result-set-mapping="mappingMultipleJoinsEntityResults">
|
||||
<query>SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM users u INNER JOIN addresses a ON u.id = a.user_id INNER JOIN phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username</query>
|
||||
</named-native-query>
|
||||
</named-native-queries>
|
||||
<sql-result-set-mappings>
|
||||
<sql-result-set-mapping name="mappingMultipleJoinsEntityResults">
|
||||
<entity-result entity-class="__CLASS__">
|
||||
<field-result name="id" column="u_id"/>
|
||||
<field-result name="name" column="u_name"/>
|
||||
<field-result name="status" column="u_status"/>
|
||||
</entity-result>
|
||||
<entity-result entity-class="Address">
|
||||
<field-result name="id" column="a_id"/>
|
||||
<field-result name="zip" column="a_zip"/>
|
||||
<field-result name="country" column="a_country"/>
|
||||
</entity-result>
|
||||
<column-result name="numphones"/>
|
||||
</sql-result-set-mapping>
|
||||
</sql-result-set-mappings>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
.. code-block:: yaml
|
||||
|
||||
MyProject\Model\User:
|
||||
type: entity
|
||||
namedNativeQueries:
|
||||
fetchMultipleJoinsEntityResults:
|
||||
name: fetchMultipleJoinsEntityResults
|
||||
resultSetMapping: mappingMultipleJoinsEntityResults
|
||||
query: SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM users u INNER JOIN addresses a ON u.id = a.user_id INNER JOIN phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username
|
||||
sqlResultSetMappings:
|
||||
mappingMultipleJoinsEntityResults:
|
||||
name: mappingMultipleJoinsEntityResults
|
||||
columnResult:
|
||||
0:
|
||||
name: numphones
|
||||
entityResult:
|
||||
0:
|
||||
entityClass: __CLASS__
|
||||
fieldResult:
|
||||
0:
|
||||
name: id
|
||||
column: u_id
|
||||
1:
|
||||
name: name
|
||||
column: u_name
|
||||
2:
|
||||
name: status
|
||||
column: u_status
|
||||
1:
|
||||
entityClass: Address
|
||||
fieldResult:
|
||||
0:
|
||||
name: id
|
||||
column: a_id
|
||||
1:
|
||||
name: zip
|
||||
column: a_zip
|
||||
2:
|
||||
name: country
|
||||
column: a_country
|
||||
|
||||
|
||||
Things to note:
|
||||
- The resultset mapping declares the entities retrieved by this native query.
|
||||
- Each field of the entity is bound to a SQL alias (or column name).
|
||||
- All fields of the entity including the ones of subclasses
|
||||
and the foreign key columns of related entities have to be present in the SQL query.
|
||||
- Field definitions are optional provided that they map to the same
|
||||
column name as the one declared on the class property.
|
||||
- ``__CLASS__`` is an alias for the mapped class
|
||||
|
||||
|
||||
In the above example,
|
||||
the ``fetchJoinedAddress`` named query use the joinMapping result set mapping.
|
||||
This mapping returns 2 entities, User and Address, each property is declared and associated to a column name,
|
||||
actually the column name retrieved by the query.
|
||||
|
||||
Let's now see an implicit declaration of the property / column.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace MyProject\Model;
|
||||
/**
|
||||
* @NamedNativeQueries({
|
||||
* @NamedNativeQuery(
|
||||
* name = "findAll",
|
||||
* resultSetMapping = "mappingFindAll",
|
||||
* query = "SELECT * FROM addresses"
|
||||
* ),
|
||||
* })
|
||||
* @SqlResultSetMappings({
|
||||
* @SqlResultSetMapping(
|
||||
* name = "mappingFindAll",
|
||||
* entities= {
|
||||
* @EntityResult(
|
||||
* entityClass = "Address"
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* })
|
||||
*/
|
||||
class Address
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
public $id;
|
||||
|
||||
/** @Column() */
|
||||
public $country;
|
||||
|
||||
/** @Column() */
|
||||
public $zip;
|
||||
|
||||
/** @Column()*/
|
||||
public $city;
|
||||
|
||||
// ....
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="MyProject\Model\Address">
|
||||
<named-native-queries>
|
||||
<named-native-query name="findAll" result-set-mapping="mappingFindAll">
|
||||
<query>SELECT * FROM addresses</query>
|
||||
</named-native-query>
|
||||
</named-native-queries>
|
||||
<sql-result-set-mappings>
|
||||
<sql-result-set-mapping name="mappingFindAll">
|
||||
<entity-result entity-class="Address"/>
|
||||
</sql-result-set-mapping>
|
||||
</sql-result-set-mappings>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
.. code-block:: yaml
|
||||
|
||||
MyProject\Model\Address:
|
||||
type: entity
|
||||
namedNativeQueries:
|
||||
findAll:
|
||||
resultSetMapping: mappingFindAll
|
||||
query: SELECT * FROM addresses
|
||||
sqlResultSetMappings:
|
||||
mappingFindAll:
|
||||
name: mappingFindAll
|
||||
entityResult:
|
||||
address:
|
||||
entityClass: Address
|
||||
|
||||
|
||||
In this example, we only describe the entity member of the result set mapping.
|
||||
The property / column mappings is done using the entity mapping values.
|
||||
In this case the model property is bound to the model_txt column.
|
||||
If the association to a related entity involve a composite primary key,
|
||||
a @FieldResult element should be used for each foreign key column.
|
||||
The @FieldResult name is composed of the property name for the relationship,
|
||||
followed by a dot ("."), followed by the name or the field or property of the primary key.
|
||||
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace MyProject\Model;
|
||||
/**
|
||||
* @NamedNativeQueries({
|
||||
* @NamedNativeQuery(
|
||||
* name = "fetchJoinedAddress",
|
||||
* resultSetMapping= "mappingJoinedAddress",
|
||||
* query = "SELECT u.id, u.name, u.status, a.id AS a_id, a.country AS a_country, a.zip AS a_zip, a.city AS a_city FROM users u INNER JOIN addresses a ON u.id = a.user_id WHERE u.username = ?"
|
||||
* ),
|
||||
* })
|
||||
* @SqlResultSetMappings({
|
||||
* @SqlResultSetMapping(
|
||||
* name = "mappingJoinedAddress",
|
||||
* entities= {
|
||||
* @EntityResult(
|
||||
* entityClass = "__CLASS__",
|
||||
* fields = {
|
||||
* @FieldResult(name = "id"),
|
||||
* @FieldResult(name = "name"),
|
||||
* @FieldResult(name = "status"),
|
||||
* @FieldResult(name = "address.id", column = "a_id"),
|
||||
* @FieldResult(name = "address.zip", column = "a_zip"),
|
||||
* @FieldResult(name = "address.city", column = "a_city"),
|
||||
* @FieldResult(name = "address.country", column = "a_country"),
|
||||
* }
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* })
|
||||
*/
|
||||
class User
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
public $id;
|
||||
|
||||
/** @Column(type="string", length=50, nullable=true) */
|
||||
public $status;
|
||||
|
||||
/** @Column(type="string", length=255, unique=true) */
|
||||
public $username;
|
||||
|
||||
/** @Column(type="string", length=255) */
|
||||
public $name;
|
||||
|
||||
/** @OneToOne(targetEntity="Address") */
|
||||
public $address;
|
||||
|
||||
// ....
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="MyProject\Model\User">
|
||||
<named-native-queries>
|
||||
<named-native-query name="fetchJoinedAddress" result-set-mapping="mappingJoinedAddress">
|
||||
<query>SELECT u.id, u.name, u.status, a.id AS a_id, a.country AS a_country, a.zip AS a_zip, a.city AS a_city FROM users u INNER JOIN addresses a ON u.id = a.user_id WHERE u.username = ?</query>
|
||||
</named-native-query>
|
||||
</named-native-queries>
|
||||
<sql-result-set-mappings>
|
||||
<sql-result-set-mapping name="mappingJoinedAddress">
|
||||
<entity-result entity-class="__CLASS__">
|
||||
<field-result name="id"/>
|
||||
<field-result name="name"/>
|
||||
<field-result name="status"/>
|
||||
<field-result name="address.id" column="a_id"/>
|
||||
<field-result name="address.zip" column="a_zip"/>
|
||||
<field-result name="address.city" column="a_city"/>
|
||||
<field-result name="address.country" column="a_country"/>
|
||||
</entity-result>
|
||||
</sql-result-set-mapping>
|
||||
</sql-result-set-mappings>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
.. code-block:: yaml
|
||||
|
||||
MyProject\Model\User:
|
||||
type: entity
|
||||
namedNativeQueries:
|
||||
fetchJoinedAddress:
|
||||
name: fetchJoinedAddress
|
||||
resultSetMapping: mappingJoinedAddress
|
||||
query: SELECT u.id, u.name, u.status, a.id AS a_id, a.country AS a_country, a.zip AS a_zip, a.city AS a_city FROM users u INNER JOIN addresses a ON u.id = a.user_id WHERE u.username = ?
|
||||
sqlResultSetMappings:
|
||||
mappingJoinedAddress:
|
||||
entityResult:
|
||||
0:
|
||||
entityClass: __CLASS__
|
||||
fieldResult:
|
||||
0:
|
||||
name: id
|
||||
1:
|
||||
name: name
|
||||
2:
|
||||
name: status
|
||||
3:
|
||||
name: address.id
|
||||
column: a_id
|
||||
4:
|
||||
name: address.zip
|
||||
column: a_zip
|
||||
5:
|
||||
name: address.city
|
||||
column: a_city
|
||||
6:
|
||||
name: address.country
|
||||
column: a_country
|
||||
|
||||
|
||||
|
||||
If you retrieve a single entity and if you use the default mapping,
|
||||
you can use the resultClass attribute instead of resultSetMapping:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace MyProject\Model;
|
||||
/**
|
||||
* @NamedNativeQueries({
|
||||
* @NamedNativeQuery(
|
||||
* name = "find-by-id",
|
||||
* resultClass = "Address",
|
||||
* query = "SELECT * FROM addresses"
|
||||
* ),
|
||||
* })
|
||||
*/
|
||||
class Address
|
||||
{
|
||||
// ....
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="MyProject\Model\Address">
|
||||
<named-native-queries>
|
||||
<named-native-query name="find-by-id" result-class="Address">
|
||||
<query>SELECT * FROM addresses WHERE id = ?</query>
|
||||
</named-native-query>
|
||||
</named-native-queries>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
.. code-block:: yaml
|
||||
|
||||
MyProject\Model\Address:
|
||||
type: entity
|
||||
namedNativeQueries:
|
||||
findAll:
|
||||
name: findAll
|
||||
resultClass: Address
|
||||
query: SELECT * FROM addresses
|
||||
|
||||
|
||||
In some of your native queries, you'll have to return scalar values,
|
||||
for example when building report queries.
|
||||
You can map them in the @SqlResultsetMapping through @ColumnResult.
|
||||
You actually can even mix, entities and scalar returns in the same native query (this is probably not that common though).
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace MyProject\Model;
|
||||
/**
|
||||
* @NamedNativeQueries({
|
||||
* @NamedNativeQuery(
|
||||
* name = "count",
|
||||
* resultSetMapping= "mappingCount",
|
||||
* query = "SELECT COUNT(*) AS count FROM addresses"
|
||||
* )
|
||||
* })
|
||||
* @SqlResultSetMappings({
|
||||
* @SqlResultSetMapping(
|
||||
* name = "mappingCount",
|
||||
* columns = {
|
||||
* @ColumnResult(
|
||||
* name = "count"
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* })
|
||||
*/
|
||||
class Address
|
||||
{
|
||||
// ....
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="MyProject\Model\Address">
|
||||
<named-native-query name="count" result-set-mapping="mappingCount">
|
||||
<query>SELECT COUNT(*) AS count FROM addresses</query>
|
||||
</named-native-query>
|
||||
<sql-result-set-mappings>
|
||||
<sql-result-set-mapping name="mappingCount">
|
||||
<column-result name="count"/>
|
||||
</sql-result-set-mapping>
|
||||
</sql-result-set-mappings>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
.. code-block:: yaml
|
||||
|
||||
MyProject\Model\Address:
|
||||
type: entity
|
||||
namedNativeQueries:
|
||||
count:
|
||||
name: count
|
||||
resultSetMapping: mappingCount
|
||||
query: SELECT COUNT(*) AS count FROM addresses
|
||||
sqlResultSetMappings:
|
||||
mappingCount:
|
||||
name: mappingCount
|
||||
columnResult:
|
||||
count:
|
||||
name: count
|
||||
90
docs/en/reference/partial-objects.rst
Normal file
90
docs/en/reference/partial-objects.rst
Normal file
@@ -0,0 +1,90 @@
|
||||
Partial Objects
|
||||
===============
|
||||
|
||||
A partial object is an object whose state is not fully initialized
|
||||
after being reconstituted from the database and that is
|
||||
disconnected from the rest of its data. The following section will
|
||||
describe why partial objects are problematic and what the approach
|
||||
of Doctrine2 to this problem is.
|
||||
|
||||
.. note::
|
||||
|
||||
The partial object problem in general does not apply to
|
||||
methods or queries where you do not retrieve the query result as
|
||||
objects. Examples are: ``Query#getArrayResult()``,
|
||||
``Query#getScalarResult()``, ``Query#getSingleScalarResult()``,
|
||||
etc.
|
||||
|
||||
.. warning::
|
||||
|
||||
Use of partial objects is tricky. Fields that are not retrieved
|
||||
from the database will not be updated by the UnitOfWork even if they
|
||||
get changed in your objects. You can only promote a partial object
|
||||
to a fully-loaded object by calling ``EntityManager#refresh()``
|
||||
or a DQL query with the refresh flag.
|
||||
|
||||
|
||||
What is the problem?
|
||||
--------------------
|
||||
|
||||
In short, partial objects are problematic because they are usually
|
||||
objects with broken invariants. As such, code that uses these
|
||||
partial objects tends to be very fragile and either needs to "know"
|
||||
which fields or methods can be safely accessed or add checks around
|
||||
every field access or method invocation. The same holds true for
|
||||
the internals, i.e. the method implementations, of such objects.
|
||||
You usually simply assume the state you need in the method is
|
||||
available, after all you properly constructed this object before
|
||||
you pushed it into the database, right? These blind assumptions can
|
||||
quickly lead to null reference errors when working with such
|
||||
partial objects.
|
||||
|
||||
It gets worse with the scenario of an optional association (0..1 to
|
||||
1). When the associated field is NULL, you don't know whether this
|
||||
object does not have an associated object or whether it was simply
|
||||
not loaded when the owning object was loaded from the database.
|
||||
|
||||
These are reasons why many ORMs do not allow partial objects at all
|
||||
and instead you always have to load an object with all its fields
|
||||
(associations being proxied). One secure way to allow partial
|
||||
objects is if the programming language/platform allows the ORM tool
|
||||
to hook deeply into the object and instrument it in such a way that
|
||||
individual fields (not only associations) can be loaded lazily on
|
||||
first access. This is possible in Java, for example, through
|
||||
bytecode instrumentation. In PHP though this is not possible, so
|
||||
there is no way to have "secure" partial objects in an ORM with
|
||||
transparent persistence.
|
||||
|
||||
Doctrine, by default, does not allow partial objects. That means,
|
||||
any query that only selects partial object data and wants to
|
||||
retrieve the result as objects (i.e. ``Query#getResult()``) will
|
||||
raise an exception telling you that partial objects are dangerous.
|
||||
If you want to force a query to return you partial objects,
|
||||
possibly as a performance tweak, you can use the ``partial``
|
||||
keyword as follows:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$q = $em->createQuery("select partial u.{id,name} from MyApp\Domain\User u");
|
||||
|
||||
You can also get a partial reference instead of a proxy reference by
|
||||
calling:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$reference = $em->getPartialReference('MyApp\Domain\User', 1);
|
||||
|
||||
Partial references are objects with only the identifiers set as they
|
||||
are passed to the second argument of the ``getPartialReference()`` method.
|
||||
All other fields are null.
|
||||
|
||||
When should I force partial objects?
|
||||
------------------------------------
|
||||
|
||||
Mainly for optimization purposes, but be careful of premature
|
||||
optimization as partial objects lead to potentially more fragile
|
||||
code.
|
||||
|
||||
|
||||
310
docs/en/reference/php-mapping.rst
Normal file
310
docs/en/reference/php-mapping.rst
Normal file
@@ -0,0 +1,310 @@
|
||||
PHP Mapping
|
||||
===========
|
||||
|
||||
Doctrine 2 also allows you to provide the ORM metadata in the form
|
||||
of plain PHP code using the ``ClassMetadata`` API. You can write
|
||||
the code in PHP files or inside of a static function named
|
||||
``loadMetadata($class)`` on the entity class itself.
|
||||
|
||||
PHP Files
|
||||
---------
|
||||
|
||||
If you wish to write your mapping information inside PHP files that
|
||||
are named after the entity and included to populate the metadata
|
||||
for an entity you can do so by using the ``PHPDriver``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$driver = new PHPDriver('/path/to/php/mapping/files');
|
||||
$em->getConfiguration()->setMetadataDriverImpl($driver);
|
||||
|
||||
Now imagine we had an entity named ``Entities\User`` and we wanted
|
||||
to write a mapping file for it using the above configured
|
||||
``PHPDriver`` instance:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace Entities;
|
||||
|
||||
class User
|
||||
{
|
||||
private $id;
|
||||
private $username;
|
||||
}
|
||||
|
||||
To write the mapping information you just need to create a file
|
||||
named ``Entities.User.php`` inside of the
|
||||
``/path/to/php/mapping/files`` folder:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?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'
|
||||
));
|
||||
|
||||
Now we can easily retrieve the populated ``ClassMetadata`` instance
|
||||
where the ``PHPDriver`` includes the file and the
|
||||
``ClassMetadataFactory`` caches it for later retrieval:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$class = $em->getClassMetadata('Entities\User');
|
||||
// or
|
||||
$class = $em->getMetadataFactory()->getMetadataFor('Entities\User');
|
||||
|
||||
Static Function
|
||||
---------------
|
||||
|
||||
In addition to the PHP files you can also specify your mapping
|
||||
information inside of a static function defined on the entity class
|
||||
itself. This is useful for cases where you want to keep your entity
|
||||
and mapping information together but don't want to use annotations.
|
||||
For this you just need to use the ``StaticPHPDriver``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$driver = new StaticPHPDriver('/path/to/entities');
|
||||
$em->getConfiguration()->setMetadataDriverImpl($driver);
|
||||
|
||||
Now you just need to define a static function named
|
||||
``loadMetadata($metadata)`` on your entity:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace Entities;
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
|
||||
public static function loadMetadata(ClassMetadata $metadata)
|
||||
{
|
||||
$metadata->mapField(array(
|
||||
'id' => true,
|
||||
'fieldName' => 'id',
|
||||
'type' => 'integer'
|
||||
));
|
||||
|
||||
$metadata->mapField(array(
|
||||
'fieldName' => 'username',
|
||||
'type' => 'string'
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
ClassMetadataBuilder
|
||||
--------------------
|
||||
|
||||
To ease the use of the ClassMetadata API (which is very raw) there is a ``ClassMetadataBuilder`` that you can use.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace Entities;
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder;
|
||||
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
|
||||
public static function loadMetadata(ClassMetadata $metadata)
|
||||
{
|
||||
$builder = new ClassMetadataBuilder($metadata);
|
||||
$builder->createField('id', 'integer')->isPrimaryKey()->generatedValue()->build();
|
||||
$builder->addField('username', 'string');
|
||||
}
|
||||
}
|
||||
|
||||
The API of the ClassMetadataBuilder has the following methods with a fluent interface:
|
||||
|
||||
- ``addField($name, $type, array $mapping)``
|
||||
- ``setMappedSuperclass()``
|
||||
- ``setReadOnly()``
|
||||
- ``setCustomRepositoryClass($className)``
|
||||
- ``setTable($name)``
|
||||
- ``addIndex(array $columns, $indexName)``
|
||||
- ``addUniqueConstraint(array $columns, $constraintName)``
|
||||
- ``addNamedQuery($name, $dqlQuery)``
|
||||
- ``setJoinedTableInheritance()``
|
||||
- ``setSingleTableInheritance()``
|
||||
- ``setDiscriminatorColumn($name, $type = 'string', $length = 255)``
|
||||
- ``addDiscriminatorMapClass($name, $class)``
|
||||
- ``setChangeTrackingPolicyDeferredExplicit()``
|
||||
- ``setChangeTrackingPolicyNotify()``
|
||||
- ``addLifecycleEvent($methodName, $event)``
|
||||
- ``addManyToOne($name, $targetEntity, $inversedBy = null)``
|
||||
- ``addInverseOneToOne($name, $targetEntity, $mappedBy)``
|
||||
- ``addOwningOneToOne($name, $targetEntity, $inversedBy = null)``
|
||||
- ``addOwningManyToMany($name, $targetEntity, $inversedBy = null)``
|
||||
- ``addInverseManyToMany($name, $targetEntity, $mappedBy)``
|
||||
- ``addOneToMany($name, $targetEntity, $mappedBy)``
|
||||
|
||||
It also has several methods that create builders (which are necessary for advanced mappings):
|
||||
|
||||
- ``createField($name, $type)`` returns a ``FieldBuilder`` instance
|
||||
- ``createManyToOne($name, $targetEntity)`` returns an ``AssociationBuilder`` instance
|
||||
- ``createOneToOne($name, $targetEntity)`` returns an ``AssociationBuilder`` instance
|
||||
- ``createManyToMany($name, $targetEntity)`` returns an ``ManyToManyAssociationBuilder`` instance
|
||||
- ``createOneToMany($name, $targetEntity)`` returns an ``OneToManyAssociationBuilder`` instance
|
||||
|
||||
ClassMetadataInfo API
|
||||
---------------------
|
||||
|
||||
The ``ClassMetadataInfo`` class is the base data object for storing
|
||||
the mapping metadata for a single entity. It contains all the
|
||||
getters and setters you need populate and retrieve information for
|
||||
an entity.
|
||||
|
||||
General Setters
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
- ``setTableName($tableName)``
|
||||
- ``setPrimaryTable(array $primaryTableDefinition)``
|
||||
- ``setCustomRepositoryClass($repositoryClassName)``
|
||||
- ``setIdGeneratorType($generatorType)``
|
||||
- ``setIdGenerator($generator)``
|
||||
- ``setSequenceGeneratorDefinition(array $definition)``
|
||||
- ``setChangeTrackingPolicy($policy)``
|
||||
- ``setIdentifier(array $identifier)``
|
||||
|
||||
Inheritance Setters
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
- ``setInheritanceType($type)``
|
||||
- ``setSubclasses(array $subclasses)``
|
||||
- ``setParentClasses(array $classNames)``
|
||||
- ``setDiscriminatorColumn($columnDef)``
|
||||
- ``setDiscriminatorMap(array $map)``
|
||||
|
||||
Field Mapping Setters
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
- ``mapField(array $mapping)``
|
||||
- ``mapOneToOne(array $mapping)``
|
||||
- ``mapOneToMany(array $mapping)``
|
||||
- ``mapManyToOne(array $mapping)``
|
||||
- ``mapManyToMany(array $mapping)``
|
||||
|
||||
Lifecycle Callback Setters
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
- ``addLifecycleCallback($callback, $event)``
|
||||
- ``setLifecycleCallbacks(array $callbacks)``
|
||||
|
||||
Versioning Setters
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
- ``setVersionMapping(array &$mapping)``
|
||||
- ``setVersioned($bool)``
|
||||
- ``setVersionField()``
|
||||
|
||||
General Getters
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
- ``getTableName()``
|
||||
- ``getTemporaryIdTableName()``
|
||||
|
||||
Identifier Getters
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
- ``getIdentifierColumnNames()``
|
||||
- ``usesIdGenerator()``
|
||||
- ``isIdentifier($fieldName)``
|
||||
- ``isIdGeneratorIdentity()``
|
||||
- ``isIdGeneratorSequence()``
|
||||
- ``isIdGeneratorTable()``
|
||||
- ``isIdentifierNatural()``
|
||||
- ``getIdentifierFieldNames()``
|
||||
- ``getSingleIdentifierFieldName()``
|
||||
- ``getSingleIdentifierColumnName()``
|
||||
|
||||
Inheritance Getters
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
- ``isInheritanceTypeNone()``
|
||||
- ``isInheritanceTypeJoined()``
|
||||
- ``isInheritanceTypeSingleTable()``
|
||||
- ``isInheritanceTypeTablePerClass()``
|
||||
- ``isInheritedField($fieldName)``
|
||||
- ``isInheritedAssociation($fieldName)``
|
||||
|
||||
Change Tracking Getters
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
- ``isChangeTrackingDeferredExplicit()``
|
||||
- ``isChangeTrackingDeferredImplicit()``
|
||||
- ``isChangeTrackingNotify()``
|
||||
|
||||
Field & Association Getters
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
- ``isUniqueField($fieldName)``
|
||||
- ``isNullable($fieldName)``
|
||||
- ``getColumnName($fieldName)``
|
||||
- ``getFieldMapping($fieldName)``
|
||||
- ``getAssociationMapping($fieldName)``
|
||||
- ``getAssociationMappings()``
|
||||
- ``getFieldName($columnName)``
|
||||
- ``hasField($fieldName)``
|
||||
- ``getColumnNames(array $fieldNames = null)``
|
||||
- ``getTypeOfField($fieldName)``
|
||||
- ``getTypeOfColumn($columnName)``
|
||||
- ``hasAssociation($fieldName)``
|
||||
- ``isSingleValuedAssociation($fieldName)``
|
||||
- ``isCollectionValuedAssociation($fieldName)``
|
||||
|
||||
Lifecycle Callback Getters
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
- ``hasLifecycleCallbacks($lifecycleEvent)``
|
||||
- ``getLifecycleCallbacks($event)``
|
||||
|
||||
ClassMetadata API
|
||||
-----------------
|
||||
|
||||
The ``ClassMetadata`` class extends ``ClassMetadataInfo`` and adds
|
||||
the runtime functionality required by Doctrine. It adds a few extra
|
||||
methods related to runtime reflection for working with the entities
|
||||
themselves.
|
||||
|
||||
|
||||
- ``getReflectionClass()``
|
||||
- ``getReflectionProperties()``
|
||||
- ``getReflectionProperty($name)``
|
||||
- ``getSingleIdReflectionProperty()``
|
||||
- ``getIdentifierValues($entity)``
|
||||
- ``setIdentifierValues($entity, $id)``
|
||||
- ``setFieldValue($entity, $field, $value)``
|
||||
- ``getFieldValue($entity, $field)``
|
||||
|
||||
|
||||
541
docs/en/reference/query-builder.rst
Normal file
541
docs/en/reference/query-builder.rst
Normal file
@@ -0,0 +1,541 @@
|
||||
The QueryBuilder
|
||||
================
|
||||
|
||||
A ``QueryBuilder`` provides an API that is designed for
|
||||
conditionally constructing a DQL query in several steps.
|
||||
|
||||
It provides a set of classes and methods that is able to
|
||||
programmatically build queries, and also provides a fluent API.
|
||||
This means that you can change between one methodology to the other
|
||||
as you want, and also pick one if you prefer.
|
||||
|
||||
Constructing a new QueryBuilder object
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The same way you build a normal Query, you build a ``QueryBuilder``
|
||||
object, just providing the correct method name. Here is an example
|
||||
how to build a ``QueryBuilder`` object:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// $em instanceof EntityManager
|
||||
|
||||
// example1: creating a QueryBuilder instance
|
||||
$qb = $em->createQueryBuilder();
|
||||
|
||||
Once you have created an instance of QueryBuilder, it provides a
|
||||
set of useful informative functions that you can use. One good
|
||||
example is to inspect what type of object the ``QueryBuilder`` is.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// $qb instanceof QueryBuilder
|
||||
|
||||
// example2: retrieving type of QueryBuilder
|
||||
echo $qb->getType(); // Prints: 0
|
||||
|
||||
There're currently 3 possible return values for ``getType()``:
|
||||
|
||||
|
||||
- ``QueryBuilder::SELECT``, which returns value 0
|
||||
- ``QueryBuilder::DELETE``, returning value 1
|
||||
- ``QueryBuilder::UPDATE``, which returns value 2
|
||||
|
||||
It is possible to retrieve the associated ``EntityManager`` of the
|
||||
current ``QueryBuilder``, its DQL and also a ``Query`` object when
|
||||
you finish building your DQL.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// $qb instanceof QueryBuilder
|
||||
|
||||
// example3: retrieve the associated EntityManager
|
||||
$em = $qb->getEntityManager();
|
||||
|
||||
// example4: retrieve the DQL string of what was defined in QueryBuilder
|
||||
$dql = $qb->getDql();
|
||||
|
||||
// example5: retrieve the associated Query object with the processed DQL
|
||||
$q = $qb->getQuery();
|
||||
|
||||
Internally, ``QueryBuilder`` works with a DQL cache to increase
|
||||
performance. Any changes that may affect the generated DQL actually
|
||||
modifies the state of ``QueryBuilder`` to a stage we call
|
||||
STATE\_DIRTY. One ``QueryBuilder`` can be in two different states:
|
||||
|
||||
|
||||
- ``QueryBuilder::STATE_CLEAN``, which means DQL haven't been
|
||||
altered since last retrieval or nothing were added since its
|
||||
instantiation
|
||||
- ``QueryBuilder::STATE_DIRTY``, means DQL query must (and will)
|
||||
be processed on next retrieval
|
||||
|
||||
Working with QueryBuilder
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
High level API methods
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To simplify even more the way you build a query in Doctrine, we can take
|
||||
advantage of what we call Helper methods. For all base code, there
|
||||
is a set of useful methods to simplify a programmer's life. To
|
||||
illustrate how to work with them, here is the same example 6
|
||||
re-written using ``QueryBuilder`` helper methods:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// $qb instanceof QueryBuilder
|
||||
|
||||
$qb->select('u')
|
||||
->from('User', 'u')
|
||||
->where('u.id = ?1')
|
||||
->orderBy('u.name', 'ASC');
|
||||
|
||||
``QueryBuilder`` helper methods are considered the standard way to
|
||||
build DQL queries. Although it is supported, it should be avoided
|
||||
to use string based queries and greatly encouraged to use
|
||||
``$qb->expr()->*`` methods. Here is a converted example 8 to
|
||||
suggested standard way to build queries:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// $qb instanceof QueryBuilder
|
||||
|
||||
$qb->select(array('u')) // string 'u' is converted to array internally
|
||||
->from('User', 'u')
|
||||
->where($qb->expr()->orX(
|
||||
$qb->expr()->eq('u.id', '?1'),
|
||||
$qb->expr()->like('u.nickname', '?2')
|
||||
))
|
||||
->orderBy('u.surname', 'ASC'));
|
||||
|
||||
Here is a complete list of helper methods available in ``QueryBuilder``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class QueryBuilder
|
||||
{
|
||||
// Example - $qb->select('u')
|
||||
// Example - $qb->select(array('u', 'p'))
|
||||
// Example - $qb->select($qb->expr()->select('u', 'p'))
|
||||
public function select($select = null);
|
||||
|
||||
// Example - $qb->delete('User', 'u')
|
||||
public function delete($delete = null, $alias = null);
|
||||
|
||||
// Example - $qb->update('Group', 'g')
|
||||
public function update($update = null, $alias = null);
|
||||
|
||||
// Example - $qb->set('u.firstName', $qb->expr()->literal('Arnold'))
|
||||
// Example - $qb->set('u.numChilds', 'u.numChilds + ?1')
|
||||
// Example - $qb->set('u.numChilds', $qb->expr()->sum('u.numChilds', '?1'))
|
||||
public function set($key, $value);
|
||||
|
||||
// Example - $qb->from('Phonenumber', 'p')
|
||||
public function from($from, $alias = 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->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);
|
||||
|
||||
// NOTE: ->where() overrides all previously set conditions
|
||||
//
|
||||
// Example - $qb->where('u.firstName = ?1', $qb->expr()->eq('u.surname', '?2'))
|
||||
// Example - $qb->where($qb->expr()->andX($qb->expr()->eq('u.firstName', '?1'), $qb->expr()->eq('u.surname', '?2')))
|
||||
// Example - $qb->where('u.firstName = ?1 AND u.surname = ?2')
|
||||
public function where($where);
|
||||
|
||||
// Example - $qb->andWhere($qb->expr()->orX($qb->expr()->lte('u.age', 40), 'u.numChild = 0'))
|
||||
public function andWhere($where);
|
||||
|
||||
// Example - $qb->orWhere($qb->expr()->between('u.id', 1, 10));
|
||||
public function orWhere($where);
|
||||
|
||||
// NOTE: -> groupBy() overrides all previously set grouping conditions
|
||||
//
|
||||
// Example - $qb->groupBy('u.id')
|
||||
public function groupBy($groupBy);
|
||||
|
||||
// Example - $qb->addGroupBy('g.name')
|
||||
public function addGroupBy($groupBy);
|
||||
|
||||
// NOTE: -> having() overrides all previously set having conditions
|
||||
//
|
||||
// Example - $qb->having('u.salary >= ?1')
|
||||
// Example - $qb->having($qb->expr()->gte('u.salary', '?1'))
|
||||
public function having($having);
|
||||
|
||||
// Example - $qb->andHaving($qb->expr()->gt($qb->expr()->count('u.numChild'), 0))
|
||||
public function andHaving($having);
|
||||
|
||||
// Example - $qb->orHaving($qb->expr()->lte('g.managerLevel', '100'))
|
||||
public function orHaving($having);
|
||||
|
||||
// NOTE: -> orderBy() overrides all previously set ordering conditions
|
||||
//
|
||||
// Example - $qb->orderBy('u.surname', 'DESC')
|
||||
public function orderBy($sort, $order = null);
|
||||
|
||||
// Example - $qb->addOrderBy('u.firstName')
|
||||
public function addOrderBy($sort, $order = null); // Default $order = 'ASC'
|
||||
}
|
||||
|
||||
Binding parameters to your query
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Doctrine supports dynamic binding of parameters to your query,
|
||||
similar to preparing queries. You can use both strings and numbers
|
||||
as placeholders, although both have a slightly different syntax.
|
||||
Additionally, you must make your choice: Mixing both styles is not
|
||||
allowed. Binding parameters can simply be achieved as follows:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// $qb instanceof QueryBuilder
|
||||
|
||||
$qb->select('u')
|
||||
->from('User u')
|
||||
->where('u.id = ?1')
|
||||
->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
|
||||
alternative syntax is available:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// $qb instanceof QueryBuilder
|
||||
|
||||
$qb->select('u')
|
||||
->from('User u')
|
||||
->where('u.id = :identifier')
|
||||
->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
|
||||
while the named placeholders start with a : followed by a string.
|
||||
|
||||
Calling ``setParameter()`` automatically infers which type you are setting as
|
||||
value. This works for integers, arrays of strings/integers, DateTime instances
|
||||
and for managed entities. If you want to set a type explicitly you can call
|
||||
the third argument to ``setParameter()`` explicitly. It accepts either a PDO
|
||||
type or a DBAL Type name for conversion.
|
||||
|
||||
If you've got several parameters to bind to your query, you can
|
||||
also use setParameters() instead of setParameter() with the
|
||||
following syntax:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// $qb instanceof QueryBuilder
|
||||
|
||||
// Query here...
|
||||
$qb->setParameters(array(1 => 'value for ?1', 2 => 'value for ?2'));
|
||||
|
||||
Getting already bound parameters is easy - simply use the above
|
||||
mentioned syntax with "getParameter()" or "getParameters()":
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// $qb instanceof QueryBuilder
|
||||
|
||||
// See example above
|
||||
$params = $qb->getParameters();
|
||||
// $params instanceof \Doctrine\Common\Collections\ArrayCollection
|
||||
|
||||
// Equivalent to
|
||||
$param = $qb->getParameter(1);
|
||||
// $param instanceof \Doctrine\ORM\Query\Parameter
|
||||
|
||||
Note: If you try to get a parameter that was not bound yet,
|
||||
getParameter() simply returns NULL.
|
||||
|
||||
The API of a Query Parameter is:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
namespace Doctrine\ORM\Query;
|
||||
|
||||
class Parameter
|
||||
{
|
||||
public function getName();
|
||||
public function getValue();
|
||||
public function getType();
|
||||
public function setValue($value, $type = null);
|
||||
}
|
||||
|
||||
Limiting the Result
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To limit a result the query builder has some methods in common with
|
||||
the Query object which can be retrieved from ``EntityManager#createQuery()``.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// $qb instanceof QueryBuilder
|
||||
$offset = (int)$_GET['offset'];
|
||||
$limit = (int)$_GET['limit'];
|
||||
|
||||
$qb->add('select', 'u')
|
||||
->add('from', 'User u')
|
||||
->add('orderBy', 'u.name ASC')
|
||||
->setFirstResult( $offset )
|
||||
->setMaxResults( $limit );
|
||||
|
||||
Executing a Query
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
The QueryBuilder is a builder object only, it has no means of actually
|
||||
executing the Query. Additionally a set of parameters such as query hints
|
||||
cannot be set on the QueryBuilder itself. This is why you always have to convert
|
||||
a querybuilder instance into a Query object:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// $qb instanceof QueryBuilder
|
||||
$query = $qb->getQuery();
|
||||
|
||||
// Set additional Query options
|
||||
$query->setQueryHint('foo', 'bar');
|
||||
$query->useResultCache('my_cache_id');
|
||||
|
||||
// Execute Query
|
||||
$result = $query->getResult();
|
||||
$single = $query->getSingleResult();
|
||||
$array = $query->getArrayResult();
|
||||
$scalar = $query->getScalarResult();
|
||||
$singleScalar = $query->getSingleScalarResult();
|
||||
|
||||
The Expr class
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
To workaround some of the issues that ``add()`` method may cause,
|
||||
Doctrine created a class that can be considered as a helper for
|
||||
building expressions. This class is called ``Expr``, which provides a
|
||||
set of useful methods to help build expressions:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?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
|
||||
$qb->add('select', new Expr\Select(array('u')))
|
||||
->add('from', new Expr\From('User', 'u'))
|
||||
->add('where', $qb->expr()->orX(
|
||||
$qb->expr()->eq('u.id', '?1'),
|
||||
$qb->expr()->like('u.nickname', '?2')
|
||||
))
|
||||
->add('orderBy', new Expr\OrderBy('u.name', 'ASC'));
|
||||
|
||||
Although it still sounds complex, the ability to programmatically
|
||||
create conditions are the main feature of ``Expr``. Here it is a
|
||||
complete list of supported helper methods available:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class Expr
|
||||
{
|
||||
/** Conditional objects **/
|
||||
|
||||
// Example - $qb->expr()->andX($cond1 [, $condN])->add(...)->...
|
||||
public function andX($x = null); // Returns Expr\AndX instance
|
||||
|
||||
// Example - $qb->expr()->orX($cond1 [, $condN])->add(...)->...
|
||||
public function orX($x = null); // Returns Expr\OrX instance
|
||||
|
||||
|
||||
/** Comparison objects **/
|
||||
|
||||
// Example - $qb->expr()->eq('u.id', '?1') => u.id = ?1
|
||||
public function eq($x, $y); // Returns Expr\Comparison instance
|
||||
|
||||
// Example - $qb->expr()->neq('u.id', '?1') => u.id <> ?1
|
||||
public function neq($x, $y); // Returns Expr\Comparison instance
|
||||
|
||||
// Example - $qb->expr()->lt('u.id', '?1') => u.id < ?1
|
||||
public function lt($x, $y); // Returns Expr\Comparison instance
|
||||
|
||||
// Example - $qb->expr()->lte('u.id', '?1') => u.id <= ?1
|
||||
public function lte($x, $y); // Returns Expr\Comparison instance
|
||||
|
||||
// Example - $qb->expr()->gt('u.id', '?1') => u.id > ?1
|
||||
public function gt($x, $y); // Returns Expr\Comparison instance
|
||||
|
||||
// Example - $qb->expr()->gte('u.id', '?1') => u.id >= ?1
|
||||
public function gte($x, $y); // Returns Expr\Comparison instance
|
||||
|
||||
// Example - $qb->expr()->isNull('u.id') => u.id IS NULL
|
||||
public function isNull($x); // Returns string
|
||||
|
||||
// Example - $qb->expr()->isNotNull('u.id') => u.id IS NOT NULL
|
||||
public function isNotNull($x); // Returns string
|
||||
|
||||
|
||||
/** Arithmetic objects **/
|
||||
|
||||
// Example - $qb->expr()->prod('u.id', '2') => u.id * 2
|
||||
public function prod($x, $y); // Returns Expr\Math instance
|
||||
|
||||
// Example - $qb->expr()->diff('u.id', '2') => u.id - 2
|
||||
public function diff($x, $y); // Returns Expr\Math instance
|
||||
|
||||
// Example - $qb->expr()->sum('u.id', '2') => u.id + 2
|
||||
public function sum($x, $y); // Returns Expr\Math instance
|
||||
|
||||
// Example - $qb->expr()->quot('u.id', '2') => u.id / 2
|
||||
public function quot($x, $y); // Returns Expr\Math instance
|
||||
|
||||
|
||||
/** Pseudo-function objects **/
|
||||
|
||||
// Example - $qb->expr()->exists($qb2->getDql())
|
||||
public function exists($subquery); // Returns Expr\Func instance
|
||||
|
||||
// Example - $qb->expr()->all($qb2->getDql())
|
||||
public function all($subquery); // Returns Expr\Func instance
|
||||
|
||||
// Example - $qb->expr()->some($qb2->getDql())
|
||||
public function some($subquery); // Returns Expr\Func instance
|
||||
|
||||
// Example - $qb->expr()->any($qb2->getDql())
|
||||
public function any($subquery); // Returns Expr\Func instance
|
||||
|
||||
// Example - $qb->expr()->not($qb->expr()->eq('u.id', '?1'))
|
||||
public function not($restriction); // Returns Expr\Func instance
|
||||
|
||||
// Example - $qb->expr()->in('u.id', array(1, 2, 3))
|
||||
// Make sure that you do NOT use something similar to $qb->expr()->in('value', array('stringvalue')) as this will cause Doctrine to throw an Exception.
|
||||
// Instead, use $qb->expr()->in('value', array('?1')) and bind your parameter to ?1 (see section above)
|
||||
public function in($x, $y); // Returns Expr\Func instance
|
||||
|
||||
// Example - $qb->expr()->notIn('u.id', '2')
|
||||
public function notIn($x, $y); // Returns Expr\Func instance
|
||||
|
||||
// Example - $qb->expr()->like('u.firstname', $qb->expr()->literal('Gui%'))
|
||||
public function like($x, $y); // Returns Expr\Comparison instance
|
||||
|
||||
// Example - $qb->expr()->between('u.id', '1', '10')
|
||||
public function between($val, $x, $y); // Returns Expr\Func
|
||||
|
||||
|
||||
/** Function objects **/
|
||||
|
||||
// Example - $qb->expr()->trim('u.firstname')
|
||||
public function trim($x); // Returns Expr\Func
|
||||
|
||||
// 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()->lower('u.firstname')
|
||||
public function lower($x); // Returns Expr\Func
|
||||
|
||||
// Example - $qb->expr()->upper('u.firstname')
|
||||
public function upper($x); // Returns Expr\Func
|
||||
|
||||
// Example - $qb->expr()->length('u.firstname')
|
||||
public function length($x); // Returns Expr\Func
|
||||
|
||||
// Example - $qb->expr()->avg('u.age')
|
||||
public function avg($x); // Returns Expr\Func
|
||||
|
||||
// Example - $qb->expr()->max('u.age')
|
||||
public function max($x); // Returns Expr\Func
|
||||
|
||||
// Example - $qb->expr()->min('u.age')
|
||||
public function min($x); // Returns Expr\Func
|
||||
|
||||
// Example - $qb->expr()->abs('u.currentBalance')
|
||||
public function abs($x); // Returns Expr\Func
|
||||
|
||||
// Example - $qb->expr()->sqrt('u.currentBalance')
|
||||
public function sqrt($x); // Returns Expr\Func
|
||||
|
||||
// Example - $qb->expr()->count('u.firstname')
|
||||
public function count($x); // Returns Expr\Func
|
||||
|
||||
// Example - $qb->expr()->countDistinct('u.surname')
|
||||
public function countDistinct($x); // Returns Expr\Func
|
||||
}
|
||||
|
||||
|
||||
Low Level API
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Now we have describe the low level (thought of as the
|
||||
hardcore method) of creating queries. It may be useful to work at
|
||||
this level for optimization purposes, but most of the time it is
|
||||
preferred to work at a higher level of abstraction.
|
||||
|
||||
All helper methods in ``QueryBuilder`` actually rely on a single
|
||||
one: ``add()``. This method is responsible of building every piece
|
||||
of DQL. It takes 3 parameters: ``$dqlPartName``, ``$dqlPart`` and
|
||||
``$append`` (default=false)
|
||||
|
||||
|
||||
- ``$dqlPartName``: Where the ``$dqlPart`` should be placed.
|
||||
Possible values: select, from, where, groupBy, having, orderBy
|
||||
- ``$dqlPart``: What should be placed in ``$dqlPartName``. Accepts
|
||||
a string or any instance of ``Doctrine\ORM\Query\Expr\*``
|
||||
- ``$append``: Optional flag (default=false) if the ``$dqlPart``
|
||||
should override all previously defined items in ``$dqlPartName`` or
|
||||
not (no effect on the ``where`` and ``having`` DQL query parts,
|
||||
which always override all previously defined items)
|
||||
|
||||
-
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?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
|
||||
$qb->add('select', 'u')
|
||||
->add('from', 'User u')
|
||||
->add('where', 'u.id = ?1')
|
||||
->add('orderBy', 'u.name ASC');
|
||||
|
||||
Expr\* classes
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
When you call ``add()`` with string, it internally evaluates to an
|
||||
instance of ``Doctrine\ORM\Query\Expr\Expr\*`` class. Here is the
|
||||
same query of example 6 written using
|
||||
``Doctrine\ORM\Query\Expr\Expr\*`` classes:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?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
|
||||
$qb->add('select', new Expr\Select(array('u')))
|
||||
->add('from', new Expr\From('User', 'u'))
|
||||
->add('where', new Expr\Comparison('u.id', '=', '?1'))
|
||||
->add('orderBy', new Expr\OrderBy('u.name', 'ASC'));
|
||||
|
||||
Of course this is the hardest way to build a DQL query in Doctrine.
|
||||
To simplify some of these efforts, we introduce what we call as
|
||||
``Expr`` helper class.
|
||||
|
||||
509
docs/en/reference/tools.rst
Normal file
509
docs/en/reference/tools.rst
Normal file
@@ -0,0 +1,509 @@
|
||||
Tools
|
||||
=====
|
||||
|
||||
Doctrine Console
|
||||
----------------
|
||||
|
||||
The Doctrine Console is a Command Line Interface tool for simplifying common
|
||||
administration tasks during the development of a project that uses Doctrine 2.
|
||||
|
||||
Take a look at the :doc:`Installation and Configuration <configuration>`
|
||||
chapter for more information how to setup the console command.
|
||||
|
||||
Display Help Information
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Type ``php vendor/bin/doctrine`` on the command line and you should see an
|
||||
overview of the available commands or use the --help flag to get
|
||||
information on the available commands. If you want to know more
|
||||
about the use of generate entities for example, you can call:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$> php vendor/bin/doctrine orm:generate-entities --help
|
||||
|
||||
|
||||
Configuration
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Whenever the ``doctrine`` command line tool is invoked, it can
|
||||
access all Commands that were registered by developer. There is no
|
||||
auto-detection mechanism at work. The Doctrine binary
|
||||
already registers all the commands that currently ship with
|
||||
Doctrine DBAL and ORM. If you want to use additional commands you
|
||||
have to register them yourself.
|
||||
|
||||
All the commands of the Doctrine Console require access to the EntityManager
|
||||
or DBAL Connection. You have to inject them into the console application
|
||||
using so called Helper-Sets. This requires either the ``db``
|
||||
or the ``em`` helpers to be defined in order to work correctly.
|
||||
|
||||
Whenever you invoke the Doctrine binary the current folder is searched for a
|
||||
``cli-config.php`` file. This file contains the project specific configuration:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
|
||||
'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($conn)
|
||||
));
|
||||
$cli->setHelperSet($helperSet);
|
||||
|
||||
When dealing with the ORM package, the EntityManagerHelper is
|
||||
required:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
|
||||
'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em)
|
||||
));
|
||||
$cli->setHelperSet($helperSet);
|
||||
|
||||
The HelperSet instance has to be generated in a separate file (i.e.
|
||||
``cli-config.php``) that contains typical Doctrine bootstrap code
|
||||
and predefines the needed HelperSet attributes mentioned above. A
|
||||
sample ``cli-config.php`` file looks as follows:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// cli-config.php
|
||||
require_once 'my_bootstrap.php';
|
||||
|
||||
// Any way to access the EntityManager from your application
|
||||
$em = GetMyEntityManager();
|
||||
|
||||
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
|
||||
'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()),
|
||||
'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em)
|
||||
));
|
||||
|
||||
It is important to define a correct HelperSet that Doctrine binary
|
||||
script will ultimately use. The Doctrine Binary will automatically
|
||||
find the first instance of HelperSet in the global variable
|
||||
namespace and use this.
|
||||
|
||||
.. note::
|
||||
|
||||
You have to adjust this snippet for your specific application or framework
|
||||
and use their facilities to access the Doctrine EntityManager and
|
||||
Connection Resources.
|
||||
|
||||
Command Overview
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
The following Commands are currently available:
|
||||
|
||||
|
||||
- ``help`` Displays help for a command (?)
|
||||
- ``list`` Lists commands
|
||||
- ``dbal:import`` Import SQL file(s) directly to Database.
|
||||
- ``dbal:run-sql`` Executes arbitrary SQL directly from the
|
||||
command line.
|
||||
- ``orm:clear-cache:metadata`` Clear all metadata cache of the
|
||||
various cache drivers.
|
||||
- ``orm:clear-cache:query`` Clear all query cache of the various
|
||||
cache drivers.
|
||||
- ``orm:clear-cache:result`` Clear result cache of the various
|
||||
cache drivers.
|
||||
- ``orm:convert-d1-schema`` Converts Doctrine 1.X schema into a
|
||||
Doctrine 2.X schema.
|
||||
- ``orm:convert-mapping`` Convert mapping information between
|
||||
supported formats.
|
||||
- ``orm:ensure-production-settings`` Verify that Doctrine is
|
||||
properly configured for a production environment.
|
||||
- ``orm:generate-entities`` Generate entity classes and method
|
||||
stubs from your mapping information.
|
||||
- ``orm:generate-proxies`` Generates proxy classes for entity
|
||||
classes.
|
||||
- ``orm:generate-repositories`` Generate repository classes from
|
||||
your mapping information.
|
||||
- ``orm:run-dql`` Executes arbitrary DQL directly from the command
|
||||
line.
|
||||
- ``orm:schema-tool:create`` Processes the schema and either
|
||||
create it directly on EntityManager Storage Connection or generate
|
||||
the SQL output.
|
||||
- ``orm:schema-tool:drop`` Processes the schema and either drop
|
||||
the database schema of EntityManager Storage Connection or generate
|
||||
the SQL output.
|
||||
- ``orm:schema-tool:update`` Processes the schema and either
|
||||
update the database schema of EntityManager Storage Connection or
|
||||
generate the SQL output.
|
||||
|
||||
For these commands are also available aliases:
|
||||
|
||||
|
||||
- ``orm:convert:d1-schema`` is alias for ``orm:convert-d1-schema``.
|
||||
- ``orm:convert:mapping`` is alias for ``orm:convert-mapping``.
|
||||
- ``orm:generate:entities`` is alias for ``orm:generate-entities``.
|
||||
- ``orm:generate:proxies`` is alias for ``orm:generate-proxies``.
|
||||
- ``orm:generate:repositories`` is alias for ``orm:generate-repositories``.
|
||||
|
||||
.. note::
|
||||
|
||||
Console also supports auto completion, for example, instead of
|
||||
``orm:clear-cache:query`` you can use just ``o:c:q``.
|
||||
|
||||
Database Schema Generation
|
||||
--------------------------
|
||||
|
||||
.. note::
|
||||
|
||||
SchemaTool can do harm to your database. It will drop or alter
|
||||
tables, indexes, sequences and such. Please use this tool with
|
||||
caution in development and not on a production server. It is meant
|
||||
for helping you develop your Database Schema, but NOT with
|
||||
migrating schema from A to B in production. A safe approach would
|
||||
be generating the SQL on development server and saving it into SQL
|
||||
Migration files that are executed manually on the production
|
||||
server.
|
||||
|
||||
SchemaTool assumes your Doctrine Project uses the given database on
|
||||
its own. Update and Drop commands will mess with other tables if
|
||||
they are not related to the current project that is using Doctrine.
|
||||
Please be careful!
|
||||
|
||||
|
||||
To generate your database schema from your Doctrine mapping files
|
||||
you can use the ``SchemaTool`` class or the ``schema-tool`` Console
|
||||
Command.
|
||||
|
||||
When using the SchemaTool class directly, create your schema using
|
||||
the ``createSchema()`` method. First create an instance of the
|
||||
``SchemaTool`` and pass it an instance of the ``EntityManager``
|
||||
that you want to use to create the schema. This method receives an
|
||||
array of ``ClassMetadataInfo`` instances.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$tool = new \Doctrine\ORM\Tools\SchemaTool($em);
|
||||
$classes = array(
|
||||
$em->getClassMetadata('Entities\User'),
|
||||
$em->getClassMetadata('Entities\Profile')
|
||||
);
|
||||
$tool->createSchema($classes);
|
||||
|
||||
To drop the schema you can use the ``dropSchema()`` method.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$tool->dropSchema($classes);
|
||||
|
||||
This drops all the tables that are currently used by your metadata
|
||||
model. When you are changing your metadata a lot during development
|
||||
you might want to drop the complete database instead of only the
|
||||
tables of the current model to clean up with orphaned tables.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$tool->dropSchema($classes, \Doctrine\ORM\Tools\SchemaTool::DROP_DATABASE);
|
||||
|
||||
You can also use database introspection to update your schema
|
||||
easily with the ``updateSchema()`` method. It will compare your
|
||||
existing database schema to the passed array of
|
||||
``ClassMetdataInfo`` instances.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$tool->updateSchema($classes);
|
||||
|
||||
If you want to use this functionality from the command line you can
|
||||
use the ``schema-tool`` command.
|
||||
|
||||
To create the schema use the ``create`` command:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php doctrine orm:schema-tool:create
|
||||
|
||||
To drop the schema use the ``drop`` command:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php doctrine orm:schema-tool:drop
|
||||
|
||||
If you want to drop and then recreate the schema then use both
|
||||
options:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php doctrine orm:schema-tool:drop
|
||||
$ php doctrine orm:schema-tool:create
|
||||
|
||||
As you would think, if you want to update your schema use the
|
||||
``update`` command:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php doctrine orm:schema-tool:update
|
||||
|
||||
All of the above commands also accept a ``--dump-sql`` option that
|
||||
will output the SQL for the ran operation.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php doctrine orm:schema-tool:create --dump-sql
|
||||
|
||||
Before using the orm:schema-tool commands, remember to configure
|
||||
your cli-config.php properly.
|
||||
|
||||
.. note::
|
||||
|
||||
When using the Annotation Mapping Driver you have to either setup
|
||||
your autoloader in the cli-config.php correctly to find all the
|
||||
entities, or you can use the second argument of the
|
||||
``EntityManagerHelper`` to specify all the paths of your entities
|
||||
(or mapping files), i.e.
|
||||
``new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em, $mappingPaths);``
|
||||
|
||||
Entity Generation
|
||||
-----------------
|
||||
|
||||
Generate entity classes and method stubs from your mapping information.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php doctrine orm:generate-entities
|
||||
$ php doctrine orm:generate-entities --update-entities
|
||||
$ php doctrine orm:generate-entities --regenerate-entities
|
||||
|
||||
This command is not suited for constant usage. It is a little helper and does
|
||||
not support all the mapping edge cases very well. You still have to put work
|
||||
in your entities after using this command.
|
||||
|
||||
It is possible to use the EntityGenerator on code that you have already written. It will
|
||||
not be lost. The EntityGenerator will only append new code to your
|
||||
file and will not delete the old code. However this approach may still be prone
|
||||
to error and we suggest you use code repositories such as GIT or SVN to make
|
||||
backups of your code.
|
||||
|
||||
It makes sense to generate the entity code if you are using entities as Data
|
||||
Access Objects only and don't put much additional logic on them. If you are
|
||||
however putting much more logic on the entities you should refrain from using
|
||||
the entity-generator and code your entities manually.
|
||||
|
||||
.. note::
|
||||
|
||||
Even if you specified Inheritance options in your
|
||||
XML or YAML Mapping files the generator cannot generate the base and
|
||||
child classes for you correctly, because it doesn't know which
|
||||
class is supposed to extend which. You have to adjust the entity
|
||||
code manually for inheritance to work!
|
||||
|
||||
|
||||
Convert Mapping Information
|
||||
---------------------------
|
||||
|
||||
Convert mapping information between supported formats.
|
||||
|
||||
This is an **execute one-time** command. It should not be necessary for
|
||||
you to call this method multiple times, especially when using the ``--from-database``
|
||||
flag.
|
||||
|
||||
Converting an existing database schema into mapping files only solves about 70-80%
|
||||
of the necessary mapping information. Additionally the detection from an existing
|
||||
database cannot detect inverse associations, inheritance types,
|
||||
entities with foreign keys as primary keys and many of the
|
||||
semantical operations on associations such as cascade.
|
||||
|
||||
.. note::
|
||||
|
||||
There is no need to convert YAML or XML mapping files to annotations
|
||||
every time you make changes. All mapping drivers are first class citizens
|
||||
in Doctrine 2 and can be used as runtime mapping for the ORM. See the
|
||||
docs on XML and YAML Mapping for an example how to register this metadata
|
||||
drivers as primary mapping source.
|
||||
|
||||
To convert some mapping information between the various supported
|
||||
formats you can use the ``ClassMetadataExporter`` to get exporter
|
||||
instances for the different formats:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cme = new \Doctrine\ORM\Tools\Export\ClassMetadataExporter();
|
||||
|
||||
Once you have a instance you can use it to get an exporter. For
|
||||
example, the yml exporter:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$exporter = $cme->getExporter('yml', '/path/to/export/yml');
|
||||
|
||||
Now you can export some ``ClassMetadata`` instances:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$classes = array(
|
||||
$em->getClassMetadata('Entities\User'),
|
||||
$em->getClassMetadata('Entities\Profile')
|
||||
);
|
||||
$exporter->setMetadata($classes);
|
||||
$exporter->export();
|
||||
|
||||
This functionality is also available from the command line to
|
||||
convert your loaded mapping information to another format. The
|
||||
``orm:convert-mapping`` command accepts two arguments, the type to
|
||||
convert to and the path to generate it:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php doctrine orm:convert-mapping xml /path/to/mapping-path-converted-to-xml
|
||||
|
||||
Reverse Engineering
|
||||
-------------------
|
||||
|
||||
You can use the ``DatabaseDriver`` to reverse engineer a database
|
||||
to an array of ``ClassMetadataInfo`` instances and generate YAML,
|
||||
XML, etc. from them.
|
||||
|
||||
.. note::
|
||||
|
||||
Reverse Engineering is a **one-time** process that can get you started with a project.
|
||||
Converting an existing database schema into mapping files only detects about 70-80%
|
||||
of the necessary mapping information. Additionally the detection from an existing
|
||||
database cannot detect inverse associations, inheritance types,
|
||||
entities with foreign keys as primary keys and many of the
|
||||
semantical operations on associations such as cascade.
|
||||
|
||||
First you need to retrieve the metadata instances with the
|
||||
``DatabaseDriver``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$em->getConfiguration()->setMetadataDriverImpl(
|
||||
new \Doctrine\ORM\Mapping\Driver\DatabaseDriver(
|
||||
$em->getConnection()->getSchemaManager()
|
||||
)
|
||||
);
|
||||
|
||||
$cmf = new DisconnectedClassMetadataFactory();
|
||||
$cmf->setEntityManager($em);
|
||||
$metadata = $cmf->getAllMetadata();
|
||||
|
||||
Now you can get an exporter instance and export the loaded metadata
|
||||
to yml:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$exporter = $cme->getExporter('yml', '/path/to/export/yml');
|
||||
$exporter->setMetadata($metadata);
|
||||
$exporter->export();
|
||||
|
||||
You can also reverse engineer a database using the
|
||||
``orm:convert-mapping`` command:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php doctrine orm:convert-mapping --from-database yml /path/to/mapping-path-converted-to-yml
|
||||
|
||||
.. note::
|
||||
|
||||
Reverse Engineering is not always working perfectly
|
||||
depending on special cases. It will only detect Many-To-One
|
||||
relations (even if they are One-To-One) and will try to create
|
||||
entities from Many-To-Many tables. It also has problems with naming
|
||||
of foreign keys that have multiple column names. Any Reverse
|
||||
Engineered Database-Schema needs considerable manual work to become
|
||||
a useful domain model.
|
||||
|
||||
|
||||
Runtime vs Development Mapping Validation
|
||||
-----------------------------------------
|
||||
|
||||
For performance reasons Doctrine 2 has to skip some of the
|
||||
necessary validation of metadata mappings. You have to execute
|
||||
this validation in your development workflow to verify the
|
||||
associations are correctly defined.
|
||||
|
||||
You can either use the Doctrine Command Line Tool:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
doctrine orm:validate-schema
|
||||
|
||||
Or you can trigger the validation manually:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Tools\SchemaValidator;
|
||||
|
||||
$validator = new SchemaValidator($entityManager);
|
||||
$errors = $validator->validateMapping();
|
||||
|
||||
if (count($errors) > 0) {
|
||||
// Lots of errors!
|
||||
echo implode("\n\n", $errors);
|
||||
}
|
||||
|
||||
If the mapping is invalid the errors array contains a positive
|
||||
number of elements with error messages.
|
||||
|
||||
.. warning::
|
||||
|
||||
One mapping option that is not validated is the use of the referenced column name.
|
||||
It has to point to the equivalent primary key otherwise Doctrine will not work.
|
||||
|
||||
.. note::
|
||||
|
||||
One common error is to use a backlash in front of the
|
||||
fully-qualified class-name. Whenever a FQCN is represented inside a
|
||||
string (such as in your mapping definitions) you have to drop the
|
||||
prefix backslash. PHP does this with ``get_class()`` or Reflection
|
||||
methods for backwards compatibility reasons.
|
||||
|
||||
|
||||
Adding own commands
|
||||
-------------------
|
||||
|
||||
You can also add your own commands on-top of the Doctrine supported
|
||||
tools if you are using a manually built console script.
|
||||
|
||||
To include a new command on Doctrine Console, you need to do modify the
|
||||
``doctrine.php`` file a little:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// doctrine.php
|
||||
use Symfony\Component\Console\Helper\Application;
|
||||
|
||||
// as before ...
|
||||
|
||||
// replace the ConsoleRunner::run() statement with:
|
||||
$cli = new Application('Doctrine Command Line Interface', \Doctrine\ORM\Version::VERSION);
|
||||
$cli->setCatchExceptions(true);
|
||||
$cli->setHelperSet($helperSet);
|
||||
|
||||
// Register All Doctrine Commands
|
||||
ConsoleRunner::addCommands($cli);
|
||||
|
||||
// Register your own command
|
||||
$cli->addCommand(new \MyProject\Tools\Console\Commands\MyCustomCommand);
|
||||
|
||||
// Runs console application
|
||||
$cli->run();
|
||||
|
||||
Additionally, include multiple commands (and overriding previously
|
||||
defined ones) is possible through the command:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
$cli->addCommands(array(
|
||||
new \MyProject\Tools\Console\Commands\MyCustomCommand(),
|
||||
new \MyProject\Tools\Console\Commands\SomethingCommand(),
|
||||
new \MyProject\Tools\Console\Commands\AnotherCommand(),
|
||||
new \MyProject\Tools\Console\Commands\OneMoreCommand(),
|
||||
));
|
||||
353
docs/en/reference/transactions-and-concurrency.rst
Normal file
353
docs/en/reference/transactions-and-concurrency.rst
Normal file
@@ -0,0 +1,353 @@
|
||||
Transactions and Concurrency
|
||||
============================
|
||||
|
||||
Transaction Demarcation
|
||||
-----------------------
|
||||
|
||||
Transaction demarcation is the task of defining your transaction
|
||||
boundaries. Proper transaction demarcation is very important
|
||||
because if not done properly it can negatively affect the
|
||||
performance of your application. Many databases and database
|
||||
abstraction layers like PDO by default operate in auto-commit mode,
|
||||
which means that every single SQL statement is wrapped in a small
|
||||
transaction. Without any explicit transaction demarcation from your
|
||||
side, this quickly results in poor performance because transactions
|
||||
are not cheap.
|
||||
|
||||
For the most part, Doctrine 2 already takes care of proper
|
||||
transaction demarcation for you: All the write operations
|
||||
(INSERT/UPDATE/DELETE) are queued until ``EntityManager#flush()``
|
||||
is invoked which wraps all of these changes in a single
|
||||
transaction.
|
||||
|
||||
However, Doctrine 2 also allows (and encourages) you to take over
|
||||
and control transaction demarcation yourself.
|
||||
|
||||
These are two ways to deal with transactions when using the
|
||||
Doctrine ORM and are now described in more detail.
|
||||
|
||||
Approach 1: Implicitly
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The first approach is to use the implicit transaction handling
|
||||
provided by the Doctrine ORM EntityManager. Given the following
|
||||
code snippet, without any explicit transaction demarcation:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// $em instanceof EntityManager
|
||||
$user = new User;
|
||||
$user->setName('George');
|
||||
$em->persist($user);
|
||||
$em->flush();
|
||||
|
||||
Since we do not do any custom transaction demarcation in the above
|
||||
code, ``EntityManager#flush()`` will begin and commit/rollback a
|
||||
transaction. This behavior is made possible by the aggregation of
|
||||
the DML operations by the Doctrine ORM and is sufficient if all the
|
||||
data manipulation that is part of a unit of work happens through
|
||||
the domain model and thus the ORM.
|
||||
|
||||
Approach 2: Explicitly
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The explicit alternative is to use the ``Doctrine\DBAL\Connection``
|
||||
API directly to control the transaction boundaries. The code then
|
||||
looks like this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// $em instanceof EntityManager
|
||||
$em->getConnection()->beginTransaction(); // suspend auto-commit
|
||||
try {
|
||||
//... do some work
|
||||
$user = new User;
|
||||
$user->setName('George');
|
||||
$em->persist($user);
|
||||
$em->flush();
|
||||
$em->getConnection()->commit();
|
||||
} catch (Exception $e) {
|
||||
$em->getConnection()->rollback();
|
||||
$em->close();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
Explicit transaction demarcation is required when you want to
|
||||
include custom DBAL operations in a unit of work or when you want
|
||||
to make use of some methods of the ``EntityManager`` API that
|
||||
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:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// $em instanceof EntityManager
|
||||
$em->transactional(function($em) {
|
||||
//... do some work
|
||||
$user = new User;
|
||||
$user->setName('George');
|
||||
$em->persist($user);
|
||||
});
|
||||
|
||||
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).
|
||||
|
||||
Exception Handling
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When using implicit transaction demarcation and an exception occurs
|
||||
during ``EntityManager#flush()``, the transaction is automatically
|
||||
rolled back and the ``EntityManager`` closed.
|
||||
|
||||
When using explicit transaction demarcation and an exception
|
||||
occurs, the transaction should be rolled back immediately and the
|
||||
``EntityManager`` closed by invoking ``EntityManager#close()`` and
|
||||
subsequently discarded, as demonstrated in the example above. This
|
||||
can be handled elegantly by the control abstractions shown earlier.
|
||||
Note that when catching ``Exception`` you should generally re-throw
|
||||
the exception. If you intend to recover from some exceptions, catch
|
||||
them explicitly in earlier catch blocks (but do not forget to
|
||||
rollback the transaction and close the ``EntityManager`` there as
|
||||
well). All other best practices of exception handling apply
|
||||
similarly (i.e. either log or re-throw, not both, etc.).
|
||||
|
||||
As a result of this procedure, all previously managed or removed
|
||||
instances of the ``EntityManager`` become detached. The state of
|
||||
the detached objects will be the state at the point at which the
|
||||
transaction was rolled back. The state of the objects is in no way
|
||||
rolled back and thus the objects are now out of synch with the
|
||||
database. The application can continue to use the detached objects,
|
||||
knowing that their state is potentially no longer accurate.
|
||||
|
||||
If you intend to start another unit of work after an exception has
|
||||
occurred you should do that with a new ``EntityManager``.
|
||||
|
||||
Locking Support
|
||||
---------------
|
||||
|
||||
Doctrine 2 offers support for Pessimistic- and Optimistic-locking
|
||||
strategies natively. This allows to take very fine-grained control
|
||||
over what kind of locking is required for your Entities in your
|
||||
application.
|
||||
|
||||
Optimistic Locking
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Database transactions are fine for concurrency control during a
|
||||
single request. However, a database transaction should not span
|
||||
across requests, the so-called "user think time". Therefore a
|
||||
long-running "business transaction" that spans multiple requests
|
||||
needs to involve several database transactions. Thus, database
|
||||
transactions alone can no longer control concurrency during such a
|
||||
long-running business transaction. Concurrency control becomes the
|
||||
partial responsibility of the application itself.
|
||||
|
||||
Doctrine has integrated support for automatic optimistic locking
|
||||
via a version field. In this approach any entity that should be
|
||||
protected against concurrent modifications during long-running
|
||||
business transactions gets a version field that is either a simple
|
||||
number (mapping type: integer) or a timestamp (mapping type:
|
||||
datetime). When changes to such an entity are persisted at the end
|
||||
of a long-running conversation the version of the entity is
|
||||
compared to the version in the database and if they don't match, an
|
||||
``OptimisticLockException`` is thrown, indicating that the entity
|
||||
has been modified by someone else already.
|
||||
|
||||
You designate a version field in an entity as follows. In this
|
||||
example we'll use an integer.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
/** @Version @Column(type="integer") */
|
||||
private $version;
|
||||
// ...
|
||||
}
|
||||
|
||||
Alternatively a datetime type can be used (which maps to a SQL
|
||||
timestamp or datetime):
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
/** @Version @Column(type="datetime") */
|
||||
private $version;
|
||||
// ...
|
||||
}
|
||||
|
||||
Version numbers (not timestamps) should however be preferred as
|
||||
they can not potentially conflict in a highly concurrent
|
||||
environment, unlike timestamps where this is a possibility,
|
||||
depending on the resolution of the timestamp on the particular
|
||||
database platform.
|
||||
|
||||
When a version conflict is encountered during
|
||||
``EntityManager#flush()``, an ``OptimisticLockException`` is thrown
|
||||
and the active transaction rolled back (or marked for rollback).
|
||||
This exception can be caught and handled. Potential responses to an
|
||||
OptimisticLockException are to present the conflict to the user or
|
||||
to refresh or reload objects in a new transaction and then retrying
|
||||
the transaction.
|
||||
|
||||
With PHP promoting a share-nothing architecture, the time between
|
||||
showing an update form and actually modifying the entity can in the
|
||||
worst scenario be as long as your applications session timeout. If
|
||||
changes happen to the entity in that time frame you want to know
|
||||
directly when retrieving the entity that you will hit an optimistic
|
||||
locking exception:
|
||||
|
||||
You can always verify the version of an entity during a request
|
||||
either when calling ``EntityManager#find()``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\ORM\OptimisticLockException;
|
||||
|
||||
$theEntityId = 1;
|
||||
$expectedVersion = 184;
|
||||
|
||||
try {
|
||||
$entity = $em->find('User', $theEntityId, LockMode::OPTIMISTIC, $expectedVersion);
|
||||
|
||||
// do the work
|
||||
|
||||
$em->flush();
|
||||
} catch(OptimisticLockException $e) {
|
||||
echo "Sorry, but someone else has already changed this entity. Please apply the changes again!";
|
||||
}
|
||||
|
||||
Or you can use ``EntityManager#lock()`` to find out:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\ORM\OptimisticLockException;
|
||||
|
||||
$theEntityId = 1;
|
||||
$expectedVersion = 184;
|
||||
|
||||
$entity = $em->find('User', $theEntityId);
|
||||
|
||||
try {
|
||||
// assert version
|
||||
$em->lock($entity, LockMode::OPTIMISTIC, $expectedVersion);
|
||||
|
||||
} catch(OptimisticLockException $e) {
|
||||
echo "Sorry, but someone else has already changed this entity. Please apply the changes again!";
|
||||
}
|
||||
|
||||
Important Implementation Notes
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can easily get the optimistic locking workflow wrong if you
|
||||
compare the wrong versions. Say you have Alice and Bob editing a
|
||||
hypothetical blog post:
|
||||
|
||||
- Alice reads the headline of the blog post being "Foo", at
|
||||
optimistic lock version 1 (GET Request)
|
||||
- Bob reads the headline of the blog post being "Foo", at
|
||||
optimistic lock version 1 (GET Request)
|
||||
- Bob updates the headline to "Bar", upgrading the optimistic lock
|
||||
version to 2 (POST Request of a Form)
|
||||
- Alice updates the headline to "Baz", ... (POST Request of a
|
||||
Form)
|
||||
|
||||
Now at the last stage of this scenario the blog post has to be read
|
||||
again from the database before Alice's headline can be applied. At
|
||||
this point you will want to check if the blog post is still at
|
||||
version 1 (which it is not in this scenario).
|
||||
|
||||
Using optimistic locking correctly, you *have* to add the version
|
||||
as an additional hidden field (or into the SESSION for more
|
||||
safety). Otherwise you cannot verify the version is still the one
|
||||
being originally read from the database when Alice performed her
|
||||
GET request for the blog post. If this happens you might see lost
|
||||
updates you wanted to prevent with Optimistic Locking.
|
||||
|
||||
See the example code, The form (GET Request):
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$post = $em->find('BlogPost', 123456);
|
||||
|
||||
echo '<input type="hidden" name="id" value="' . $post->getId() . '" />';
|
||||
echo '<input type="hidden" name="version" value="' . $post->getCurrentVersion() . '" />';
|
||||
|
||||
And the change headline action (POST Request):
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$postId = (int)$_GET['id'];
|
||||
$postVersion = (int)$_GET['version'];
|
||||
|
||||
$post = $em->find('BlogPost', $postId, \Doctrine\DBAL\LockMode::OPTIMISTIC, $postVersion);
|
||||
|
||||
Pessimistic Locking
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Doctrine 2 supports Pessimistic Locking at the database level. No
|
||||
attempt is being made to implement pessimistic locking inside
|
||||
Doctrine, rather vendor-specific and ANSI-SQL commands are used to
|
||||
acquire row-level locks. Every Entity can be part of a pessimistic
|
||||
lock, there is no special metadata required to use this feature.
|
||||
|
||||
However for Pessimistic Locking to work you have to disable the
|
||||
Auto-Commit Mode of your Database and start a transaction around
|
||||
your pessimistic lock use-case using the "Approach 2: Explicit
|
||||
Transaction Demarcation" described above. Doctrine 2 will throw an
|
||||
Exception if you attempt to acquire an pessimistic lock and no
|
||||
transaction is running.
|
||||
|
||||
Doctrine 2 currently supports two pessimistic lock modes:
|
||||
|
||||
|
||||
- Pessimistic Write
|
||||
(``Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE``), locks the
|
||||
underlying database rows for concurrent Read and Write Operations.
|
||||
- Pessimistic Read (``Doctrine\DBAL\LockMode::PESSIMISTIC_READ``),
|
||||
locks other concurrent requests that attempt to update or lock rows
|
||||
in write mode.
|
||||
|
||||
You can use pessimistic locks in three different scenarios:
|
||||
|
||||
|
||||
1. Using
|
||||
``EntityManager#find($className, $id, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
|
||||
or
|
||||
``EntityManager#find($className, $id, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``
|
||||
2. Using
|
||||
``EntityManager#lock($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
|
||||
or
|
||||
``EntityManager#lock($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``
|
||||
3. Using
|
||||
``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
|
||||
or
|
||||
``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``
|
||||
|
||||
|
||||
60
docs/en/reference/unitofwork-associations.rst
Normal file
60
docs/en/reference/unitofwork-associations.rst
Normal file
@@ -0,0 +1,60 @@
|
||||
Association Updates: Owning Side and Inverse Side
|
||||
=================================================
|
||||
|
||||
When mapping bidirectional associations it is important to
|
||||
understand the concept of the owning and inverse sides. The
|
||||
following general rules apply:
|
||||
|
||||
- Relationships may be bidirectional or unidirectional.
|
||||
- A bidirectional relationship has both an owning side and an inverse side
|
||||
- A unidirectional relationship only has an owning side.
|
||||
- Doctrine will **only** check the owning side of an association for changes.
|
||||
|
||||
Bidirectional Associations
|
||||
--------------------------
|
||||
|
||||
The following rules apply to **bidirectional** associations:
|
||||
|
||||
- The inverse side has to use the ``mappedBy`` attribute of the OneToOne,
|
||||
OneToMany, or ManyToMany mapping declaration. The mappedBy
|
||||
attribute contains the name of the association-field on the owning side.
|
||||
- The owning side has to use the ``inversedBy`` attribute of the
|
||||
OneToOne, ManyToOne, or ManyToMany mapping declaration.
|
||||
The inversedBy attribute contains the name of the association-field
|
||||
on the inverse-side.
|
||||
- ManyToOne is always the owning side of a bidirectional association.
|
||||
- OneToMany is always the inverse side of a bidirectional association.
|
||||
- The owning side of a OneToOne association is the entity with the table
|
||||
containing the foreign key.
|
||||
- You can pick the owning side of a many-to-many association yourself.
|
||||
|
||||
Important concepts
|
||||
------------------
|
||||
|
||||
**Doctrine will only check the owning side of an association for changes.**
|
||||
|
||||
To fully understand this, remember how bidirectional associations
|
||||
are maintained in the object world. There are 2 references on each
|
||||
side of the association and these 2 references both represent the
|
||||
same association but can change independently of one another. Of
|
||||
course, in a correct application the semantics of the bidirectional
|
||||
association are properly maintained by the application developer
|
||||
(that's his responsibility). Doctrine needs to know which of these
|
||||
two in-memory references is the one that should be persisted and
|
||||
which not. This is what the owning/inverse concept is mainly used
|
||||
for.
|
||||
|
||||
**Changes made only to the inverse side of an association are ignored. Make sure to update both sides of a bidirectional association (or at least the owning side, from Doctrine's point of view)**
|
||||
|
||||
The owning side of a bidirectional association is the side Doctrine
|
||||
"looks at" when determining the state of the association, and
|
||||
consequently whether there is anything to do to update the
|
||||
association in the database.
|
||||
|
||||
.. note::
|
||||
|
||||
"Owning side" and "inverse side" are technical concepts of
|
||||
the ORM technology, not concepts of your domain model. What you
|
||||
consider as the owning side in your domain model can be different
|
||||
from what the owning side is for Doctrine. These are unrelated.
|
||||
|
||||
201
docs/en/reference/unitofwork.rst
Normal file
201
docs/en/reference/unitofwork.rst
Normal file
@@ -0,0 +1,201 @@
|
||||
Doctrine Internals explained
|
||||
============================
|
||||
|
||||
Object relational mapping is a complex topic and sufficiently understanding how Doctrine works internally helps you use its full power.
|
||||
|
||||
How Doctrine keeps track of Objects
|
||||
-----------------------------------
|
||||
|
||||
Doctrine uses the Identity Map pattern to track objects. Whenever you fetch an
|
||||
object from the database, Doctrine will keep a reference to this object inside
|
||||
its UnitOfWork. The array holding all the entity references is two-levels deep
|
||||
and has the keys "root entity name" and "id". Since Doctrine allows composite
|
||||
keys the id is a sorted, serialized version of all the key columns.
|
||||
|
||||
This allows Doctrine room for optimizations. If you call the EntityManager and
|
||||
ask for an entity with a specific ID twice, it will return the same instance:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
public function testIdentityMap()
|
||||
{
|
||||
$objectA = $this->entityManager->find('EntityName', 1);
|
||||
$objectB = $this->entityManager->find('EntityName', 1);
|
||||
|
||||
$this->assertSame($objectA, $objectB)
|
||||
}
|
||||
|
||||
Only one SELECT query will be fired against the database here. In the second
|
||||
``EntityManager#find()`` call Doctrine will check the identity map first and
|
||||
doesn't need to make that database roundtrip.
|
||||
|
||||
Even if you get a proxy object first then fetch the object by the same id you
|
||||
will still end up with the same reference:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
public function testIdentityMapReference()
|
||||
{
|
||||
$objectA = $this->entityManager->getReference('EntityName', 1);
|
||||
// check for proxyinterface
|
||||
$this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $objectA);
|
||||
|
||||
$objectB = $this->entityManager->find('EntityName', 1);
|
||||
|
||||
$this->assertSame($objectA, $objectB)
|
||||
}
|
||||
|
||||
The identity map being indexed by primary keys only allows shortcuts when you
|
||||
ask for objects by primary key. Assume you have the following ``persons``
|
||||
table:
|
||||
|
||||
::
|
||||
|
||||
id | name
|
||||
-------------
|
||||
1 | Benjamin
|
||||
2 | Bud
|
||||
|
||||
Take the following example where two
|
||||
consecutive calls are made against a repository to fetch an entity by a set of
|
||||
criteria:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
public function testIdentityMapRepositoryFindBy()
|
||||
{
|
||||
$repository = $this->entityManager->getRepository('Person');
|
||||
$objectA = $repository->findOneBy(array('name' => 'Benjamin'));
|
||||
$objectB = $repository->findOneBy(array('name' => 'Benjamin'));
|
||||
|
||||
$this->assertSame($objectA, $objectB);
|
||||
}
|
||||
|
||||
This query will still return the same references and `$objectA` and `$objectB`
|
||||
are indeed referencing the same object. However when checking your SQL logs you
|
||||
will realize that two queries have been executed against the database. Doctrine
|
||||
only knows objects by id, so a query for different criteria has to go to the
|
||||
database, even if it was executed just before.
|
||||
|
||||
But instead of creating a second Person object Doctrine first gets the primary
|
||||
key from the row and check if it already has an object inside the UnitOfWork
|
||||
with that primary key. In our example it finds an object and decides to return
|
||||
this instead of creating a new one.
|
||||
|
||||
The identity map has a second use-case. When you call ``EntityManager#flush``
|
||||
Doctrine will ask the identity map for all objects that are currently managed.
|
||||
This means you don't have to call ``EntityManager#persist`` over and over again
|
||||
to pass known objects to the EntityManager. This is a NO-OP for known entities,
|
||||
but leads to much code written that is confusing to other developers.
|
||||
|
||||
The following code WILL update your database with the changes made to the
|
||||
``Person`` object, even if you did not call ``EntityManager#persist``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$user = $entityManager->find("Person", 1);
|
||||
$user->setName("Guilherme");
|
||||
$entityManager->flush();
|
||||
|
||||
How Doctrine Detects Changes
|
||||
----------------------------
|
||||
|
||||
Doctrine is a data-mapper that tries to achieve persistence-ignorance (PI).
|
||||
This means you map php objects into a relational database that don't
|
||||
necessarily know about the database at all. A natural question would now be,
|
||||
"how does Doctrine even detect objects have changed?".
|
||||
|
||||
For this Doctrine keeps a second map inside the UnitOfWork. Whenever you fetch
|
||||
an object from the database Doctrine will keep a copy of all the properties and
|
||||
associations inside the UnitOfWork. Because variables in the PHP language are
|
||||
subject to "copy-on-write" the memory usage of a PHP request that only reads
|
||||
objects from the database is the same as if Doctrine did not keep this variable
|
||||
copy. Only if you start changing variables PHP will create new variables internally
|
||||
that consume new memory.
|
||||
|
||||
Now whenever you call ``EntityManager#flush`` Doctrine will iterate over the
|
||||
Identity Map and for each object compares the original property and association
|
||||
values with the values that are currently set on the object. If changes are
|
||||
detected then the object is queued for a SQL UPDATE operation. Only the fields
|
||||
that actually changed are updated.
|
||||
|
||||
This process has an obvious performance impact. The larger the size of the
|
||||
UnitOfWork is, the longer this computation takes. There are several ways to
|
||||
optimize the performance of the Flush Operation:
|
||||
|
||||
- Mark entities as read only. These entities can only be inserted or removed,
|
||||
but are never updated. They are omitted in the changeset calculation.
|
||||
- Temporarily mark entities as read only. If you have a very large UnitOfWork
|
||||
but know that a large set of entities has not changed, just mark them as read
|
||||
only with ``$entityManager->getUnitOfWork()->markReadOnly($entity)``.
|
||||
- Flush only a single entity with ``$entityManager->flush($entity)``.
|
||||
- Use :doc:`Change Tracking Policies <change-tracking-policies>` to use more
|
||||
explicit strategies of notifying the UnitOfWork what objects/properties
|
||||
changed.
|
||||
|
||||
|
||||
Query Internals
|
||||
---------------
|
||||
|
||||
The different ORM Layers
|
||||
------------------------
|
||||
|
||||
Doctrine ships with a set of layers with different responsibilities. This
|
||||
section gives a short explanation of each layer.
|
||||
|
||||
Hydration
|
||||
~~~~~~~~~
|
||||
|
||||
Responsible for creating a final result from a raw database statement and a
|
||||
result-set mapping object. The developer can choose which kind of result he
|
||||
wishes to be hydrated. Default result-types include:
|
||||
|
||||
- SQL to Entities
|
||||
- SQL to structured Arrays
|
||||
- SQL to simple scalar result arrays
|
||||
- SQL to a single result variable
|
||||
|
||||
Hydration to entities and arrays is one of most complex parts of Doctrine
|
||||
algorithm-wise. It can built results with for example:
|
||||
|
||||
- Single table selects
|
||||
- Joins with n:1 or 1:n cardinality, grouping belonging to the same parent.
|
||||
- Mixed results of objects and scalar values
|
||||
- Hydration of results by a given scalar value as key.
|
||||
|
||||
Persisters
|
||||
~~~~~~~~~~
|
||||
|
||||
tbr
|
||||
|
||||
UnitOfWork
|
||||
~~~~~~~~~~
|
||||
|
||||
tbr
|
||||
|
||||
ResultSetMapping
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
tbr
|
||||
|
||||
DQL Parser
|
||||
~~~~~~~~~~
|
||||
|
||||
tbr
|
||||
|
||||
SQLWalker
|
||||
~~~~~~~~~
|
||||
|
||||
tbr
|
||||
|
||||
EntityManager
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
tbr
|
||||
|
||||
ClassMetadataFactory
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
tbr
|
||||
|
||||
712
docs/en/reference/working-with-associations.rst
Normal file
712
docs/en/reference/working-with-associations.rst
Normal file
@@ -0,0 +1,712 @@
|
||||
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:
|
||||
|
||||
|
||||
- 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
|
||||
``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
|
||||
----------------------------
|
||||
|
||||
We will use a simple comment system with Users and Comments as
|
||||
entities to show examples of association management. See the PHP
|
||||
docblocks of each association in the following example for
|
||||
information about its type and if it's the owning or inverse side.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
class User
|
||||
{
|
||||
/** @Id @GeneratedValue @Column(type="string") */
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* 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")}
|
||||
* )
|
||||
*/
|
||||
private $favorites;
|
||||
|
||||
/**
|
||||
* 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")}
|
||||
* )
|
||||
*/
|
||||
private $commentsRead;
|
||||
|
||||
/**
|
||||
* Bidirectional - One-To-Many (INVERSE SIDE)
|
||||
*
|
||||
* @OneToMany(targetEntity="Comment", mappedBy="author")
|
||||
*/
|
||||
private $commentsAuthored;
|
||||
|
||||
/**
|
||||
* Unidirectional - Many-To-One
|
||||
*
|
||||
* @ManyToOne(targetEntity="Comment")
|
||||
*/
|
||||
private $firstComment;
|
||||
}
|
||||
|
||||
/** @Entity */
|
||||
class Comment
|
||||
{
|
||||
/** @Id @GeneratedValue @Column(type="string") */
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* Bidirectional - Many comments are favorited by many users (INVERSE SIDE)
|
||||
*
|
||||
* @ManyToMany(targetEntity="User", mappedBy="favorites")
|
||||
*/
|
||||
private $userFavorites;
|
||||
|
||||
/**
|
||||
* Bidirectional - Many Comments are authored by one user (OWNING SIDE)
|
||||
*
|
||||
* @ManyToOne(targetEntity="User", inversedBy="commentsAuthored")
|
||||
*/
|
||||
private $author;
|
||||
}
|
||||
|
||||
This two entities generate the following MySQL Schema (Foreign Key
|
||||
definitions omitted):
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
CREATE TABLE User (
|
||||
id VARCHAR(255) NOT NULL,
|
||||
firstComment_id VARCHAR(255) DEFAULT NULL,
|
||||
PRIMARY KEY(id)
|
||||
) ENGINE = InnoDB;
|
||||
|
||||
CREATE TABLE Comment (
|
||||
id VARCHAR(255) NOT NULL,
|
||||
author_id VARCHAR(255) DEFAULT NULL,
|
||||
PRIMARY KEY(id)
|
||||
) ENGINE = InnoDB;
|
||||
|
||||
CREATE TABLE user_favorite_comments (
|
||||
user_id VARCHAR(255) NOT NULL,
|
||||
favorite_comment_id VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY(user_id, favorite_comment_id)
|
||||
) ENGINE = InnoDB;
|
||||
|
||||
CREATE TABLE user_read_comments (
|
||||
user_id VARCHAR(255) NOT NULL,
|
||||
comment_id VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY(user_id, comment_id)
|
||||
) ENGINE = InnoDB;
|
||||
|
||||
Establishing Associations
|
||||
-------------------------
|
||||
|
||||
Establishing an association between two entities is
|
||||
straight-forward. Here are some examples for the unidirectional
|
||||
relations of the ``User``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
public function getReadComments() {
|
||||
return $this->commentsRead;
|
||||
}
|
||||
|
||||
public function setFirstComment(Comment $c) {
|
||||
$this->firstComment = $c;
|
||||
}
|
||||
}
|
||||
|
||||
The interaction code would then look like in the following snippet
|
||||
(``$em`` here is an instance of the EntityManager):
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$user = $em->find('User', $userId);
|
||||
|
||||
// unidirectional many to many
|
||||
$comment = $em->find('Comment', $readCommentId);
|
||||
$user->getReadComments()->add($comment);
|
||||
|
||||
$em->flush();
|
||||
|
||||
// unidirectional many to one
|
||||
$myFirstComment = new Comment();
|
||||
$user->setFirstComment($myFirstComment);
|
||||
|
||||
$em->persist($myFirstComment);
|
||||
$em->flush();
|
||||
|
||||
In the case of bi-directional associations you have to update the
|
||||
fields on both sides:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class User
|
||||
{
|
||||
// ..
|
||||
|
||||
public function getAuthoredComments() {
|
||||
return $this->commentsAuthored;
|
||||
}
|
||||
|
||||
public function getFavoriteComments() {
|
||||
return $this->favorites;
|
||||
}
|
||||
}
|
||||
|
||||
class Comment
|
||||
{
|
||||
// ...
|
||||
|
||||
public function getUserFavorites() {
|
||||
return $this->userFavorites;
|
||||
}
|
||||
|
||||
public function setAuthor(User $author = null) {
|
||||
$this->author = $author;
|
||||
}
|
||||
}
|
||||
|
||||
// Many-to-Many
|
||||
$user->getFavorites()->add($favoriteComment);
|
||||
$favoriteComment->getUserFavorites()->add($user);
|
||||
|
||||
$em->flush();
|
||||
|
||||
// Many-To-One / One-To-Many Bidirectional
|
||||
$newComment = new Comment();
|
||||
$user->getAuthoredComments()->add($newComment);
|
||||
$newComment->setAuthor($user);
|
||||
|
||||
$em->persist($newComment);
|
||||
$em->flush();
|
||||
|
||||
Notice how always both sides of the bidirectional association are
|
||||
updated. The previous unidirectional associations were simpler to
|
||||
handle.
|
||||
|
||||
Removing Associations
|
||||
---------------------
|
||||
|
||||
Removing an association between two entities is similarly
|
||||
straight-forward. There are two strategies to do so, by key and by
|
||||
element. Here are some examples:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// Remove by Elements
|
||||
$user->getComments()->removeElement($comment);
|
||||
$comment->setAuthor(null);
|
||||
|
||||
$user->getFavorites()->removeElement($comment);
|
||||
$comment->getUserFavorites()->removeElement($user);
|
||||
|
||||
// Remove by Key
|
||||
$user->getComments()->remove($ithComment);
|
||||
$comment->setAuthor(null);
|
||||
|
||||
You need to call ``$em->flush()`` to make persist these changes in
|
||||
the database permanently.
|
||||
|
||||
Notice how both sides of the bidirectional association are always
|
||||
updated. Unidirectional associations are consequently simpler to
|
||||
handle. Also note that if you use type-hinting in your methods, i.e.
|
||||
``setAddress(Address $address)``, PHP will only allow null
|
||||
values if ``null`` is set as default value. Otherwise
|
||||
setAddress(null) will fail for removing the association. If you
|
||||
insist on type-hinting a typical way to deal with this is to
|
||||
provide a special method, like ``removeAddress()``. This can also
|
||||
provide better encapsulation as it hides the internal meaning of
|
||||
not having an address.
|
||||
|
||||
When working with collections, keep in mind that a Collection is
|
||||
essentially an ordered map (just like a PHP array). That is why the
|
||||
``remove`` operation accepts an index/key. ``removeElement`` is a
|
||||
separate method that has O(n) complexity using ``array_search``,
|
||||
where n is the size of the map.
|
||||
|
||||
.. note::
|
||||
|
||||
Since Doctrine always only looks at the owning side of a
|
||||
bidirectional association for updates, it is not necessary for
|
||||
write operations that an inverse collection of a bidirectional
|
||||
one-to-many or many-to-many association is updated. This knowledge
|
||||
can often be used to improve performance by avoiding the loading of
|
||||
the inverse collection.
|
||||
|
||||
|
||||
You can also clear the contents of a whole collection using the
|
||||
``Collections::clear()`` method. You should be aware that using
|
||||
this method can lead to a straight and optimized database delete or
|
||||
update call during the flush operation that is not aware of
|
||||
entities that have been re-added to the collection.
|
||||
|
||||
Say you clear a collection of tags by calling
|
||||
``$post->getTags()->clear();`` and then call
|
||||
``$post->getTags()->add($tag)``. This will not recognize the tag having
|
||||
already been added previously and will consequently issue two separate database
|
||||
calls.
|
||||
|
||||
Association Management Methods
|
||||
------------------------------
|
||||
|
||||
It is generally a good idea to encapsulate proper association
|
||||
management inside the entity classes. This makes it easier to use
|
||||
the class correctly and can encapsulate details about how the
|
||||
association is maintained.
|
||||
|
||||
The following code shows updates to the previous User and Comment
|
||||
example that encapsulate much of the association management code:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class User
|
||||
{
|
||||
//...
|
||||
public function markCommentRead(Comment $comment) {
|
||||
// Collections implement ArrayAccess
|
||||
$this->commentsRead[] = $comment;
|
||||
}
|
||||
|
||||
public function addComment(Comment $comment) {
|
||||
if (count($this->commentsAuthored) == 0) {
|
||||
$this->setFirstComment($comment);
|
||||
}
|
||||
$this->comments[] = $comment;
|
||||
$comment->setAuthor($this);
|
||||
}
|
||||
|
||||
private function setFirstComment(Comment $c) {
|
||||
$this->firstComment = $c;
|
||||
}
|
||||
|
||||
public function addFavorite(Comment $comment) {
|
||||
$this->favorites->add($comment);
|
||||
$comment->addUserFavorite($this);
|
||||
}
|
||||
|
||||
public function removeFavorite(Comment $comment) {
|
||||
$this->favorites->removeElement($comment);
|
||||
$comment->removeUserFavorite($this);
|
||||
}
|
||||
}
|
||||
|
||||
class Comment
|
||||
{
|
||||
// ..
|
||||
|
||||
public function addUserFavorite(User $user) {
|
||||
$this->userFavorites[] = $user;
|
||||
}
|
||||
|
||||
public function removeUserFavorite(User $user) {
|
||||
$this->userFavorites->removeElement($user);
|
||||
}
|
||||
}
|
||||
|
||||
You will notice that ``addUserFavorite`` and ``removeUserFavorite``
|
||||
do not call ``addFavorite`` and ``removeFavorite``, thus the
|
||||
bidirectional association is strictly-speaking still incomplete.
|
||||
However if you would naively add the ``addFavorite`` in
|
||||
``addUserFavorite``, you end up with an infinite loop, so more work
|
||||
is needed. As you can see, proper bidirectional association
|
||||
management in plain OOP is a non-trivial task and encapsulating all
|
||||
the details inside the classes can be challenging.
|
||||
|
||||
.. note::
|
||||
|
||||
If you want to make sure that your collections are perfectly
|
||||
encapsulated you should not return them from a
|
||||
``getCollectionName()`` method directly, but call
|
||||
``$collection->toArray()``. This way a client programmer for the
|
||||
entity cannot circumvent the logic you implement on your entity for
|
||||
association management. For example:
|
||||
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class User {
|
||||
public function getReadComments() {
|
||||
return $this->commentsRead->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
This will however always initialize the collection, with all the
|
||||
performance penalties given the size. In some scenarios of large
|
||||
collections it might even be a good idea to completely hide the
|
||||
read access behind methods on the EntityRepository.
|
||||
|
||||
There is no single, best way for association management. It greatly
|
||||
depends on the requirements of your concrete domain model as well
|
||||
as your preferences.
|
||||
|
||||
Synchronizing Bidirectional Collections
|
||||
---------------------------------------
|
||||
|
||||
In the case of Many-To-Many associations you as the developer have the
|
||||
responsibility of keeping the collections on the owning and inverse side
|
||||
in sync when you apply changes to them. Doctrine can only
|
||||
guarantee a consistent state for the hydration, not for your client
|
||||
code.
|
||||
|
||||
Using the User-Comment entities from above, a very simple example
|
||||
can show the possible caveats you can encounter:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$user->getFavorites()->add($favoriteComment);
|
||||
// not calling $favoriteComment->getUserFavorites()->add($user);
|
||||
|
||||
$user->getFavorites()->contains($favoriteComment); // TRUE
|
||||
$favoriteComment->getUserFavorites()->contains($user); // FALSE
|
||||
|
||||
There are two approaches to handle this problem in your code:
|
||||
|
||||
|
||||
1. Ignore updating the inverse side of bidirectional collections,
|
||||
BUT never read from them in requests that changed their state. In
|
||||
the next Request Doctrine hydrates the consistent collection state
|
||||
again.
|
||||
2. Always keep the bidirectional collections in sync through
|
||||
association management methods. Reads of the Collections directly
|
||||
after changes are consistent then.
|
||||
|
||||
Transitive persistence / Cascade Operations
|
||||
-------------------------------------------
|
||||
|
||||
Persisting, removing, detaching and merging individual entities can
|
||||
become pretty cumbersome, especially when a highly interweaved object graph
|
||||
is involved. Therefore Doctrine 2 provides a
|
||||
mechanism for transitive persistence through cascading of these
|
||||
operations. Each association to another entity or a collection of
|
||||
entities can be configured to automatically cascade certain
|
||||
operations. By default, no operations are cascaded.
|
||||
|
||||
The following cascade options exist:
|
||||
|
||||
|
||||
- persist : Cascades persist operations to the associated
|
||||
entities.
|
||||
- remove : Cascades remove operations to the associated entities.
|
||||
- merge : Cascades merge operations to the associated entities.
|
||||
- detach : Cascades detach operations to the associated entities.
|
||||
- all : Cascades persist, remove, merge and detach operations to
|
||||
associated entities.
|
||||
|
||||
.. note::
|
||||
|
||||
Cascade operations are performed in memory. That means collections and related entities
|
||||
are fetched into memory, even if they are still marked as lazy when
|
||||
the cascade operation is about to be performed. However this approach allows
|
||||
entity lifecycle events to be performed for each of these operations.
|
||||
|
||||
However, pulling objects graph into memory on cascade can cause considerable performance
|
||||
overhead, especially when cascading collections are large. Makes sure
|
||||
to weigh the benefits and downsides of each cascade operation that you define.
|
||||
|
||||
To rely on the database level cascade operations for the delete operation instead, you can
|
||||
configure each join column with the **onDelete** option. See the respective
|
||||
mapping driver chapters for more information.
|
||||
|
||||
The following example is an extension to the User-Comment example
|
||||
of this chapter. Suppose in our application a user is created
|
||||
whenever he writes his first comment. In this case we would use the
|
||||
following code:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$user = new User();
|
||||
$myFirstComment = new Comment();
|
||||
$user->addComment($myFirstComment);
|
||||
|
||||
$em->persist($user);
|
||||
$em->persist($myFirstComment);
|
||||
$em->flush();
|
||||
|
||||
Even if you *persist* a new User that contains our new Comment this
|
||||
code would fail if you removed the call to
|
||||
``EntityManager#persist($myFirstComment)``. Doctrine 2 does not
|
||||
cascade the persist operation to all nested entities that are new
|
||||
as well.
|
||||
|
||||
More complicated is the deletion of all of a user's comments when he is
|
||||
removed from the system:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$user = $em->find('User', $deleteUserId);
|
||||
|
||||
foreach ($user->getAuthoredComments() AS $comment) {
|
||||
$em->remove($comment);
|
||||
}
|
||||
$em->remove($user);
|
||||
$em->flush();
|
||||
|
||||
Without the loop over all the authored comments Doctrine would use
|
||||
an UPDATE statement only to set the foreign key to NULL and only
|
||||
the User would be deleted from the database during the
|
||||
flush()-Operation.
|
||||
|
||||
To have Doctrine handle both cases automatically we can change the
|
||||
``User#commentsAuthored`` property to cascade both the "persist"
|
||||
and the "remove" operation.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class User
|
||||
{
|
||||
//...
|
||||
/**
|
||||
* Bidirectional - One-To-Many (INVERSE SIDE)
|
||||
*
|
||||
* @OneToMany(targetEntity="Comment", mappedBy="author", cascade={"persist", "remove"})
|
||||
*/
|
||||
private $commentsAuthored;
|
||||
//...
|
||||
}
|
||||
|
||||
Even though automatic cascading is convenient it should be used
|
||||
with care. Do not blindly apply cascade=all to all associations as
|
||||
it will unnecessarily degrade the performance of your application.
|
||||
For each cascade operation that gets activated Doctrine also
|
||||
applies that operation to the association, be it single or
|
||||
collection valued.
|
||||
|
||||
Persistence by Reachability: Cascade Persist
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
There are additional semantics that apply to the Cascade Persist
|
||||
operation. During each flush() operation Doctrine detects if there
|
||||
are new entities in any collection and three possible cases can
|
||||
happen:
|
||||
|
||||
|
||||
1. New entities in a collection marked as cascade persist will be
|
||||
directly persisted by Doctrine.
|
||||
2. New entities in a collection not marked as cascade persist will
|
||||
produce an Exception and rollback the flush() operation.
|
||||
3. Collections without new entities are skipped.
|
||||
|
||||
This concept is called Persistence by Reachability: New entities
|
||||
that are found on already managed entities are automatically
|
||||
persisted as long as the association is defined as cascade
|
||||
persist.
|
||||
|
||||
Orphan Removal
|
||||
--------------
|
||||
|
||||
There is another concept of cascading that is relevant only when removing entities
|
||||
from collections. If an Entity of type ``A`` contains references to privately
|
||||
owned Entities ``B`` then if the reference from ``A`` to ``B`` is removed the
|
||||
entity ``B`` should also be removed, because it is not used anymore.
|
||||
|
||||
OrphanRemoval works with one-to-one, one-to-many and many-to-many associations.
|
||||
|
||||
.. note::
|
||||
|
||||
When using the ``orphanRemoval=true`` option Doctrine makes the assumption
|
||||
that the entities are privately owned and will **NOT** be reused by other entities.
|
||||
If you neglect this assumption your entities will get deleted by Doctrine even if
|
||||
you assigned the orphaned entity to another one.
|
||||
|
||||
As a better example consider an Addressbook application where you have Contacts, Addresses
|
||||
and StandingData:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
namespace Addressbook;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class Contact
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private $id;
|
||||
|
||||
/** @OneToOne(targetEntity="StandingData", orphanRemoval=true) */
|
||||
private $standingData;
|
||||
|
||||
/** @OneToMany(targetEntity="Address", mappedBy="contact", orphanRemoval=true) */
|
||||
private $addresses;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->addresses = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function newStandingData(StandingData $sd)
|
||||
{
|
||||
$this->standingData = $sd;
|
||||
}
|
||||
|
||||
public function removeAddress($pos)
|
||||
{
|
||||
unset($this->addresses[$pos]);
|
||||
}
|
||||
}
|
||||
|
||||
Now two examples of what happens when you remove the references:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
$contact = $em->find("Addressbook\Contact", $contactId);
|
||||
$contact->newStandingData(new StandingData("Firstname", "Lastname", "Street"));
|
||||
$contact->removeAddress(1);
|
||||
|
||||
$em->flush();
|
||||
|
||||
In this case you have not only changed the ``Contact`` entity itself but
|
||||
you have also removed the references for standing data and as well as one
|
||||
address reference. When flush is called not only are the references removed
|
||||
but both the old standing data and the one address entity are also deleted
|
||||
from the database.
|
||||
|
||||
Filtering Collections
|
||||
---------------------
|
||||
|
||||
.. filtering-collections:
|
||||
|
||||
Collections have a filtering API that allows to slice parts of data from
|
||||
a collection. If the collection has not been loaded from the database yet,
|
||||
the filtering API can work on the SQL level to make optimized access to
|
||||
large collections.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
|
||||
$group = $entityManager->find('Group', $groupId);
|
||||
$userCollection = $group->getUsers();
|
||||
|
||||
$criteria = Criteria::create()
|
||||
->where(Criteria::expr()->eq("birthday", "1982-02-17"))
|
||||
->orderBy(array("username" => "ASC"))
|
||||
->setFirstResult(0)
|
||||
->setMaxResults(20)
|
||||
;
|
||||
|
||||
$birthdayUsers = $userCollection->matching($criteria);
|
||||
|
||||
.. tip::
|
||||
|
||||
You can move the access of slices of collections into dedicated methods of
|
||||
an entity. For example ``Group#getTodaysBirthdayUsers()``.
|
||||
|
||||
The Criteria has a limited matching language that works both on the
|
||||
SQL and on the PHP collection level. This means you can use collection matching
|
||||
interchangeably, independent of in-memory or sql-backed collections.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\Common\Collections;
|
||||
|
||||
class Criteria
|
||||
{
|
||||
/**
|
||||
* @return Criteria
|
||||
*/
|
||||
static public function create();
|
||||
/**
|
||||
* @param Expression $where
|
||||
* @return Criteria
|
||||
*/
|
||||
public function where(Expression $where);
|
||||
/**
|
||||
* @param Expression $where
|
||||
* @return Criteria
|
||||
*/
|
||||
public function andWhere(Expression $where);
|
||||
/**
|
||||
* @param Expression $where
|
||||
* @return Criteria
|
||||
*/
|
||||
public function orWhere(Expression $where);
|
||||
/**
|
||||
* @param array $orderings
|
||||
* @return Criteria
|
||||
*/
|
||||
public function orderBy(array $orderings);
|
||||
/**
|
||||
* @param int $firstResult
|
||||
* @return Criteria
|
||||
*/
|
||||
public function setFirstResult($firstResult);
|
||||
/**
|
||||
* @param int $maxResults
|
||||
* @return Criteria
|
||||
*/
|
||||
public function setMaxResults($maxResults);
|
||||
public function getOrderings();
|
||||
public function getWhereExpression();
|
||||
public function getFirstResult();
|
||||
public function getMaxResults();
|
||||
}
|
||||
|
||||
You can build expressions through the ExpressionBuilder. It has the following
|
||||
methods:
|
||||
|
||||
* ``andX($arg1, $arg2, ...)``
|
||||
* ``orX($arg1, $arg2, ...)``
|
||||
* ``eq($field, $value)``
|
||||
* ``gt($field, $value)``
|
||||
* ``lt($field, $value)``
|
||||
* ``lte($field, $value)``
|
||||
* ``gte($field, $value)``
|
||||
* ``neq($field, $value)``
|
||||
* ``isNull($field)``
|
||||
* ``in($field, array $values)``
|
||||
* ``notIn($field, array $values)``
|
||||
|
||||
|
||||
850
docs/en/reference/working-with-objects.rst
Normal file
850
docs/en/reference/working-with-objects.rst
Normal file
@@ -0,0 +1,850 @@
|
||||
Working with Objects
|
||||
====================
|
||||
|
||||
In this chapter we will help you understand the ``EntityManager``
|
||||
and the ``UnitOfWork``. A Unit of Work is similar to an
|
||||
object-level transaction. A new Unit of Work is implicitly started
|
||||
when an EntityManager is initially created or after
|
||||
``EntityManager#flush()`` has been invoked. A Unit of Work is
|
||||
committed (and a new one started) by invoking
|
||||
``EntityManager#flush()``.
|
||||
|
||||
A Unit of Work can be manually closed by calling
|
||||
EntityManager#close(). Any changes to objects within this Unit of
|
||||
Work that have not yet been persisted are lost.
|
||||
|
||||
.. note::
|
||||
|
||||
It is very important to understand that only
|
||||
``EntityManager#flush()`` ever causes write operations against the
|
||||
database to be executed. Any other methods such as
|
||||
``EntityManager#persist($entity)`` or
|
||||
``EntityManager#remove($entity)`` only notify the UnitOfWork to
|
||||
perform these operations during flush.
|
||||
|
||||
Not calling ``EntityManager#flush()`` will lead to all changes
|
||||
during that request being lost.
|
||||
|
||||
|
||||
Entities and the Identity Map
|
||||
-----------------------------
|
||||
|
||||
Entities are objects with identity. Their identity has a conceptual
|
||||
meaning inside your domain. In a CMS application each article has a
|
||||
unique id. You can uniquely identify each article by that id.
|
||||
|
||||
Take the following example, where you find an article with the
|
||||
headline "Hello World" with the ID 1234:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$article = $entityManager->find('CMS\Article', 1234);
|
||||
$article->setHeadline('Hello World dude!');
|
||||
|
||||
$article2 = $entityManager->find('CMS\Article', 1234);
|
||||
echo $article2->getHeadline();
|
||||
|
||||
In this case the Article is accessed from the entity manager twice,
|
||||
but modified in between. Doctrine 2 realizes this and will only
|
||||
ever give you access to one instance of the Article with ID 1234,
|
||||
no matter how often do you retrieve it from the EntityManager and
|
||||
even no matter what kind of Query method you are using (find,
|
||||
Repository Finder or DQL). This is called "Identity Map" pattern,
|
||||
which means Doctrine keeps a map of each entity and ids that have
|
||||
been retrieved per PHP request and keeps returning you the same
|
||||
instances.
|
||||
|
||||
In the previous example the echo prints "Hello World dude!" to the
|
||||
screen. You can even verify that ``$article`` and ``$article2`` are
|
||||
indeed pointing to the same instance by running the following
|
||||
code:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
if ($article === $article2) {
|
||||
echo "Yes we are the same!";
|
||||
}
|
||||
|
||||
Sometimes you want to clear the identity map of an EntityManager to
|
||||
start over. We use this regularly in our unit-tests to enforce
|
||||
loading objects from the database again instead of serving them
|
||||
from the identity map. You can call ``EntityManager#clear()`` to
|
||||
achieve this result.
|
||||
|
||||
Entity Object Graph Traversal
|
||||
-----------------------------
|
||||
|
||||
Although Doctrine allows for a complete separation of your domain
|
||||
model (Entity classes) there will never be a situation where
|
||||
objects are "missing" when traversing associations. You can walk
|
||||
all the associations inside your entity models as deep as you
|
||||
want.
|
||||
|
||||
Take the following example of a single ``Article`` entity fetched
|
||||
from newly opened EntityManager.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
class Article
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private $id;
|
||||
|
||||
/** @Column(type="string") */
|
||||
private $headline;
|
||||
|
||||
/** @ManyToOne(targetEntity="User") */
|
||||
private $author;
|
||||
|
||||
/** @OneToMany(targetEntity="Comment", mappedBy="article") */
|
||||
private $comments;
|
||||
|
||||
public function __construct {
|
||||
$this->comments = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getAuthor() { return $this->author; }
|
||||
public function getComments() { return $this->comments; }
|
||||
}
|
||||
|
||||
$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.
|
||||
You can still access the associated properties author and comments
|
||||
and the associated objects they contain.
|
||||
|
||||
This works by utilizing the lazy loading pattern. Instead of
|
||||
passing you back a real Author instance and a collection of
|
||||
comments Doctrine will create proxy instances for you. Only if you
|
||||
access these proxies for the first time they will go through the
|
||||
EntityManager and load their state from the database.
|
||||
|
||||
This lazy-loading process happens behind the scenes, hidden from
|
||||
your code. See the following code:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$article = $em->find('Article', 1);
|
||||
|
||||
// accessing a method of the user instance triggers the lazy-load
|
||||
echo "Author: " . $article->getAuthor()->getName() . "\n";
|
||||
|
||||
// Lazy Loading Proxies pass instanceof tests:
|
||||
if ($article->getAuthor() instanceof User) {
|
||||
// a User Proxy is a generated "UserProxy" class
|
||||
}
|
||||
|
||||
// accessing the comments as an iterator triggers the lazy-load
|
||||
// retrieving ALL the comments of this article from the database
|
||||
// using a single SELECT statement
|
||||
foreach ($article->getComments() AS $comment) {
|
||||
echo $comment->getText() . "\n\n";
|
||||
}
|
||||
|
||||
// Article::$comments passes instanceof tests for the Collection interface
|
||||
// But it will NOT pass for the ArrayCollection interface
|
||||
if ($article->getComments() instanceof \Doctrine\Common\Collections\Collection) {
|
||||
echo "This will always be true!";
|
||||
}
|
||||
|
||||
A slice of the generated proxy classes code looks like the
|
||||
following piece of code. A real proxy class override ALL public
|
||||
methods along the lines of the ``getName()`` method shown below:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class UserProxy extends User implements Proxy
|
||||
{
|
||||
private function _load()
|
||||
{
|
||||
// lazy loading code
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
$this->_load();
|
||||
return parent::getName();
|
||||
}
|
||||
// .. other public methods of User
|
||||
}
|
||||
|
||||
.. warning::
|
||||
|
||||
Traversing the object graph for parts that are lazy-loaded will
|
||||
easily trigger lots of SQL queries and will perform badly if used
|
||||
to heavily. Make sure to use DQL to fetch-join all the parts of the
|
||||
object-graph that you need as efficiently as possible.
|
||||
|
||||
|
||||
Persisting entities
|
||||
-------------------
|
||||
|
||||
An entity can be made persistent by passing it to the
|
||||
``EntityManager#persist($entity)`` method. By applying the persist
|
||||
operation on some entity, that entity becomes MANAGED, which means
|
||||
that its persistence is from now on managed by an EntityManager. As
|
||||
a result the persistent state of such an entity will subsequently
|
||||
be properly synchronized with the database when
|
||||
``EntityManager#flush()`` is invoked.
|
||||
|
||||
.. note::
|
||||
|
||||
Invoking the ``persist`` method on an entity does NOT
|
||||
cause an immediate SQL INSERT to be issued on the database.
|
||||
Doctrine applies a strategy called "transactional write-behind",
|
||||
which means that it will delay most SQL commands until
|
||||
``EntityManager#flush()`` is invoked which will then issue all
|
||||
necessary SQL statements to synchronize your objects with the
|
||||
database in the most efficient way and a single, short transaction,
|
||||
taking care of maintaining referential integrity.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$user = new User;
|
||||
$user->setName('Mr.Right');
|
||||
$em->persist($user);
|
||||
$em->flush();
|
||||
|
||||
.. note::
|
||||
|
||||
Generated entity identifiers / primary keys are
|
||||
guaranteed to be available after the next successful flush
|
||||
operation that involves the entity in question. You can not rely on
|
||||
a generated identifier to be available directly after invoking
|
||||
``persist``. The inverse is also true. You can not rely on a
|
||||
generated identifier being not available after a failed flush
|
||||
operation.
|
||||
|
||||
|
||||
The semantics of the persist operation, applied on an entity X, are
|
||||
as follows:
|
||||
|
||||
|
||||
- If X is a new entity, it becomes managed. The entity X will be
|
||||
entered into the database as a result of the flush operation.
|
||||
- If X is a preexisting managed entity, it is ignored by the
|
||||
persist operation. However, the persist operation is cascaded to
|
||||
entities referenced by X, if the relationships from X to these
|
||||
other entities are mapped with cascade=PERSIST or cascade=ALL (see
|
||||
"Transitive Persistence").
|
||||
- If X is a removed entity, it becomes managed.
|
||||
- If X is a detached entity, an exception will be thrown on
|
||||
flush.
|
||||
|
||||
Removing entities
|
||||
-----------------
|
||||
|
||||
An entity can be removed from persistent storage by passing it to
|
||||
the ``EntityManager#remove($entity)`` method. By applying the
|
||||
``remove`` operation on some entity, that entity becomes REMOVED,
|
||||
which means that its persistent state will be deleted once
|
||||
``EntityManager#flush()`` is invoked.
|
||||
|
||||
.. note::
|
||||
|
||||
Just like ``persist``, invoking ``remove`` on an entity
|
||||
does NOT cause an immediate SQL DELETE to be issued on the
|
||||
database. The entity will be deleted on the next invocation of
|
||||
``EntityManager#flush()`` that involves that entity. This
|
||||
means that entities scheduled for removal can still be queried
|
||||
for and appear in query and collection results. See
|
||||
the section on :ref:`Database and UnitOfWork Out-Of-Sync <workingobjects_database_uow_outofsync>`
|
||||
for more information.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$em->remove($user);
|
||||
$em->flush();
|
||||
|
||||
The semantics of the remove operation, applied to an entity X are
|
||||
as follows:
|
||||
|
||||
|
||||
- If X is a new entity, it is ignored by the remove operation.
|
||||
However, the remove operation is cascaded to entities referenced by
|
||||
X, if the relationship from X to these other entities is mapped
|
||||
with cascade=REMOVE or cascade=ALL (see "Transitive Persistence").
|
||||
- If X is a managed entity, the remove operation causes it to
|
||||
become removed. The remove operation is cascaded to entities
|
||||
referenced by X, if the relationships from X to these other
|
||||
entities is mapped with cascade=REMOVE or cascade=ALL (see
|
||||
"Transitive Persistence").
|
||||
- If X is a detached entity, an InvalidArgumentException will be
|
||||
thrown.
|
||||
- If X is a removed entity, it is ignored by the remove operation.
|
||||
- A removed entity X will be removed from the database as a result
|
||||
of the flush operation.
|
||||
|
||||
After an entity has been removed its in-memory state is the same as
|
||||
before the removal, except for generated identifiers.
|
||||
|
||||
Removing an entity will also automatically delete any existing
|
||||
records in many-to-many join tables that link this entity. The
|
||||
action taken depends on the value of the ``@joinColumn`` mapping
|
||||
attribute "onDelete". Either Doctrine issues a dedicated ``DELETE``
|
||||
statement for records of each join table or it depends on the
|
||||
foreign key semantics of onDelete="CASCADE".
|
||||
|
||||
Deleting an object with all its associated objects can be achieved
|
||||
in multiple ways with very different performance impacts.
|
||||
|
||||
|
||||
1. If an association is marked as ``CASCADE=REMOVE`` Doctrine 2
|
||||
will fetch this association. If its a Single association it will
|
||||
pass this entity to
|
||||
´EntityManager#remove()``. If the association is a collection, Doctrine will loop over all its elements and pass them to``EntityManager#remove()\`.
|
||||
In both cases the cascade remove semantics are applied recursively.
|
||||
For large object graphs this removal strategy can be very costly.
|
||||
2. Using a DQL ``DELETE`` statement allows you to delete multiple
|
||||
entities of a type with a single command and without hydrating
|
||||
these entities. This can be very efficient to delete large object
|
||||
graphs from the database.
|
||||
3. Using foreign key semantics ``onDelete="CASCADE"`` can force the
|
||||
database to remove all associated objects internally. This strategy
|
||||
is a bit tricky to get right but can be very powerful and fast. You
|
||||
should be aware however that using strategy 1 (``CASCADE=REMOVE``)
|
||||
completely by-passes any foreign key ``onDelete=CASCADE`` option,
|
||||
because Doctrine will fetch and remove all associated entities
|
||||
explicitly nevertheless.
|
||||
|
||||
Detaching entities
|
||||
------------------
|
||||
|
||||
An entity is detached from an EntityManager and thus no longer
|
||||
managed by invoking the ``EntityManager#detach($entity)`` method on
|
||||
it or by cascading the detach operation to it. Changes made to the
|
||||
detached entity, if any (including removal of the entity), will not
|
||||
be synchronized to the database after the entity has been
|
||||
detached.
|
||||
|
||||
Doctrine will not hold on to any references to a detached entity.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$em->detach($entity);
|
||||
|
||||
The semantics of the detach operation, applied to an entity X are
|
||||
as follows:
|
||||
|
||||
|
||||
- If X is a managed entity, the detach operation causes it to
|
||||
become detached. The detach operation is cascaded to entities
|
||||
referenced by X, if the relationships from X to these other
|
||||
entities is mapped with cascade=DETACH or cascade=ALL (see
|
||||
"Transitive Persistence"). Entities which previously referenced X
|
||||
will continue to reference X.
|
||||
- If X is a new or detached entity, it is ignored by the detach
|
||||
operation.
|
||||
- If X is a removed entity, the detach operation is cascaded to
|
||||
entities referenced by X, if the relationships from X to these
|
||||
other entities is mapped with cascade=DETACH or cascade=ALL (see
|
||||
"Transitive Persistence"). Entities which previously referenced X
|
||||
will continue to reference X.
|
||||
|
||||
There are several situations in which an entity is detached
|
||||
automatically without invoking the ``detach`` method:
|
||||
|
||||
|
||||
- When ``EntityManager#clear()`` is invoked, all entities that are
|
||||
currently managed by the EntityManager instance become detached.
|
||||
- When serializing an entity. The entity retrieved upon subsequent
|
||||
unserialization will be detached (This is the case for all entities
|
||||
that are serialized and stored in some cache, i.e. when using the
|
||||
Query Result Cache).
|
||||
|
||||
The ``detach`` operation is usually not as frequently needed and
|
||||
used as ``persist`` and ``remove``.
|
||||
|
||||
Merging entities
|
||||
----------------
|
||||
|
||||
Merging entities refers to the merging of (usually detached)
|
||||
entities into the context of an EntityManager so that they become
|
||||
managed again. To merge the state of an entity into an
|
||||
EntityManager use the ``EntityManager#merge($entity)`` method. The
|
||||
state of the passed entity will be merged into a managed copy of
|
||||
this entity and this copy will subsequently be returned.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$detachedEntity = unserialize($serializedEntity); // some detached entity
|
||||
$entity = $em->merge($detachedEntity);
|
||||
// $entity now refers to the fully managed copy returned by the merge operation.
|
||||
// The EntityManager $em now manages the persistence of $entity as usual.
|
||||
|
||||
.. note::
|
||||
|
||||
When you want to serialize/unserialize entities you
|
||||
have to make all entity properties protected, never private. The
|
||||
reason for this is, if you serialize a class that was a proxy
|
||||
instance before, the private variables won't be serialized and a
|
||||
PHP Notice is thrown.
|
||||
|
||||
|
||||
The semantics of the merge operation, applied to an entity X, are
|
||||
as follows:
|
||||
|
||||
|
||||
- If X is a detached entity, the state of X is copied onto a
|
||||
pre-existing managed entity instance X' of the same identity.
|
||||
- If X is a new entity instance, a new managed copy X' will be
|
||||
created and the state of X is copied onto this managed instance.
|
||||
- If X is a removed entity instance, an InvalidArgumentException
|
||||
will be thrown.
|
||||
- If X is a managed entity, it is ignored by the merge operation,
|
||||
however, the merge operation is cascaded to entities referenced by
|
||||
relationships from X if these relationships have been mapped with
|
||||
the cascade element value MERGE or ALL (see "Transitive
|
||||
Persistence").
|
||||
- For all entities Y referenced by relationships from X having the
|
||||
cascade element value MERGE or ALL, Y is merged recursively as Y'.
|
||||
For all such Y referenced by X, X' is set to reference Y'. (Note
|
||||
that if X is managed then X is the same object as X'.)
|
||||
- If X is an entity merged to X', with a reference to another
|
||||
entity Y, where cascade=MERGE or cascade=ALL is not specified, then
|
||||
navigation of the same association from X' yields a reference to a
|
||||
managed object Y' with the same persistent identity as Y.
|
||||
|
||||
The ``merge`` operation will throw an ``OptimisticLockException``
|
||||
if the entity being merged uses optimistic locking through a
|
||||
version field and the versions of the entity being merged and the
|
||||
managed copy don't match. This usually means that the entity has
|
||||
been modified while being detached.
|
||||
|
||||
The ``merge`` operation is usually not as frequently needed and
|
||||
used as ``persist`` and ``remove``. The most common scenario for
|
||||
the ``merge`` operation is to reattach entities to an EntityManager
|
||||
that come from some cache (and are therefore detached) and you want
|
||||
to modify and persist such an entity.
|
||||
|
||||
.. warning::
|
||||
|
||||
If you need to perform multiple merges of entities that share certain subparts
|
||||
of their object-graphs and cascade merge, then you have to call ``EntityManager#clear()`` between the
|
||||
successive calls to ``EntityManager#merge()``. Otherwise you might end up with
|
||||
multiple copies of the "same" object in the database, however with different ids.
|
||||
|
||||
.. note::
|
||||
|
||||
If you load some detached entities from a cache and you do
|
||||
not need to persist or delete them or otherwise make use of them
|
||||
without the need for persistence services there is no need to use
|
||||
``merge``. I.e. you can simply pass detached objects from a cache
|
||||
directly to the view.
|
||||
|
||||
|
||||
Synchronization with the Database
|
||||
---------------------------------
|
||||
|
||||
The state of persistent entities is synchronized with the database
|
||||
on flush of an ``EntityManager`` which commits the underlying
|
||||
``UnitOfWork``. The synchronization involves writing any updates to
|
||||
persistent entities and their relationships to the database.
|
||||
Thereby bidirectional relationships are persisted based on the
|
||||
references held by the owning side of the relationship as explained
|
||||
in the Association Mapping chapter.
|
||||
|
||||
When ``EntityManager#flush()`` is called, Doctrine inspects all
|
||||
managed, new and removed entities and will perform the following
|
||||
operations.
|
||||
|
||||
.. _workingobjects_database_uow_outofsync:
|
||||
|
||||
Effects of Database and UnitOfWork being Out-Of-Sync
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As soon as you begin to change the state of entities, call persist or remove the
|
||||
contents of the UnitOfWork and the database will drive out of sync. They can
|
||||
only be synchronized by calling ``EntityManager#flush()``. This section
|
||||
describes the effects of database and UnitOfWork being out of sync.
|
||||
|
||||
- Entities that are scheduled for removal can still be queried from the database.
|
||||
They are returned from DQL and Repository queries and are visible in collections.
|
||||
- Entities that are passed to ``EntityManager#persist`` do not turn up in query
|
||||
results.
|
||||
- Entities that have changed will not be overwritten with the state from the database.
|
||||
This is because the identity map will detect the construction of an already existing
|
||||
entity and assumes its the most up to date version.
|
||||
|
||||
``EntityManager#flush()`` is never called implicitly by Doctrine. You always have to trigger it manually.
|
||||
|
||||
Synchronizing New and Managed Entities
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The flush operation applies to a managed entity with the following
|
||||
semantics:
|
||||
|
||||
|
||||
- The entity itself is synchronized to the database using a SQL
|
||||
UPDATE statement, only if at least one persistent field has
|
||||
changed.
|
||||
- No SQL updates are executed if the entity did not change.
|
||||
|
||||
The flush operation applies to a new entity with the following
|
||||
semantics:
|
||||
|
||||
|
||||
- The entity itself is synchronized to the database using a SQL
|
||||
INSERT statement.
|
||||
|
||||
For all (initialized) relationships of the new or managed entity
|
||||
the following semantics apply to each associated entity X:
|
||||
|
||||
|
||||
- If X is new and persist operations are configured to cascade on
|
||||
the relationship, X will be persisted.
|
||||
- If X is new and no persist operations are configured to cascade
|
||||
on the relationship, an exception will be thrown as this indicates
|
||||
a programming error.
|
||||
- If X is removed and persist operations are configured to cascade
|
||||
on the relationship, an exception will be thrown as this indicates
|
||||
a programming error (X would be re-persisted by the cascade).
|
||||
- If X is detached and persist operations are configured to
|
||||
cascade on the relationship, an exception will be thrown (This is
|
||||
semantically the same as passing X to persist()).
|
||||
|
||||
Synchronizing Removed Entities
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The flush operation applies to a removed entity by deleting its
|
||||
persistent state from the database. No cascade options are relevant
|
||||
for removed entities on flush, the cascade remove option is already
|
||||
executed during ``EntityManager#remove($entity)``.
|
||||
|
||||
The size of a Unit of Work
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The size of a Unit of Work mainly refers to the number of managed
|
||||
entities at a particular point in time.
|
||||
|
||||
The cost of flushing
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
How costly a flush operation is, mainly depends on two factors:
|
||||
|
||||
|
||||
- The size of the EntityManager's current UnitOfWork.
|
||||
- The configured change tracking policies
|
||||
|
||||
You can get the size of a UnitOfWork as follows:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$uowSize = $em->getUnitOfWork()->size();
|
||||
|
||||
The size represents the number of managed entities in the Unit of
|
||||
Work. This size affects the performance of flush() operations due
|
||||
to change tracking (see "Change Tracking Policies") and, of course,
|
||||
memory consumption, so you may want to check it from time to time
|
||||
during development.
|
||||
|
||||
.. note::
|
||||
|
||||
Do not invoke ``flush`` after every change to an entity
|
||||
or every single invocation of persist/remove/merge/... This is an
|
||||
anti-pattern and unnecessarily reduces the performance of your
|
||||
application. Instead, form units of work that operate on your
|
||||
objects and call ``flush`` when you are done. While serving a
|
||||
single HTTP request there should be usually no need for invoking
|
||||
``flush`` more than 0-2 times.
|
||||
|
||||
|
||||
Direct access to a Unit of Work
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can get direct access to the Unit of Work by calling
|
||||
``EntityManager#getUnitOfWork()``. This will return the UnitOfWork
|
||||
instance the EntityManager is currently using.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$uow = $em->getUnitOfWork();
|
||||
|
||||
.. note::
|
||||
|
||||
Directly manipulating a UnitOfWork is not recommended.
|
||||
When working directly with the UnitOfWork API, respect methods
|
||||
marked as INTERNAL by not using them and carefully read the API
|
||||
documentation.
|
||||
|
||||
|
||||
Entity State
|
||||
~~~~~~~~~~~~
|
||||
|
||||
As outlined in the architecture overview an entity can be in one of
|
||||
four possible states: NEW, MANAGED, REMOVED, DETACHED. If you
|
||||
explicitly need to find out what the current state of an entity is
|
||||
in the context of a certain ``EntityManager`` you can ask the
|
||||
underlying ``UnitOfWork``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
switch ($em->getUnitOfWork()->getEntityState($entity)) {
|
||||
case UnitOfWork::STATE_MANAGED:
|
||||
...
|
||||
case UnitOfWork::STATE_REMOVED:
|
||||
...
|
||||
case UnitOfWork::STATE_DETACHED:
|
||||
...
|
||||
case UnitOfWork::STATE_NEW:
|
||||
...
|
||||
}
|
||||
|
||||
An entity is in MANAGED state if it is associated with an
|
||||
``EntityManager`` and it is not REMOVED.
|
||||
|
||||
An entity is in REMOVED state after it has been passed to
|
||||
``EntityManager#remove()`` until the next flush operation of the
|
||||
same EntityManager. A REMOVED entity is still associated with an
|
||||
``EntityManager`` until the next flush operation.
|
||||
|
||||
An entity is in DETACHED state if it has persistent state and
|
||||
identity but is currently not associated with an
|
||||
``EntityManager``.
|
||||
|
||||
An entity is in NEW state if has no persistent state and identity
|
||||
and is not associated with an ``EntityManager`` (for example those
|
||||
just created via the "new" operator).
|
||||
|
||||
Querying
|
||||
--------
|
||||
|
||||
Doctrine 2 provides the following ways, in increasing level of
|
||||
power and flexibility, to query for persistent objects. You should
|
||||
always start with the simplest one that suits your needs.
|
||||
|
||||
By Primary Key
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
The most basic way to query for a persistent object is by its
|
||||
identifier / primary key using the
|
||||
``EntityManager#find($entityName, $id)`` method. Here is an
|
||||
example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// $em instanceof EntityManager
|
||||
$user = $em->find('MyProject\Domain\User', $id);
|
||||
|
||||
The return value is either the found entity instance or null if no
|
||||
instance could be found with the given identifier.
|
||||
|
||||
Essentially, ``EntityManager#find()`` is just a shortcut for the
|
||||
following:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// $em instanceof EntityManager
|
||||
$user = $em->getRepository('MyProject\Domain\User')->find($id);
|
||||
|
||||
``EntityManager#getRepository($entityName)`` returns a repository
|
||||
object which provides many ways to retrieve entities of the
|
||||
specified type. By default, the repository instance is of type
|
||||
``Doctrine\ORM\EntityRepository``. You can also use custom
|
||||
repository classes as shown later.
|
||||
|
||||
By Simple Conditions
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To query for one or more entities based on several conditions that
|
||||
form a logical conjunction, use the ``findBy`` and ``findOneBy``
|
||||
methods on a repository as follows:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// $em instanceof EntityManager
|
||||
|
||||
// All users that are 20 years old
|
||||
$users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20));
|
||||
|
||||
// All users that are 20 years old and have a surname of 'Miller'
|
||||
$users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20, 'surname' => 'Miller'));
|
||||
|
||||
// A single user by its nickname
|
||||
$user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb'));
|
||||
|
||||
You can also load by owning side associations through the repository:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$number = $em->find('MyProject\Domain\Phonenumber', 1234);
|
||||
$user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('phone' => $number->getId()));
|
||||
|
||||
Be careful that this only works by passing the ID of the associated entity, not yet by passing the associated entity itself.
|
||||
|
||||
The ``EntityRepository#findBy()`` method additionally accepts orderings, limit and offset as second to fourth parameters:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$tenUsers = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20), array('name' => 'ASC'), 10, 0);
|
||||
|
||||
If you pass an array of values Doctrine will convert the query into a WHERE field IN (..) query automatically:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => array(20, 30, 40)));
|
||||
// translates roughly to: SELECT * FROM users WHERE age IN (20, 30, 40)
|
||||
|
||||
An EntityRepository also provides a mechanism for more concise
|
||||
calls through its use of ``__call``. Thus, the following two
|
||||
examples are equivalent:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// A single user by its nickname
|
||||
$user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb'));
|
||||
|
||||
// A single user by its nickname (__call magic)
|
||||
$user = $em->getRepository('MyProject\Domain\User')->findOneByNickname('romanb');
|
||||
|
||||
By Criteria
|
||||
~~~~~~~~~~~
|
||||
|
||||
.. versionadded:: 2.3
|
||||
|
||||
The Repository implement the ``Doctrine\Common\Collections\Selectable``
|
||||
interface. That means you can build ``Doctrine\Common\Collections\Criteria``
|
||||
and pass them to the ``matching($criteria)`` method.
|
||||
|
||||
See the :ref:`Working with Associations: Filtering collections
|
||||
<filtering-collections>`.
|
||||
|
||||
By Eager Loading
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Whenever you query for an entity that has persistent associations
|
||||
and these associations are mapped as EAGER, they will automatically
|
||||
be loaded together with the entity being queried and is thus
|
||||
immediately available to your application.
|
||||
|
||||
By Lazy Loading
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Whenever you have a managed entity instance at hand, you can
|
||||
traverse and use any associations of that entity that are
|
||||
configured LAZY as if they were in-memory already. Doctrine will
|
||||
automatically load the associated objects on demand through the
|
||||
concept of lazy-loading.
|
||||
|
||||
By DQL
|
||||
~~~~~~
|
||||
|
||||
The most powerful and flexible method to query for persistent
|
||||
objects is the Doctrine Query Language, an object query language.
|
||||
DQL enables you to query for persistent objects in the language of
|
||||
objects. DQL understands classes, fields, inheritance and
|
||||
associations. DQL is syntactically very similar to the familiar SQL
|
||||
but *it is not SQL*.
|
||||
|
||||
A DQL query is represented by an instance of the
|
||||
``Doctrine\ORM\Query`` class. You create a query using
|
||||
``EntityManager#createQuery($dql)``. Here is a simple example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// $em instanceof EntityManager
|
||||
|
||||
// All users with an age between 20 and 30 (inclusive).
|
||||
$q = $em->createQuery("select u from MyDomain\Model\User u where u.age >= 20 and u.age <= 30");
|
||||
$users = $q->getResult();
|
||||
|
||||
Note that this query contains no knowledge about the relational
|
||||
schema, only about the object model. DQL supports positional as
|
||||
well as named parameters, many functions, (fetch) joins,
|
||||
aggregates, subqueries and much more. Detailed information about
|
||||
DQL and its syntax as well as the Doctrine class can be found in
|
||||
:doc:`the dedicated chapter <dql-doctrine-query-language>`.
|
||||
For programmatically building up queries based on conditions that
|
||||
are only known at runtime, Doctrine provides the special
|
||||
``Doctrine\ORM\QueryBuilder`` class. More information on
|
||||
constructing queries with a QueryBuilder can be found
|
||||
:doc:`in Query Builder chapter <query-builder>`.
|
||||
|
||||
By Native Queries
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
As an alternative to DQL or as a fallback for special SQL
|
||||
statements native queries can be used. Native queries are built by
|
||||
using a hand-crafted SQL query and a ResultSetMapping that
|
||||
describes how the SQL result set should be transformed by Doctrine.
|
||||
More information about native queries can be found in
|
||||
:doc:`the dedicated chapter <native-sql>`.
|
||||
|
||||
Custom Repositories
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
By default the EntityManager returns a default implementation of
|
||||
``Doctrine\ORM\EntityRepository`` when you call
|
||||
``EntityManager#getRepository($entityClass)``. You can overwrite
|
||||
this behaviour by specifying the class name of your own Entity
|
||||
Repository in the Annotation, XML or YAML metadata. In large
|
||||
applications that require lots of specialized DQL queries using a
|
||||
custom repository is one recommended way of grouping these queries
|
||||
in a central location.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace MyDomain\Model;
|
||||
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
/**
|
||||
* @entity(repositoryClass="MyDomain\Model\UserRepository")
|
||||
*/
|
||||
class User
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
class UserRepository extends EntityRepository
|
||||
{
|
||||
public function getAllAdminUsers()
|
||||
{
|
||||
return $this->_em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"')
|
||||
->getResult();
|
||||
}
|
||||
}
|
||||
|
||||
You can access your repository now by calling:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// $em instanceof EntityManager
|
||||
|
||||
$admins = $em->getRepository('MyDomain\Model\User')->getAllAdminUsers();
|
||||
|
||||
|
||||
747
docs/en/reference/xml-mapping.rst
Normal file
747
docs/en/reference/xml-mapping.rst
Normal file
@@ -0,0 +1,747 @@
|
||||
XML Mapping
|
||||
===========
|
||||
|
||||
The XML mapping driver enables you to provide the ORM metadata in
|
||||
form of XML documents.
|
||||
|
||||
The XML driver is backed by an XML Schema document that describes
|
||||
the structure of a mapping document. The most recent version of the
|
||||
XML Schema document is available online at
|
||||
`http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd <http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd>`_.
|
||||
In order to point to the latest version of the document of a
|
||||
particular stable release branch, just append the release number,
|
||||
i.e.: doctrine-mapping-2.0.xsd The most convenient way to work with
|
||||
XML mapping files is to use an IDE/editor that can provide
|
||||
code-completion based on such an XML Schema document. The following
|
||||
is an outline of a XML mapping document with the proper xmlns/xsi
|
||||
setup for the latest code in trunk.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
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
|
||||
time it is requested and subsequently stored in the metadata cache.
|
||||
In order to work, this requires certain conventions:
|
||||
|
||||
|
||||
- Each entity/mapped superclass must get its own dedicated XML
|
||||
mapping document.
|
||||
- The name of the mapping document must consist of the fully
|
||||
qualified name of the class, where namespace separators are
|
||||
replaced by dots (.). For example an Entity with the fully
|
||||
qualified class-name "MyProject" would require a mapping file
|
||||
"MyProject.Entities.User.dcm.xml" unless the extension is changed.
|
||||
- All mapping documents should get the extension ".dcm.xml" to
|
||||
identify it as a Doctrine mapping file. This is more of a
|
||||
convention and you are not forced to do this. You can change the
|
||||
file extension easily enough.
|
||||
|
||||
-
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$driver->setFileExtension('.xml');
|
||||
|
||||
It is recommended to put all XML mapping documents in a single
|
||||
folder but you can spread the documents over several folders if you
|
||||
want to. In order to tell the XmlDriver where to look for your
|
||||
mapping documents, supply an array of paths as the first argument
|
||||
of the constructor, like this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$driver = new \Doctrine\ORM\Mapping\Driver\XmlDriver(array('/path/to/files1', '/path/to/files2'));
|
||||
$config->setMetadataDriverImpl($driver);
|
||||
|
||||
Simplified XML Driver
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The Symfony project sponsored a driver that simplifies usage of the XML Driver.
|
||||
The changes between the original driver are:
|
||||
|
||||
1. File Extension is .orm.xml
|
||||
2. Filenames are shortened, "MyProject\Entities\User" will become User.orm.xml
|
||||
3. You can add a global file and add multiple entities in this file.
|
||||
|
||||
Configuration of this client works a little bit different:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$namespaces = array(
|
||||
'MyProject\Entities' => '/path/to/files1',
|
||||
'OtherProject\Entities' => '/path/to/files2'
|
||||
);
|
||||
$driver = new \Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver($namespaces);
|
||||
$driver->setGlobalBasename('global'); // global.orm.xml
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
As a quick start, here is a small example document that makes use
|
||||
of several common elements:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
// Doctrine.Tests.ORM.Mapping.User.dcm.xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
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/>
|
||||
</cascade>
|
||||
<order-by>
|
||||
<order-by-field name="number" direction="ASC" />
|
||||
</order-by>
|
||||
</one-to-many>
|
||||
|
||||
<many-to-many field="groups" target-entity="Group">
|
||||
<cascade>
|
||||
<cascade-all/>
|
||||
</cascade>
|
||||
<join-table name="cms_users_groups">
|
||||
<join-columns>
|
||||
<join-column name="user_id" referenced-column-name="id" nullable="false" unique="false" />
|
||||
</join-columns>
|
||||
<inverse-join-columns>
|
||||
<join-column name="group_id" referenced-column-name="id" column-definition="INT NULL" />
|
||||
</inverse-join-columns>
|
||||
</join-table>
|
||||
</many-to-many>
|
||||
|
||||
</entity>
|
||||
|
||||
</doctrine-mapping>
|
||||
|
||||
Be aware that class-names specified in the XML files should be
|
||||
fully qualified.
|
||||
|
||||
XML-Element Reference
|
||||
---------------------
|
||||
|
||||
The XML-Element reference explains all the tags and attributes that
|
||||
the Doctrine Mapping XSD Schema defines. You should read the
|
||||
Basic-, Association- and Inheritance Mapping chapters to understand
|
||||
what each of this definitions means in detail.
|
||||
|
||||
Defining an Entity
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Each XML Mapping File contains the definition of one entity,
|
||||
specified as the ``<entity />`` element as a direct child of the
|
||||
``<doctrine-mapping />`` element:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="MyProject\User" table="cms_users" repository-class="MyProject\UserRepository">
|
||||
<!-- definition here -->
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
Required attributes:
|
||||
|
||||
|
||||
- name - The fully qualified class-name of the entity.
|
||||
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- **table** - The Table-Name to be used for this entity. Otherwise the
|
||||
Unqualified Class-Name is used by default.
|
||||
- **repository-class** - The fully qualified class-name of an
|
||||
alternative ``Doctrine\ORM\EntityRepository`` implementation to be
|
||||
used with this entity.
|
||||
- **inheritance-type** - The type of inheritance, defaults to none. A
|
||||
more detailed description follows in the
|
||||
*Defining Inheritance Mappings* section.
|
||||
- **read-only** - (>= 2.1) Specifies that this entity is marked as read only and not
|
||||
considered for change-tracking. Entities of this type can be persisted
|
||||
and removed though.
|
||||
|
||||
Defining Fields
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Each entity class can contain zero to infinite fields that are
|
||||
managed by Doctrine. You can define them using the ``<field />``
|
||||
element as a children to the ``<entity />`` element. The field
|
||||
element is only used for primitive types that are not the ID of the
|
||||
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" />
|
||||
</entity>
|
||||
|
||||
Required attributes:
|
||||
|
||||
|
||||
- name - The name of the Property/Field on the given Entity PHP
|
||||
class.
|
||||
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- type - The ``Doctrine\DBAL\Types\Type`` name, defaults to
|
||||
"string"
|
||||
- column - Name of the column in the database, defaults to the
|
||||
field name.
|
||||
- length - The length of the given type, for use with strings
|
||||
only.
|
||||
- unique - Should this field contain a unique value across the
|
||||
table? Defaults to false.
|
||||
- nullable - Should this field allow NULL as a value? Defaults to
|
||||
false.
|
||||
- version - Should this field be used for optimistic locking? Only
|
||||
works on fields with type integer or datetime.
|
||||
- scale - Scale of a decimal type.
|
||||
- precision - Precision of a decimal type.
|
||||
- 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.
|
||||
|
||||
Defining Identity and Generator Strategies
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
An entity has to have at least one ``<id />`` element. For
|
||||
composite keys you can specify more than one id-element, however
|
||||
surrogate keys are recommended for use with Doctrine 2. The Id
|
||||
field allows to define properties of the identifier and allows a
|
||||
subset of the ``<field />`` element attributes:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<entity name="MyProject\User">
|
||||
<id name="id" type="integer" column="user_id" />
|
||||
</entity>
|
||||
|
||||
Required attributes:
|
||||
|
||||
|
||||
- name - The name of the Property/Field on the given Entity PHP
|
||||
class.
|
||||
- type - The ``Doctrine\DBAL\Types\Type`` name, preferably
|
||||
"string" or "integer".
|
||||
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- column - Name of the column in the database, defaults to the
|
||||
field name.
|
||||
|
||||
Using the simplified definition above Doctrine will use no
|
||||
identifier strategy for this entity. That means you have to
|
||||
manually set the identifier before calling
|
||||
``EntityManager#persist($entity)``. This is the so called
|
||||
``ASSIGNED`` strategy.
|
||||
|
||||
If you want to switch the identifier generation strategy you have
|
||||
to nest a ``<generator />`` element inside the id-element. This of
|
||||
course only works for surrogate keys. For composite keys you always
|
||||
have to use the ``ASSIGNED`` strategy.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<entity name="MyProject\User">
|
||||
<id name="id" type="integer" column="user_id">
|
||||
<generator strategy="AUTO" />
|
||||
</id>
|
||||
</entity>
|
||||
|
||||
The following values are allowed for the ``<generator />`` strategy
|
||||
attribute:
|
||||
|
||||
|
||||
- AUTO - Automatic detection of the identifier strategy based on
|
||||
the preferred solution of the database vendor.
|
||||
- IDENTITY - Use of a IDENTIFY strategy such as Auto-Increment IDs
|
||||
available to Doctrine AFTER the INSERT statement has been executed.
|
||||
- SEQUENCE - Use of a database sequence to retrieve the
|
||||
entity-ids. This is possible before the INSERT statement is
|
||||
executed.
|
||||
|
||||
If you are using the SEQUENCE strategy you can define an additional
|
||||
element to describe the sequence:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<entity name="MyProject\User">
|
||||
<id name="id" type="integer" column="user_id">
|
||||
<generator strategy="SEQUENCE" />
|
||||
<sequence-generator sequence-name="user_seq" allocation-size="5" initial-value="1" />
|
||||
</id>
|
||||
</entity>
|
||||
|
||||
Required attributes for ``<sequence-generator />``:
|
||||
|
||||
|
||||
- sequence-name - The name of the sequence
|
||||
|
||||
Optional attributes for ``<sequence-generator />``:
|
||||
|
||||
|
||||
- allocation-size - By how much steps should the sequence be
|
||||
incremented when a value is retrieved. Defaults to 1
|
||||
- initial-value - What should the initial value of the sequence
|
||||
be.
|
||||
|
||||
**NOTE**
|
||||
|
||||
If you want to implement a cross-vendor compatible application you
|
||||
have to specify and additionally define the <sequence-generator />
|
||||
element, if Doctrine chooses the sequence strategy for a
|
||||
platform.
|
||||
|
||||
|
||||
Defining a Mapped Superclass
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Sometimes you want to define a class that multiple entities inherit
|
||||
from, which itself is not an entity however. The chapter on
|
||||
*Inheritance Mapping* describes a Mapped Superclass in detail. You
|
||||
can define it in XML using the ``<mapped-superclass />`` tag.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<mapped-superclass name="MyProject\BaseClass">
|
||||
<field name="created" type="datetime" />
|
||||
<field name="updated" type="datetime" />
|
||||
</mapped-superclass>
|
||||
</doctrine-mapping>
|
||||
|
||||
Required attributes:
|
||||
|
||||
|
||||
- name - Class name of the mapped superclass.
|
||||
|
||||
You can nest any number of ``<field />`` and unidirectional
|
||||
``<many-to-one />`` or ``<one-to-one />`` associations inside a
|
||||
mapped superclass.
|
||||
|
||||
Defining Inheritance Mappings
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
There are currently two inheritance persistence strategies that you
|
||||
can choose from when defining entities that inherit from each
|
||||
other. Single Table inheritance saves the fields of the complete
|
||||
inheritance hierarchy in a single table, joined table inheritance
|
||||
creates a table for each entity combining the fields using join
|
||||
conditions.
|
||||
|
||||
You can specify the inheritance type in the ``<entity />`` element
|
||||
and then use the ``<discriminator-column />`` and
|
||||
``<discriminator-mapping />`` attributes.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<entity name="MyProject\Animal" inheritance-type="JOINED">
|
||||
<discriminator-column name="discr" type="string" />
|
||||
<discriminator-map>
|
||||
<discriminator-mapping value="cat" class="MyProject\Cat" />
|
||||
<discriminator-mapping value="dog" class="MyProject\Dog" />
|
||||
<discriminator-mapping value="mouse" class="MyProject\Mouse" />
|
||||
</discriminator-map>
|
||||
</entity>
|
||||
|
||||
The allowed values for inheritance-type attribute are ``JOINED`` or
|
||||
``SINGLE_TABLE``.
|
||||
|
||||
.. note::
|
||||
|
||||
All inheritance related definitions have to be defined on the root
|
||||
entity of the hierarchy.
|
||||
|
||||
|
||||
Defining Lifecycle Callbacks
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can define the lifecycle callback methods on your entities
|
||||
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>
|
||||
</entity>
|
||||
|
||||
Defining One-To-One Relations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can define One-To-One Relations/Associations using the
|
||||
``<one-to-one />`` element. The required and optional attributes
|
||||
depend on the associations being on the inverse or owning side.
|
||||
|
||||
For the inverse side the mapping is as simple as:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<entity class="MyProject\User">
|
||||
<one-to-one field="address" target-entity="Address" mapped-by="user" />
|
||||
</entity>
|
||||
|
||||
Required attributes for inverse One-To-One:
|
||||
|
||||
|
||||
- field - Name of the property/field on the entity's PHP class.
|
||||
- target-entity - Name of the entity associated entity class. If
|
||||
this is not qualified the namespace of the current class is
|
||||
prepended. *IMPORTANT:* No leading backslash!
|
||||
- mapped-by - Name of the field on the owning side (here Address
|
||||
entity) that contains the owning side association.
|
||||
|
||||
For the owning side this mapping would look like:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<entity class="MyProject\Address">
|
||||
<one-to-one field="user" target-entity="User" inversed-by="address" />
|
||||
</entity>
|
||||
|
||||
Required attributes for owning One-to-One:
|
||||
|
||||
|
||||
- field - Name of the property/field on the entity's PHP class.
|
||||
- target-entity - Name of the entity associated entity class. If
|
||||
this is not qualified the namespace of the current class is
|
||||
prepended. *IMPORTANT:* No leading backslash!
|
||||
|
||||
Optional attributes for owning One-to-One:
|
||||
|
||||
|
||||
- inversed-by - If the association is bidirectional the
|
||||
inversed-by attribute has to be specified with the name of the
|
||||
field on the inverse entity that contains the back-reference.
|
||||
- orphan-removal - If true, the inverse side entity is always
|
||||
deleted when the owning side entity is. Defaults to false.
|
||||
- fetch - Either LAZY or EAGER, defaults to LAZY. This attribute
|
||||
makes only sense on the owning side, the inverse side *ALWAYS* has
|
||||
to use the ``FETCH`` strategy.
|
||||
|
||||
The definition for the owning side relies on a bunch of mapping
|
||||
defaults for the join column names. Without the nested
|
||||
``<join-column />`` element Doctrine assumes to foreign key to be
|
||||
called ``user_id`` on the Address Entities table. This is because
|
||||
the ``MyProject\Address`` entity is the owning side of this
|
||||
association, which means it contains the foreign key.
|
||||
|
||||
The completed explicitly defined mapping is:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<entity class="MyProject\Address">
|
||||
<one-to-one field="user" target-entity="User" inversed-by="address">
|
||||
<join-column name="user_id" referenced-column-name="id" />
|
||||
</one-to-one>
|
||||
</entity>
|
||||
|
||||
Defining Many-To-One Associations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The many-to-one association is *ALWAYS* the owning side of any
|
||||
bidirectional association. This simplifies the mapping compared to
|
||||
the one-to-one case. The minimal mapping for this association looks
|
||||
like:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<entity class="MyProject\Article">
|
||||
<many-to-one field="author" target-entity="User" />
|
||||
</entity>
|
||||
|
||||
Required attributes:
|
||||
|
||||
|
||||
- field - Name of the property/field on the entity's PHP class.
|
||||
- target-entity - Name of the entity associated entity class. If
|
||||
this is not qualified the namespace of the current class is
|
||||
prepended. *IMPORTANT:* No leading backslash!
|
||||
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- inversed-by - If the association is bidirectional the
|
||||
inversed-by attribute has to be specified with the name of the
|
||||
field on the inverse entity that contains the back-reference.
|
||||
- orphan-removal - If true the entity on the inverse side is
|
||||
always deleted when the owning side entity is and it is not
|
||||
connected to any other owning side entity anymore. Defaults to
|
||||
false.
|
||||
- fetch - Either LAZY or EAGER, defaults to LAZY.
|
||||
|
||||
This definition relies on a bunch of mapping defaults with regards
|
||||
to the naming of the join-column/foreign key. The explicitly
|
||||
defined mapping includes a ``<join-column />`` tag nested inside
|
||||
the many-to-one association tag:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<entity class="MyProject\Article">
|
||||
<many-to-one field="author" target-entity="User">
|
||||
<join-column name="author_id" referenced-column-name="id" />
|
||||
</many-to-one>
|
||||
</entity>
|
||||
|
||||
The join-column attribute ``name`` specifies the column name of the
|
||||
foreign key and the ``referenced-column-name`` attribute specifies
|
||||
the name of the primary key column on the User entity.
|
||||
|
||||
Defining One-To-Many Associations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The one-to-many association is *ALWAYS* the inverse side of any
|
||||
association. There exists no such thing as a uni-directional
|
||||
one-to-many association, which means this association only ever
|
||||
exists for bi-directional associations.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<entity class="MyProject\User">
|
||||
<one-to-many field="phonenumbers" target-entity="Phonenumber" mapped-by="user" />
|
||||
</entity>
|
||||
|
||||
Required attributes:
|
||||
|
||||
|
||||
- field - Name of the property/field on the entity's PHP class.
|
||||
- target-entity - Name of the entity associated entity class. If
|
||||
this is not qualified the namespace of the current class is
|
||||
prepended. *IMPORTANT:* No leading backslash!
|
||||
- mapped-by - Name of the field on the owning side (here
|
||||
Phonenumber entity) that contains the owning side association.
|
||||
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- fetch - Either LAZY, EXTRA_LAZY or EAGER, defaults to LAZY.
|
||||
- index-by: Index the collection by a field on the target entity.
|
||||
|
||||
Defining Many-To-Many Associations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
From all the associations the many-to-many has the most complex
|
||||
definition. When you rely on the mapping defaults you can omit many
|
||||
definitions and rely on their implicit values.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<entity class="MyProject\User">
|
||||
<many-to-many field="groups" target-entity="Group" />
|
||||
</entity>
|
||||
|
||||
Required attributes:
|
||||
|
||||
|
||||
- field - Name of the property/field on the entity's PHP class.
|
||||
- target-entity - Name of the entity associated entity class. If
|
||||
this is not qualified the namespace of the current class is
|
||||
prepended. *IMPORTANT:* No leading backslash!
|
||||
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- mapped-by - Name of the field on the owning side that contains
|
||||
the owning side association if the defined many-to-many association
|
||||
is on the inverse side.
|
||||
- inversed-by - If the association is bidirectional the
|
||||
inversed-by attribute has to be specified with the name of the
|
||||
field on the inverse entity that contains the back-reference.
|
||||
- fetch - Either LAZY, EXTRA_LAZY or EAGER, defaults to LAZY.
|
||||
- index-by: Index the collection by a field on the target entity.
|
||||
|
||||
The mapping defaults would lead to a join-table with the name
|
||||
"User\_Group" being created that contains two columns "user\_id"
|
||||
and "group\_id". The explicit definition of this mapping would be:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<entity class="MyProject\User">
|
||||
<many-to-many field="groups" target-entity="Group">
|
||||
<join-table name="cms_users_groups">
|
||||
<join-columns>
|
||||
<join-column name="user_id" referenced-column-name="id"/>
|
||||
</join-columns>
|
||||
<inverse-join-columns>
|
||||
<join-column name="group_id" referenced-column-name="id"/>
|
||||
</inverse-join-columns>
|
||||
</join-table>
|
||||
</many-to-many>
|
||||
</entity>
|
||||
|
||||
Here both the ``<join-columns>`` and ``<inverse-join-columns>``
|
||||
tags are necessary to tell Doctrine for which side the specified
|
||||
join-columns apply. These are nested inside a ``<join-table />``
|
||||
attribute which allows to specify the table name of the
|
||||
many-to-many join-table.
|
||||
|
||||
Cascade Element
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Doctrine allows cascading of several UnitOfWork operations to
|
||||
related entities. You can specify the cascade operations in the
|
||||
``<cascade />`` element inside any of the association mapping
|
||||
tags.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<entity class="MyProject\User">
|
||||
<many-to-many field="groups" target-entity="Group">
|
||||
<cascade>
|
||||
<cascade-all/>
|
||||
</cascade>
|
||||
</many-to-many>
|
||||
</entity>
|
||||
|
||||
Besides ``<cascade-all />`` the following operations can be
|
||||
specified by their respective tags:
|
||||
|
||||
|
||||
- ``<cascade-persist />``
|
||||
- ``<cascade-merge />``
|
||||
- ``<cascade-remove />``
|
||||
- ``<cascade-refresh />``
|
||||
|
||||
Join Column Element
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In any explicitly defined association mapping you will need the
|
||||
``<join-column />`` tag. It defines how the foreign key and primary
|
||||
key names are called that are used for joining two entities.
|
||||
|
||||
Required attributes:
|
||||
|
||||
|
||||
- name - The column name of the foreign key.
|
||||
- referenced-column-name - The column name of the associated
|
||||
entities primary key
|
||||
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- unique - If the join column should contain a UNIQUE constraint.
|
||||
This makes sense for Many-To-Many join-columns only to simulate a
|
||||
one-to-many unidirectional using a join-table.
|
||||
- nullable - should the join column be nullable, defaults to true.
|
||||
- on-delete - Foreign Key Cascade action to perform when entity is
|
||||
deleted, defaults to NO ACTION/RESTRICT but can be set to
|
||||
"CASCADE".
|
||||
|
||||
Defining Order of To-Many Associations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can require one-to-many or many-to-many associations to be
|
||||
retrieved using an additional ``ORDER BY``.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<entity class="MyProject\User">
|
||||
<many-to-many field="groups" target-entity="Group">
|
||||
<order-by>
|
||||
<order-by-field name="name" direction="ASC" />
|
||||
</order-by>
|
||||
</many-to-many>
|
||||
</entity>
|
||||
|
||||
Defining Indexes or Unique Constraints
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To define additional indexes or unique constraints on the entities
|
||||
table you can use the ``<indexes />`` and
|
||||
``<unique-constraints />`` elements:
|
||||
|
||||
.. 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>
|
||||
</entity>
|
||||
|
||||
You have to specify the column and not the entity-class field names
|
||||
in the index and unique-constraint definitions.
|
||||
|
||||
Derived Entities ID syntax
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If the primary key of an entity contains a foreign key to another entity we speak of a derived
|
||||
entity relationship. You can define this in XML with the "association-key" attribute in the ``<id>`` tag.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Application\Model\ArticleAttribute">
|
||||
<id name="article" association-key="true" />
|
||||
<id name="attribute" type="string" />
|
||||
|
||||
<field name="value" type="string" />
|
||||
|
||||
<many-to-one field="article" target-entity="Article" inversed-by="attributes" />
|
||||
<entity>
|
||||
|
||||
</doctrine-mapping>
|
||||
117
docs/en/reference/yaml-mapping.rst
Normal file
117
docs/en/reference/yaml-mapping.rst
Normal file
@@ -0,0 +1,117 @@
|
||||
YAML Mapping
|
||||
============
|
||||
|
||||
The YAML mapping driver enables you to provide the ORM metadata in
|
||||
form of YAML documents.
|
||||
|
||||
The YAML mapping document of a class is loaded on-demand the first
|
||||
time it is requested and subsequently stored in the metadata cache.
|
||||
In order to work, this requires certain conventions:
|
||||
|
||||
|
||||
- Each entity/mapped superclass must get its own dedicated YAML
|
||||
mapping document.
|
||||
- The name of the mapping document must consist of the fully
|
||||
qualified name of the class, where namespace separators are
|
||||
replaced by dots (.).
|
||||
- All mapping documents should get the extension ".dcm.yml" to
|
||||
identify it as a Doctrine mapping file. This is more of a
|
||||
convention and you are not forced to do this. You can change the
|
||||
file extension easily enough.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$driver->setFileExtension('.yml');
|
||||
|
||||
It is recommended to put all YAML mapping documents in a single
|
||||
folder but you can spread the documents over several folders if you
|
||||
want to. In order to tell the YamlDriver where to look for your
|
||||
mapping documents, supply an array of paths as the first argument
|
||||
of the constructor, like this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Mapping\Driver\YamlDriver;
|
||||
|
||||
// $config instanceof Doctrine\ORM\Configuration
|
||||
$driver = new YamlDriver(array('/path/to/files'));
|
||||
$config->setMetadataDriverImpl($driver);
|
||||
|
||||
Simplified YAML Driver
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The Symfony project sponsored a driver that simplifies usage of the YAML Driver.
|
||||
The changes between the original driver are:
|
||||
|
||||
- File Extension is .orm.yml
|
||||
- Filenames are shortened, "MyProject\\Entities\\User" will become User.orm.yml
|
||||
- You can add a global file and add multiple entities in this file.
|
||||
|
||||
Configuration of this client works a little bit different:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$namespaces = array(
|
||||
'/path/to/files1' => 'MyProject\Entities',
|
||||
'/path/to/files2' => 'OtherProject\Entities'
|
||||
);
|
||||
$driver = new \Doctrine\ORM\Mapping\Driver\SimplifiedYamlDriver($namespaces);
|
||||
$driver->setGlobalBasename('global'); // global.orm.yml
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
As a quick start, here is a small example document that makes use
|
||||
of several common elements:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# Doctrine.Tests.ORM.Mapping.User.dcm.yml
|
||||
Doctrine\Tests\ORM\Mapping\User:
|
||||
type: entity
|
||||
table: cms_users
|
||||
indexes:
|
||||
name_index:
|
||||
columns: [ name ]
|
||||
id:
|
||||
id:
|
||||
type: integer
|
||||
generator:
|
||||
strategy: AUTO
|
||||
fields:
|
||||
name:
|
||||
type: string
|
||||
length: 50
|
||||
oneToOne:
|
||||
address:
|
||||
targetEntity: Address
|
||||
joinColumn:
|
||||
name: address_id
|
||||
referencedColumnName: id
|
||||
oneToMany:
|
||||
phonenumbers:
|
||||
targetEntity: Phonenumber
|
||||
mappedBy: user
|
||||
cascade: ["persist", "merge"]
|
||||
manyToMany:
|
||||
groups:
|
||||
targetEntity: Group
|
||||
joinTable:
|
||||
name: cms_users_groups
|
||||
joinColumns:
|
||||
user_id:
|
||||
referencedColumnName: id
|
||||
inverseJoinColumns:
|
||||
group_id:
|
||||
referencedColumnName: id
|
||||
lifecycleCallbacks:
|
||||
prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ]
|
||||
postPersist: [ doStuffOnPostPersist ]
|
||||
|
||||
Be aware that class-names specified in the YAML files should be
|
||||
fully qualified.
|
||||
|
||||
|
||||
80
docs/en/toc.rst
Normal file
80
docs/en/toc.rst
Normal file
@@ -0,0 +1,80 @@
|
||||
Welcome to Doctrine 2 ORM's documentation!
|
||||
==========================================
|
||||
|
||||
Tutorials
|
||||
---------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
tutorials/getting-started
|
||||
tutorials/working-with-indexed-associations
|
||||
tutorials/extra-lazy-associations
|
||||
tutorials/composite-primary-keys
|
||||
tutorials/ordered-associations
|
||||
tutorials/in-ten-quick-steps
|
||||
tutorials/override-field-association-mappings-in-subclasses
|
||||
|
||||
Reference Guide
|
||||
---------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:numbered:
|
||||
|
||||
reference/introduction
|
||||
reference/architecture
|
||||
reference/configuration
|
||||
reference/faq
|
||||
reference/basic-mapping
|
||||
reference/association-mapping
|
||||
reference/inheritance-mapping
|
||||
reference/working-with-objects
|
||||
reference/working-with-associations
|
||||
reference/events
|
||||
reference/unitofwork
|
||||
reference/unitofwork-associations
|
||||
reference/transactions-and-concurrency
|
||||
reference/batch-processing
|
||||
reference/dql-doctrine-query-language
|
||||
reference/query-builder
|
||||
reference/native-sql
|
||||
reference/change-tracking-policies
|
||||
reference/partial-objects
|
||||
reference/xml-mapping
|
||||
reference/yaml-mapping
|
||||
reference/annotations-reference
|
||||
reference/php-mapping
|
||||
reference/caching
|
||||
reference/improving-performance
|
||||
reference/tools
|
||||
reference/metadata-drivers
|
||||
reference/best-practices
|
||||
reference/limitations-and-known-issues
|
||||
tutorials/pagination.rst
|
||||
reference/filters.rst
|
||||
reference/namingstrategy.rst
|
||||
|
||||
|
||||
Cookbook
|
||||
--------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
cookbook/aggregate-fields
|
||||
cookbook/decorator-pattern
|
||||
cookbook/dql-custom-walkers
|
||||
cookbook/dql-user-defined-functions
|
||||
cookbook/implementing-arrayaccess-for-domain-objects
|
||||
cookbook/implementing-the-notify-changetracking-policy
|
||||
cookbook/implementing-wakeup-or-clone
|
||||
cookbook/integrating-with-codeigniter
|
||||
cookbook/sql-table-prefixes
|
||||
cookbook/strategy-cookbook-introduction
|
||||
cookbook/validation-of-entities
|
||||
cookbook/working-with-datetime
|
||||
cookbook/mysql-enums
|
||||
cookbook/advanced-field-value-conversion-using-custom-mapping-types
|
||||
cookbook/entities-in-session
|
||||
|
||||
375
docs/en/tutorials/composite-primary-keys.rst
Normal file
375
docs/en/tutorials/composite-primary-keys.rst
Normal file
@@ -0,0 +1,375 @@
|
||||
Composite and Foreign Keys as Primary Key
|
||||
=========================================
|
||||
|
||||
.. versionadded:: 2.1
|
||||
|
||||
Doctrine 2 supports composite primary keys natively. Composite keys are a very powerful relational database concept
|
||||
and we took good care to make sure Doctrine 2 supports as many of the composite primary key use-cases.
|
||||
For Doctrine 2.0 composite keys of primitive data-types are supported, for Doctrine 2.1 even foreign keys as
|
||||
primary keys are supported.
|
||||
|
||||
This tutorial shows how the semantics of composite primary keys work and how they map to the database.
|
||||
|
||||
General Considerations
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Every entity with a composite key cannot use an id generator other than "ASSIGNED". That means
|
||||
the ID fields have to have their values set before you call ``EntityManager#persist($entity)``.
|
||||
|
||||
Primitive Types only
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Even in version 2.0 you can have composite keys as long as they only consist of the primitive types
|
||||
``integer`` and ``string``. Suppose you want to create a database of cars and use the model-name
|
||||
and year of production as primary keys:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace VehicleCatalogue\Model;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class Car
|
||||
{
|
||||
/** @Id @Column(type="string") */
|
||||
private $name;
|
||||
/** @Id @Column(type="integer") */
|
||||
private $year
|
||||
|
||||
public function __construct($name, $year)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->year = $year;
|
||||
}
|
||||
|
||||
public function getModelName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getYearOfProduction()
|
||||
{
|
||||
return $this->year;
|
||||
}
|
||||
}
|
||||
|
||||
.. 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://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="VehicleCatalogue\Model\Car">
|
||||
<id field="name" type="string" />
|
||||
<id field="year" type="integer" />
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
VehicleCatalogue\Model\Car:
|
||||
type: entity
|
||||
id:
|
||||
name:
|
||||
type: string
|
||||
year:
|
||||
type: integer
|
||||
|
||||
Now you can use this entity:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace VehicleCatalogue\Model;
|
||||
|
||||
// $em is the EntityManager
|
||||
|
||||
$car = new Car("Audi A8", 2010);
|
||||
$em->persist($car);
|
||||
$em->flush();
|
||||
|
||||
And for querying you can use arrays to both DQL and EntityRepositories:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace VehicleCatalogue\Model;
|
||||
|
||||
// $em is the EntityManager
|
||||
$audi = $em->find("VehicleCatalogue\Model\Car", array("name" => "Audi A8", "year" => 2010));
|
||||
|
||||
$dql = "SELECT c FROM VehicleCatalogue\Model\Car c WHERE c.id = ?1";
|
||||
$audi = $em->createQuery($dql)
|
||||
->setParameter(1, array("name" => "Audi A8", "year" => 2010))
|
||||
->getSingleResult();
|
||||
|
||||
You can also use this entity in associations. Doctrine will then generate two foreign keys one for ``name``
|
||||
and to ``year`` to the related entities.
|
||||
|
||||
.. note::
|
||||
|
||||
This example shows how you can nicely solve the requirement for existing
|
||||
values before ``EntityManager#persist()``: By adding them as mandatory values for the constructor.
|
||||
|
||||
Identity through foreign Entities
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. note::
|
||||
|
||||
Identity through foreign entities is only supported with Doctrine 2.1
|
||||
|
||||
There are tons of use-cases where the identity of an Entity should be determined by the entity
|
||||
of one or many parent entities.
|
||||
|
||||
- Dynamic Attributes of an Entity (for example Article). Each Article has many
|
||||
attributes with primary key "article_id" and "attribute_name".
|
||||
- Address object of a Person, the primary key of the address is "user_id". This is not a case of a composite primary
|
||||
key, but the identity is derived through a foreign entity and a foreign key.
|
||||
- Join Tables with metadata can be modelled as Entity, for example connections between two articles
|
||||
with a little description and a score.
|
||||
|
||||
The semantics of mapping identity through foreign entities are easy:
|
||||
|
||||
- Only allowed on Many-To-One or One-To-One associations.
|
||||
- Plug an ``@Id`` annotation onto every association.
|
||||
- Set an attribute ``association-key`` with the field name of the association in XML.
|
||||
- Set a key ``associationKey:`` with the field name of the association in YAML.
|
||||
|
||||
Use-Case 1: Dynamic Attributes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
We keep up the example of an Article with arbitrary attributes, the mapping looks like this:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace Application\Model;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class Article
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private $id;
|
||||
/** @Column(type="string") */
|
||||
private $title;
|
||||
|
||||
/**
|
||||
* @OneToMany(targetEntity="ArticleAttribute", mappedBy="article", cascade={"ALL"}, indexBy="attribute")
|
||||
*/
|
||||
private $attributes;
|
||||
|
||||
public function addAttribute($name, $value)
|
||||
{
|
||||
$this->attributes[$name] = new ArticleAttribute($name, $value, $this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class ArticleAttribute
|
||||
{
|
||||
/** @Id @ManyToOne(targetEntity="Article", inversedBy="attributes") */
|
||||
private $article;
|
||||
|
||||
/** @Id @Column(type="string") */
|
||||
private $attribute;
|
||||
|
||||
/** @Column(type="string") */
|
||||
private $value;
|
||||
|
||||
public function __construct($name, $value, $article)
|
||||
{
|
||||
$this->attribute = $name;
|
||||
$this->value = $value;
|
||||
$this->article = $article;
|
||||
}
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Application\Model\ArticleAttribute">
|
||||
<id name="article" association-key="true" />
|
||||
<id name="attribute" type="string" />
|
||||
|
||||
<field name="value" type="string" />
|
||||
|
||||
<many-to-one field="article" target-entity="Article" inversed-by="attributes" />
|
||||
<entity>
|
||||
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
Application\Model\ArticleAttribute:
|
||||
type: entity
|
||||
id:
|
||||
article:
|
||||
associationKey: true
|
||||
attribute:
|
||||
type: string
|
||||
fields:
|
||||
value:
|
||||
type: string
|
||||
manyToOne:
|
||||
article:
|
||||
targetEntity: Article
|
||||
inversedBy: attributes
|
||||
|
||||
|
||||
Use-Case 2: Simple Derived Identity
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Sometimes you have the requirement that two objects are related by a One-To-One association
|
||||
and that the dependent class should re-use the primary key of the class it depends on.
|
||||
One good example for this is a user-address relationship:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class User
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class Address
|
||||
{
|
||||
/** @Id @OneToOne(targetEntity="User") */
|
||||
private $user;
|
||||
}
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
User:
|
||||
type: entity
|
||||
id:
|
||||
id:
|
||||
type: integer
|
||||
generator:
|
||||
strategy: AUTO
|
||||
|
||||
Address:
|
||||
type: entity
|
||||
id:
|
||||
user:
|
||||
associationKey: true
|
||||
oneToOne:
|
||||
user:
|
||||
targetEntity: User
|
||||
|
||||
|
||||
Use-Case 3: Join-Table with Metadata
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In the classic order product shop example there is the concept of the order item
|
||||
which contains references to order and product and additional data such as the amount
|
||||
of products purchased and maybe even the current price.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
/** @Entity */
|
||||
class Order
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private $id;
|
||||
|
||||
/** @ManyToOne(targetEntity="Customer") */
|
||||
private $customer;
|
||||
/** @OneToMany(targetEntity="OrderItem", mappedBy="order") */
|
||||
private $items;
|
||||
|
||||
/** @Column(type="boolean") */
|
||||
private $payed = false;
|
||||
/** @Column(type="boolean") */
|
||||
private $shipped = false;
|
||||
/** @Column(type="datetime") */
|
||||
private $created;
|
||||
|
||||
public function __construct(Customer $customer)
|
||||
{
|
||||
$this->customer = $customer;
|
||||
$this->items = new ArrayCollection();
|
||||
$this->created = new \DateTime("now");
|
||||
}
|
||||
}
|
||||
|
||||
/** @Entity */
|
||||
class Product
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private $id;
|
||||
|
||||
/** @Column(type="string") */
|
||||
private $name;
|
||||
|
||||
/** @Column(type="decimal") */
|
||||
private $currentPrice;
|
||||
|
||||
public function getCurrentPrice()
|
||||
{
|
||||
return $this->currentPrice;
|
||||
}
|
||||
}
|
||||
|
||||
/** @Entity */
|
||||
class OrderItem
|
||||
{
|
||||
/** @Id @ManyToOne(targetEntity="Order") */
|
||||
private $order;
|
||||
|
||||
/** @Id @ManyToOne(targetEntity="Product") */
|
||||
private $product;
|
||||
|
||||
/** @Column(type="integer") */
|
||||
private $amount = 1;
|
||||
|
||||
/** @Column(type="decimal") */
|
||||
private $offeredPrice;
|
||||
|
||||
public function __construct(Order $order, Product $product, $amount = 1)
|
||||
{
|
||||
$this->order = $order;
|
||||
$this->product = $product;
|
||||
$this->offeredPrice = $product->getCurrentPrice();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Performance Considerations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Using composite keys always comes with a performance hit compared to using entities with
|
||||
a simple surrogate key. This performance impact is mostly due to additional PHP code that is
|
||||
necessary to handle this kind of keys, most notably when using derived identifiers.
|
||||
|
||||
On the SQL side there is not much overhead as no additional or unexpected queries have to be
|
||||
executed to manage entities with derived foreign keys.
|
||||
84
docs/en/tutorials/extra-lazy-associations.rst
Normal file
84
docs/en/tutorials/extra-lazy-associations.rst
Normal file
@@ -0,0 +1,84 @@
|
||||
Extra Lazy Associations
|
||||
=======================
|
||||
|
||||
.. versionadded:: 2.1
|
||||
|
||||
In many cases associations between entities can get pretty large. Even in a simple scenario like a blog.
|
||||
where posts can be commented, you always have to assume that a post draws hundreds of comments.
|
||||
In Doctrine 2.0 if you accessed an association it would always get loaded completely into memory. This
|
||||
can lead to pretty serious performance problems, if your associations contain several hundreds or thousands
|
||||
of entities.
|
||||
|
||||
With Doctrine 2.1 a feature called **Extra Lazy** is introduced for associations. Associations
|
||||
are marked as **Lazy** by default, which means the whole collection object for an association is populated
|
||||
the first time its accessed. If you mark an association as extra lazy the following methods on collections
|
||||
can be called without triggering a full load of the collection:
|
||||
|
||||
- ``Collection#contains($entity)``
|
||||
- ``Collection#count()``
|
||||
- ``Collection#slice($offset, $length = null)``
|
||||
|
||||
For each of this three methods the following semantics apply:
|
||||
|
||||
- For each call, if the Collection is not yet loaded, issue a straight SELECT statement against the database.
|
||||
- For each call, if the collection is already loaded, fallback to the default functionality for lazy collections. No additional SELECT statements are executed.
|
||||
|
||||
Additionally even with Doctrine 2.0 the following methods do not trigger the collection load:
|
||||
|
||||
- ``Collection#add($entity)``
|
||||
- ``Collection#offsetSet($key, $entity)`` - ArrayAccess with no specific key ``$coll[] = $entity``, it does
|
||||
not work when setting specific keys like ``$coll[0] = $entity``.
|
||||
|
||||
With extra lazy collections you can now not only add entities to large collections but also paginate them
|
||||
easily using a combination of ``count`` and ``slice``.
|
||||
|
||||
|
||||
Enabling Extra-Lazy Associations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The mapping configuration is simple. Instead of using the default value of ``fetch="LAZY"`` you have to
|
||||
switch to extra lazy as shown in these examples:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace Doctrine\Tests\Models\CMS;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class CmsGroup
|
||||
{
|
||||
/**
|
||||
* @ManyToMany(targetEntity="CmsUser", mappedBy="groups", fetch="EXTRA_LAZY")
|
||||
*/
|
||||
public $users;
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Doctrine\Tests\Models\CMS\CmsGroup">
|
||||
<!-- ... -->
|
||||
<many-to-many field="users" target-entity="CmsUser" mapped-by="groups" fetch="EXTRA_LAZY" />
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
Doctrine\Tests\Models\CMS\CmsGroup:
|
||||
type: entity
|
||||
# ...
|
||||
manyToMany:
|
||||
users:
|
||||
targetEntity: CmsUser
|
||||
mappedBy: groups
|
||||
fetch: EXTRA_LAZY
|
||||
|
||||
27
docs/en/tutorials/getting-started-database.rst
Normal file
27
docs/en/tutorials/getting-started-database.rst
Normal file
@@ -0,0 +1,27 @@
|
||||
Getting Started: Database First
|
||||
===============================
|
||||
|
||||
.. note:: *Development Workflows*
|
||||
|
||||
When you :doc:`Code First <getting-started>`, you
|
||||
start with developing Objects and then map them onto your database. When
|
||||
you :doc:`Model First <getting-started-models>`, you are modelling your application using tools (for
|
||||
example UML) and generate database schema and PHP code from this model.
|
||||
When you have a :doc:`Database First <getting-started-database>`, you already have a database schema
|
||||
and generate the corresponding PHP code from it.
|
||||
|
||||
.. note::
|
||||
|
||||
This getting started guide is in development.
|
||||
|
||||
Development of new applications often starts with an existing database schema.
|
||||
When the database schema is the starting point for your application, then
|
||||
development is said to use the *Database First* approach to Doctrine.
|
||||
|
||||
In this workflow you would modify the database schema first and then
|
||||
regenerate the PHP code to use with this schema. You need a flexible
|
||||
code-generator for this task and up to Doctrine 2.2, the code generator hasn't
|
||||
been flexible enough to achieve this.
|
||||
|
||||
We spinned off a subproject, Doctrine CodeGenerator, that will fill this gap and
|
||||
allow you to do *Database First* development.
|
||||
24
docs/en/tutorials/getting-started-models.rst
Normal file
24
docs/en/tutorials/getting-started-models.rst
Normal file
@@ -0,0 +1,24 @@
|
||||
Getting Started: Model First
|
||||
============================
|
||||
|
||||
.. note:: *Development Workflows*
|
||||
|
||||
When you :doc:`Code First <getting-started>`, you
|
||||
start with developing Objects and then map them onto your database. When
|
||||
you :doc:`Model First <getting-started-models>`, you are modelling your application using tools (for
|
||||
example UML) and generate database schema and PHP code from this model.
|
||||
When you have a :doc:`Database First <getting-started-database>`, then you already have a database schema
|
||||
and generate the corresponding PHP code from it.
|
||||
|
||||
.. note::
|
||||
|
||||
This getting started guide is in development.
|
||||
|
||||
There are applications when you start with a high-level description of the
|
||||
model using modelling tools such as UML. Modelling tools could also be Excel,
|
||||
XML or CSV files that describe the model in some structured way. If your
|
||||
application is using a modelling tool, then the development workflow is said to
|
||||
be a *Model First* approach to Doctrine2.
|
||||
|
||||
In this workflow you always change the model description and then regenerate
|
||||
both PHP code and database schema from this model.
|
||||
1537
docs/en/tutorials/getting-started.rst
Normal file
1537
docs/en/tutorials/getting-started.rst
Normal file
File diff suppressed because it is too large
Load Diff
110
docs/en/tutorials/ordered-associations.rst
Normal file
110
docs/en/tutorials/ordered-associations.rst
Normal file
@@ -0,0 +1,110 @@
|
||||
Ordering To-Many Associations
|
||||
-----------------------------
|
||||
|
||||
There are use-cases when you'll want to sort collections when they are
|
||||
retrieved from the database. In userland you do this as long as you
|
||||
haven't initially saved an entity with its associations into the
|
||||
database. To retrieve a sorted collection from the database you can
|
||||
use the ``@OrderBy`` annotation with an collection that specifies
|
||||
an DQL snippet that is appended to all queries with this
|
||||
collection.
|
||||
|
||||
Additional to any ``@OneToMany`` or ``@ManyToMany`` annotation you
|
||||
can specify the ``@OrderBy`` in the following way:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity **/
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* @ManyToMany(targetEntity="Group")
|
||||
* @OrderBy({"name" = "ASC"})
|
||||
**/
|
||||
private $groups;
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="User">
|
||||
<many-to-many field="groups" target-entity="Group">
|
||||
<order-by>
|
||||
<order-by-field name="name" direction="ASC" />
|
||||
</order-by>
|
||||
</many-to-many>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
User:
|
||||
type: entity
|
||||
manyToMany:
|
||||
groups:
|
||||
orderBy: { 'name': 'ASC' }
|
||||
targetEntity: Group
|
||||
joinTable:
|
||||
name: users_groups
|
||||
joinColumns:
|
||||
user_id:
|
||||
referencedColumnName: id
|
||||
inverseJoinColumns:
|
||||
group_id:
|
||||
referencedColumnName: id
|
||||
|
||||
The DQL Snippet in OrderBy is only allowed to consist of
|
||||
unqualified, unquoted field names and of an optional ASC/DESC
|
||||
positional statement. Multiple Fields are separated by a comma (,).
|
||||
The referenced field names have to exist on the ``targetEntity``
|
||||
class of the ``@ManyToMany`` or ``@OneToMany`` annotation.
|
||||
|
||||
The semantics of this feature can be described as follows.
|
||||
|
||||
|
||||
- ``@OrderBy`` acts as an implicit ORDER BY clause for the given
|
||||
fields, that is appended to all the explicitly given ORDER BY
|
||||
items.
|
||||
- All collections of the ordered type are always retrieved in an
|
||||
ordered fashion.
|
||||
- To keep the database impact low, these implicit ORDER BY items
|
||||
are only added to an DQL Query if the collection is fetch joined in
|
||||
the DQL query.
|
||||
|
||||
Given our previously defined example, the following would not add
|
||||
ORDER BY, since g is not fetch joined:
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
SELECT u FROM User u JOIN u.groups g WHERE SIZE(g) > 10
|
||||
|
||||
However the following:
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10
|
||||
|
||||
...would internally be rewritten to:
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name ASC
|
||||
|
||||
You can reverse the order with an explicit DQL ORDER BY:
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC
|
||||
|
||||
...is internally rewritten to:
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC, g.name ASC
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
Override Field Association Mappings In Subclasses
|
||||
-------------------------------------------------
|
||||
|
||||
Sometimes there is a need to persist entities but override all or part of the
|
||||
mapping metadata. Sometimes also the mapping to override comes from entities
|
||||
using traits where the traits have mapping metadata.
|
||||
This tutorial explains how to override mapping metadata,
|
||||
i.e. attributes and associations metadata in particular. The example here shows
|
||||
the overriding of a class that uses a trait but is similar when extending a base
|
||||
class as shown at the end of this tutorial.
|
||||
|
||||
Suppose we have a class ExampleEntityWithOverride. This class uses trait ExampleTrait:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
*
|
||||
* @AttributeOverrides({
|
||||
* @AttributeOverride(name="foo",
|
||||
* column=@Column(
|
||||
* name = "foo_overridden",
|
||||
* type = "integer",
|
||||
* length = 140,
|
||||
* nullable = false,
|
||||
* unique = false
|
||||
* )
|
||||
* )
|
||||
* })
|
||||
*
|
||||
* @AssociationOverrides({
|
||||
* @AssociationOverride(name="bar",
|
||||
* joinColumns=@JoinColumn(
|
||||
* name="example_entity_overridden_bar_id", referencedColumnName="id"
|
||||
* )
|
||||
* )
|
||||
* })
|
||||
*/
|
||||
class ExampleEntityWithOverride
|
||||
{
|
||||
use ExampleTrait;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class Bar
|
||||
{
|
||||
/** @Id @Column(type="string") */
|
||||
private $id;
|
||||
}
|
||||
|
||||
The docblock is showing metadata override of the attribute and association type. It
|
||||
basically changes the names of the columns mapped for a property ``foo`` and for
|
||||
the association ``bar`` which relates to Bar class shown above. Here is the trait
|
||||
which has mapping metadata that is overridden by the annotation above:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
/**
|
||||
* Trait class
|
||||
*/
|
||||
trait ExampleTrait
|
||||
{
|
||||
/** @Id @Column(type="string") */
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @Column(name="trait_foo", type="integer", length=100, nullable=true, unique=true)
|
||||
*/
|
||||
protected $foo;
|
||||
|
||||
/**
|
||||
* @OneToOne(targetEntity="Bar", cascade={"persist", "merge"})
|
||||
* @JoinColumn(name="example_trait_bar_id", referencedColumnName="id")
|
||||
*/
|
||||
protected $bar;
|
||||
}
|
||||
|
||||
The case for just extending a class would be just the same but:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
class ExampleEntityWithOverride extends BaseEntityWithSomeMapping
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
Overriding is also supported via XML and YAML.
|
||||
43
docs/en/tutorials/pagination.rst
Normal file
43
docs/en/tutorials/pagination.rst
Normal file
@@ -0,0 +1,43 @@
|
||||
Pagination
|
||||
==========
|
||||
|
||||
.. versionadded:: 2.2
|
||||
|
||||
Starting with version 2.2 Doctrine ships with a Paginator for DQL queries. It
|
||||
has a very simple API and implements the SPL interfaces ``Countable`` and
|
||||
``IteratorAggregate``.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Tools\Pagination\Paginator;
|
||||
|
||||
$dql = "SELECT p, c FROM BlogPost p JOIN p.comments c";
|
||||
$query = $entityManager->createQuery($dql)
|
||||
->setFirstResult(0)
|
||||
->setMaxResults(100);
|
||||
|
||||
$paginator = new Paginator($query, $fetchJoinCollection = true);
|
||||
|
||||
$c = count($paginator);
|
||||
foreach ($paginator as $post) {
|
||||
echo $post->getHeadline() . "\n";
|
||||
}
|
||||
|
||||
Paginating Doctrine queries is not as simple as you might think in the
|
||||
beginning. If you have complex fetch-join scenarios with one-to-many or
|
||||
many-to-many associations using the "default" LIMIT functionality of database
|
||||
vendors is not sufficient to get the correct results.
|
||||
|
||||
By default the pagination extension does the following steps to compute the
|
||||
correct result:
|
||||
|
||||
- Perform a Count query using `DISTINCT` keyword.
|
||||
- Perform a Limit Subquery with `DISTINCT` to find all ids of the entity in from on the current page.
|
||||
- Perform a WHERE IN query to get all results for the current page.
|
||||
|
||||
This behavior is only necessary if you actually fetch join a to-many
|
||||
collection. You can disable this behavior by setting the
|
||||
``$fetchJoinCollection`` flag to ``false``; in that case only 2 instead of the 3 queries
|
||||
described are executed. We hope to automate the detection for this in
|
||||
the future.
|
||||
298
docs/en/tutorials/working-with-indexed-associations.rst
Normal file
298
docs/en/tutorials/working-with-indexed-associations.rst
Normal file
@@ -0,0 +1,298 @@
|
||||
Working with Indexed Associations
|
||||
=================================
|
||||
|
||||
.. note::
|
||||
|
||||
This feature is scheduled for version 2.1 of Doctrine and not included in the 2.0.x series.
|
||||
|
||||
Doctrine 2 collections are modelled after PHPs native arrays. PHP arrays are an ordered hashmap, but in
|
||||
the first version of Doctrine keys retrieved from the database were always numerical unless ``INDEX BY``
|
||||
was used. Starting with Doctrine 2.1 you can index your collections by a value in the related entity.
|
||||
This is a first step towards full ordered hashmap support through the Doctrine ORM.
|
||||
The feature works like an implicit ``INDEX BY`` for the selected association but has several
|
||||
downsides also:
|
||||
|
||||
- You have to manage both the key and field if you want to change the index by field value.
|
||||
- On each request the keys are regenerated from the field value not from the previous collection key.
|
||||
- Values of the Index-By keys are never considered during persistence, it only exists for accessing purposes.
|
||||
- Fields that are used for the index by feature **HAVE** to be unique in the database. The behavior for multiple entities
|
||||
with the same index-by field value is undefined.
|
||||
|
||||
As an example we will design a simple stock exchange list view. The domain consists of the entity ``Stock``
|
||||
and ``Market`` where each Stock has a symbol and is traded on a single market. Instead of having a numerical
|
||||
list of stocks traded on a market they will be indexed by their symbol, which is unique across all markets.
|
||||
|
||||
Mapping Indexed Associations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can map indexed associations by adding:
|
||||
|
||||
* ``indexBy`` attribute to any ``@OneToMany`` or ``@ManyToMany`` annotation.
|
||||
* ``index-by`` attribute to any ``<one-to-many />`` or ``<many-to-many />`` xml element.
|
||||
* ``indexBy:`` key-value pair to any association defined in ``manyToMany:`` or ``oneToMany:`` YAML mapping files.
|
||||
|
||||
The code and mappings for the Market entity looks like this:
|
||||
|
||||
.. configuration-block::
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace Doctrine\Tests\Models\StockExchange;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @Table(name="exchange_markets")
|
||||
*/
|
||||
class Market
|
||||
{
|
||||
/**
|
||||
* @Id @Column(type="integer") @GeneratedValue
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @Column(type="string")
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* @OneToMany(targetEntity="Stock", mappedBy="market", indexBy="symbol")
|
||||
* @var Stock[]
|
||||
*/
|
||||
private $stocks;
|
||||
|
||||
public function __construct($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->stocks = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function addStock(Stock $stock)
|
||||
{
|
||||
$this->stocks[$stock->getSymbol()] = $stock;
|
||||
}
|
||||
|
||||
public function getStock($symbol)
|
||||
{
|
||||
if (!isset($this->stocks[$symbol])) {
|
||||
throw new \InvalidArgumentException("Symbol is not traded on this market.");
|
||||
}
|
||||
|
||||
return $this->stocks[$symbol];
|
||||
}
|
||||
|
||||
public function getStocks()
|
||||
{
|
||||
return $this->stocks->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
.. 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://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Doctrine\Tests\Models\StockExchange\Market">
|
||||
<id name="id" type="integer">
|
||||
<generator strategy="AUTO" />
|
||||
</id>
|
||||
|
||||
<field name="name" type="string"/>
|
||||
|
||||
<one-to-many target-entity="Stock" mapped-by="market" field="stocks" index-by="symbol" />
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
Doctrine\Tests\Models\StockExchange\Market:
|
||||
type: entity
|
||||
id:
|
||||
id:
|
||||
type: integer
|
||||
generator:
|
||||
strategy: AUTO
|
||||
fields:
|
||||
name:
|
||||
type:string
|
||||
oneToMany:
|
||||
stocks:
|
||||
targetEntity: Stock
|
||||
mappedBy: market
|
||||
indexBy: symbol
|
||||
|
||||
Inside the ``addStock()`` method you can see how we directly set the key of the association to the symbol,
|
||||
so that we can work with the indexed association directly after invoking ``addStock()``. Inside ``getStock($symbol)``
|
||||
we pick a stock traded on the particular market by symbol. If this stock doesn't exist an exception is thrown.
|
||||
|
||||
The ``Stock`` entity doesn't contain any special instructions that are new, but for completeness
|
||||
here are the code and mappings for it:
|
||||
|
||||
.. configuration-block::
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace Doctrine\Tests\Models\StockExchange;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @Table(name="exchange_stocks")
|
||||
*/
|
||||
class Stock
|
||||
{
|
||||
/**
|
||||
* @Id @GeneratedValue @Column(type="integer")
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* For real this column would have to be unique=true. But I want to test behavior of non-unique overrides.
|
||||
*
|
||||
* @Column(type="string", unique=true)
|
||||
*/
|
||||
private $symbol;
|
||||
|
||||
/**
|
||||
* @ManyToOne(targetEntity="Market", inversedBy="stocks")
|
||||
* @var Market
|
||||
*/
|
||||
private $market;
|
||||
|
||||
public function __construct($symbol, Market $market)
|
||||
{
|
||||
$this->symbol = $symbol;
|
||||
$this->market = $market;
|
||||
$market->addStock($this);
|
||||
}
|
||||
|
||||
public function getSymbol()
|
||||
{
|
||||
return $this->symbol;
|
||||
}
|
||||
}
|
||||
|
||||
.. 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://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Doctrine\Tests\Models\StockExchange\Stock">
|
||||
<id name="id" type="integer">
|
||||
<generator strategy="AUTO" />
|
||||
</id>
|
||||
|
||||
<field name="symbol" type="string" unique="true" />
|
||||
<many-to-one target-entity="Market" field="market" inversed-by="stocks" />
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
Doctrine\Tests\Models\StockExchange\Stock:
|
||||
type: entity
|
||||
id:
|
||||
id:
|
||||
type: integer
|
||||
generator:
|
||||
strategy: AUTO
|
||||
fields:
|
||||
symbol:
|
||||
type: string
|
||||
manyToOne:
|
||||
market:
|
||||
targetEntity: Market
|
||||
inversedBy: stocks
|
||||
|
||||
Querying indexed associations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Now that we defined the stocks collection to be indexed by symbol we can take a look at some code,
|
||||
that makes use of the indexing.
|
||||
|
||||
First we will populate our database with two example stocks traded on a single market:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// $em is the EntityManager
|
||||
|
||||
$market = new Market("Some Exchange");
|
||||
$stock1 = new Stock("AAPL", $market);
|
||||
$stock2 = new Stock("GOOG", $market);
|
||||
|
||||
$em->persist($market);
|
||||
$em->persist($stock1);
|
||||
$em->persist($stock2);
|
||||
$em->flush();
|
||||
|
||||
This code is not particular interesting since the indexing feature is not yet used. In a new request we could
|
||||
now query for the market:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// $em is the EntityManager
|
||||
$marketId = 1;
|
||||
$symbol = "AAPL";
|
||||
|
||||
$market = $em->find("Doctrine\Tests\Models\StockExchange\Market", $marketId);
|
||||
|
||||
// Access the stocks by symbol now:
|
||||
$stock = $market->getStock($symbol);
|
||||
|
||||
echo $stock->getSymbol(); // will print "AAPL"
|
||||
|
||||
The implementation ``Market::addStock()`` in combination with ``indexBy`` allows to access the collection
|
||||
consistently by the Stock symbol. It does not matter if Stock is managed by Doctrine or not.
|
||||
|
||||
The same applies to DQL queries: The ``indexBy`` configuration acts as implicit "INDEX BY" to a join association.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// $em is the EntityManager
|
||||
$marketId = 1;
|
||||
$symbol = "AAPL";
|
||||
|
||||
$dql = "SELECT m, s FROM Doctrine\Tests\Models\StockExchange\Market m JOIN m.stocks s WHERE m.id = ?1";
|
||||
$market = $em->createQuery($dql)
|
||||
->setParameter(1, $marketId)
|
||||
->getSingleResult();
|
||||
|
||||
// Access the stocks by symbol now:
|
||||
$stock = $market->getStock($symbol);
|
||||
|
||||
echo $stock->getSymbol(); // will print "AAPL"
|
||||
|
||||
If you want to use ``INDEX BY`` explicitly on an indexed association you are free to do so. Additionally
|
||||
indexed associations also work with the ``Collection::slice()`` functionality, no matter if marked as
|
||||
LAZY or EXTRA_LAZY.
|
||||
|
||||
Outlook into the Future
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
For the inverse side of a many-to-many associations there will be a way to persist the keys and the order
|
||||
as a third and fourth parameter into the join table. This feature is discussed in `DDC-213 <http://www.doctrine-project.org/jira/browse/DDC-213>`_
|
||||
This feature cannot be implemented for One-To-Many associations, because they are never the owning side.
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
<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"/>
|
||||
@@ -51,6 +52,7 @@
|
||||
<xs:enumeration value="preRemove"/>
|
||||
<xs:enumeration value="postRemove"/>
|
||||
<xs:enumeration value="postLoad"/>
|
||||
<xs:enumeration value="preFlush"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
@@ -85,8 +87,11 @@
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="named-native-query">
|
||||
<xs:sequence>
|
||||
<xs:element name="query" type="xs:string" minOccurs="1" maxOccurs="1"/>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:string" use="required" />
|
||||
<xs:attribute name="query" type="xs:string" use="required"/>
|
||||
<xs:attribute name="result-class" type="xs:string" />
|
||||
<xs:attribute name="result-set-mapping" type="xs:string" />
|
||||
</xs:complexType>
|
||||
@@ -94,7 +99,21 @@
|
||||
<xs:complexType name="named-native-queries">
|
||||
<xs:sequence>
|
||||
<xs:element name="named-native-query" type="orm:named-native-query" minOccurs="1" maxOccurs="unbounded" />
|
||||
<xs:any minOccurs="1" maxOccurs="unbounded" namespace="##other"/>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="entity-listener">
|
||||
<xs:sequence>
|
||||
<xs:element name="lifecycle-callback" type="orm:lifecycle-callback" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="class" type="xs:string"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="entity-listeners">
|
||||
<xs:sequence>
|
||||
<xs:element name="entity-listener" type="orm:entity-listener" minOccurs="1" maxOccurs="unbounded" />
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
@@ -112,20 +131,24 @@
|
||||
<xs:element name="field-result" type="orm:field-result" minOccurs="0" maxOccurs="unbounded" />
|
||||
</xs:sequence>
|
||||
<xs:attribute name="entity-class" type="xs:string" use="required" />
|
||||
<xs:attribute name="discriminator-column" type="xs:string" use="optional" />
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="sql-result-set-mapping">
|
||||
<xs:sequence>
|
||||
<xs:element name="entity-result" type="orm:entity-result" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xs:element name="column-result" type="orm:column-result" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xs:any minOccurs="1" maxOccurs="unbounded" namespace="##other"/>
|
||||
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:element name="entity-result" type="orm:entity-result"/>
|
||||
<xs:element name="column-result" type="orm:column-result"/>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:choice>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:string" use="required" />
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="sql-result-set-mappings">
|
||||
<xs:sequence>
|
||||
<xs:element name="sql-result-set-mapping" type="orm:sql-result-set-mapping" minOccurs="1" maxOccurs="unbounded" />
|
||||
<xs:any minOccurs="1" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
@@ -137,8 +160,10 @@
|
||||
<xs:element name="discriminator-column" type="orm:discriminator-column" minOccurs="0"/>
|
||||
<xs:element name="discriminator-map" type="orm:discriminator-map" minOccurs="0"/>
|
||||
<xs:element name="lifecycle-callbacks" type="orm:lifecycle-callbacks" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="entity-listeners" type="orm:entity-listeners" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="named-queries" type="orm:named-queries" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="named-native-queries" type="orm:named-native-queries" minOccurs="0" maxOccurs="1" />
|
||||
<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="one-to-one" type="orm:one-to-one" minOccurs="0" maxOccurs="unbounded"/>
|
||||
@@ -205,6 +230,7 @@
|
||||
|
||||
<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"/>
|
||||
@@ -329,6 +355,7 @@
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="type" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="column" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="length" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="association-key" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="column-definition" type="xs:string" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
@@ -363,7 +390,7 @@
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="optional" />
|
||||
<xs:attribute name="referenced-column-name" type="xs:NMTOKEN" use="optional" default="id" />
|
||||
<xs:attribute name="unique" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="nullable" type="xs:boolean" default="true" />
|
||||
@@ -488,7 +515,7 @@
|
||||
<xs:complexType name="association-overrides">
|
||||
<xs:sequence>
|
||||
<xs:element name="association-override" type="orm:association-override" minOccurs="1" maxOccurs="unbounded" />
|
||||
<xs:any minOccurs="1" maxOccurs="unbounded" namespace="##other"/>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
@@ -504,16 +531,33 @@
|
||||
<xs:complexType name="attribute-overrides">
|
||||
<xs:sequence>
|
||||
<xs:element name="attribute-override" type="orm:attribute-override" minOccurs="1" maxOccurs="unbounded" />
|
||||
<xs:any minOccurs="1" maxOccurs="unbounded" namespace="##other"/>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="attribute-override">
|
||||
<xs:sequence>
|
||||
<xs:element name="field" type="orm:field" minOccurs="1" />
|
||||
<xs:any minOccurs="1" maxOccurs="unbounded" namespace="##other"/>
|
||||
<xs:element name="field" type="orm:attribute-override-field" minOccurs="1" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="attribute-override-field">
|
||||
<xs:sequence>
|
||||
<xs:element name="options" type="orm:options" minOccurs="0" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="type" type="xs:NMTOKEN" default="string" />
|
||||
<xs:attribute name="column" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="length" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="unique" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="nullable" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="version" type="xs:boolean" />
|
||||
<xs:attribute name="column-definition" type="xs:string" />
|
||||
<xs:attribute name="precision" type="xs:integer" use="optional" />
|
||||
<xs:attribute name="scale" type="xs:integer" use="optional" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
</xs:schema>
|
||||
|
||||
@@ -26,6 +26,7 @@ use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Cache\QueryCacheProfile;
|
||||
|
||||
use Doctrine\ORM\Query\QueryException;
|
||||
use Doctrine\ORM\ORMInvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Base contract for ORM queries. Base class for Query and NativeQuery.
|
||||
@@ -41,18 +42,22 @@ use Doctrine\ORM\Query\QueryException;
|
||||
abstract class AbstractQuery
|
||||
{
|
||||
/* Hydration mode constants */
|
||||
|
||||
/**
|
||||
* Hydrates an object graph. This is the default behavior.
|
||||
*/
|
||||
const HYDRATE_OBJECT = 1;
|
||||
|
||||
/**
|
||||
* Hydrates an array graph.
|
||||
*/
|
||||
const HYDRATE_ARRAY = 2;
|
||||
|
||||
/**
|
||||
* Hydrates a flat, rectangular result set with scalar values.
|
||||
*/
|
||||
const HYDRATE_SCALAR = 3;
|
||||
|
||||
/**
|
||||
* Hydrates a single scalar value.
|
||||
*/
|
||||
@@ -64,27 +69,37 @@ abstract class AbstractQuery
|
||||
const HYDRATE_SIMPLEOBJECT = 5;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\Common\Collections\ArrayCollection The parameter map of this query.
|
||||
* The parameter map of this query.
|
||||
*
|
||||
* @var \Doctrine\Common\Collections\ArrayCollection
|
||||
*/
|
||||
protected $parameters;
|
||||
|
||||
/**
|
||||
* @var ResultSetMapping The user-specified ResultSetMapping to use.
|
||||
* The user-specified ResultSetMapping to use.
|
||||
*
|
||||
* @var \Doctrine\ORM\Query\ResultSetMapping
|
||||
*/
|
||||
protected $_resultSetMapping;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\EntityManager The entity manager used by this query object.
|
||||
* The entity manager used by this query object.
|
||||
*
|
||||
* @var \Doctrine\ORM\EntityManager
|
||||
*/
|
||||
protected $_em;
|
||||
|
||||
/**
|
||||
* @var array The map of query hints.
|
||||
* The map of query hints.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_hints = array();
|
||||
|
||||
/**
|
||||
* @var integer The hydration mode.
|
||||
* The hydration mode.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $_hydrationMode = self::HYDRATE_OBJECT;
|
||||
|
||||
@@ -94,7 +109,9 @@ abstract class AbstractQuery
|
||||
protected $_queryCacheProfile;
|
||||
|
||||
/**
|
||||
* @var boolean Boolean value that indicates whether or not expire the result cache.
|
||||
* Whether or not expire the result cache.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $_expireResultCache = false;
|
||||
|
||||
@@ -106,7 +123,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
|
||||
*
|
||||
* @param \Doctrine\ORM\EntityManager $entityManager
|
||||
* @param \Doctrine\ORM\EntityManager $em
|
||||
*/
|
||||
public function __construct(EntityManager $em)
|
||||
{
|
||||
@@ -207,11 +224,11 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Sets a query parameter.
|
||||
*
|
||||
* @param string|integer $key The parameter position or name.
|
||||
* @param mixed $value The parameter value.
|
||||
* @param string $type The parameter type. If specified, the given value will be run through
|
||||
* the type conversion of this type. This is usually not needed for
|
||||
* strings and numeric types.
|
||||
* @param string|int $key The parameter position or name.
|
||||
* @param mixed $value The parameter value.
|
||||
* @param string|null $type The parameter type. If specified, the given value will be run through
|
||||
* the type conversion of this type. This is usually not needed for
|
||||
* strings and numeric types.
|
||||
*
|
||||
* @return \Doctrine\ORM\AbstractQuery This query instance.
|
||||
*/
|
||||
@@ -240,51 +257,35 @@ abstract class AbstractQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an individual parameter value
|
||||
* Processes an individual parameter value.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws ORMInvalidArgumentException
|
||||
*/
|
||||
public function processParameterValue($value)
|
||||
{
|
||||
switch (true) {
|
||||
case is_array($value):
|
||||
foreach ($value as $key => $paramValue) {
|
||||
$paramValue = $this->processParameterValue($paramValue);
|
||||
$value[$key] = is_array($paramValue) ? $paramValue[key($paramValue)] : $paramValue;
|
||||
}
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $key => $paramValue) {
|
||||
$paramValue = $this->processParameterValue($paramValue);
|
||||
$value[$key] = is_array($paramValue) ? reset($paramValue) : $paramValue;
|
||||
}
|
||||
|
||||
return $value;
|
||||
|
||||
case is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value)):
|
||||
return $this->convertObjectParameterToScalarValue($value);
|
||||
|
||||
default:
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
private function convertObjectParameterToScalarValue($value)
|
||||
{
|
||||
$class = $this->_em->getClassMetadata(get_class($value));
|
||||
|
||||
if ($class->isIdentifierComposite) {
|
||||
throw new \InvalidArgumentException(
|
||||
"Binding an entity with a composite primary key to a query is not supported. " .
|
||||
"You should split the parameter into the explicit fields and bind them seperately."
|
||||
);
|
||||
return $value;
|
||||
}
|
||||
|
||||
$values = ($this->_em->getUnitOfWork()->getEntityState($value) === UnitOfWork::STATE_MANAGED)
|
||||
? $this->_em->getUnitOfWork()->getEntityIdentifier($value)
|
||||
: $class->getIdentifierValues($value);
|
||||
if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value))) {
|
||||
$value = $this->_em->getUnitOfWork()->getSingleIdentifierValue($value);
|
||||
|
||||
$value = $values[$class->getSingleIdentifierFieldName()];
|
||||
if ($value === null) {
|
||||
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $value) {
|
||||
throw new \InvalidArgumentException(
|
||||
"Binding entities to query parameters only allowed for entities that have an identifier."
|
||||
);
|
||||
if ($value instanceof Mapping\ClassMetadata) {
|
||||
return $value->name;
|
||||
}
|
||||
|
||||
return $value;
|
||||
@@ -293,16 +294,37 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Sets the ResultSetMapping that should be used for hydration.
|
||||
*
|
||||
* @param ResultSetMapping $rsm
|
||||
* @param \Doctrine\ORM\Query\ResultSetMapping $rsm
|
||||
*
|
||||
* @return \Doctrine\ORM\AbstractQuery
|
||||
*/
|
||||
public function setResultSetMapping(Query\ResultSetMapping $rsm)
|
||||
{
|
||||
$this->translateNamespaces($rsm);
|
||||
$this->_resultSetMapping = $rsm;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to translate entity namespaces to full qualified names.
|
||||
*
|
||||
* @param Query\ResultSetMapping $rsm
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function translateNamespaces(Query\ResultSetMapping $rsm)
|
||||
{
|
||||
$entityManager = $this->_em;
|
||||
|
||||
$translate = function ($alias) use ($entityManager) {
|
||||
return $entityManager->getClassMetadata($alias)->getName();
|
||||
};
|
||||
|
||||
$rsm->aliasMap = array_map($translate, $rsm->aliasMap);
|
||||
$rsm->declaringClasses = array_map($translate, $rsm->declaringClasses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a cache profile for hydration caching.
|
||||
*
|
||||
@@ -322,6 +344,7 @@ abstract class AbstractQuery
|
||||
* $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey));
|
||||
*
|
||||
* @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
|
||||
*
|
||||
* @return \Doctrine\ORM\AbstractQuery
|
||||
*/
|
||||
public function setHydrationCacheProfile(QueryCacheProfile $profile = null)
|
||||
@@ -351,6 +374,7 @@ abstract class AbstractQuery
|
||||
* result cache driver is used from the configuration.
|
||||
*
|
||||
* @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
|
||||
*
|
||||
* @return \Doctrine\ORM\AbstractQuery
|
||||
*/
|
||||
public function setResultCacheProfile(QueryCacheProfile $profile = null)
|
||||
@@ -366,10 +390,13 @@ abstract class AbstractQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a cache driver to be used for caching result sets and implictly enables caching.
|
||||
* Defines a cache driver to be used for caching result sets and implicitly enables caching.
|
||||
*
|
||||
* @param \Doctrine\Common\Cache\Cache|null $resultCacheDriver Cache driver
|
||||
*
|
||||
* @param \Doctrine\Common\Cache\Cache $driver Cache driver
|
||||
* @return \Doctrine\ORM\AbstractQuery
|
||||
*
|
||||
* @throws ORMException
|
||||
*/
|
||||
public function setResultCacheDriver($resultCacheDriver = null)
|
||||
{
|
||||
@@ -388,6 +415,7 @@ abstract class AbstractQuery
|
||||
* Returns the cache driver used for caching result sets.
|
||||
*
|
||||
* @deprecated
|
||||
*
|
||||
* @return \Doctrine\Common\Cache\Cache Cache driver
|
||||
*/
|
||||
public function getResultCacheDriver()
|
||||
@@ -405,7 +433,8 @@ abstract class AbstractQuery
|
||||
*
|
||||
* @param boolean $bool
|
||||
* @param integer $lifetime
|
||||
* @param string $resultCacheId
|
||||
* @param string $resultCacheId
|
||||
*
|
||||
* @return \Doctrine\ORM\AbstractQuery This query instance.
|
||||
*/
|
||||
public function useResultCache($bool, $lifetime = null, $resultCacheId = null)
|
||||
@@ -426,6 +455,7 @@ abstract class AbstractQuery
|
||||
* Defines how long the result cache will be active before expire.
|
||||
*
|
||||
* @param integer $lifetime How long the cache entry is valid.
|
||||
*
|
||||
* @return \Doctrine\ORM\AbstractQuery This query instance.
|
||||
*/
|
||||
public function setResultCacheLifetime($lifetime)
|
||||
@@ -443,6 +473,7 @@ abstract class AbstractQuery
|
||||
* Retrieves the lifetime of resultset cache.
|
||||
*
|
||||
* @deprecated
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getResultCacheLifetime()
|
||||
@@ -454,6 +485,7 @@ abstract class AbstractQuery
|
||||
* Defines if the result cache is active or not.
|
||||
*
|
||||
* @param boolean $expire Whether or not to force resultset cache expiration.
|
||||
*
|
||||
* @return \Doctrine\ORM\AbstractQuery This query instance.
|
||||
*/
|
||||
public function expireResultCache($expire = true)
|
||||
@@ -486,9 +518,10 @@ abstract class AbstractQuery
|
||||
*
|
||||
* $fetchMode can be one of ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY
|
||||
*
|
||||
* @param string $class
|
||||
* @param string $assocName
|
||||
* @param int $fetchMode
|
||||
* @param string $class
|
||||
* @param string $assocName
|
||||
* @param int $fetchMode
|
||||
*
|
||||
* @return AbstractQuery
|
||||
*/
|
||||
public function setFetchMode($class, $assocName, $fetchMode)
|
||||
@@ -507,6 +540,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.
|
||||
*/
|
||||
public function setHydrationMode($hydrationMode)
|
||||
@@ -531,6 +565,8 @@ abstract class AbstractQuery
|
||||
*
|
||||
* Alias for execute(null, $hydrationMode = HYDRATE_OBJECT).
|
||||
*
|
||||
* @param int $hydrationMode
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getResult($hydrationMode = self::HYDRATE_OBJECT)
|
||||
@@ -565,9 +601,11 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Get exactly one result or null.
|
||||
*
|
||||
* @throws NonUniqueResultException
|
||||
* @param int $hydrationMode
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws NonUniqueResultException
|
||||
*/
|
||||
public function getOneOrNullResult($hydrationMode = null)
|
||||
{
|
||||
@@ -597,9 +635,11 @@ abstract class AbstractQuery
|
||||
* If there is no result, a NoResultException is thrown.
|
||||
*
|
||||
* @param integer $hydrationMode
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws NonUniqueResultException If the query result is not unique.
|
||||
* @throws NoResultException If the query returned no result.
|
||||
* @throws NoResultException If the query returned no result.
|
||||
*/
|
||||
public function getSingleResult($hydrationMode = null)
|
||||
{
|
||||
@@ -626,6 +666,7 @@ abstract class AbstractQuery
|
||||
* Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws QueryException If the query result is not unique.
|
||||
*/
|
||||
public function getSingleScalarResult()
|
||||
@@ -636,8 +677,9 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Sets a query hint. If the hint name is not recognized, it is silently ignored.
|
||||
*
|
||||
* @param string $name The name of the hint.
|
||||
* @param mixed $value The value of the hint.
|
||||
* @param string $name The name of the hint.
|
||||
* @param mixed $value The value of the hint.
|
||||
*
|
||||
* @return \Doctrine\ORM\AbstractQuery
|
||||
*/
|
||||
public function setHint($name, $value)
|
||||
@@ -651,6 +693,7 @@ abstract class AbstractQuery
|
||||
* Gets the value of a query hint. If the hint name is not recognized, FALSE is returned.
|
||||
*
|
||||
* @param string $name The name of the hint.
|
||||
*
|
||||
* @return mixed The value of the hint or FALSE, if the hint name is not recognized.
|
||||
*/
|
||||
public function getHint($name)
|
||||
@@ -658,6 +701,18 @@ abstract class AbstractQuery
|
||||
return isset($this->_hints[$name]) ? $this->_hints[$name] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the query has a hint
|
||||
*
|
||||
* @param string $name The name of the hint
|
||||
*
|
||||
* @return bool False if the query does not have any hint
|
||||
*/
|
||||
public function hasHint($name)
|
||||
{
|
||||
return isset($this->_hints[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key value map of query hints that are currently set.
|
||||
*
|
||||
@@ -672,8 +727,9 @@ abstract class AbstractQuery
|
||||
* Executes the query and returns an IterableResult that can be used to incrementally
|
||||
* iterate over the result.
|
||||
*
|
||||
* @param \Doctrine\Common\Collections\ArrayCollection|array $parameters The query parameters.
|
||||
* @param integer $hydrationMode The hydration mode to use.
|
||||
* @param ArrayCollection|array|null $parameters The query parameters.
|
||||
* @param integer|null $hydrationMode The hydration mode to use.
|
||||
*
|
||||
* @return \Doctrine\ORM\Internal\Hydration\IterableResult
|
||||
*/
|
||||
public function iterate($parameters = null, $hydrationMode = null)
|
||||
@@ -696,8 +752,9 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Executes the query.
|
||||
*
|
||||
* @param \Doctrine\Common\Collections\ArrayCollection|array $parameters Query parameters.
|
||||
* @param integer $hydrationMode Processing mode to be used during the hydration process.
|
||||
* @param ArrayCollection|array|null $parameters Query parameters.
|
||||
* @param integer|null $hydrationMode Processing mode to be used during the hydration process.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function execute($parameters = null, $hydrationMode = null)
|
||||
@@ -742,7 +799,7 @@ abstract class AbstractQuery
|
||||
return $stmt;
|
||||
}
|
||||
|
||||
$data = $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
|
||||
$data = $this->_em->newHydrator($this->_hydrationMode)->hydrateAll(
|
||||
$stmt, $this->_resultSetMapping, $this->_hints
|
||||
);
|
||||
|
||||
@@ -782,6 +839,7 @@ abstract class AbstractQuery
|
||||
* generated for you.
|
||||
*
|
||||
* @param string $id
|
||||
*
|
||||
* @return \Doctrine\ORM\AbstractQuery This query instance.
|
||||
*/
|
||||
public function setResultCacheId($id)
|
||||
@@ -797,6 +855,7 @@ abstract class AbstractQuery
|
||||
* Get the result cache id to use to store the result set cache entry if set.
|
||||
*
|
||||
* @deprecated
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getResultCacheId()
|
||||
|
||||
@@ -19,18 +19,22 @@
|
||||
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use Doctrine\Common\Cache\Cache,
|
||||
Doctrine\Common\Cache\ArrayCache,
|
||||
Doctrine\Common\Annotations\AnnotationRegistry,
|
||||
Doctrine\Common\Annotations\AnnotationReader,
|
||||
Doctrine\Common\Persistence\Mapping\Driver\MappingDriver,
|
||||
Doctrine\ORM\Mapping\Driver\AnnotationDriver,
|
||||
Doctrine\ORM\Mapping\QuoteStrategy,
|
||||
Doctrine\ORM\Mapping\DefaultQuoteStrategy,
|
||||
Doctrine\ORM\Mapping\NamingStrategy,
|
||||
Doctrine\ORM\Mapping\DefaultNamingStrategy,
|
||||
Doctrine\Common\Annotations\SimpleAnnotationReader,
|
||||
Doctrine\Common\Annotations\CachedReader;
|
||||
use Doctrine\Common\Annotations\AnnotationReader;
|
||||
use Doctrine\Common\Annotations\AnnotationRegistry;
|
||||
use Doctrine\Common\Annotations\CachedReader;
|
||||
use Doctrine\Common\Annotations\SimpleAnnotationReader;
|
||||
use Doctrine\Common\Cache\ArrayCache;
|
||||
use Doctrine\Common\Cache\Cache;
|
||||
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;
|
||||
use Doctrine\ORM\Mapping\DefaultEntityListenerResolver;
|
||||
use Doctrine\ORM\Mapping\DefaultNamingStrategy;
|
||||
use Doctrine\ORM\Mapping\DefaultQuoteStrategy;
|
||||
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
|
||||
use Doctrine\ORM\Mapping\EntityListenerResolver;
|
||||
use Doctrine\ORM\Mapping\NamingStrategy;
|
||||
use Doctrine\ORM\Mapping\QuoteStrategy;
|
||||
use Doctrine\ORM\Repository\DefaultRepositoryFactory;
|
||||
use Doctrine\ORM\Repository\RepositoryFactory;
|
||||
|
||||
/**
|
||||
* Configuration container for all configuration options of Doctrine.
|
||||
@@ -49,6 +53,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Sets the directory where Doctrine generates any necessary proxy class files.
|
||||
*
|
||||
* @param string $dir
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setProxyDir($dir)
|
||||
{
|
||||
@@ -58,7 +64,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
/**
|
||||
* Gets the directory where Doctrine generates any necessary proxy class files.
|
||||
*
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
public function getProxyDir()
|
||||
{
|
||||
@@ -85,6 +91,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* during each script execution.
|
||||
*
|
||||
* @param boolean $bool
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setAutoGenerateProxyClasses($bool)
|
||||
{
|
||||
@@ -94,7 +102,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
/**
|
||||
* Gets the namespace where proxy classes reside.
|
||||
*
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
public function getProxyNamespace()
|
||||
{
|
||||
@@ -107,6 +115,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Sets the namespace where proxy classes reside.
|
||||
*
|
||||
* @param string $ns
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setProxyNamespace($ns)
|
||||
{
|
||||
@@ -117,6 +127,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Sets the cache driver implementation that is used for metadata caching.
|
||||
*
|
||||
* @param MappingDriver $driverImpl
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @todo Force parameter to be a Closure to ensure lazy evaluation
|
||||
* (as soon as a metadata cache is in effect, the driver never needs to initialize).
|
||||
*/
|
||||
@@ -126,11 +139,12 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new default annotation driver with a correctly configured annotation reader. If $useSimpleAnnotationReader
|
||||
* Adds a new default annotation driver with a correctly configured annotation reader. If $useSimpleAnnotationReader
|
||||
* is true, the notation `@Entity` will work, otherwise, the notation `@ORM\Entity` will be supported.
|
||||
*
|
||||
* @param array $paths
|
||||
* @param bool $useSimpleAnnotationReader
|
||||
* @param bool $useSimpleAnnotationReader
|
||||
*
|
||||
* @return AnnotationDriver
|
||||
*/
|
||||
public function newDefaultAnnotationDriver($paths = array(), $useSimpleAnnotationReader = true)
|
||||
@@ -157,6 +171,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*
|
||||
* @param string $alias
|
||||
* @param string $namespace
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addEntityNamespace($alias, $namespace)
|
||||
{
|
||||
@@ -167,8 +183,10 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Resolves a registered namespace alias to the full namespace.
|
||||
*
|
||||
* @param string $entityNamespaceAlias
|
||||
* @throws ORMException
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws ORMException
|
||||
*/
|
||||
public function getEntityNamespace($entityNamespaceAlias)
|
||||
{
|
||||
@@ -180,9 +198,11 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the entity alias map
|
||||
* Sets the entity alias map.
|
||||
*
|
||||
* @param array $entityNamespaces
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setEntityNamespaces(array $entityNamespaces)
|
||||
{
|
||||
@@ -202,8 +222,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
/**
|
||||
* Gets the cache driver implementation that is used for the mapping metadata.
|
||||
*
|
||||
* @return MappingDriver|null
|
||||
*
|
||||
* @throws ORMException
|
||||
* @return MappingDriver
|
||||
*/
|
||||
public function getMetadataDriverImpl()
|
||||
{
|
||||
@@ -215,7 +236,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
/**
|
||||
* Gets the cache driver implementation that is used for the query cache (SQL cache).
|
||||
*
|
||||
* @return \Doctrine\Common\Cache\Cache
|
||||
* @return \Doctrine\Common\Cache\Cache|null
|
||||
*/
|
||||
public function getQueryCacheImpl()
|
||||
{
|
||||
@@ -228,6 +249,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Sets the cache driver implementation that is used for the query cache (SQL cache).
|
||||
*
|
||||
* @param \Doctrine\Common\Cache\Cache $cacheImpl
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setQueryCacheImpl(Cache $cacheImpl)
|
||||
{
|
||||
@@ -237,7 +260,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
/**
|
||||
* Gets the cache driver implementation that is used for the hydration cache (SQL cache).
|
||||
*
|
||||
* @return \Doctrine\Common\Cache\Cache
|
||||
* @return \Doctrine\Common\Cache\Cache|null
|
||||
*/
|
||||
public function getHydrationCacheImpl()
|
||||
{
|
||||
@@ -250,6 +273,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Sets the cache driver implementation that is used for the hydration cache (SQL cache).
|
||||
*
|
||||
* @param \Doctrine\Common\Cache\Cache $cacheImpl
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setHydrationCacheImpl(Cache $cacheImpl)
|
||||
{
|
||||
@@ -259,7 +284,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
/**
|
||||
* Gets the cache driver implementation that is used for metadata caching.
|
||||
*
|
||||
* @return \Doctrine\Common\Cache\Cache
|
||||
* @return \Doctrine\Common\Cache\Cache|null
|
||||
*/
|
||||
public function getMetadataCacheImpl()
|
||||
{
|
||||
@@ -272,6 +297,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Sets the cache driver implementation that is used for metadata caching.
|
||||
*
|
||||
* @param \Doctrine\Common\Cache\Cache $cacheImpl
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setMetadataCacheImpl(Cache $cacheImpl)
|
||||
{
|
||||
@@ -282,7 +309,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Adds a named DQL query to the configuration.
|
||||
*
|
||||
* @param string $name The name of the query.
|
||||
* @param string $dql The DQL query string.
|
||||
* @param string $dql The DQL query string.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addNamedQuery($name, $dql)
|
||||
{
|
||||
@@ -293,8 +322,10 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Gets a previously registered named DQL query.
|
||||
*
|
||||
* @param string $name The name of the query.
|
||||
* @throws ORMException
|
||||
*
|
||||
* @return string The DQL query.
|
||||
*
|
||||
* @throws ORMException
|
||||
*/
|
||||
public function getNamedQuery($name)
|
||||
{
|
||||
@@ -311,6 +342,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* @param string $name The name of the query.
|
||||
* @param string $sql The native SQL query string.
|
||||
* @param Query\ResultSetMapping $rsm The ResultSetMapping used for the results of the SQL query.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addNamedNativeQuery($name, $sql, Query\ResultSetMapping $rsm)
|
||||
{
|
||||
@@ -320,10 +353,12 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
/**
|
||||
* Gets the components of a previously registered named native query.
|
||||
*
|
||||
* @param string $name The name of the query.
|
||||
* @param string $name The name of the query.
|
||||
*
|
||||
* @return array A tuple with the first element being the SQL string and the second
|
||||
* element being the ResultSetMapping.
|
||||
*
|
||||
* @throws ORMException
|
||||
* @return array A tuple with the first element being the SQL string and the second
|
||||
* element being the ResultSetMapping.
|
||||
*/
|
||||
public function getNamedNativeQuery($name)
|
||||
{
|
||||
@@ -338,6 +373,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Ensures that this Configuration instance contains settings that are
|
||||
* suitable for a production environment.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMException If a configuration setting has a value that is not
|
||||
* suitable for a production environment.
|
||||
*/
|
||||
@@ -365,6 +402,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $className
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMException
|
||||
*/
|
||||
public function addCustomStringFunction($name, $className)
|
||||
@@ -380,7 +420,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Gets the implementation class name of a registered custom string DQL function.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getCustomStringFunction($name)
|
||||
{
|
||||
@@ -400,6 +441,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Any previously added string functions are discarded.
|
||||
*
|
||||
* @param array $functions The map of custom DQL string functions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setCustomStringFunctions(array $functions)
|
||||
{
|
||||
@@ -417,6 +460,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $className
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMException
|
||||
*/
|
||||
public function addCustomNumericFunction($name, $className)
|
||||
@@ -432,7 +478,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Gets the implementation class name of a registered custom numeric DQL function.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getCustomNumericFunction($name)
|
||||
{
|
||||
@@ -452,6 +499,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Any previously added numeric functions are discarded.
|
||||
*
|
||||
* @param array $functions The map of custom DQL numeric functions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setCustomNumericFunctions(array $functions)
|
||||
{
|
||||
@@ -469,6 +518,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $className
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMException
|
||||
*/
|
||||
public function addCustomDatetimeFunction($name, $className)
|
||||
@@ -484,7 +536,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Gets the implementation class name of a registered custom date/time DQL function.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getCustomDatetimeFunction($name)
|
||||
{
|
||||
@@ -504,6 +557,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Any previously added date/time functions are discarded.
|
||||
*
|
||||
* @param array $functions The map of custom DQL date/time functions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setCustomDatetimeFunctions(array $functions)
|
||||
{
|
||||
@@ -513,9 +568,11 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the custom hydrator modes in one pass.
|
||||
* Sets the custom hydrator modes in one pass.
|
||||
*
|
||||
* @param array An array of ($modeName => $hydrator)
|
||||
* @param array $modes An array of ($modeName => $hydrator).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setCustomHydrationModes($modes)
|
||||
{
|
||||
@@ -527,10 +584,11 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hydrator class for the given hydration mode name.
|
||||
* Gets the hydrator class for the given hydration mode name.
|
||||
*
|
||||
* @param string $modeName The hydration mode name.
|
||||
* @return string $hydrator The hydrator class name.
|
||||
*
|
||||
* @return string|null The hydrator class name.
|
||||
*/
|
||||
public function getCustomHydrationMode($modeName)
|
||||
{
|
||||
@@ -540,10 +598,12 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a custom hydration mode.
|
||||
* Adds a custom hydration mode.
|
||||
*
|
||||
* @param string $modeName The hydration mode name.
|
||||
* @param string $hydrator The hydrator class name.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addCustomHydrationMode($modeName, $hydrator)
|
||||
{
|
||||
@@ -551,9 +611,11 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a class metadata factory.
|
||||
* Sets a class metadata factory.
|
||||
*
|
||||
* @param string $cmfName
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setClassMetadataFactoryName($cmfName)
|
||||
{
|
||||
@@ -573,9 +635,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a filter to the list of possible filters.
|
||||
* Adds a filter to the list of possible filters.
|
||||
*
|
||||
* @param string $name The name of the filter.
|
||||
* @param string $name The name of the filter.
|
||||
* @param string $className The class name of the filter.
|
||||
*/
|
||||
public function addFilter($name, $className)
|
||||
@@ -599,10 +661,14 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default repository class.
|
||||
* Sets default repository class.
|
||||
*
|
||||
* @since 2.2
|
||||
*
|
||||
* @param string $className
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMException If not is a \Doctrine\Common\Persistence\ObjectRepository
|
||||
*/
|
||||
public function setDefaultRepositoryClassName($className)
|
||||
@@ -620,6 +686,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Get default repository class.
|
||||
*
|
||||
* @since 2.2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDefaultRepositoryClassName()
|
||||
@@ -630,10 +697,13 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
}
|
||||
|
||||
/**
|
||||
* Set naming strategy.
|
||||
* Sets naming strategy.
|
||||
*
|
||||
* @since 2.3
|
||||
*
|
||||
* @param NamingStrategy $namingStrategy
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setNamingStrategy(NamingStrategy $namingStrategy)
|
||||
{
|
||||
@@ -641,9 +711,10 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
}
|
||||
|
||||
/**
|
||||
* Get naming strategy..
|
||||
* Gets naming strategy..
|
||||
*
|
||||
* @since 2.3
|
||||
*
|
||||
* @return NamingStrategy
|
||||
*/
|
||||
public function getNamingStrategy()
|
||||
@@ -656,10 +727,13 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
}
|
||||
|
||||
/**
|
||||
* Set quote strategy.
|
||||
* Sets quote strategy.
|
||||
*
|
||||
* @since 2.3
|
||||
* @param Doctrine\ORM\Mapping\QuoteStrategy $quoteStrategy
|
||||
*
|
||||
* @param \Doctrine\ORM\Mapping\QuoteStrategy $quoteStrategy
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setQuoteStrategy(QuoteStrategy $quoteStrategy)
|
||||
{
|
||||
@@ -667,10 +741,11 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
}
|
||||
|
||||
/**
|
||||
* Get quote strategy.
|
||||
* Gets quote strategy.
|
||||
*
|
||||
* @since 2.3
|
||||
* @return Doctrine\ORM\Mapping\QuoteStrategy
|
||||
*
|
||||
* @return \Doctrine\ORM\Mapping\QuoteStrategy
|
||||
*/
|
||||
public function getQuoteStrategy()
|
||||
{
|
||||
@@ -680,4 +755,54 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
|
||||
return $this->_attributes['quoteStrategy'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the entity listener resolver.
|
||||
*
|
||||
* @since 2.4
|
||||
* @param \Doctrine\ORM\Mapping\EntityListenerResolver $resolver
|
||||
*/
|
||||
public function setEntityListenerResolver(EntityListenerResolver $resolver)
|
||||
{
|
||||
$this->_attributes['entityListenerResolver'] = $resolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity listener resolver.
|
||||
*
|
||||
* @since 2.4
|
||||
* @return \Doctrine\ORM\Mapping\EntityListenerResolver
|
||||
*/
|
||||
public function getEntityListenerResolver()
|
||||
{
|
||||
if ( ! isset($this->_attributes['entityListenerResolver'])) {
|
||||
$this->_attributes['entityListenerResolver'] = new DefaultEntityListenerResolver();
|
||||
}
|
||||
|
||||
return $this->_attributes['entityListenerResolver'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the entity repository factory.
|
||||
*
|
||||
* @since 2.4
|
||||
* @param \Doctrine\ORM\Repository\RepositoryFactory $repositoryFactory
|
||||
*/
|
||||
public function setRepositoryFactory(RepositoryFactory $repositoryFactory)
|
||||
{
|
||||
$this->_attributes['repositoryFactory'] = $repositoryFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity repository factory.
|
||||
*
|
||||
* @since 2.4
|
||||
* @return \Doctrine\ORM\Repository\RepositoryFactory
|
||||
*/
|
||||
public function getRepositoryFactory()
|
||||
{
|
||||
return isset($this->_attributes['repositoryFactory'])
|
||||
? $this->_attributes['repositoryFactory']
|
||||
: new DefaultRepositoryFactory();
|
||||
}
|
||||
}
|
||||
|
||||
271
lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php
Normal file
271
lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php
Normal file
@@ -0,0 +1,271 @@
|
||||
<?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\Decorator;
|
||||
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\Common\Persistence\ObjectManagerDecorator;
|
||||
|
||||
/**
|
||||
* Base class for EntityManager decorators
|
||||
*
|
||||
* @since 2.4
|
||||
* @author Lars Strojny <lars@strojny.net
|
||||
*/
|
||||
abstract class EntityManagerDecorator extends ObjectManagerDecorator implements EntityManagerInterface
|
||||
{
|
||||
/**
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
protected $wrapped;
|
||||
|
||||
/**
|
||||
* @param EntityManagerInterface $wrapped
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $wrapped)
|
||||
{
|
||||
$this->wrapped = $wrapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConnection()
|
||||
{
|
||||
return $this->wrapped->getConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getExpressionBuilder()
|
||||
{
|
||||
return $this->wrapped->getExpressionBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function beginTransaction()
|
||||
{
|
||||
return $this->wrapped->beginTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transactional($func)
|
||||
{
|
||||
return $this->wrapped->transactional($func);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function commit()
|
||||
{
|
||||
return $this->wrapped->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rollback()
|
||||
{
|
||||
return $this->wrapped->rollback();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createQuery($dql = '')
|
||||
{
|
||||
return $this->wrapped->createQuery($dql);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createNamedQuery($name)
|
||||
{
|
||||
return $this->wrapped->createNamedQuery($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createNativeQuery($sql, ResultSetMapping $rsm)
|
||||
{
|
||||
return $this->wrapped->createNativeQuery($sql, $rsm);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createNamedNativeQuery($name)
|
||||
{
|
||||
return $this->wrapped->createNamedNativeQuery($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createQueryBuilder()
|
||||
{
|
||||
return $this->wrapped->createQueryBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getReference($entityName, $id)
|
||||
{
|
||||
return $this->wrapped->getReference($entityName, $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPartialReference($entityName, $identifier)
|
||||
{
|
||||
return $this->wrapped->getPartialReference($entityName, $identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
return $this->wrapped->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function copy($entity, $deep = false)
|
||||
{
|
||||
return $this->wrapped->copy($entity, $deep);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lock($entity, $lockMode, $lockVersion = null)
|
||||
{
|
||||
return $this->wrapped->lock($entity, $lockMode, $lockVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function find($entityName, $id, $lockMode = LockMode::NONE, $lockVersion = null)
|
||||
{
|
||||
return $this->wrapped->find($entityName, $id, $lockMode, $lockVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function flush($entity = null)
|
||||
{
|
||||
return $this->wrapped->flush($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEventManager()
|
||||
{
|
||||
return $this->wrapped->getEventManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfiguration()
|
||||
{
|
||||
return $this->wrapped->getConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isOpen()
|
||||
{
|
||||
return $this->wrapped->isOpen();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUnitOfWork()
|
||||
{
|
||||
return $this->wrapped->getUnitOfWork();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHydrator($hydrationMode)
|
||||
{
|
||||
return $this->wrapped->getHydrator($hydrationMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function newHydrator($hydrationMode)
|
||||
{
|
||||
return $this->wrapped->newHydrator($hydrationMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getProxyFactory()
|
||||
{
|
||||
return $this->wrapped->getProxyFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFilters()
|
||||
{
|
||||
return $this->wrapped->getFilters();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isFiltersStateClean()
|
||||
{
|
||||
return $this->wrapped->isFiltersStateClean();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasFilters()
|
||||
{
|
||||
return $this->wrapped->hasFilters();
|
||||
}
|
||||
}
|
||||
@@ -19,27 +19,50 @@
|
||||
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use Exception,
|
||||
Doctrine\Common\EventManager,
|
||||
Doctrine\Common\Persistence\ObjectManager,
|
||||
Doctrine\DBAL\Connection,
|
||||
Doctrine\DBAL\LockMode,
|
||||
Doctrine\ORM\Mapping\ClassMetadata,
|
||||
Doctrine\ORM\Mapping\ClassMetadataFactory,
|
||||
Doctrine\ORM\Query\ResultSetMapping,
|
||||
Doctrine\ORM\Proxy\ProxyFactory,
|
||||
Doctrine\ORM\Query\FilterCollection;
|
||||
use Exception;
|
||||
use Doctrine\Common\EventManager;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\ClassMetadataFactory;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\ORM\Proxy\ProxyFactory;
|
||||
use Doctrine\ORM\Query\FilterCollection;
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
|
||||
/**
|
||||
* The EntityManager is the central access point to ORM functionality.
|
||||
*
|
||||
* It is a facade to all different ORM subsystems such as UnitOfWork,
|
||||
* Query Language and Repository API. Instantiation is done through
|
||||
* the static create() method. The quickest way to obtain a fully
|
||||
* configured EntityManager is:
|
||||
*
|
||||
* use Doctrine\ORM\Tools\Setup;
|
||||
* use Doctrine\ORM\EntityManager;
|
||||
*
|
||||
* $paths = array('/path/to/entity/mapping/files');
|
||||
*
|
||||
* $config = Setup::createAnnotationMetadataConfiguration($paths);
|
||||
* $dbParams = array('driver' => 'pdo_sqlite', 'memory' => true);
|
||||
* $entityManager = EntityManager::create($dbParams, $config);
|
||||
*
|
||||
* For more information see
|
||||
* {@link http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/configuration.html}
|
||||
*
|
||||
* You should never attempt to inherit from the EntityManager: Inheritance
|
||||
* is not a valid extension point for the EntityManager. Instead you
|
||||
* should take a look at the {@see \Doctrine\ORM\Decorator\EntityManagerDecorator}
|
||||
* and wrap your entity manager in a decorator.
|
||||
*
|
||||
* @since 2.0
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @author Jonathan Wage <jonwage@gmail.com>
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
*/
|
||||
class EntityManager implements ObjectManager
|
||||
/* final */class EntityManager implements EntityManagerInterface
|
||||
{
|
||||
/**
|
||||
* The used Configuration.
|
||||
@@ -62,13 +85,6 @@ class EntityManager implements ObjectManager
|
||||
*/
|
||||
private $metadataFactory;
|
||||
|
||||
/**
|
||||
* The EntityRepository instances.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $repositories = array();
|
||||
|
||||
/**
|
||||
* The UnitOfWork used to coordinate object-level transactions.
|
||||
*
|
||||
@@ -83,13 +99,6 @@ class EntityManager implements ObjectManager
|
||||
*/
|
||||
private $eventManager;
|
||||
|
||||
/**
|
||||
* The maintained (cached) hydrators. One instance per type.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $hydrators = array();
|
||||
|
||||
/**
|
||||
* The proxy factory used to create dynamic proxies.
|
||||
*
|
||||
@@ -97,6 +106,13 @@ class EntityManager implements ObjectManager
|
||||
*/
|
||||
private $proxyFactory;
|
||||
|
||||
/**
|
||||
* The repository factory used to create dynamic repositories.
|
||||
*
|
||||
* @var \Doctrine\ORM\Repository\RepositoryFactory
|
||||
*/
|
||||
private $repositoryFactory;
|
||||
|
||||
/**
|
||||
* The expression builder instance used to generate query expressions.
|
||||
*
|
||||
@@ -114,7 +130,7 @@ class EntityManager implements ObjectManager
|
||||
/**
|
||||
* Collection of query filters.
|
||||
*
|
||||
* @var Doctrine\ORM\Query\FilterCollection
|
||||
* @var \Doctrine\ORM\Query\FilterCollection
|
||||
*/
|
||||
private $filterCollection;
|
||||
|
||||
@@ -122,15 +138,15 @@ class EntityManager implements ObjectManager
|
||||
* Creates a new EntityManager that operates on the given database connection
|
||||
* and uses the given Configuration and EventManager implementations.
|
||||
*
|
||||
* @param \Doctrine\DBAL\Connection $conn
|
||||
* @param \Doctrine\ORM\Configuration $config
|
||||
* @param \Doctrine\DBAL\Connection $conn
|
||||
* @param \Doctrine\ORM\Configuration $config
|
||||
* @param \Doctrine\Common\EventManager $eventManager
|
||||
*/
|
||||
protected function __construct(Connection $conn, Configuration $config, EventManager $eventManager)
|
||||
{
|
||||
$this->conn = $conn;
|
||||
$this->config = $config;
|
||||
$this->eventManager = $eventManager;
|
||||
$this->conn = $conn;
|
||||
$this->config = $config;
|
||||
$this->eventManager = $eventManager;
|
||||
|
||||
$metadataFactoryClassName = $config->getClassMetadataFactoryName();
|
||||
|
||||
@@ -138,8 +154,9 @@ class EntityManager implements ObjectManager
|
||||
$this->metadataFactory->setEntityManager($this);
|
||||
$this->metadataFactory->setCacheDriver($this->config->getMetadataCacheImpl());
|
||||
|
||||
$this->unitOfWork = new UnitOfWork($this);
|
||||
$this->proxyFactory = new ProxyFactory(
|
||||
$this->repositoryFactory = $config->getRepositoryFactory();
|
||||
$this->unitOfWork = new UnitOfWork($this);
|
||||
$this->proxyFactory = new ProxyFactory(
|
||||
$this,
|
||||
$config->getProxyDir(),
|
||||
$config->getProxyNamespace(),
|
||||
@@ -192,6 +209,8 @@ class EntityManager implements ObjectManager
|
||||
|
||||
/**
|
||||
* Starts a transaction on the underlying database connection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function beginTransaction()
|
||||
{
|
||||
@@ -209,7 +228,8 @@ class EntityManager implements ObjectManager
|
||||
* the transaction is rolled back, the EntityManager closed and the exception re-thrown.
|
||||
*
|
||||
* @param callable $func The function to execute transactionally.
|
||||
* @return mixed Returns the non-empty value returned from the closure or true instead
|
||||
*
|
||||
* @return mixed The non-empty value returned from the closure or true instead.
|
||||
*/
|
||||
public function transactional($func)
|
||||
{
|
||||
@@ -236,6 +256,8 @@ class EntityManager implements ObjectManager
|
||||
|
||||
/**
|
||||
* Commits a transaction on the underlying database connection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function commit()
|
||||
{
|
||||
@@ -244,6 +266,8 @@ class EntityManager implements ObjectManager
|
||||
|
||||
/**
|
||||
* Performs a rollback on the underlying database connection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function rollback()
|
||||
{
|
||||
@@ -260,7 +284,10 @@ class EntityManager implements ObjectManager
|
||||
* MyProject\Domain\User
|
||||
* sales:PriceRequest
|
||||
*
|
||||
* @param string $className
|
||||
*
|
||||
* @return \Doctrine\ORM\Mapping\ClassMetadata
|
||||
*
|
||||
* @internal Performance-sensitive method.
|
||||
*/
|
||||
public function getClassMetadata($className)
|
||||
@@ -272,9 +299,10 @@ class EntityManager implements ObjectManager
|
||||
* Creates a new Query object.
|
||||
*
|
||||
* @param string $dql The DQL string.
|
||||
*
|
||||
* @return \Doctrine\ORM\Query
|
||||
*/
|
||||
public function createQuery($dql = "")
|
||||
public function createQuery($dql = '')
|
||||
{
|
||||
$query = new Query($this);
|
||||
|
||||
@@ -289,6 +317,7 @@ class EntityManager implements ObjectManager
|
||||
* Creates a Query from a named query.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return \Doctrine\ORM\Query
|
||||
*/
|
||||
public function createNamedQuery($name)
|
||||
@@ -299,8 +328,9 @@ class EntityManager implements ObjectManager
|
||||
/**
|
||||
* Creates a native SQL query.
|
||||
*
|
||||
* @param string $sql
|
||||
* @param string $sql
|
||||
* @param ResultSetMapping $rsm The ResultSetMapping to use.
|
||||
*
|
||||
* @return NativeQuery
|
||||
*/
|
||||
public function createNativeQuery($sql, ResultSetMapping $rsm)
|
||||
@@ -317,6 +347,7 @@ class EntityManager implements ObjectManager
|
||||
* Creates a NativeQuery from a named native query.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return \Doctrine\ORM\NativeQuery
|
||||
*/
|
||||
public function createNamedNativeQuery($name)
|
||||
@@ -329,7 +360,7 @@ class EntityManager implements ObjectManager
|
||||
/**
|
||||
* Create a QueryBuilder instance
|
||||
*
|
||||
* @return QueryBuilder $qb
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function createQueryBuilder()
|
||||
{
|
||||
@@ -344,7 +375,10 @@ class EntityManager implements ObjectManager
|
||||
* If an entity is explicitly passed to this method only this entity and
|
||||
* the cascade-persist semantics + scheduled inserts/removals are synchronized.
|
||||
*
|
||||
* @param object $entity
|
||||
* @param null|object|array $entity
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \Doctrine\ORM\OptimisticLockException If a version check on an entity that
|
||||
* makes use of optimistic locking fails.
|
||||
*/
|
||||
@@ -354,21 +388,34 @@ class EntityManager implements ObjectManager
|
||||
|
||||
$this->unitOfWork->commit($entity);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds an Entity by its identifier.
|
||||
*
|
||||
* @param string $entityName
|
||||
* @param mixed $id
|
||||
* @param integer $lockMode
|
||||
* @param integer $lockVersion
|
||||
* @param string $entityName
|
||||
* @param mixed $id
|
||||
* @param integer $lockMode
|
||||
* @param integer|null $lockVersion
|
||||
*
|
||||
* @return object
|
||||
* @return object|null The entity instance or NULL if the entity can not be found.
|
||||
*
|
||||
* @throws OptimisticLockException
|
||||
* @throws ORMInvalidArgumentException
|
||||
* @throws TransactionRequiredException
|
||||
* @throws ORMException
|
||||
*/
|
||||
public function find($entityName, $id, $lockMode = LockMode::NONE, $lockVersion = null)
|
||||
{
|
||||
$class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
|
||||
|
||||
if (is_object($id) && $this->metadataFactory->hasMetadataFor(ClassUtils::getClass($id))) {
|
||||
$id = $this->unitOfWork->getSingleIdentifierValue($id);
|
||||
|
||||
if ($id === null) {
|
||||
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! is_array($id)) {
|
||||
$id = array($class->identifier[0] => $id);
|
||||
}
|
||||
@@ -437,8 +484,11 @@ class EntityManager implements ObjectManager
|
||||
* without actually loading it, if the entity is not yet loaded.
|
||||
*
|
||||
* @param string $entityName The name of the entity type.
|
||||
* @param mixed $id The entity identifier.
|
||||
* @param mixed $id The entity identifier.
|
||||
*
|
||||
* @return object The entity reference.
|
||||
*
|
||||
* @throws ORMException
|
||||
*/
|
||||
public function getReference($entityName, $id)
|
||||
{
|
||||
@@ -494,7 +544,8 @@ class EntityManager implements ObjectManager
|
||||
* never be loaded in the first place.
|
||||
*
|
||||
* @param string $entityName The name of the entity type.
|
||||
* @param mixed $identifier The entity identifier.
|
||||
* @param mixed $identifier The entity identifier.
|
||||
*
|
||||
* @return object The (partial) entity reference.
|
||||
*/
|
||||
public function getPartialReference($entityName, $identifier)
|
||||
@@ -524,7 +575,9 @@ class EntityManager implements ObjectManager
|
||||
* Clears the EntityManager. All entities that are currently managed
|
||||
* by this EntityManager become detached.
|
||||
*
|
||||
* @param string $entityName if given, only entities of this type will get detached
|
||||
* @param string|null $entityName if given, only entities of this type will get detached
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear($entityName = null)
|
||||
{
|
||||
@@ -535,6 +588,8 @@ class EntityManager implements ObjectManager
|
||||
* Closes the EntityManager. All entities that are currently managed
|
||||
* by this EntityManager become detached. The EntityManager may no longer
|
||||
* be used after it is closed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
@@ -552,7 +607,11 @@ class EntityManager implements ObjectManager
|
||||
* NOTE: The persist operation always considers entities that are not yet known to
|
||||
* this EntityManager as NEW. Do not pass detached entities to the persist operation.
|
||||
*
|
||||
* @param object $object The instance to make managed and persistent.
|
||||
* @param object $entity The instance to make managed and persistent.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMInvalidArgumentException
|
||||
*/
|
||||
public function persist($entity)
|
||||
{
|
||||
@@ -572,6 +631,10 @@ class EntityManager implements ObjectManager
|
||||
* or as a result of the flush operation.
|
||||
*
|
||||
* @param object $entity The entity instance to remove.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMInvalidArgumentException
|
||||
*/
|
||||
public function remove($entity)
|
||||
{
|
||||
@@ -589,6 +652,10 @@ class EntityManager implements ObjectManager
|
||||
* overriding any local changes that have not yet been persisted.
|
||||
*
|
||||
* @param object $entity The entity to refresh.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMInvalidArgumentException
|
||||
*/
|
||||
public function refresh($entity)
|
||||
{
|
||||
@@ -609,6 +676,10 @@ class EntityManager implements ObjectManager
|
||||
* reference it.
|
||||
*
|
||||
* @param object $entity The entity to detach.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMInvalidArgumentException
|
||||
*/
|
||||
public function detach($entity)
|
||||
{
|
||||
@@ -625,7 +696,10 @@ class EntityManager implements ObjectManager
|
||||
* The entity passed to merge will not become associated/managed with this EntityManager.
|
||||
*
|
||||
* @param object $entity The detached entity to merge into the persistence context.
|
||||
*
|
||||
* @return object The managed copy of the entity.
|
||||
*
|
||||
* @throws ORMInvalidArgumentException
|
||||
*/
|
||||
public function merge($entity)
|
||||
{
|
||||
@@ -641,8 +715,13 @@ class EntityManager implements ObjectManager
|
||||
/**
|
||||
* Creates a copy of the given entity. Can create a shallow or a deep copy.
|
||||
*
|
||||
* @param object $entity The entity to copy.
|
||||
* @return object The new entity.
|
||||
* @param object $entity The entity to copy.
|
||||
* @param boolean $deep FALSE for a shallow copy, TRUE for a deep copy.
|
||||
*
|
||||
* @return object The new entity.
|
||||
*
|
||||
* @throws \BadMethodCallException
|
||||
*
|
||||
* @todo Implementation need. This is necessary since $e2 = clone $e1; throws an E_FATAL when access anything on $e:
|
||||
* Fatal error: Maximum function nesting level of '100' reached, aborting!
|
||||
*/
|
||||
@@ -654,9 +733,12 @@ class EntityManager implements ObjectManager
|
||||
/**
|
||||
* Acquire a lock on the given entity.
|
||||
*
|
||||
* @param object $entity
|
||||
* @param int $lockMode
|
||||
* @param int $lockVersion
|
||||
* @param object $entity
|
||||
* @param int $lockMode
|
||||
* @param int|null $lockVersion
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws OptimisticLockException
|
||||
* @throws PessimisticLockException
|
||||
*/
|
||||
@@ -669,34 +751,19 @@ class EntityManager implements ObjectManager
|
||||
* Gets the repository for an entity class.
|
||||
*
|
||||
* @param string $entityName The name of the entity.
|
||||
* @return EntityRepository The repository class.
|
||||
*
|
||||
* @return \Doctrine\ORM\EntityRepository The repository class.
|
||||
*/
|
||||
public function getRepository($entityName)
|
||||
{
|
||||
$entityName = ltrim($entityName, '\\');
|
||||
|
||||
if (isset($this->repositories[$entityName])) {
|
||||
return $this->repositories[$entityName];
|
||||
}
|
||||
|
||||
$metadata = $this->getClassMetadata($entityName);
|
||||
$repositoryClassName = $metadata->customRepositoryClassName;
|
||||
|
||||
if ($repositoryClassName === null) {
|
||||
$repositoryClassName = $this->config->getDefaultRepositoryClassName();
|
||||
}
|
||||
|
||||
$repository = new $repositoryClassName($this, $metadata);
|
||||
|
||||
$this->repositories[$entityName] = $repository;
|
||||
|
||||
return $repository;
|
||||
return $this->repositoryFactory->getRepository($this, $entityName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether an entity instance is managed in this EntityManager.
|
||||
*
|
||||
* @param object $entity
|
||||
*
|
||||
* @return boolean TRUE if this EntityManager currently manages the given entity, FALSE otherwise.
|
||||
*/
|
||||
public function contains($entity)
|
||||
@@ -729,6 +796,8 @@ class EntityManager implements ObjectManager
|
||||
/**
|
||||
* Throws an exception if the EntityManager is closed or currently not active.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMException If the EntityManager is closed.
|
||||
*/
|
||||
private function errorIfClosed()
|
||||
@@ -764,23 +833,25 @@ class EntityManager implements ObjectManager
|
||||
* This method caches the hydrator instances which is used for all queries that don't
|
||||
* selectively iterate over the result.
|
||||
*
|
||||
* @deprecated
|
||||
*
|
||||
* @param int $hydrationMode
|
||||
*
|
||||
* @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator
|
||||
*/
|
||||
public function getHydrator($hydrationMode)
|
||||
{
|
||||
if ( ! isset($this->hydrators[$hydrationMode])) {
|
||||
$this->hydrators[$hydrationMode] = $this->newHydrator($hydrationMode);
|
||||
}
|
||||
|
||||
return $this->hydrators[$hydrationMode];
|
||||
return $this->newHydrator($hydrationMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance for the given hydration mode.
|
||||
*
|
||||
* @param int $hydrationMode
|
||||
* @param int $hydrationMode
|
||||
*
|
||||
* @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator
|
||||
*
|
||||
* @throws ORMException
|
||||
*/
|
||||
public function newHydrator($hydrationMode)
|
||||
{
|
||||
@@ -825,6 +896,8 @@ class EntityManager implements ObjectManager
|
||||
* This method is a no-op for other objects
|
||||
*
|
||||
* @param object $obj
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initializeObject($obj)
|
||||
{
|
||||
@@ -834,11 +907,14 @@ class EntityManager implements ObjectManager
|
||||
/**
|
||||
* Factory method to create EntityManager instances.
|
||||
*
|
||||
* @param mixed $conn An array with the connection parameters or an existing
|
||||
* Connection instance.
|
||||
* @param Configuration $config The Configuration instance to use.
|
||||
* @param EventManager $eventManager The EventManager instance to use.
|
||||
* @param mixed $conn An array with the connection parameters or an existing Connection instance.
|
||||
* @param Configuration $config The Configuration instance to use.
|
||||
* @param EventManager $eventManager The EventManager instance to use.
|
||||
*
|
||||
* @return EntityManager The created EntityManager.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws ORMException
|
||||
*/
|
||||
public static function create($conn, Configuration $config, EventManager $eventManager = null)
|
||||
{
|
||||
@@ -893,7 +969,7 @@ class EntityManager implements ObjectManager
|
||||
/**
|
||||
* Checks whether the Entity Manager has filters.
|
||||
*
|
||||
* @return True, if the EM has a filter collection.
|
||||
* @return boolean True, if the EM has a filter collection.
|
||||
*/
|
||||
public function hasFilters()
|
||||
{
|
||||
|
||||
60
lib/Doctrine/ORM/EntityManagerInterface.php
Normal file
60
lib/Doctrine/ORM/EntityManagerInterface.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?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;
|
||||
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
|
||||
/**
|
||||
* EntityManager interface
|
||||
*
|
||||
* @since 2.4
|
||||
* @author Lars Strojny <lars@strojny.net
|
||||
*/
|
||||
interface EntityManagerInterface extends ObjectManager
|
||||
{
|
||||
public function getConnection();
|
||||
public function getExpressionBuilder();
|
||||
public function beginTransaction();
|
||||
public function transactional($func);
|
||||
public function commit();
|
||||
public function rollback();
|
||||
public function createQuery($dql = '');
|
||||
public function createNamedQuery($name);
|
||||
public function createNativeQuery($sql, ResultSetMapping $rsm);
|
||||
public function createNamedNativeQuery($name);
|
||||
public function createQueryBuilder();
|
||||
public function getReference($entityName, $id);
|
||||
public function getPartialReference($entityName, $identifier);
|
||||
public function close();
|
||||
public function copy($entity, $deep = false);
|
||||
public function lock($entity, $lockMode, $lockVersion = null);
|
||||
public function getEventManager();
|
||||
public function getConfiguration();
|
||||
public function isOpen();
|
||||
public function getUnitOfWork();
|
||||
public function getHydrator($hydrationMode);
|
||||
public function newHydrator($hydrationMode);
|
||||
public function getProxyFactory();
|
||||
public function getFilters();
|
||||
public function isFiltersStateClean();
|
||||
public function hasFilters();
|
||||
}
|
||||
@@ -27,6 +27,9 @@ namespace Doctrine\ORM;
|
||||
*/
|
||||
class EntityNotFoundException extends ORMException
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('Entity was not found.');
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
||||
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\Common\Persistence\ObjectRepository;
|
||||
|
||||
@@ -59,8 +61,8 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
/**
|
||||
* Initializes a new <tt>EntityRepository</tt>.
|
||||
*
|
||||
* @param EntityManager $em The EntityManager to use.
|
||||
* @param ClassMetadata $classMetadata The class descriptor.
|
||||
* @param EntityManager $em The EntityManager to use.
|
||||
* @param Mapping\ClassMetadata $class The class descriptor.
|
||||
*/
|
||||
public function __construct($em, Mapping\ClassMetadata $class)
|
||||
{
|
||||
@@ -70,10 +72,11 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new QueryBuilder instance that is prepopulated for this entity name
|
||||
* Creates a new QueryBuilder instance that is prepopulated for this entity name.
|
||||
*
|
||||
* @param string $alias
|
||||
* @return QueryBuilder $qb
|
||||
*
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function createQueryBuilder($alias)
|
||||
{
|
||||
@@ -83,9 +86,27 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Query instance based on a predefined metadata named query.
|
||||
* Creates a new result set mapping builder for this entity.
|
||||
*
|
||||
* The column naming strategy is "INCREMENT".
|
||||
*
|
||||
* @param string $alias
|
||||
*
|
||||
* @return ResultSetMappingBuilder
|
||||
*/
|
||||
public function createResultSetMappingBuilder($alias)
|
||||
{
|
||||
$rsm = new ResultSetMappingBuilder($this->_em, ResultSetMappingBuilder::COLUMN_RENAMING_INCREMENT);
|
||||
$rsm->addRootEntityFromClassMetadata($this->_entityName, $alias);
|
||||
|
||||
return $rsm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Query instance based on a predefined metadata named query.
|
||||
*
|
||||
* @param string $queryName
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function createNamedQuery($queryName)
|
||||
@@ -97,6 +118,7 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
* Creates a native SQL query.
|
||||
*
|
||||
* @param string $queryName
|
||||
*
|
||||
* @return NativeQuery
|
||||
*/
|
||||
public function createNativeNamedQuery($queryName)
|
||||
@@ -110,6 +132,8 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
|
||||
/**
|
||||
* Clears the repository, causing all managed entities to become detached.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
@@ -119,11 +143,11 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
/**
|
||||
* Finds an entity by its primary key / identifier.
|
||||
*
|
||||
* @param mixed $id The identifier.
|
||||
* @param integer $lockMode
|
||||
* @param integer $lockVersion
|
||||
* @param mixed $id The identifier.
|
||||
* @param int $lockMode The lock mode.
|
||||
* @param int|null $lockVersion The lock version.
|
||||
*
|
||||
* @return object The entity.
|
||||
* @return object|null The entity instance or NULL if the entity can not be found.
|
||||
*/
|
||||
public function find($id, $lockMode = LockMode::NONE, $lockVersion = null)
|
||||
{
|
||||
@@ -143,10 +167,11 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
/**
|
||||
* Finds entities by a set of criteria.
|
||||
*
|
||||
* @param array $criteria
|
||||
* @param array $criteria
|
||||
* @param array|null $orderBy
|
||||
* @param int|null $limit
|
||||
* @param int|null $offset
|
||||
* @param int|null $limit
|
||||
* @param int|null $offset
|
||||
*
|
||||
* @return array The objects.
|
||||
*/
|
||||
public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
@@ -160,20 +185,27 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
* Finds a single entity by a set of criteria.
|
||||
*
|
||||
* @param array $criteria
|
||||
* @return object
|
||||
* @param array|null $orderBy
|
||||
*
|
||||
* @return object|null The entity instance or NULL if the entity can not be found.
|
||||
*/
|
||||
public function findOneBy(array $criteria)
|
||||
public function findOneBy(array $criteria, array $orderBy = null)
|
||||
{
|
||||
$persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
|
||||
|
||||
return $persister->load($criteria, null, null, array(), 0, 1);
|
||||
return $persister->load($criteria, null, null, array(), 0, 1, $orderBy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds support for magic finders.
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $arguments
|
||||
*
|
||||
* @return array|object The found entity/entities.
|
||||
* @throws BadMethodCallException If the method called is an invalid find* method
|
||||
*
|
||||
* @throws ORMException
|
||||
* @throws \BadMethodCallException If the method called is an invalid find* method
|
||||
* or no find* method at all and therefore an invalid
|
||||
* method call.
|
||||
*/
|
||||
@@ -214,7 +246,7 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
case 3:
|
||||
return $this->$method(array($fieldName => $arguments[0]), $arguments[1], $arguments[2]);
|
||||
|
||||
case 4;
|
||||
case 4:
|
||||
return $this->$method(array($fieldName => $arguments[0]), $arguments[1], $arguments[2], $arguments[3]);
|
||||
|
||||
default:
|
||||
@@ -272,4 +304,3 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
return new ArrayCollection($persister->loadCriteria($criteria));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
|
||||
namespace Doctrine\ORM\Event;
|
||||
|
||||
use Doctrine\Common\EventArgs;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\Common\Persistence\Event\LifecycleEventArgs as BaseLifecycleEventArgs;
|
||||
|
||||
/**
|
||||
* Lifecycle Events are triggered by the UnitOfWork during lifecycle transitions
|
||||
@@ -31,47 +31,25 @@ use Doctrine\ORM\EntityManager;
|
||||
* @author Roman Borschel <roman@code-factory.de>
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class LifecycleEventArgs extends EventArgs
|
||||
class LifecycleEventArgs extends BaseLifecycleEventArgs
|
||||
{
|
||||
/**
|
||||
* @var \Doctrine\ORM\EntityManager
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* @var object
|
||||
*/
|
||||
private $entity;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param object $entity
|
||||
* @param \Doctrine\ORM\EntityManager $em
|
||||
*/
|
||||
public function __construct($entity, EntityManager $em)
|
||||
{
|
||||
$this->entity = $entity;
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve associated Entity.
|
||||
* Retrieves associated Entity.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function getEntity()
|
||||
{
|
||||
return $this->entity;
|
||||
return $this->getObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve associated EntityManager.
|
||||
* Retrieves associated EntityManager.
|
||||
*
|
||||
* @return \Doctrine\ORM\EntityManager
|
||||
*/
|
||||
public function getEntityManager()
|
||||
{
|
||||
return $this->em;
|
||||
return $this->getObjectManager();
|
||||
}
|
||||
}
|
||||
|
||||
120
lib/Doctrine/ORM/Event/ListenersInvoker.php
Normal file
120
lib/Doctrine/ORM/Event/ListenersInvoker.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?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\Event;
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\Common\EventArgs;
|
||||
|
||||
/**
|
||||
* A method invoker based on entity lifecycle.
|
||||
*
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
* @since 2.4
|
||||
*/
|
||||
class ListenersInvoker
|
||||
{
|
||||
const INVOKE_NONE = 0;
|
||||
const INVOKE_LISTENERS = 1;
|
||||
const INVOKE_CALLBACKS = 2;
|
||||
const INVOKE_MANAGER = 4;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Mapping\EntityListenerResolver The Entity listener resolver.
|
||||
*/
|
||||
private $resolver;
|
||||
|
||||
/**
|
||||
* The EventManager used for dispatching events.
|
||||
*
|
||||
* @var \Doctrine\Common\EventManager
|
||||
*/
|
||||
private $eventManager;
|
||||
|
||||
/**
|
||||
* Initializes a new ListenersInvoker instance.
|
||||
*
|
||||
* @param \Doctrine\ORM\EntityManager $em
|
||||
*/
|
||||
public function __construct(EntityManager $em)
|
||||
{
|
||||
$this->eventManager = $em->getEventManager();
|
||||
$this->resolver = $em->getConfiguration()->getEntityListenerResolver();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the subscribed event systems
|
||||
*
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
|
||||
* @param string $eventName The entity lifecycle event.
|
||||
*
|
||||
* @return integer Bitmask of subscribed event systems.
|
||||
*/
|
||||
public function getSubscribedSystems(ClassMetadata $metadata, $eventName)
|
||||
{
|
||||
$invoke = self::INVOKE_NONE;
|
||||
|
||||
if (isset($metadata->lifecycleCallbacks[$eventName])) {
|
||||
$invoke |= self::INVOKE_CALLBACKS;
|
||||
}
|
||||
|
||||
if (isset($metadata->entityListeners[$eventName])) {
|
||||
$invoke |= self::INVOKE_LISTENERS;
|
||||
}
|
||||
|
||||
if ($this->eventManager->hasListeners($eventName)) {
|
||||
$invoke |= self::INVOKE_MANAGER;
|
||||
}
|
||||
|
||||
return $invoke;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches the lifecycle event of the given entity.
|
||||
*
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
|
||||
* @param string $eventName The entity lifecycle event.
|
||||
* @param object $entity The Entity on which the event occurred.
|
||||
* @param \Doctrine\Common\EventArgs $event The Event args.
|
||||
* @param integer $invoke Bitmask to invoke listeners.
|
||||
*/
|
||||
public function invoke(ClassMetadata $metadata, $eventName, $entity, EventArgs $event, $invoke)
|
||||
{
|
||||
if($invoke & self::INVOKE_CALLBACKS) {
|
||||
foreach ($metadata->lifecycleCallbacks[$eventName] as $callback) {
|
||||
$entity->$callback($event);
|
||||
}
|
||||
}
|
||||
|
||||
if($invoke & self::INVOKE_LISTENERS) {
|
||||
foreach ($metadata->entityListeners[$eventName] as $listener) {
|
||||
$class = $listener['class'];
|
||||
$method = $listener['method'];
|
||||
$instance = $this->resolver->resolve($class);
|
||||
|
||||
$instance->$method($entity, $event);
|
||||
}
|
||||
}
|
||||
|
||||
if($invoke & self::INVOKE_MANAGER) {
|
||||
$this->eventManager->dispatchEvent($eventName, $event);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ namespace Doctrine\ORM\Event;
|
||||
use Doctrine\Common\EventArgs;
|
||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\Common\Persistence\Event\LoadClassMetadataEventArgs as BaseLoadClassMetadataEventArgs;
|
||||
|
||||
/**
|
||||
* Class that holds event arguments for a loadMetadata event.
|
||||
@@ -29,40 +30,8 @@ use Doctrine\ORM\EntityManager;
|
||||
* @author Jonathan H. Wage <jonwage@gmail.com>
|
||||
* @since 2.0
|
||||
*/
|
||||
class LoadClassMetadataEventArgs extends EventArgs
|
||||
class LoadClassMetadataEventArgs extends BaseLoadClassMetadataEventArgs
|
||||
{
|
||||
/**
|
||||
* @var \Doctrine\ORM\Mapping\ClassMetadata
|
||||
*/
|
||||
private $classMetadata;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\EntityManager
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata
|
||||
* @param \Doctrine\ORM\EntityManager $em
|
||||
*/
|
||||
public function __construct(ClassMetadataInfo $classMetadata, EntityManager $em)
|
||||
{
|
||||
$this->classMetadata = $classMetadata;
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve associated ClassMetadata.
|
||||
*
|
||||
* @return \Doctrine\ORM\Mapping\ClassMetadataInfo
|
||||
*/
|
||||
public function getClassMetadata()
|
||||
{
|
||||
return $this->classMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve associated EntityManager.
|
||||
*
|
||||
@@ -70,7 +39,6 @@ class LoadClassMetadataEventArgs extends EventArgs
|
||||
*/
|
||||
public function getEntityManager()
|
||||
{
|
||||
return $this->em;
|
||||
return $this->getObjectManager();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
namespace Doctrine\ORM\Event;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
|
||||
/**
|
||||
* Provides event arguments for the onClear event.
|
||||
*
|
||||
@@ -44,16 +46,16 @@ class OnClearEventArgs extends \Doctrine\Common\EventArgs
|
||||
* Constructor.
|
||||
*
|
||||
* @param \Doctrine\ORM\EntityManager $em
|
||||
* @param string $entityClass Optional entity class
|
||||
* @param string|null $entityClass Optional entity class.
|
||||
*/
|
||||
public function __construct($em, $entityClass = null)
|
||||
public function __construct(EntityManager $em, $entityClass = null)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->entityClass = $entityClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve associated EntityManager.
|
||||
* Retrieves associated EntityManager.
|
||||
*
|
||||
* @return \Doctrine\ORM\EntityManager
|
||||
*/
|
||||
@@ -65,7 +67,7 @@ class OnClearEventArgs extends \Doctrine\Common\EventArgs
|
||||
/**
|
||||
* Name of the entity class that is cleared, or empty if all are cleared.
|
||||
*
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
public function getEntityClass()
|
||||
{
|
||||
@@ -73,7 +75,7 @@ class OnClearEventArgs extends \Doctrine\Common\EventArgs
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if event clears all entities.
|
||||
* Checks if event clears all entities.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
namespace Doctrine\ORM\Event;
|
||||
|
||||
use Doctrine\Common\EventArgs;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
|
||||
/**
|
||||
@@ -30,16 +31,13 @@ use Doctrine\ORM\EntityManager;
|
||||
* @author Roman Borschel <roman@code-factory.de>
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class OnFlushEventArgs extends \Doctrine\Common\EventArgs
|
||||
class OnFlushEventArgs extends EventArgs
|
||||
{
|
||||
/**
|
||||
* @var Doctirne\ORM\EntityManager
|
||||
* @var \Doctrine\ORM\EntityManager
|
||||
*/
|
||||
private $em;
|
||||
|
||||
//private $entitiesToPersist = array();
|
||||
//private $entitiesToRemove = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
@@ -60,25 +58,4 @@ class OnFlushEventArgs extends \Doctrine\Common\EventArgs
|
||||
return $this->em;
|
||||
}
|
||||
|
||||
/*
|
||||
public function addEntityToPersist($entity)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function addEntityToRemove($entity)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function addEntityToUpdate($entity)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function getEntitiesToPersist()
|
||||
{
|
||||
return $this->_entitiesToPersist;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ class PostFlushEventArgs extends EventArgs
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve associated EntityManager.
|
||||
* Retrieves associated EntityManager.
|
||||
*
|
||||
* @return \Doctrine\ORM\EntityManager
|
||||
*/
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
|
||||
namespace Doctrine\ORM\Event;
|
||||
|
||||
use Doctrine\Common\EventArgs;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
|
||||
/**
|
||||
* Provides event arguments for the preFlush event.
|
||||
*
|
||||
@@ -28,23 +31,28 @@ namespace Doctrine\ORM\Event;
|
||||
* @author Roman Borschel <roman@code-factory.de>
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class PreFlushEventArgs extends \Doctrine\Common\EventArgs
|
||||
class PreFlushEventArgs extends EventArgs
|
||||
{
|
||||
/**
|
||||
* @var EntityManager
|
||||
* @var \Doctrine\ORM\EntityManager
|
||||
*/
|
||||
private $_em;
|
||||
private $em;
|
||||
|
||||
public function __construct($em)
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \Doctrine\ORM\EntityManager $em
|
||||
*/
|
||||
public function __construct(EntityManager $em)
|
||||
{
|
||||
$this->_em = $em;
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EntityManager
|
||||
* @return \Doctrine\ORM\EntityManager
|
||||
*/
|
||||
public function getEntityManager()
|
||||
{
|
||||
return $this->_em;
|
||||
return $this->em;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
|
||||
namespace Doctrine\ORM\Event;
|
||||
|
||||
use Doctrine\Common\EventArgs,
|
||||
Doctrine\ORM\EntityManager;
|
||||
use Doctrine\Common\EventArgs;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
|
||||
/**
|
||||
* Class that holds event arguments for a preInsert/preUpdate event.
|
||||
@@ -40,9 +40,9 @@ class PreUpdateEventArgs extends LifecycleEventArgs
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param object $entity
|
||||
* @param \Doctrine\ORM\EntityManager $em
|
||||
* @param array $changeSet
|
||||
* @param object $entity
|
||||
* @param EntityManager $em
|
||||
* @param array $changeSet
|
||||
*/
|
||||
public function __construct($entity, EntityManager $em, array &$changeSet)
|
||||
{
|
||||
@@ -52,7 +52,7 @@ class PreUpdateEventArgs extends LifecycleEventArgs
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve entity changeset.
|
||||
* Retrieves entity changeset.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
@@ -62,7 +62,9 @@ class PreUpdateEventArgs extends LifecycleEventArgs
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if field has a changeset.
|
||||
* Checks if field has a changeset.
|
||||
*
|
||||
* @param string $field
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
@@ -72,9 +74,10 @@ class PreUpdateEventArgs extends LifecycleEventArgs
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the old value of the changeset of the changed field.
|
||||
* Gets the old value of the changeset of the changed field.
|
||||
*
|
||||
* @param string $field
|
||||
*
|
||||
* @param string $field
|
||||
* @return mixed
|
||||
*/
|
||||
public function getOldValue($field)
|
||||
@@ -85,9 +88,10 @@ class PreUpdateEventArgs extends LifecycleEventArgs
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the new value of the changeset of the changed field.
|
||||
* Gets the new value of the changeset of the changed field.
|
||||
*
|
||||
* @param string $field
|
||||
*
|
||||
* @param string $field
|
||||
* @return mixed
|
||||
*/
|
||||
public function getNewValue($field)
|
||||
@@ -98,10 +102,12 @@ class PreUpdateEventArgs extends LifecycleEventArgs
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the new value of this field.
|
||||
* Sets the new value of this field.
|
||||
*
|
||||
* @param string $field
|
||||
* @param mixed $value
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setNewValue($field, $value)
|
||||
{
|
||||
@@ -111,9 +117,13 @@ class PreUpdateEventArgs extends LifecycleEventArgs
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the field exists in changeset.
|
||||
* Asserts the field exists in changeset.
|
||||
*
|
||||
* @param string $field
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
private function assertValidField($field)
|
||||
{
|
||||
@@ -126,4 +136,3 @@ class PreUpdateEventArgs extends LifecycleEventArgs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,13 @@ namespace Doctrine\ORM;
|
||||
*/
|
||||
final class Events
|
||||
{
|
||||
private function __construct() {}
|
||||
/**
|
||||
* Private constructor. This class is not meant to be instantiated.
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* The preRemove event occurs for a given entity before the respective
|
||||
* EntityManager remove operation for that entity is executed.
|
||||
@@ -39,6 +45,7 @@ final class Events
|
||||
* @var string
|
||||
*/
|
||||
const preRemove = 'preRemove';
|
||||
|
||||
/**
|
||||
* The postRemove event occurs for an entity after the entity has
|
||||
* been deleted. It will be invoked after the database delete operations.
|
||||
@@ -48,6 +55,7 @@ final class Events
|
||||
* @var string
|
||||
*/
|
||||
const postRemove = 'postRemove';
|
||||
|
||||
/**
|
||||
* The prePersist event occurs for a given entity before the respective
|
||||
* EntityManager persist operation for that entity is executed.
|
||||
@@ -57,6 +65,7 @@ final class Events
|
||||
* @var string
|
||||
*/
|
||||
const prePersist = 'prePersist';
|
||||
|
||||
/**
|
||||
* The postPersist event occurs for an entity after the entity has
|
||||
* been made persistent. It will be invoked after the database insert operations.
|
||||
@@ -67,6 +76,7 @@ final class Events
|
||||
* @var string
|
||||
*/
|
||||
const postPersist = 'postPersist';
|
||||
|
||||
/**
|
||||
* The preUpdate event occurs before the database update operations to
|
||||
* entity data.
|
||||
@@ -76,6 +86,7 @@ final class Events
|
||||
* @var string
|
||||
*/
|
||||
const preUpdate = 'preUpdate';
|
||||
|
||||
/**
|
||||
* The postUpdate event occurs after the database update operations to
|
||||
* entity data.
|
||||
@@ -85,6 +96,7 @@ final class Events
|
||||
* @var string
|
||||
*/
|
||||
const postUpdate = 'postUpdate';
|
||||
|
||||
/**
|
||||
* The postLoad event occurs for an entity after the entity has been loaded
|
||||
* into the current EntityManager from the database or after the refresh operation
|
||||
@@ -99,6 +111,7 @@ final class Events
|
||||
* @var string
|
||||
*/
|
||||
const postLoad = 'postLoad';
|
||||
|
||||
/**
|
||||
* The loadClassMetadata event occurs after the mapping metadata for a class
|
||||
* has been loaded from a mapping source (annotations/xml/yaml).
|
||||
@@ -109,7 +122,7 @@ final class Events
|
||||
|
||||
/**
|
||||
* The preFlush event occurs when the EntityManager#flush() operation is invoked,
|
||||
* but before any changes to managed entites have been calculated. This event is
|
||||
* but before any changes to managed entities have been calculated. This event is
|
||||
* always raised right after EntityManager#flush() call.
|
||||
*/
|
||||
const preFlush = 'preFlush';
|
||||
|
||||
@@ -26,7 +26,9 @@ abstract class AbstractIdGenerator
|
||||
/**
|
||||
* Generates an identifier for an entity.
|
||||
*
|
||||
* @param \Doctrine\ORM\Entity $entity
|
||||
* @param \Doctrine\ORM\EntityManager $em
|
||||
* @param \Doctrine\ORM\Mapping\Entity $entity
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function generate(EntityManager $em, $entity);
|
||||
|
||||
@@ -23,7 +23,7 @@ use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\ORMException;
|
||||
|
||||
/**
|
||||
* Special generator for application-assigned identifiers (doesnt really generate anything).
|
||||
* Special generator for application-assigned identifiers (doesn't really generate anything).
|
||||
*
|
||||
* @since 2.0
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
@@ -36,8 +36,13 @@ class AssignedGenerator extends AbstractIdGenerator
|
||||
/**
|
||||
* Returns the identifier assigned to the given entity.
|
||||
*
|
||||
* @param object $entity
|
||||
* @param EntityManager $em
|
||||
* @param object $entity
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Doctrine\ORM\ORMException
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
public function generate(EntityManager $em, $entity)
|
||||
@@ -47,7 +52,7 @@ class AssignedGenerator extends AbstractIdGenerator
|
||||
$identifier = array();
|
||||
|
||||
foreach ($idFields as $idField) {
|
||||
$value = $class->reflFields[$idField]->getValue($entity);
|
||||
$value = $class->getFieldValue($entity, $idField);
|
||||
|
||||
if ( ! isset($value)) {
|
||||
throw ORMException::entityMissingAssignedIdForField($entity, $idField);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user