mirror of
https://github.com/doctrine/orm.git
synced 2026-03-24 15:02:22 +01:00
Compare commits
640 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03d2b0f30c | ||
|
|
5544895cf9 | ||
|
|
6af72006e4 | ||
|
|
fd76138a56 | ||
|
|
1364f23d8c | ||
|
|
681d3ef45a | ||
|
|
53c08b59b5 | ||
|
|
07ee8c5249 | ||
|
|
0c69f4ea9c | ||
|
|
57acceaff2 | ||
|
|
7d3fee59bc | ||
|
|
cab10eb96d | ||
|
|
3f0a148dc2 | ||
|
|
134ea63849 | ||
|
|
39d0b14bdb | ||
|
|
22cebf9880 | ||
|
|
015cbc46be | ||
|
|
734629af08 | ||
|
|
e47b5a2b28 | ||
|
|
5152c48ce3 | ||
|
|
92028473f0 | ||
|
|
3e7c9a1ff4 | ||
|
|
e6b994eecc | ||
|
|
e094c93035 | ||
|
|
c98cc258e8 | ||
|
|
53bb7e36e3 | ||
|
|
490bdd23f7 | ||
|
|
0805f69101 | ||
|
|
9503cf6ff6 | ||
|
|
affa6feb8a | ||
|
|
061ea2b337 | ||
|
|
e1647229cd | ||
|
|
9111d794d5 | ||
|
|
34ec1e3248 | ||
|
|
3c52fa5ea6 | ||
|
|
b3ed478fb4 | ||
|
|
c5973f8019 | ||
|
|
6512902eae | ||
|
|
798a8bca92 | ||
|
|
7bca37e827 | ||
|
|
818a5d5904 | ||
|
|
20ac6f383c | ||
|
|
60d97e068c | ||
|
|
94162d1c03 | ||
|
|
1c042b5b8d | ||
|
|
b770a6687a | ||
|
|
0abf2484ce | ||
|
|
7d8aa2213d | ||
|
|
12a639d4a2 | ||
|
|
432a035baa | ||
|
|
97df2f8749 | ||
|
|
61aec8fdcd | ||
|
|
292fbd9756 | ||
|
|
c2112c0584 | ||
|
|
05c7e0055a | ||
|
|
fb7b5c6fb1 | ||
|
|
da0e3439ab | ||
|
|
a1df81e18c | ||
|
|
1dea57013c | ||
|
|
91b1a25bb2 | ||
|
|
473ba2f04c | ||
|
|
e80eae4c98 | ||
|
|
68184a3803 | ||
|
|
3412ef935a | ||
|
|
4215a24969 | ||
|
|
02d8802570 | ||
|
|
0e107880cf | ||
|
|
3b259dcb42 | ||
|
|
d87e90d392 | ||
|
|
c1ad170bed | ||
|
|
565962ec8b | ||
|
|
eb9812feb5 | ||
|
|
d9b39f6fce | ||
|
|
b1db2ad0d8 | ||
|
|
92b3d0a383 | ||
|
|
fc8997a935 | ||
|
|
88d937cc89 | ||
|
|
bff55577cd | ||
|
|
7ff30e273a | ||
|
|
9c81989349 | ||
|
|
e0bb84e1da | ||
|
|
692f423047 | ||
|
|
0814e8abc7 | ||
|
|
d4a1ea8995 | ||
|
|
c2febabdd0 | ||
|
|
633e342d82 | ||
|
|
2e7318dcc2 | ||
|
|
6b3edbbbf9 | ||
|
|
fdcb46e780 | ||
|
|
6da0314deb | ||
|
|
b61d109fe2 | ||
|
|
960c94f661 | ||
|
|
515b0205c6 | ||
|
|
d76c5b11f6 | ||
|
|
88043577a4 | ||
|
|
d0b9f7cb2b | ||
|
|
10a3f5e10e | ||
|
|
eeddaecd26 | ||
|
|
50f7f3a48e | ||
|
|
e94a293446 | ||
|
|
08e5e47780 | ||
|
|
e3a5ffbb55 | ||
|
|
bf06c3418b | ||
|
|
6a8172902a | ||
|
|
d9328ea5e7 | ||
|
|
8cd8c993c8 | ||
|
|
f45d8f488b | ||
|
|
6257daf097 | ||
|
|
d46910f6bb | ||
|
|
5c4e3d9bd0 | ||
|
|
7d0d06cd9a | ||
|
|
e4f49231bf | ||
|
|
ca600f466e | ||
|
|
01d6b73f78 | ||
|
|
c36b1f1d00 | ||
|
|
588f7fc765 | ||
|
|
1becb8dfd5 | ||
|
|
394447d92d | ||
|
|
f834c61a2b | ||
|
|
67cf5b7160 | ||
|
|
e3a04c3458 | ||
|
|
cb7e27460e | ||
|
|
9dc5c5ddce | ||
|
|
bbd01ea457 | ||
|
|
360c578c5d | ||
|
|
ca289bf1ea | ||
|
|
17341cf051 | ||
|
|
5638b8c5c0 | ||
|
|
ccaf00cfab | ||
|
|
b57f4ecb52 | ||
|
|
7ae96dd9c7 | ||
|
|
0b3b4ecef6 | ||
|
|
8e3fc30477 | ||
|
|
db2c51d860 | ||
|
|
b6ac1b4962 | ||
|
|
7734e3e2ec | ||
|
|
45494fd6a0 | ||
|
|
efad33a70e | ||
|
|
ba9feb1519 | ||
|
|
4683d76c52 | ||
|
|
4ddd4a68ef | ||
|
|
d9f9228d95 | ||
|
|
ef4d8410f6 | ||
|
|
a62cd45476 | ||
|
|
7369a2de68 | ||
|
|
623ab9cee9 | ||
|
|
144d0de0ab | ||
|
|
5c6164ce07 | ||
|
|
e960ba24eb | ||
|
|
e55b700670 | ||
|
|
e4dd5e83e4 | ||
|
|
082c2ded8d | ||
|
|
7f9cb9567a | ||
|
|
90c50b013d | ||
|
|
6a05e7f393 | ||
|
|
a8e4d8f52d | ||
|
|
47314c116c | ||
|
|
c2eb6e6104 | ||
|
|
3b6a1e11f3 | ||
|
|
87650428b2 | ||
|
|
1de27437b5 | ||
|
|
52c75d1633 | ||
|
|
8875d549eb | ||
|
|
cf10ee4d02 | ||
|
|
1faa3a945e | ||
|
|
22f37d4178 | ||
|
|
6ad21462fe | ||
|
|
e9c29135f7 | ||
|
|
12af7d24c0 | ||
|
|
4f3282d328 | ||
|
|
60e7efa5b9 | ||
|
|
0a4c1028a1 | ||
|
|
2c3757d087 | ||
|
|
bf892a8ece | ||
|
|
878710d013 | ||
|
|
be800a991c | ||
|
|
e295168c19 | ||
|
|
c03d7dd086 | ||
|
|
70938fa7f9 | ||
|
|
83dbde9cda | ||
|
|
05d12e20be | ||
|
|
9b3b6052da | ||
|
|
ffdc5e8d32 | ||
|
|
6380795827 | ||
|
|
6e9575b121 | ||
|
|
099e2c1bd3 | ||
|
|
90c47a0510 | ||
|
|
67133f8886 | ||
|
|
4b4efe5483 | ||
|
|
6291139dd1 | ||
|
|
90f96c4fba | ||
|
|
23de4a0fe4 | ||
|
|
68faa589f5 | ||
|
|
7633f7b7ae | ||
|
|
5f665a9f3c | ||
|
|
4819594a71 | ||
|
|
64d3715e79 | ||
|
|
b22f692406 | ||
|
|
a1ed28a39b | ||
|
|
9b41b6106c | ||
|
|
a24557bc27 | ||
|
|
295281c890 | ||
|
|
57c5491494 | ||
|
|
b43fffce8b | ||
|
|
e426c26ade | ||
|
|
e23f5bc825 | ||
|
|
ce4f98aaab | ||
|
|
16d18c7f83 | ||
|
|
6d62484065 | ||
|
|
6d1067b90c | ||
|
|
3c3e5cbf41 | ||
|
|
aabf39940a | ||
|
|
1fedd0e7d3 | ||
|
|
a0a03947a3 | ||
|
|
420da54620 | ||
|
|
7bb77fc437 | ||
|
|
bb3f6957d4 | ||
|
|
5ef63c1c0d | ||
|
|
09c957fdc0 | ||
|
|
64bc782bdb | ||
|
|
8fb5b40fc1 | ||
|
|
448811eb6a | ||
|
|
ecc79c8ca0 | ||
|
|
d2320128cf | ||
|
|
e5df347ea3 | ||
|
|
9e2b98ca16 | ||
|
|
515b44126b | ||
|
|
1feac7ae9e | ||
|
|
1929ab6a75 | ||
|
|
dfdb735306 | ||
|
|
cb49648eed | ||
|
|
438dd9141f | ||
|
|
db37d974c8 | ||
|
|
6b54cceed7 | ||
|
|
73f908f25c | ||
|
|
550fcbc17f | ||
|
|
ffca455788 | ||
|
|
e4f2a56277 | ||
|
|
cbe14a694a | ||
|
|
f589cd0d9f | ||
|
|
43d8466fa9 | ||
|
|
5299bd788f | ||
|
|
a0a81db045 | ||
|
|
5362206297 | ||
|
|
e32e141012 | ||
|
|
4603e94fe9 | ||
|
|
8b7e2a9f32 | ||
|
|
5701036068 | ||
|
|
0f68355ce0 | ||
|
|
9395eeed3d | ||
|
|
6d035be3e3 | ||
|
|
01935e6661 | ||
|
|
379584fb26 | ||
|
|
f1c073e080 | ||
|
|
66e92b147d | ||
|
|
53c799987d | ||
|
|
8850efb0eb | ||
|
|
551f6d05d9 | ||
|
|
e899205300 | ||
|
|
5afc097527 | ||
|
|
ed516edf90 | ||
|
|
52431251cb | ||
|
|
69944017d2 | ||
|
|
ca0bea1d8a | ||
|
|
7efe071ac4 | ||
|
|
ebe95af30c | ||
|
|
a607e2ec7a | ||
|
|
a73a1e8437 | ||
|
|
07f568e2b4 | ||
|
|
0dd1dc20c8 | ||
|
|
7367e255ae | ||
|
|
10b70df1af | ||
|
|
fe8b28a09f | ||
|
|
db80b2b135 | ||
|
|
a5cddb0c11 | ||
|
|
3717ae3c53 | ||
|
|
2caf0fff60 | ||
|
|
c05fffcc93 | ||
|
|
fff0204e6d | ||
|
|
c7c430032c | ||
|
|
1c2ade61ab | ||
|
|
c62e27898c | ||
|
|
6f8ac21273 | ||
|
|
82f0c244e8 | ||
|
|
197744a57f | ||
|
|
054ac220ac | ||
|
|
c6746d4a4a | ||
|
|
f0bc3d925d | ||
|
|
7810f5fe69 | ||
|
|
decd1482de | ||
|
|
fa7574b2ba | ||
|
|
32b146ea8a | ||
|
|
1aa90dc872 | ||
|
|
989d375be5 | ||
|
|
ede68ec87b | ||
|
|
02f06b6d52 | ||
|
|
699ccfddb6 | ||
|
|
626e467a17 | ||
|
|
1fed340793 | ||
|
|
0cd0ae49a1 | ||
|
|
713f4654fd | ||
|
|
22bdb1520a | ||
|
|
42c5382a03 | ||
|
|
98bc3c4e40 | ||
|
|
d1106a730b | ||
|
|
da2d83fc7d | ||
|
|
ec748b2a16 | ||
|
|
5ff44b5ec7 | ||
|
|
dd329c903d | ||
|
|
3be6218341 | ||
|
|
71082bdba8 | ||
|
|
866534334f | ||
|
|
b37c8f6a23 | ||
|
|
df500033bf | ||
|
|
05bf8477a3 | ||
|
|
2b52eff4eb | ||
|
|
d5daf161c6 | ||
|
|
12dfb5ee38 | ||
|
|
39ee9e5e5b | ||
|
|
c7eaf77d15 | ||
|
|
524c799e37 | ||
|
|
fe527fbf1a | ||
|
|
037daff891 | ||
|
|
9945296472 | ||
|
|
fdbc909bde | ||
|
|
1f6b49d236 | ||
|
|
3cdb4e007d | ||
|
|
a4cbb23fc8 | ||
|
|
97573425f9 | ||
|
|
4371e8fab0 | ||
|
|
22826ac10d | ||
|
|
543432bf53 | ||
|
|
d17d0f5452 | ||
|
|
d3ab9b51fa | ||
|
|
ddb647f39f | ||
|
|
70d756d59c | ||
|
|
f3677554e8 | ||
|
|
3cdff65761 | ||
|
|
ff30f86082 | ||
|
|
1038a866a4 | ||
|
|
2c9a12771b | ||
|
|
bda4165bf8 | ||
|
|
84aefd6340 | ||
|
|
acaf08d4b7 | ||
|
|
875912bffd | ||
|
|
86c3744b8c | ||
|
|
875f5c1fa8 | ||
|
|
6468740915 | ||
|
|
79643e32ed | ||
|
|
bf0775fbb6 | ||
|
|
7ee8dc4e44 | ||
|
|
23540c17f1 | ||
|
|
bb873826ca | ||
|
|
93521217a6 | ||
|
|
693fc090b5 | ||
|
|
ca6ea65b1f | ||
|
|
3adbf0de39 | ||
|
|
a0d79b03e7 | ||
|
|
2b3d0a209c | ||
|
|
6d724ad9ff | ||
|
|
0bb0937372 | ||
|
|
cec62db2d8 | ||
|
|
262ae7c942 | ||
|
|
85fb1a3ebb | ||
|
|
a979852ee6 | ||
|
|
9ea03de84f | ||
|
|
9f01e9563f | ||
|
|
f4021e7469 | ||
|
|
5d81e867be | ||
|
|
85d40847ac | ||
|
|
5e938b3147 | ||
|
|
2cfdf2b05d | ||
|
|
d45f7c1302 | ||
|
|
34ad2ccbf1 | ||
|
|
83372bd144 | ||
|
|
1b4f0a5e1f | ||
|
|
14d630ae1c | ||
|
|
cb3615ab47 | ||
|
|
e66970b78b | ||
|
|
2a7364bb18 | ||
|
|
f4d62b317e | ||
|
|
551247d11a | ||
|
|
08f2af489e | ||
|
|
e538128645 | ||
|
|
1e9e2de737 | ||
|
|
8e3fdc5adc | ||
|
|
814473c27d | ||
|
|
a45a02fd64 | ||
|
|
905e05cd36 | ||
|
|
75b69f946d | ||
|
|
a112be79ef | ||
|
|
54a0109d5d | ||
|
|
0d0d61935f | ||
|
|
b959ab37bf | ||
|
|
b025b2b343 | ||
|
|
bffca232e2 | ||
|
|
19dfe7b891 | ||
|
|
ce1e446227 | ||
|
|
5d1905de13 | ||
|
|
23131d6860 | ||
|
|
d4569baa11 | ||
|
|
b5520aa304 | ||
|
|
c53baa9935 | ||
|
|
d0b95bb31c | ||
|
|
7a068c206e | ||
|
|
5a6ac2fb56 | ||
|
|
6b3dfaccfc | ||
|
|
e3d2a0e293 | ||
|
|
a141aaf663 | ||
|
|
85d6b9fd39 | ||
|
|
e9067eb4a3 | ||
|
|
0c955fe54f | ||
|
|
ea5a623c88 | ||
|
|
73c7605a5c | ||
|
|
0b1077a3b3 | ||
|
|
5179ff921b | ||
|
|
abbb3e8656 | ||
|
|
1f665e6ba8 | ||
|
|
d72217a00a | ||
|
|
261d3c892e | ||
|
|
2c50faf5b2 | ||
|
|
7dd0dd273e | ||
|
|
b462cfbe2b | ||
|
|
67b89eaa4f | ||
|
|
ed355d2eb6 | ||
|
|
c6d725d6e8 | ||
|
|
42230a4c51 | ||
|
|
7929aea45a | ||
|
|
f09d299660 | ||
|
|
41b3a372d3 | ||
|
|
7aaecacc5b | ||
|
|
237a05b302 | ||
|
|
f73c7f3be0 | ||
|
|
fe66d8bc04 | ||
|
|
26bd3e3811 | ||
|
|
0b7feb359d | ||
|
|
af4cf0d0ba | ||
|
|
4d561651a1 | ||
|
|
7e3265e7c7 | ||
|
|
822481d360 | ||
|
|
7905f2a972 | ||
|
|
88dc18f88a | ||
|
|
e685d59604 | ||
|
|
300df03743 | ||
|
|
a329007526 | ||
|
|
5784c7bacd | ||
|
|
db82ef3e61 | ||
|
|
ea52b3cc8f | ||
|
|
24a7a72f59 | ||
|
|
b1b17376ff | ||
|
|
c46d835146 | ||
|
|
20dc72ef9a | ||
|
|
e9c4f612cf | ||
|
|
34ad308599 | ||
|
|
9a75277dd4 | ||
|
|
003ab06465 | ||
|
|
36985ee704 | ||
|
|
21acb67b01 | ||
|
|
bda15231da | ||
|
|
5022b04ff9 | ||
|
|
6ed0ff0a12 | ||
|
|
789739f3e4 | ||
|
|
4f1af0114f | ||
|
|
5d1b4f98de | ||
|
|
706cc838e5 | ||
|
|
17cbb34952 | ||
|
|
1f50dee8a8 | ||
|
|
e126315c1b | ||
|
|
ccb5c57784 | ||
|
|
7a41a205ee | ||
|
|
8430aefe21 | ||
|
|
edfdbe10a0 | ||
|
|
108ceae313 | ||
|
|
e42a227a7c | ||
|
|
7e262dd42c | ||
|
|
ac175d2c40 | ||
|
|
c77dbd859b | ||
|
|
efe26d00a1 | ||
|
|
62755cc647 | ||
|
|
8c8a658dfd | ||
|
|
53ca54ced6 | ||
|
|
e757e3beaf | ||
|
|
5192306d39 | ||
|
|
4b98e3ea8e | ||
|
|
b7e522d7a7 | ||
|
|
6d27b4760f | ||
|
|
1b46208aa5 | ||
|
|
b3c01903b4 | ||
|
|
a04ba44874 | ||
|
|
9a8e8ce35d | ||
|
|
234d2e5f0f | ||
|
|
180078d0f6 | ||
|
|
38ad25ad4c | ||
|
|
1bc4b62805 | ||
|
|
7c7106b1c1 | ||
|
|
4677883acd | ||
|
|
8794d35867 | ||
|
|
595c19207c | ||
|
|
077ae9cee9 | ||
|
|
925f1c281c | ||
|
|
7d1fca1ca2 | ||
|
|
fd502631c7 | ||
|
|
60eb755fe9 | ||
|
|
a31289b9d7 | ||
|
|
112f9d1480 | ||
|
|
851f44a066 | ||
|
|
d9c8a9eecb | ||
|
|
03630df20d | ||
|
|
3d37e436dd | ||
|
|
32df9451fd | ||
|
|
e0b835178b | ||
|
|
78aa893efd | ||
|
|
8c7261e7c3 | ||
|
|
ae61272c13 | ||
|
|
67ae22b911 | ||
|
|
c0d26f2308 | ||
|
|
d58ae2ecda | ||
|
|
1d5fef4144 | ||
|
|
49195ebe17 | ||
|
|
9a33bd083d | ||
|
|
b2c7a9c7fc | ||
|
|
8b9f12d924 | ||
|
|
521705a0f2 | ||
|
|
c144df9be3 | ||
|
|
1eb7f92956 | ||
|
|
9125413786 | ||
|
|
afc9495b3f | ||
|
|
3eea19dcfa | ||
|
|
04ab0cd8fc | ||
|
|
b13c29944b | ||
|
|
68bb0c1ae1 | ||
|
|
a18a7bb678 | ||
|
|
834203d868 | ||
|
|
99c7924292 | ||
|
|
505d9e2154 | ||
|
|
bca927f861 | ||
|
|
70d2cbe857 | ||
|
|
dcf358f154 | ||
|
|
2b2d9e7a1d | ||
|
|
328a5fe49a | ||
|
|
839b6dd5e4 | ||
|
|
c988a99d55 | ||
|
|
a4a184b27c | ||
|
|
68a4099684 | ||
|
|
64088fce5d | ||
|
|
35a152318e | ||
|
|
3d0d31ddf8 | ||
|
|
c456f27f60 | ||
|
|
4ecb582c76 | ||
|
|
7390030854 | ||
|
|
da2dee03e2 | ||
|
|
9768d08458 | ||
|
|
a9fe9f43e9 | ||
|
|
61e2cdc6b0 | ||
|
|
60203af9b2 | ||
|
|
4532c2255a | ||
|
|
17c1ed948e | ||
|
|
266d85e917 | ||
|
|
4122abf558 | ||
|
|
f9c1464879 | ||
|
|
277e0aee8c | ||
|
|
957aefe69e | ||
|
|
3515df913f | ||
|
|
8869678c0f | ||
|
|
05f41278a6 | ||
|
|
961cb6e9a3 | ||
|
|
ed53f8aa74 | ||
|
|
be2e00c991 | ||
|
|
f1809ce180 | ||
|
|
549965b4b2 | ||
|
|
f70ee3a038 | ||
|
|
975d6ada66 | ||
|
|
a6e63d2676 | ||
|
|
c5593be7c9 | ||
|
|
65bbdc30de | ||
|
|
482ff2d009 | ||
|
|
fd44894e9a | ||
|
|
92ed05fc84 | ||
|
|
5d333045b9 | ||
|
|
bcafa19386 | ||
|
|
03698e4068 | ||
|
|
fdee7a9ae0 | ||
|
|
078e19d1c7 | ||
|
|
7a2c99353a | ||
|
|
3539b32629 | ||
|
|
247fc43cef | ||
|
|
a3cab174ca | ||
|
|
89e7e8623c | ||
|
|
cbfdf61976 | ||
|
|
3acc05d953 | ||
|
|
685e327b43 | ||
|
|
f572be92e2 | ||
|
|
75d59d8695 | ||
|
|
7c567b305a | ||
|
|
d3d9957fd4 | ||
|
|
c998797c55 | ||
|
|
78d4277e4b | ||
|
|
f3abf9a30a | ||
|
|
9177dc3d52 | ||
|
|
34b303845f | ||
|
|
c1edd5848f | ||
|
|
ed7ec261d0 | ||
|
|
a2cc9f0f6d | ||
|
|
2a005019bf | ||
|
|
c2bbaa9ead | ||
|
|
194a90923d | ||
|
|
e1ed0bf52a | ||
|
|
7112b551e2 | ||
|
|
3498f4d6ee | ||
|
|
8658376713 | ||
|
|
7a40e3f6f2 | ||
|
|
58019fbac0 | ||
|
|
2d27a99a0b | ||
|
|
337e2fa043 | ||
|
|
e7b4dca611 | ||
|
|
1d5b24ecc5 | ||
|
|
2d89ddfb1f | ||
|
|
eeca184836 | ||
|
|
1d2b2b2c8b | ||
|
|
26bb7978b5 | ||
|
|
aa6ac3d6b0 | ||
|
|
1720527f81 | ||
|
|
fe672d2f61 | ||
|
|
d488e84d8d | ||
|
|
a4f88407c2 | ||
|
|
e46c65db09 | ||
|
|
6988b55f50 | ||
|
|
b736c4216c | ||
|
|
5bd8ffa53c | ||
|
|
1496250833 | ||
|
|
772e592489 | ||
|
|
fb44fa6b5a | ||
|
|
5799e391c6 | ||
|
|
e45c52b024 | ||
|
|
db936035e0 | ||
|
|
7b07a17886 | ||
|
|
e3a4c8ddeb | ||
|
|
013262a9b7 | ||
|
|
10f47389ae | ||
|
|
c697a2d47f |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,3 @@
|
||||
build.properties
|
||||
build/
|
||||
logs/
|
||||
reports/
|
||||
|
||||
9
.gitmodules
vendored
9
.gitmodules
vendored
@@ -4,3 +4,12 @@
|
||||
[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 "lib/vendor/doctrine-build-common"]
|
||||
path = lib/vendor/doctrine-build-common
|
||||
url = https://github.com/doctrine/doctrine-build-common.git
|
||||
|
||||
19
.travis.yml
Normal file
19
.travis.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
language: php
|
||||
|
||||
php:
|
||||
- 5.3
|
||||
- 5.4
|
||||
env:
|
||||
- DB=mysql
|
||||
- DB=pgsql
|
||||
- DB=sqlite
|
||||
|
||||
before_script:
|
||||
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'DROP DATABASE IF EXISTS doctrine_tests;' -U postgres; fi"
|
||||
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'DROP DATABASE IF EXISTS doctrine_tests_tmp;' -U postgres; fi"
|
||||
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'create database doctrine_tests;' -U postgres; fi"
|
||||
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'create database doctrine_tests_tmp;' -U postgres; fi"
|
||||
- sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS doctrine_tests_tmp;create database IF NOT EXISTS doctrine_tests;'; fi"
|
||||
- git submodule update --init
|
||||
|
||||
script: phpunit --configuration tests/travis/$DB.travis.xml
|
||||
24
UPGRADE_TO_2_1
Normal file
24
UPGRADE_TO_2_1
Normal file
@@ -0,0 +1,24 @@
|
||||
This document details all the possible changes that you should investigate when updating
|
||||
your project from Doctrine 2.0.x to 2.1
|
||||
|
||||
## Interface for EntityRepository
|
||||
|
||||
The EntityRepository now has an interface Doctrine\Common\Persistence\ObjectRepository. This means that your classes that override EntityRepository and extend find(), findOneBy() or findBy() must be adjusted to follow this interface.
|
||||
|
||||
## AnnotationReader changes
|
||||
|
||||
The annotation reader was heavily refactored between 2.0 and 2.1-RC1. In theory the operation of the new reader should be backwards compatible, but it has to be setup differently to work that way:
|
||||
|
||||
\Doctrine\Common\Annotations\AnnotationRegistry::registerFile('/doctrine-src/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php');
|
||||
|
||||
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
|
||||
$reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\');
|
||||
// new code necessary starting here
|
||||
$reader->setIgnoreNotImportedAnnotations(true);
|
||||
$reader->setEnableParsePhpImports(false);
|
||||
$reader = new \Doctrine\Common\Annotations\CachedReader(
|
||||
new \Doctrine\Common\Annotations\IndexedReader($reader), new ArrayCache()
|
||||
);
|
||||
|
||||
This is already done inside the ``$config->newDefaultAnnotationDriver``, so everything should automatically work if you are using this method. You can verify if everything still works by executing a console command such as schema-validate that loads all metadata into memory.
|
||||
|
||||
11
build.properties
Normal file
11
build.properties
Normal file
@@ -0,0 +1,11 @@
|
||||
# Project Name
|
||||
project.name=DoctrineORM
|
||||
|
||||
# Dependency minimum versions
|
||||
dependencies.common=2.1.0
|
||||
dependencies.dbal=2.1.0
|
||||
dependencies.sfconsole=2.0.0
|
||||
|
||||
# Version class and file
|
||||
project.version_class = Doctrine\ORM\Version
|
||||
project.version_file = lib/Doctrine/ORM/Version.php
|
||||
@@ -1,15 +0,0 @@
|
||||
version=2.0.0BETA2
|
||||
dependencies.common=2.0.0BETA4
|
||||
dependencies.dbal=2.0.0BETA4
|
||||
stability=beta
|
||||
build.dir=build
|
||||
dist.dir=dist
|
||||
report.dir=reports
|
||||
log.archive.dir=logs
|
||||
project.pirum_dir=
|
||||
project.download_dir=
|
||||
test.phpunit_configuration_file=
|
||||
test.phpunit_generate_coverage=0
|
||||
test.pmd_reports=0
|
||||
test.pdepend_exec=
|
||||
test.phpmd_exec=
|
||||
142
build.xml
142
build.xml
@@ -1,11 +1,7 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<!--
|
||||
Doctrine 2 build file.
|
||||
-->
|
||||
|
||||
<project name="Doctrine2" default="build" basedir=".">
|
||||
<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" />
|
||||
|
||||
@@ -14,6 +10,8 @@
|
||||
-->
|
||||
<fileset id="shared-artifacts" dir=".">
|
||||
<include name="LICENSE"/>
|
||||
<include name="UPGRADE*" />
|
||||
<include name="doctrine-mapping.xsd" />
|
||||
</fileset>
|
||||
|
||||
<!--
|
||||
@@ -51,98 +49,34 @@
|
||||
-->
|
||||
<fileset id="symfony-sources" dir="./lib/vendor">
|
||||
<include name="Symfony/Component/**"/>
|
||||
<exclude name="**/.git/**" />
|
||||
</fileset>
|
||||
|
||||
<!--
|
||||
Clean the directory for the next build.
|
||||
-->
|
||||
<target name="clean">
|
||||
<available file="./build.properties" property="build_properties_exist" value="true"/>
|
||||
<fail unless="build_properties_exist" message="The build.properties file is missing." />
|
||||
|
||||
<delete dir="${build.dir}" includeemptydirs="true" />
|
||||
<delete dir="${dist.dir}" includeemptydirs="true" />
|
||||
<delete dir="${report.dir}" includeemptydirs="true" />
|
||||
</target>
|
||||
|
||||
<!--
|
||||
Prepare the new build directories after cleaning
|
||||
-->
|
||||
<target name="prepare" depends="clean">
|
||||
<echo msg="Creating build directory: ${build.dir}" />
|
||||
<mkdir dir="${build.dir}" />
|
||||
<echo msg="Creating distribution directory: ${dist.dir}" />
|
||||
<mkdir dir="${dist.dir}" />
|
||||
<echo msg="Creating report directory: ${report.dir}" />
|
||||
<mkdir dir="${report.dir}" />
|
||||
<mkdir dir="${build.dir}/logs"/>
|
||||
<mkdir dir="${report.dir}/tests"/>
|
||||
</target>
|
||||
|
||||
<!--
|
||||
Builds ORM package, preparing it for distribution.
|
||||
-->
|
||||
<target name="build-orm" depends="prepare">
|
||||
<exec command="grep '${version}' ${project.basedir}/lib/Doctrine/ORM/Version.php" checkreturn="true"/>
|
||||
<copy todir="${build.dir}/doctrine-orm">
|
||||
<target name="copy-files" depends="prepare">
|
||||
<copy todir="${build.dir}/${project.name}-${version}">
|
||||
<fileset refid="shared-artifacts"/>
|
||||
</copy>
|
||||
<copy todir="${build.dir}/doctrine-orm">
|
||||
<copy todir="${build.dir}/${project.name}-${version}">
|
||||
<fileset refid="common-sources"/>
|
||||
<fileset refid="dbal-sources"/>
|
||||
<fileset refid="orm-sources"/>
|
||||
</copy>
|
||||
<copy todir="${build.dir}/doctrine-orm/Doctrine">
|
||||
<copy todir="${build.dir}/${project.name}-${version}/Doctrine">
|
||||
<fileset refid="symfony-sources"/>
|
||||
</copy>
|
||||
<copy todir="${build.dir}/doctrine-orm/bin">
|
||||
<copy todir="${build.dir}/${project.name}-${version}/bin">
|
||||
<fileset refid="bin-scripts"/>
|
||||
</copy>
|
||||
<exec command="sed 's/${version}-DEV/${version}/' ${build.dir}/doctrine-orm/Doctrine/ORM/Version.php > ${build.dir}/doctrine-orm/Doctrine/ORM/Version2.php" passthru="true" />
|
||||
<exec command="mv ${build.dir}/doctrine-orm/Doctrine/ORM/Version2.php ${build.dir}/doctrine-orm/Doctrine/ORM/Version.php" passthru="true" />
|
||||
</target>
|
||||
|
||||
<target name="build" depends="test, build-orm"/>
|
||||
|
||||
<!--
|
||||
Runs the full test suite.
|
||||
-->
|
||||
<target name="test" depends="prepare">
|
||||
<if><equals arg1="${test.phpunit_generate_coverage}" arg2="1" />
|
||||
<then>
|
||||
<property name="test.phpunit_coverage_file" value="${build.dir}/logs/clover.xml" />
|
||||
</then>
|
||||
<else>
|
||||
<property name="test.phpunit_coverage_file" value="false" />
|
||||
</else>
|
||||
</if>
|
||||
|
||||
<nativephpunit
|
||||
testfile="./tests/Doctrine/Tests/AllTests.php" junitlogfile="${build.dir}/logs/testsuites.xml"
|
||||
testdirectory="./tests" coverageclover="${test.phpunit_coverage_file}" configuration="${test.phpunit_configuration_file}"
|
||||
/>
|
||||
<phpunitreport infile="${build.dir}/logs/testsuites.xml" format="frames" todir="${report.dir}/tests" />
|
||||
|
||||
<nativephpunit testfile="./tests/Doctrine/Tests/ORM/Performance/AllTests.php" testdirectory="./tests" haltonfailure="false" haltonerror="false" />
|
||||
<tstamp/>
|
||||
<copy file="${build.dir}/logs/testsuites.xml" tofile="${log.archive.dir}/latest/log.xml" overwrite="true"/>
|
||||
|
||||
<if><equals arg1="${test.pmd_reports}" arg2="1" />
|
||||
<then>
|
||||
<exec command="${test.pdepend_exec} --jdepend-xml=${build.dir}/logs/jdepend.xml ./lib/Doctrine" />
|
||||
<exec command="${test.phpmd_exec} ./lib/Doctrine xml codesize --reportfile ${build.dir}/logs/phpmd.xml" />
|
||||
|
||||
<copy file="${build.dir}/logs/jdepend.xml" tofile="${log.archive.dir}/latest/jdepend.xml" overwrite="true"/>
|
||||
<copy file="${build.dir}/logs/phpmd.xml" tofile="${log.archive.dir}/latest/phpmd.xml" overwrite="true"/>
|
||||
</then>
|
||||
</if>
|
||||
</target>
|
||||
|
||||
<!--
|
||||
Builds distributable PEAR packages.
|
||||
-->
|
||||
<target name="build-packages" depends="build-orm">
|
||||
<d51pearpkg2 baseinstalldir="/" dir="${build.dir}/doctrine-orm">
|
||||
<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>
|
||||
@@ -152,61 +86,29 @@
|
||||
<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="${version}" api="${version}" />
|
||||
<stability release="${stability}" api="${stability}" />
|
||||
<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="DoctrineCommon" channel="pear.doctrine-project.org" minimum_version="${dependencies.common}" maximum_version="2.1.99" />
|
||||
<package name="DoctrineDBAL" channel="pear.doctrine-project.org" minimum_version="${dependencies.dbal}" maximum_version="2.1.99" />
|
||||
<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.php" />
|
||||
<install as="doctrine.bat" name="bin/doctrine.bat" />
|
||||
</release>
|
||||
<replacement path="bin/doctrine.bat" type="pear-config" from="@php_bin@" to="php_bin" />
|
||||
<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>
|
||||
<exec command="pear package" dir="${build.dir}/doctrine-orm" passthru="true" />
|
||||
<exec command="mv DoctrineORM-${version}.tgz ../../dist" dir="${build.dir}/doctrine-orm" passthru="true" />
|
||||
<tar destfile="dist/DoctrineORM-${version}-full.tar.gz" compression="gzip" basedir="${build.dir}">
|
||||
<fileset dir="${build.dir}">
|
||||
<include name="**/**" />
|
||||
<exclude name="logs/" />
|
||||
<exclude name="doctrine-orm/package.xml" />
|
||||
</fileset>
|
||||
</tar>
|
||||
</target>
|
||||
|
||||
<target name="git-tag">
|
||||
<exec command="grep '${version}' ${project.basedir}/lib/Doctrine/ORM/Version.php" checkreturn="true"/>
|
||||
<exec command="git tag -a ${version}" passthru="true" />
|
||||
<exec command="git push origin ${version}" passthru="true" />
|
||||
</target>
|
||||
|
||||
<target name="pirum-release">
|
||||
<exec command="sudo pirum add ${project.pirum_dir} ${project.basedir}/dist/DoctrineORM-${version}.tgz" dir="." passthru="true" />
|
||||
<exec command="sudo pirum build ${project.pirum_dir}" passthru="true" />
|
||||
</target>
|
||||
|
||||
<target name="distribute-download">
|
||||
<copy file="dist/DoctrineORM-${version}-full.tar.gz" todir="${project.download_dir}" />
|
||||
</target>
|
||||
|
||||
<target name="update-dev-version">
|
||||
<exec command="grep '${version}' ${project.basedir}/lib/Doctrine/ORM/Version.php" checkreturn="true"/>
|
||||
<propertyprompt propertyName="next_version" defaultValue="${version}" promptText="Enter next version string (without -DEV)" />
|
||||
<exec command="sed 's/${version}-DEV/${next_version}-DEV/' ${project.basedir}/lib/Doctrine/ORM/Version.php > ${project.basedir}/lib/Doctrine/ORM/Version2.php" passthru="true" />
|
||||
<exec command="mv ${project.basedir}/lib/Doctrine/ORM/Version2.php ${project.basedir}/lib/Doctrine/ORM/Version.php" passthru="true" />
|
||||
<exec command="git add ${project.basedir}/lib/Doctrine/ORM/Version.php" passthru="true" />
|
||||
<exec command="git commit -m 'Bump Dev Version to ${next_version}-DEV'" passthru="true" />
|
||||
<exec command="git push origin master" passthru="true" />
|
||||
</target>
|
||||
|
||||
<target name="release" depends="git-tag,build-packages,distribute-download,pirum-release,update-dev-version" />
|
||||
</project>
|
||||
</project>
|
||||
|
||||
23
composer.json
Normal file
23
composer.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "doctrine/orm",
|
||||
"type": "library","version":"2.1.7",
|
||||
"description": "Object-Relational-Mapper for PHP",
|
||||
"keywords": ["orm", "database"],
|
||||
"homepage": "http://www.doctrine-project.org",
|
||||
"license": "LGPL",
|
||||
"authors": [
|
||||
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
|
||||
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
|
||||
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
|
||||
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.2",
|
||||
"ext-pdo": "*",
|
||||
"doctrine/common": "2.1.*",
|
||||
"doctrine/dbal": "2.1.*"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": { "Doctrine\\ORM": "lib/" }
|
||||
}
|
||||
}
|
||||
@@ -17,11 +17,18 @@
|
||||
<xs:sequence>
|
||||
<xs:element name="mapped-superclass" type="orm:mapped-superclass" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xs:element name="entity" type="orm:entity" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
<xs:complexType name="emptyType"/>
|
||||
<xs:complexType name="emptyType">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="cascade-type">
|
||||
<xs:sequence>
|
||||
@@ -30,7 +37,9 @@
|
||||
<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:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:simpleType name="lifecycle-callback-type">
|
||||
@@ -46,13 +55,32 @@
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:complexType name="lifecycle-callback">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="type" type="orm:lifecycle-callback-type" use="required" />
|
||||
<xs:attribute name="method" type="xs:NMTOKEN" use="required" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="lifecycle-callbacks">
|
||||
<xs:sequence>
|
||||
<xs:element name="lifecycle-callback" type="orm:lifecycle-callback" minOccurs="1" maxOccurs="unbounded" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="named-query">
|
||||
<xs:attribute name="name" type="xs:string" use="required" />
|
||||
<xs:attribute name="query" type="xs:string" use="required" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="named-queries">
|
||||
<xs:sequence>
|
||||
<xs:element name="named-query" type="orm:named-query" minOccurs="1" maxOccurs="unbounded" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
@@ -63,12 +91,14 @@
|
||||
<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="id" type="orm:id" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="named-queries" type="orm:named-queries" minOccurs="0" maxOccurs="1" />
|
||||
<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"/>
|
||||
<xs:element name="one-to-many" type="orm:one-to-many" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xs:element name="many-to-one" type="orm:many-to-one" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xs:element name="many-to-many" type="orm:many-to-many" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:string" use="required" />
|
||||
<xs:attribute name="table" type="xs:NMTOKEN" />
|
||||
@@ -76,11 +106,18 @@
|
||||
<xs:attribute name="repository-class" type="xs:string"/>
|
||||
<xs:attribute name="inheritance-type" type="orm:inheritance-type"/>
|
||||
<xs:attribute name="change-tracking-policy" type="orm:change-tracking-policy" />
|
||||
<xs:attribute name="read-only" type="xs:boolean" default="false" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="mapped-superclass" >
|
||||
<xs:complexContent>
|
||||
<xs:extension base="orm:entity"/>
|
||||
<xs:extension base="orm:entity">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:extension>
|
||||
</xs:complexContent>
|
||||
</xs:complexType>
|
||||
|
||||
@@ -121,10 +158,14 @@
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:enumeration value="EAGER"/>
|
||||
<xs:enumeration value="LAZY"/>
|
||||
<xs:enumeration value="EXTRALAZY"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:complexType name="field">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="type" type="xs:NMTOKEN" default="string" />
|
||||
<xs:attribute name="column" type="xs:NMTOKEN" />
|
||||
@@ -135,75 +176,114 @@
|
||||
<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:complexType name="discriminator-column">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="type" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="field-name" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="length" type="xs:NMTOKEN" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="unique-constraint">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="optional"/>
|
||||
<xs:attribute name="columns" type="xs:string" use="required"/>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="unique-constraints">
|
||||
<xs:sequence>
|
||||
<xs:element name="unique-constraint" type="orm:unique-constraint" minOccurs="1" maxOccurs="unbounded"/>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="index">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="optional"/>
|
||||
<xs:attribute name="columns" type="xs:NMTOKENS" use="required"/>
|
||||
<xs:attribute name="columns" type="xs:string" use="required"/>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="indexes">
|
||||
<xs:sequence>
|
||||
<xs:element name="index" type="orm:index" minOccurs="1" maxOccurs="unbounded"/>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="discriminator-mapping">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="value" type="xs:NMTOKEN" use="required"/>
|
||||
<xs:attribute name="class" type="xs:NMTOKEN" use="required"/>
|
||||
<xs:attribute name="class" type="xs:string" use="required"/>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="discriminator-map">
|
||||
<xs:sequence>
|
||||
<xs:element name="discriminator-mapping" type="orm:discriminator-mapping" minOccurs="1" maxOccurs="unbounded"/>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="generator">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="strategy" type="orm:generator-strategy" use="optional" default="AUTO" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="id">
|
||||
<xs:sequence>
|
||||
<xs:element name="generator" type="orm:generator" minOccurs="0" />
|
||||
<xs:element name="sequence-generator" type="orm:sequence-generator" minOccurs="0" maxOccurs="1" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="type" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="type" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="column" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="association-key" type="xs:boolean" default="false" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="sequence-generator">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="sequence-name" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="allocation-size" type="xs:integer" use="optional" default="1" />
|
||||
<xs:attribute name="initial-value" type="xs:integer" use="optional" default="1" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="inverse-join-columns">
|
||||
<xs:sequence>
|
||||
<xs:element name="join-column" type="orm:join-column" minOccurs="1" maxOccurs="unbounded" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="join-column">
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="referenced-column-name" type="xs:NMTOKEN" use="optional" default="id" />
|
||||
<xs:attribute name="unique" type="xs:boolean" default="false" />
|
||||
@@ -211,32 +291,43 @@
|
||||
<xs:attribute name="on-delete" type="orm:fk-action" />
|
||||
<xs:attribute name="on-update" type="orm:fk-action" />
|
||||
<xs:attribute name="column-definition" type="xs:string" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="join-columns">
|
||||
<xs:sequence>
|
||||
<xs:element name="join-column" type="orm:join-column" minOccurs="1" maxOccurs="unbounded" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="join-table">
|
||||
<xs:sequence>
|
||||
<xs:element name="join-columns" type="orm:join-columns" />
|
||||
<xs:element name="inverse-join-columns" type="orm:join-columns" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="schema" type="xs:NMTOKEN" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="order-by">
|
||||
<xs:sequence>
|
||||
<xs:element name="order-by-field" type="orm:order-by-field" minOccurs="1" maxOccurs="unbounded" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="order-by-field">
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="direction" type="orm:order-by-direction" default="ASC" />
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="direction" type="orm:order-by-direction" default="ASC" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:simpleType name="order-by-direction">
|
||||
@@ -251,24 +342,30 @@
|
||||
<xs:element name="cascade" type="orm:cascade-type" minOccurs="0" />
|
||||
<xs:element name="join-table" type="orm:join-table" minOccurs="0" />
|
||||
<xs:element name="order-by" type="orm:order-by" minOccurs="0" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="target-entity" type="xs:string" use="required" />
|
||||
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="mapped-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="index-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="one-to-many">
|
||||
<xs:sequence>
|
||||
<xs:element name="cascade" type="orm:cascade-type" minOccurs="0" />
|
||||
<xs:element name="order-by" type="orm:order-by" minOccurs="0" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="target-entity" type="xs:string" use="required" />
|
||||
<xs:attribute name="mapped-by" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="index-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="many-to-one">
|
||||
@@ -277,13 +374,16 @@
|
||||
<xs:choice minOccurs="0" maxOccurs="1">
|
||||
<xs:element name="join-column" type="orm:join-column"/>
|
||||
<xs:element name="join-columns" type="orm:join-columns"/>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:choice>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="target-entity" type="xs:string" use="required" />
|
||||
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
||||
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="one-to-one">
|
||||
@@ -292,7 +392,9 @@
|
||||
<xs:choice minOccurs="0" maxOccurs="1">
|
||||
<xs:element name="join-column" type="orm:join-column"/>
|
||||
<xs:element name="join-columns" type="orm:join-columns"/>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:choice>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="target-entity" type="xs:string" use="required" />
|
||||
@@ -300,6 +402,7 @@
|
||||
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
</xs:schema>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<?php
|
||||
/*
|
||||
* $Id: Abstract.php 1393 2008-03-06 17:49:16Z guilhermeblanco $
|
||||
*
|
||||
* 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
|
||||
@@ -57,6 +55,11 @@ abstract class AbstractQuery
|
||||
*/
|
||||
const HYDRATE_SINGLE_SCALAR = 4;
|
||||
|
||||
/**
|
||||
* Very simple object hydrator (optimized for performance).
|
||||
*/
|
||||
const HYDRATE_SIMPLEOBJECT = 5;
|
||||
|
||||
/**
|
||||
* @var array The parameter map of this query.
|
||||
*/
|
||||
@@ -73,7 +76,7 @@ abstract class AbstractQuery
|
||||
protected $_resultSetMapping;
|
||||
|
||||
/**
|
||||
* @var Doctrine\ORM\EntityManager The entity manager used by this query object.
|
||||
* @var \Doctrine\ORM\EntityManager The entity manager used by this query object.
|
||||
*/
|
||||
protected $_em;
|
||||
|
||||
@@ -119,7 +122,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 $entityManager
|
||||
*/
|
||||
public function __construct(EntityManager $em)
|
||||
{
|
||||
@@ -129,7 +132,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Retrieves the associated EntityManager of this Query instance.
|
||||
*
|
||||
* @return Doctrine\ORM\EntityManager
|
||||
* @return \Doctrine\ORM\EntityManager
|
||||
*/
|
||||
public function getEntityManager()
|
||||
{
|
||||
@@ -159,6 +162,16 @@ abstract class AbstractQuery
|
||||
{
|
||||
return $this->_params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all defined parameter types.
|
||||
*
|
||||
* @return array The defined query parameter types.
|
||||
*/
|
||||
public function getParameterTypes()
|
||||
{
|
||||
return $this->_paramTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a query parameter.
|
||||
@@ -171,6 +184,17 @@ abstract class AbstractQuery
|
||||
return isset($this->_params[$key]) ? $this->_params[$key] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a query parameter type.
|
||||
*
|
||||
* @param mixed $key The key (index or name) of the bound parameter.
|
||||
* @return mixed The parameter type of the bound parameter.
|
||||
*/
|
||||
public function getParameterType($key)
|
||||
{
|
||||
return isset($this->_paramTypes[$key]) ? $this->_paramTypes[$key] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the SQL query that corresponds to this query object.
|
||||
* The returned SQL syntax depends on the connection driver that is used
|
||||
@@ -188,14 +212,17 @@ abstract class AbstractQuery
|
||||
* @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.
|
||||
* @return Doctrine\ORM\AbstractQuery This query instance.
|
||||
* @return \Doctrine\ORM\AbstractQuery This query instance.
|
||||
*/
|
||||
public function setParameter($key, $value, $type = null)
|
||||
{
|
||||
if ($type !== null) {
|
||||
$this->_paramTypes[$key] = $type;
|
||||
if ($type === null) {
|
||||
$type = Query\ParameterTypeInferer::inferType($value);
|
||||
}
|
||||
|
||||
$this->_paramTypes[$key] = $type;
|
||||
$this->_params[$key] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -204,7 +231,7 @@ abstract class AbstractQuery
|
||||
*
|
||||
* @param array $params
|
||||
* @param array $types
|
||||
* @return Doctrine\ORM\AbstractQuery This query instance.
|
||||
* @return \Doctrine\ORM\AbstractQuery This query instance.
|
||||
*/
|
||||
public function setParameters(array $params, array $types = array())
|
||||
{
|
||||
@@ -222,7 +249,7 @@ abstract class AbstractQuery
|
||||
* Sets the ResultSetMapping that should be used for hydration.
|
||||
*
|
||||
* @param ResultSetMapping $rsm
|
||||
* @return Doctrine\ORM\AbstractQuery
|
||||
* @return \Doctrine\ORM\AbstractQuery
|
||||
*/
|
||||
public function setResultSetMapping(Query\ResultSetMapping $rsm)
|
||||
{
|
||||
@@ -233,8 +260,8 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Defines a cache driver to be used for caching result sets.
|
||||
*
|
||||
* @param Doctrine\Common\Cache\Cache $driver Cache driver
|
||||
* @return Doctrine\ORM\AbstractQuery
|
||||
* @param \Doctrine\Common\Cache\Cache $driver Cache driver
|
||||
* @return \Doctrine\ORM\AbstractQuery
|
||||
*/
|
||||
public function setResultCacheDriver($resultCacheDriver = null)
|
||||
{
|
||||
@@ -251,7 +278,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Returns the cache driver used for caching result sets.
|
||||
*
|
||||
* @return Doctrine\Common\Cache\Cache Cache driver
|
||||
* @return \Doctrine\Common\Cache\Cache Cache driver
|
||||
*/
|
||||
public function getResultCacheDriver()
|
||||
{
|
||||
@@ -269,7 +296,7 @@ abstract class AbstractQuery
|
||||
* @param boolean $bool
|
||||
* @param integer $timeToLive
|
||||
* @param string $resultCacheId
|
||||
* @return This query instance.
|
||||
* @return \Doctrine\ORM\AbstractQuery This query instance.
|
||||
*/
|
||||
public function useResultCache($bool, $timeToLive = null, $resultCacheId = null)
|
||||
{
|
||||
@@ -287,7 +314,7 @@ abstract class AbstractQuery
|
||||
* Defines how long the result cache will be active before expire.
|
||||
*
|
||||
* @param integer $timeToLive How long the cache entry is valid.
|
||||
* @return Doctrine\ORM\AbstractQuery This query instance.
|
||||
* @return \Doctrine\ORM\AbstractQuery This query instance.
|
||||
*/
|
||||
public function setResultCacheLifetime($timeToLive)
|
||||
{
|
||||
@@ -313,7 +340,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.
|
||||
* @return \Doctrine\ORM\AbstractQuery This query instance.
|
||||
*/
|
||||
public function expireResultCache($expire = true)
|
||||
{
|
||||
@@ -331,12 +358,32 @@ abstract class AbstractQuery
|
||||
return $this->_expireResultCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the default fetch mode of an association for this query.
|
||||
*
|
||||
* $fetchMode can be one of ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY
|
||||
*
|
||||
* @param string $class
|
||||
* @param string $assocName
|
||||
* @param int $fetchMode
|
||||
* @return AbstractQuery
|
||||
*/
|
||||
public function setFetchMode($class, $assocName, $fetchMode)
|
||||
{
|
||||
if ($fetchMode !== Mapping\ClassMetadata::FETCH_EAGER) {
|
||||
$fetchMode = Mapping\ClassMetadata::FETCH_LAZY;
|
||||
}
|
||||
|
||||
$this->_hints['fetchMode'][$class][$assocName] = $fetchMode;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the processing mode to be used during hydration / result set transformation.
|
||||
*
|
||||
* @param integer $hydrationMode Doctrine processing mode to be used during hydration process.
|
||||
* One of the Query::HYDRATE_* constants.
|
||||
* @return Doctrine\ORM\AbstractQuery This query instance.
|
||||
* @return \Doctrine\ORM\AbstractQuery This query instance.
|
||||
*/
|
||||
public function setHydrationMode($hydrationMode)
|
||||
{
|
||||
@@ -390,6 +437,31 @@ abstract class AbstractQuery
|
||||
return $this->execute(array(), self::HYDRATE_SCALAR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get exactly one result or null.
|
||||
*
|
||||
* @throws NonUniqueResultException
|
||||
* @param int $hydrationMode
|
||||
* @return mixed
|
||||
*/
|
||||
public function getOneOrNullResult($hydrationMode = null)
|
||||
{
|
||||
$result = $this->execute(array(), $hydrationMode);
|
||||
|
||||
if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_array($result)) {
|
||||
if (count($result) > 1) {
|
||||
throw new NonUniqueResultException;
|
||||
}
|
||||
return array_shift($result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the single result of the query.
|
||||
*
|
||||
@@ -439,7 +511,7 @@ abstract class AbstractQuery
|
||||
*
|
||||
* @param string $name The name of the hint.
|
||||
* @param mixed $value The value of the hint.
|
||||
* @return Doctrine\ORM\AbstractQuery
|
||||
* @return \Doctrine\ORM\AbstractQuery
|
||||
*/
|
||||
public function setHint($name, $value)
|
||||
{
|
||||
@@ -458,6 +530,16 @@ abstract class AbstractQuery
|
||||
return isset($this->_hints[$name]) ? $this->_hints[$name] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key value map of query hints that are currently set.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getHints()
|
||||
{
|
||||
return $this->_hints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the query and returns an IterableResult that can be used to incrementally
|
||||
* iterate over the result.
|
||||
@@ -466,17 +548,27 @@ abstract class AbstractQuery
|
||||
* @param integer $hydrationMode The hydration mode to use.
|
||||
* @return IterableResult
|
||||
*/
|
||||
public function iterate(array $params = array(), $hydrationMode = self::HYDRATE_OBJECT)
|
||||
public function iterate(array $params = array(), $hydrationMode = null)
|
||||
{
|
||||
if ($hydrationMode !== null) {
|
||||
$this->setHydrationMode($hydrationMode);
|
||||
}
|
||||
|
||||
if ($params) {
|
||||
$this->setParameters($params);
|
||||
}
|
||||
|
||||
$stmt = $this->_doExecute();
|
||||
|
||||
return $this->_em->newHydrator($this->_hydrationMode)->iterate(
|
||||
$this->_doExecute($params, $hydrationMode), $this->_resultSetMapping, $this->_hints
|
||||
$stmt, $this->_resultSetMapping, $this->_hints
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the query.
|
||||
*
|
||||
* @param string $params Any additional query parameters.
|
||||
* @param array $params Any additional query parameters.
|
||||
* @param integer $hydrationMode Processing mode to be used during the hydration process.
|
||||
* @return mixed
|
||||
*/
|
||||
@@ -490,29 +582,25 @@ abstract class AbstractQuery
|
||||
$this->setParameters($params);
|
||||
}
|
||||
|
||||
if (isset($this->_params[0])) {
|
||||
throw QueryException::invalidParameterPosition(0);
|
||||
}
|
||||
|
||||
// Check result cache
|
||||
if ($this->_useResultCache && $cacheDriver = $this->getResultCacheDriver()) {
|
||||
$id = $this->_getResultCacheId();
|
||||
$cached = $this->_expireResultCache ? false : $cacheDriver->fetch($id);
|
||||
list($key, $hash) = $this->getResultCacheId();
|
||||
$cached = $this->_expireResultCache ? false : $cacheDriver->fetch($hash);
|
||||
|
||||
if ($cached === false) {
|
||||
if ($cached === false || !isset($cached[$key])) {
|
||||
// Cache miss.
|
||||
$stmt = $this->_doExecute();
|
||||
|
||||
$result = $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
|
||||
$stmt, $this->_resultSetMapping, $this->_hints
|
||||
);
|
||||
$stmt, $this->_resultSetMapping, $this->_hints
|
||||
);
|
||||
|
||||
$cacheDriver->save($id, $result, $this->_resultCacheTTL);
|
||||
$cacheDriver->save($hash, array($key => $result), $this->_resultCacheTTL);
|
||||
|
||||
return $result;
|
||||
} else {
|
||||
// Cache hit.
|
||||
return $cached;
|
||||
return $cached[$key];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,7 +621,7 @@ abstract class AbstractQuery
|
||||
* generated for you.
|
||||
*
|
||||
* @param string $id
|
||||
* @return Doctrine\ORM\AbstractQuery This query instance.
|
||||
* @return \Doctrine\ORM\AbstractQuery This query instance.
|
||||
*/
|
||||
public function setResultCacheId($id)
|
||||
{
|
||||
@@ -546,12 +634,12 @@ abstract class AbstractQuery
|
||||
* Will return the configured id if it exists otherwise a hash will be
|
||||
* automatically generated for you.
|
||||
*
|
||||
* @return string $id
|
||||
* @return array ($key, $hash)
|
||||
*/
|
||||
protected function _getResultCacheId()
|
||||
protected function getResultCacheId()
|
||||
{
|
||||
if ($this->_resultCacheId) {
|
||||
return $this->_resultCacheId;
|
||||
return array($this->_resultCacheId, $this->_resultCacheId);
|
||||
} else {
|
||||
$params = $this->_params;
|
||||
foreach ($params AS $key => $value) {
|
||||
@@ -563,20 +651,23 @@ abstract class AbstractQuery
|
||||
$idValues = $class->getIdentifierValues($value);
|
||||
}
|
||||
$params[$key] = $idValues;
|
||||
} else {
|
||||
$params[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$sql = $this->getSql();
|
||||
ksort($this->_hints);
|
||||
return md5(implode(";", (array)$sql) . var_export($params, true) .
|
||||
var_export($this->_hints, true)."&hydrationMode=".$this->_hydrationMode);
|
||||
$key = implode(";", (array)$sql) . var_export($params, true) .
|
||||
var_export($this->_hints, true)."&hydrationMode=".$this->_hydrationMode;
|
||||
return array($key, md5($key));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the query and returns a the resulting Statement object.
|
||||
*
|
||||
* @return Doctrine\DBAL\Driver\Statement The executed database statement that holds the results.
|
||||
* @return \Doctrine\DBAL\Driver\Statement The executed database statement that holds the results.
|
||||
*/
|
||||
abstract protected function _doExecute();
|
||||
|
||||
|
||||
@@ -20,7 +20,12 @@
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use Doctrine\Common\Cache\Cache,
|
||||
Doctrine\ORM\Mapping\Driver\Driver;
|
||||
Doctrine\Common\Cache\ArrayCache,
|
||||
Doctrine\Common\Annotations\AnnotationRegistry,
|
||||
Doctrine\Common\Annotations\AnnotationReader,
|
||||
Doctrine\Common\Annotations\SimpleAnnotationReader,
|
||||
Doctrine\ORM\Mapping\Driver\Driver,
|
||||
Doctrine\ORM\Mapping\Driver\AnnotationDriver;
|
||||
|
||||
/**
|
||||
* Configuration container for all configuration options of Doctrine.
|
||||
@@ -81,7 +86,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
|
||||
/**
|
||||
* Gets the namespace where proxy classes reside.
|
||||
*
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getProxyNamespace()
|
||||
@@ -92,7 +97,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
|
||||
/**
|
||||
* Sets the namespace where proxy classes reside.
|
||||
*
|
||||
*
|
||||
* @param string $ns
|
||||
*/
|
||||
public function setProxyNamespace($ns)
|
||||
@@ -114,16 +119,35 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
|
||||
/**
|
||||
* Add a new default annotation driver with a correctly configured annotation reader.
|
||||
*
|
||||
*
|
||||
* @param array $paths
|
||||
* @return Mapping\Driver\AnnotationDriver
|
||||
*/
|
||||
public function newDefaultAnnotationDriver($paths = array())
|
||||
{
|
||||
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
|
||||
$reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\');
|
||||
|
||||
return new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($reader, (array)$paths);
|
||||
if (version_compare(\Doctrine\Common\Version::VERSION, '2.2.0-DEV', '>=')) {
|
||||
// Register the ORM Annotations in the AnnotationRegistry
|
||||
AnnotationRegistry::registerFile(__DIR__ . '/Mapping/Driver/DoctrineAnnotations.php');
|
||||
|
||||
$reader = new SimpleAnnotationReader();
|
||||
$reader->addNamespace('Doctrine\ORM\Mapping');
|
||||
$reader = new \Doctrine\Common\Annotations\CachedReader($reader, new ArrayCache());
|
||||
} else if (version_compare(\Doctrine\Common\Version::VERSION, '2.1.0-DEV', '>=')) {
|
||||
// Register the ORM Annotations in the AnnotationRegistry
|
||||
AnnotationRegistry::registerFile(__DIR__ . '/Mapping/Driver/DoctrineAnnotations.php');
|
||||
|
||||
$reader = new AnnotationReader();
|
||||
$reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\');
|
||||
$reader->setIgnoreNotImportedAnnotations(true);
|
||||
$reader->setEnableParsePhpImports(false);
|
||||
$reader = new \Doctrine\Common\Annotations\CachedReader(
|
||||
new \Doctrine\Common\Annotations\IndexedReader($reader), new ArrayCache()
|
||||
);
|
||||
} else {
|
||||
$reader = new AnnotationReader();
|
||||
$reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\');
|
||||
}
|
||||
return new AnnotationDriver($reader, (array)$paths);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,7 +164,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
/**
|
||||
* Resolves a registered namespace alias to the full namespace.
|
||||
*
|
||||
* @param string $entityNamespaceAlias
|
||||
* @param string $entityNamespaceAlias
|
||||
* @return string
|
||||
* @throws MappingException
|
||||
*/
|
||||
@@ -164,6 +188,16 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
$this->_attributes['entityNamespaces'] = $entityNamespaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the list of registered entity namespace aliases.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getEntityNamespaces()
|
||||
{
|
||||
return $this->_attributes['entityNamespaces'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cache driver implementation that is used for the mapping metadata.
|
||||
*
|
||||
@@ -328,7 +362,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
|
||||
/**
|
||||
* Gets the implementation class name of a registered custom string DQL function.
|
||||
*
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
@@ -371,7 +405,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
|
||||
/**
|
||||
* Gets the implementation class name of a registered custom numeric DQL function.
|
||||
*
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
@@ -414,7 +448,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
|
||||
/**
|
||||
* Gets the implementation class name of a registered custom date/time DQL function.
|
||||
*
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
@@ -465,7 +499,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
|
||||
/**
|
||||
* Set a class metadata factory.
|
||||
*
|
||||
*
|
||||
* @param string $cmf
|
||||
*/
|
||||
public function setClassMetadataFactoryName($cmfName)
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace Doctrine\ORM;
|
||||
|
||||
use Closure, Exception,
|
||||
Doctrine\Common\EventManager,
|
||||
Doctrine\Common\Persistence\ObjectManager,
|
||||
Doctrine\DBAL\Connection,
|
||||
Doctrine\DBAL\LockMode,
|
||||
Doctrine\ORM\Mapping\ClassMetadata,
|
||||
@@ -37,26 +38,26 @@ use Closure, Exception,
|
||||
* @author Jonathan Wage <jonwage@gmail.com>
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
*/
|
||||
class EntityManager
|
||||
class EntityManager implements ObjectManager
|
||||
{
|
||||
/**
|
||||
* The used Configuration.
|
||||
*
|
||||
* @var Doctrine\ORM\Configuration
|
||||
* @var \Doctrine\ORM\Configuration
|
||||
*/
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* The database connection used by the EntityManager.
|
||||
*
|
||||
* @var Doctrine\DBAL\Connection
|
||||
* @var \Doctrine\DBAL\Connection
|
||||
*/
|
||||
private $conn;
|
||||
|
||||
/**
|
||||
* The metadata factory, used to retrieve the ORM metadata of entity classes.
|
||||
*
|
||||
* @var Doctrine\ORM\Mapping\ClassMetadataFactory
|
||||
* @var \Doctrine\ORM\Mapping\ClassMetadataFactory
|
||||
*/
|
||||
private $metadataFactory;
|
||||
|
||||
@@ -70,14 +71,14 @@ class EntityManager
|
||||
/**
|
||||
* The UnitOfWork used to coordinate object-level transactions.
|
||||
*
|
||||
* @var Doctrine\ORM\UnitOfWork
|
||||
* @var \Doctrine\ORM\UnitOfWork
|
||||
*/
|
||||
private $unitOfWork;
|
||||
|
||||
/**
|
||||
* The event manager that is the central point of the event system.
|
||||
*
|
||||
* @var Doctrine\Common\EventManager
|
||||
* @var \Doctrine\Common\EventManager
|
||||
*/
|
||||
private $eventManager;
|
||||
|
||||
@@ -91,17 +92,21 @@ class EntityManager
|
||||
/**
|
||||
* The proxy factory used to create dynamic proxies.
|
||||
*
|
||||
* @var Doctrine\ORM\Proxy\ProxyFactory
|
||||
* @var \Doctrine\ORM\Proxy\ProxyFactory
|
||||
*/
|
||||
private $proxyFactory;
|
||||
|
||||
/**
|
||||
* @var ExpressionBuilder The expression builder instance used to generate query expressions.
|
||||
* The expression builder instance used to generate query expressions.
|
||||
*
|
||||
* @var \Doctrine\ORM\Query\Expr
|
||||
*/
|
||||
private $expressionBuilder;
|
||||
|
||||
/**
|
||||
* Whether the EntityManager is closed or not.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $closed = false;
|
||||
|
||||
@@ -109,9 +114,9 @@ class EntityManager
|
||||
* 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\Common\EventManager $eventManager
|
||||
* @param \Doctrine\DBAL\Connection $conn
|
||||
* @param \Doctrine\ORM\Configuration $config
|
||||
* @param \Doctrine\Common\EventManager $eventManager
|
||||
*/
|
||||
protected function __construct(Connection $conn, Configuration $config, EventManager $eventManager)
|
||||
{
|
||||
@@ -123,7 +128,7 @@ class EntityManager
|
||||
$this->metadataFactory = new $metadataFactoryClassName;
|
||||
$this->metadataFactory->setEntityManager($this);
|
||||
$this->metadataFactory->setCacheDriver($this->config->getMetadataCacheImpl());
|
||||
|
||||
|
||||
$this->unitOfWork = new UnitOfWork($this);
|
||||
$this->proxyFactory = new ProxyFactory($this,
|
||||
$config->getProxyDir(),
|
||||
@@ -134,7 +139,7 @@ class EntityManager
|
||||
/**
|
||||
* Gets the database connection object used by the EntityManager.
|
||||
*
|
||||
* @return Doctrine\DBAL\Connection
|
||||
* @return \Doctrine\DBAL\Connection
|
||||
*/
|
||||
public function getConnection()
|
||||
{
|
||||
@@ -144,7 +149,7 @@ class EntityManager
|
||||
/**
|
||||
* Gets the metadata factory used to gather the metadata of classes.
|
||||
*
|
||||
* @return Doctrine\ORM\Mapping\ClassMetadataFactory
|
||||
* @return \Doctrine\ORM\Mapping\ClassMetadataFactory
|
||||
*/
|
||||
public function getMetadataFactory()
|
||||
{
|
||||
@@ -163,7 +168,7 @@ class EntityManager
|
||||
* ->where($expr->orX($expr->eq('u.id', 1), $expr->eq('u.id', 2)));
|
||||
* </code>
|
||||
*
|
||||
* @return ExpressionBuilder
|
||||
* @return \Doctrine\ORM\Query\Expr
|
||||
*/
|
||||
public function getExpressionBuilder()
|
||||
{
|
||||
@@ -198,13 +203,18 @@ class EntityManager
|
||||
public function transactional(Closure $func)
|
||||
{
|
||||
$this->conn->beginTransaction();
|
||||
|
||||
try {
|
||||
$func($this);
|
||||
$return = $func($this);
|
||||
|
||||
$this->flush();
|
||||
$this->conn->commit();
|
||||
|
||||
return $return ?: true;
|
||||
} catch (Exception $e) {
|
||||
$this->close();
|
||||
$this->conn->rollback();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
@@ -234,12 +244,12 @@ class EntityManager
|
||||
*
|
||||
* The class name must be the fully-qualified class name without a leading backslash
|
||||
* (as it is returned by get_class($obj)) or an aliased class name.
|
||||
*
|
||||
*
|
||||
* Examples:
|
||||
* MyProject\Domain\User
|
||||
* sales:PriceRequest
|
||||
*
|
||||
* @return Doctrine\ORM\Mapping\ClassMetadata
|
||||
* @return \Doctrine\ORM\Mapping\ClassMetadata
|
||||
* @internal Performance-sensitive method.
|
||||
*/
|
||||
public function getClassMetadata($className)
|
||||
@@ -251,7 +261,7 @@ class EntityManager
|
||||
* Creates a new Query object.
|
||||
*
|
||||
* @param string The DQL string.
|
||||
* @return Doctrine\ORM\Query
|
||||
* @return \Doctrine\ORM\Query
|
||||
*/
|
||||
public function createQuery($dql = "")
|
||||
{
|
||||
@@ -266,7 +276,7 @@ class EntityManager
|
||||
* Creates a Query from a named query.
|
||||
*
|
||||
* @param string $name
|
||||
* @return Doctrine\ORM\Query
|
||||
* @return \Doctrine\ORM\Query
|
||||
*/
|
||||
public function createNamedQuery($name)
|
||||
{
|
||||
@@ -292,7 +302,7 @@ class EntityManager
|
||||
* Creates a NativeQuery from a named native query.
|
||||
*
|
||||
* @param string $name
|
||||
* @return Doctrine\ORM\NativeQuery
|
||||
* @return \Doctrine\ORM\NativeQuery
|
||||
*/
|
||||
public function createNamedNativeQuery($name)
|
||||
{
|
||||
@@ -315,7 +325,7 @@ class EntityManager
|
||||
* This effectively synchronizes the in-memory state of managed objects with the
|
||||
* database.
|
||||
*
|
||||
* @throws Doctrine\ORM\OptimisticLockException If a version check on an entity that
|
||||
* @throws \Doctrine\ORM\OptimisticLockException If a version check on an entity that
|
||||
* makes use of optimistic locking fails.
|
||||
*/
|
||||
public function flush()
|
||||
@@ -354,7 +364,7 @@ class EntityManager
|
||||
|
||||
// Check identity map first, if its already in there just return it.
|
||||
if ($entity = $this->unitOfWork->tryGetById($identifier, $class->rootEntityName)) {
|
||||
return $entity;
|
||||
return ($entity instanceof $class->name) ? $entity : null;
|
||||
}
|
||||
if ($class->subClasses) {
|
||||
$entity = $this->find($entityName, $identifier);
|
||||
@@ -394,7 +404,7 @@ class EntityManager
|
||||
|
||||
// Check identity map first, if its already in there just return it.
|
||||
if ($entity = $this->unitOfWork->tryGetById($identifier, $class->rootEntityName)) {
|
||||
return $entity;
|
||||
return ($entity instanceof $class->name) ? $entity : null;
|
||||
}
|
||||
if ( ! is_array($identifier)) {
|
||||
$identifier = array($class->identifier[0] => $identifier);
|
||||
@@ -403,6 +413,7 @@ class EntityManager
|
||||
$entity = $class->newInstance();
|
||||
$class->setIdentifierValues($entity, $identifier);
|
||||
$this->unitOfWork->registerManaged($entity, $identifier, array());
|
||||
$this->unitOfWork->markReadOnly($entity);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
@@ -439,7 +450,7 @@ class EntityManager
|
||||
*
|
||||
* The entity will be entered into the database at or before transaction
|
||||
* commit or as a result of the flush operation.
|
||||
*
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
@@ -590,7 +601,7 @@ class EntityManager
|
||||
/**
|
||||
* Gets the EventManager used by the EntityManager.
|
||||
*
|
||||
* @return Doctrine\Common\EventManager
|
||||
* @return \Doctrine\Common\EventManager
|
||||
*/
|
||||
public function getEventManager()
|
||||
{
|
||||
@@ -600,7 +611,7 @@ class EntityManager
|
||||
/**
|
||||
* Gets the Configuration used by the EntityManager.
|
||||
*
|
||||
* @return Doctrine\ORM\Configuration
|
||||
* @return \Doctrine\ORM\Configuration
|
||||
*/
|
||||
public function getConfiguration()
|
||||
{
|
||||
@@ -621,7 +632,7 @@ class EntityManager
|
||||
|
||||
/**
|
||||
* Check if the Entity manager is open or closed.
|
||||
*
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isOpen()
|
||||
@@ -632,7 +643,7 @@ class EntityManager
|
||||
/**
|
||||
* Gets the UnitOfWork used by the EntityManager to coordinate operations.
|
||||
*
|
||||
* @return Doctrine\ORM\UnitOfWork
|
||||
* @return \Doctrine\ORM\UnitOfWork
|
||||
*/
|
||||
public function getUnitOfWork()
|
||||
{
|
||||
@@ -646,7 +657,7 @@ class EntityManager
|
||||
* selectively iterate over the result.
|
||||
*
|
||||
* @param int $hydrationMode
|
||||
* @return Doctrine\ORM\Internal\Hydration\AbstractHydrator
|
||||
* @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator
|
||||
*/
|
||||
public function getHydrator($hydrationMode)
|
||||
{
|
||||
@@ -661,7 +672,7 @@ class EntityManager
|
||||
* Create a new instance for the given hydration mode.
|
||||
*
|
||||
* @param int $hydrationMode
|
||||
* @return Doctrine\ORM\Internal\Hydration\AbstractHydrator
|
||||
* @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator
|
||||
*/
|
||||
public function newHydrator($hydrationMode)
|
||||
{
|
||||
@@ -678,6 +689,9 @@ class EntityManager
|
||||
case Query::HYDRATE_SINGLE_SCALAR:
|
||||
$hydrator = new Internal\Hydration\SingleScalarHydrator($this);
|
||||
break;
|
||||
case Query::HYDRATE_SIMPLEOBJECT:
|
||||
$hydrator = new Internal\Hydration\SimpleObjectHydrator($this);
|
||||
break;
|
||||
default:
|
||||
if ($class = $this->config->getCustomHydrationMode($hydrationMode)) {
|
||||
$hydrator = new $class($this);
|
||||
@@ -699,6 +713,14 @@ class EntityManager
|
||||
return $this->proxyFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function initializeObject($entity)
|
||||
{
|
||||
$this->unitOfWork->initializeObject($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create EntityManager instances.
|
||||
*
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\Common\Persistence\ObjectRepository;
|
||||
|
||||
/**
|
||||
* An EntityRepository serves as a repository for entities with generic as well as
|
||||
@@ -34,7 +35,7 @@ use Doctrine\DBAL\LockMode;
|
||||
* @author Jonathan Wage <jonwage@gmail.com>
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
*/
|
||||
class EntityRepository
|
||||
class EntityRepository implements ObjectRepository
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
@@ -47,7 +48,7 @@ class EntityRepository
|
||||
protected $_em;
|
||||
|
||||
/**
|
||||
* @var Doctrine\ORM\Mapping\ClassMetadata
|
||||
* @var \Doctrine\ORM\Mapping\ClassMetadata
|
||||
*/
|
||||
protected $_class;
|
||||
|
||||
@@ -77,6 +78,17 @@ class EntityRepository
|
||||
->from($this->_entityName, $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Query instance based on a predefined metadata named query.
|
||||
*
|
||||
* @param string $queryName
|
||||
* @return Query
|
||||
*/
|
||||
public function createNamedQuery($queryName)
|
||||
{
|
||||
return $this->_em->createQuery($this->_class->getNamedQuery($queryName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the repository, causing all managed entities to become detached.
|
||||
*/
|
||||
@@ -97,6 +109,10 @@ class EntityRepository
|
||||
{
|
||||
// Check identity map first
|
||||
if ($entity = $this->_em->getUnitOfWork()->tryGetById($id, $this->_class->rootEntityName)) {
|
||||
if (!($entity instanceof $this->_class->name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($lockMode != LockMode::NONE) {
|
||||
$this->_em->lock($entity, $lockMode, $lockVersion);
|
||||
}
|
||||
@@ -125,7 +141,7 @@ class EntityRepository
|
||||
if (!$this->_em->getConnection()->isTransactionActive()) {
|
||||
throw TransactionRequiredException::transactionRequired();
|
||||
}
|
||||
|
||||
|
||||
return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->load($id, null, null, array(), $lockMode);
|
||||
}
|
||||
}
|
||||
@@ -144,11 +160,14 @@ class EntityRepository
|
||||
* Finds entities by a set of criteria.
|
||||
*
|
||||
* @param array $criteria
|
||||
* @return array
|
||||
* @param array|null $orderBy
|
||||
* @param int|null $limit
|
||||
* @param int|null $offset
|
||||
* @return array The objects.
|
||||
*/
|
||||
public function findBy(array $criteria)
|
||||
public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
{
|
||||
return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->loadAll($criteria);
|
||||
return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->loadAll($criteria, $orderBy, $limit, $offset);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -222,4 +241,4 @@ class EntityRepository
|
||||
{
|
||||
return $this->_class;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
54
lib/Doctrine/ORM/Event/OnClearEventArgs.php
Normal file
54
lib/Doctrine/ORM/Event/OnClearEventArgs.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Event;
|
||||
|
||||
/**
|
||||
* Provides event arguments for the onClear event.
|
||||
*
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.com
|
||||
* @since 2.0
|
||||
* @version $Revision$
|
||||
* @author Roman Borschel <roman@code-factory.de>
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class OnClearEventArgs extends \Doctrine\Common\EventArgs
|
||||
{
|
||||
/**
|
||||
* @var \Doctrine\ORM\EntityManager
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\EntityManager $em
|
||||
*/
|
||||
public function __construct($em)
|
||||
{
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Doctrine\ORM\EntityManager
|
||||
*/
|
||||
public function getEntityManager()
|
||||
{
|
||||
return $this->em;
|
||||
}
|
||||
}
|
||||
@@ -119,4 +119,12 @@ final class Events
|
||||
* @var string
|
||||
*/
|
||||
const onFlush = 'onFlush';
|
||||
|
||||
/**
|
||||
* The onClear event occurs when the EntityManager#clear() operation is invoked,
|
||||
* after all references to entities have been removed from the unit of work.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const onClear = 'onClear';
|
||||
}
|
||||
@@ -26,7 +26,7 @@ abstract class AbstractIdGenerator
|
||||
/**
|
||||
* Generates an identifier for an entity.
|
||||
*
|
||||
* @param Doctrine\ORM\Entity $entity
|
||||
* @param \Doctrine\ORM\Entity $entity
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function generate(EntityManager $em, $entity);
|
||||
|
||||
@@ -47,9 +47,18 @@ class AssignedGenerator extends AbstractIdGenerator
|
||||
if ($class->isIdentifierComposite) {
|
||||
$idFields = $class->getIdentifierFieldNames();
|
||||
foreach ($idFields as $idField) {
|
||||
$value = $class->getReflectionProperty($idField)->getValue($entity);
|
||||
$value = $class->reflFields[$idField]->getValue($entity);
|
||||
if (isset($value)) {
|
||||
$identifier[$idField] = $value;
|
||||
if (isset($class->associationMappings[$idField])) {
|
||||
if (!$em->getUnitOfWork()->isInIdentityMap($value)) {
|
||||
throw ORMException::entityMissingForeignAssignedId($entity, $value);
|
||||
}
|
||||
|
||||
// NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced.
|
||||
$identifier[$idField] = current($em->getUnitOfWork()->getEntityIdentifier($value));
|
||||
} else {
|
||||
$identifier[$idField] = $value;
|
||||
}
|
||||
} else {
|
||||
throw ORMException::entityMissingAssignedId($entity);
|
||||
}
|
||||
@@ -58,7 +67,16 @@ class AssignedGenerator extends AbstractIdGenerator
|
||||
$idField = $class->identifier[0];
|
||||
$value = $class->reflFields[$idField]->getValue($entity);
|
||||
if (isset($value)) {
|
||||
$identifier[$idField] = $value;
|
||||
if (isset($class->associationMappings[$idField])) {
|
||||
if (!$em->getUnitOfWork()->isInIdentityMap($value)) {
|
||||
throw ORMException::entityMissingForeignAssignedId($entity, $value);
|
||||
}
|
||||
|
||||
// NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced.
|
||||
$identifier[$idField] = current($em->getUnitOfWork()->getEntityIdentifier($value));
|
||||
} else {
|
||||
$identifier[$idField] = $value;
|
||||
}
|
||||
} else {
|
||||
throw ORMException::entityMissingAssignedId($entity);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
|
||||
/**
|
||||
* Initializes a new sequence generator.
|
||||
*
|
||||
* @param Doctrine\ORM\EntityManager $em The EntityManager to use.
|
||||
* @param \Doctrine\ORM\EntityManager $em The EntityManager to use.
|
||||
* @param string $sequenceName The name of the sequence.
|
||||
* @param integer $allocationSize The allocation size of the sequence.
|
||||
*/
|
||||
|
||||
@@ -58,7 +58,7 @@ abstract class AbstractHydrator
|
||||
/**
|
||||
* Initializes a new instance of a class derived from <tt>AbstractHydrator</tt>.
|
||||
*
|
||||
* @param Doctrine\ORM\EntityManager $em The EntityManager to use.
|
||||
* @param \Doctrine\ORM\EntityManager $em The EntityManager to use.
|
||||
*/
|
||||
public function __construct(EntityManager $em)
|
||||
{
|
||||
@@ -164,6 +164,11 @@ abstract class AbstractHydrator
|
||||
* field names during this procedure as well as any necessary conversions on
|
||||
* the values applied.
|
||||
*
|
||||
* @param array $data SQL Result Row
|
||||
* @param array &$cache Cache for column to field result information
|
||||
* @param array &$id Dql-Alias => ID-Hash
|
||||
* @param array &$nonemptyComponents Does this DQL-Alias has at least one non NULL value?
|
||||
*
|
||||
* @return array An array with all the fields (name => value) of the data row,
|
||||
* grouped by their component alias.
|
||||
*/
|
||||
@@ -190,9 +195,12 @@ abstract class AbstractHydrator
|
||||
continue;
|
||||
} else {
|
||||
// Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns).
|
||||
$fieldName = $this->_rsm->metaMappings[$key];
|
||||
$cache[$key]['isMetaColumn'] = true;
|
||||
$cache[$key]['fieldName'] = $this->_rsm->metaMappings[$key];
|
||||
$cache[$key]['fieldName'] = $fieldName;
|
||||
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key];
|
||||
$classMetadata = $this->_em->getClassMetadata($this->_rsm->aliasMap[$cache[$key]['dqlAlias']]);
|
||||
$cache[$key]['isIdentifier'] = isset($this->_rsm->isIdentifierColumn[$cache[$key]['dqlAlias']][$key]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,15 +211,27 @@ abstract class AbstractHydrator
|
||||
|
||||
$dqlAlias = $cache[$key]['dqlAlias'];
|
||||
|
||||
if (isset($cache[$key]['isMetaColumn'])) {
|
||||
$rowData[$dqlAlias][$cache[$key]['fieldName']] = $value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($cache[$key]['isIdentifier']) {
|
||||
$id[$dqlAlias] .= '|' . $value;
|
||||
}
|
||||
|
||||
if (isset($cache[$key]['isMetaColumn'])) {
|
||||
if ( ! isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) && $value !== null) {
|
||||
$rowData[$dqlAlias][$cache[$key]['fieldName']] = $value;
|
||||
if ($cache[$key]['isIdentifier']) {
|
||||
$nonemptyComponents[$dqlAlias] = true;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// in an inheritance hierachy the same field could be defined several times.
|
||||
// We overwrite this value so long we dont have a non-null value, that value we keep.
|
||||
// Per definition it cannot be that a field is defined several times and has several values.
|
||||
if (isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) && $value === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rowData[$dqlAlias][$cache[$key]['fieldName']] = $cache[$key]['type']->convertToPHPValue($value, $this->_platform);
|
||||
|
||||
if ( ! isset($nonemptyComponents[$dqlAlias]) && $value !== null) {
|
||||
@@ -275,4 +295,25 @@ abstract class AbstractHydrator
|
||||
|
||||
return $rowData;
|
||||
}
|
||||
|
||||
protected function registerManaged($class, $entity, $data)
|
||||
{
|
||||
if ($class->isIdentifierComposite) {
|
||||
$id = array();
|
||||
foreach ($class->identifier as $fieldName) {
|
||||
if (isset($class->associationMappings[$fieldName])) {
|
||||
$id[$fieldName] = $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']];
|
||||
} else {
|
||||
$id[$fieldName] = $data[$fieldName];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isset($class->associationMappings[$class->identifier[0]])) {
|
||||
$id = array($class->identifier[0] => $data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']]);
|
||||
} else {
|
||||
$id = array($class->identifier[0] => $data[$class->identifier[0]]);
|
||||
}
|
||||
}
|
||||
$this->_em->getUnitOfWork()->registerManaged($entity, $id, $data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +92,11 @@ class ArrayHydrator extends AbstractHydrator
|
||||
$parent = $this->_rsm->parentAliasMap[$dqlAlias];
|
||||
$path = $parent . '.' . $dqlAlias;
|
||||
|
||||
// missing parent data, skipping as RIGHT JOIN hydration is not supported.
|
||||
if ( ! isset($nonemptyComponents[$parent]) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get a reference to the right element in the result tree.
|
||||
// This element will get the associated element attached.
|
||||
if ($this->_rsm->isMixed && isset($this->_rootAliases[$parent])) {
|
||||
@@ -154,6 +159,17 @@ class ArrayHydrator extends AbstractHydrator
|
||||
// It's a root result element
|
||||
|
||||
$this->_rootAliases[$dqlAlias] = true; // Mark as root
|
||||
|
||||
// if this row has a NULL value for the root result id then make it a null result.
|
||||
if ( ! isset($nonemptyComponents[$dqlAlias]) ) {
|
||||
if ($this->_rsm->isMixed) {
|
||||
$result[] = array(0 => null);
|
||||
} else {
|
||||
$result[] = null;
|
||||
}
|
||||
++$this->_resultCounter;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for an existing element
|
||||
if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Doctrine\ORM\Internal\Hydration;
|
||||
class IterableResult implements \Iterator
|
||||
{
|
||||
/**
|
||||
* @var Doctrine\ORM\Internal\Hydration\AbstractHydrator
|
||||
* @var \Doctrine\ORM\Internal\Hydration\AbstractHydrator
|
||||
*/
|
||||
private $_hydrator;
|
||||
|
||||
@@ -49,7 +49,7 @@ class IterableResult implements \Iterator
|
||||
private $_current = null;
|
||||
|
||||
/**
|
||||
* @param Doctrine\ORM\Internal\Hydration\AbstractHydrator $hydrator
|
||||
* @param \Doctrine\ORM\Internal\Hydration\AbstractHydrator $hydrator
|
||||
*/
|
||||
public function __construct($hydrator)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,8 @@ use PDO,
|
||||
Doctrine\ORM\PersistentCollection,
|
||||
Doctrine\ORM\Query,
|
||||
Doctrine\Common\Collections\ArrayCollection,
|
||||
Doctrine\Common\Collections\Collection;
|
||||
Doctrine\Common\Collections\Collection,
|
||||
Doctrine\ORM\Proxy\Proxy;
|
||||
|
||||
/**
|
||||
* The ObjectHydrator constructs an object graph out of an SQL result set.
|
||||
@@ -39,9 +40,9 @@ class ObjectHydrator extends AbstractHydrator
|
||||
* This local cache is maintained between hydration runs and not cleared.
|
||||
*/
|
||||
private $_ce = array();
|
||||
|
||||
|
||||
/* The following parts are reinitialized on every hydration run. */
|
||||
|
||||
|
||||
private $_identifierMap;
|
||||
private $_resultPointers;
|
||||
private $_idTemplate;
|
||||
@@ -50,7 +51,7 @@ class ObjectHydrator extends AbstractHydrator
|
||||
private $_initializedCollections = array();
|
||||
private $_existingCollections = array();
|
||||
//private $_createdEntities;
|
||||
|
||||
|
||||
|
||||
/** @override */
|
||||
protected function _prepare()
|
||||
@@ -59,7 +60,10 @@ class ObjectHydrator extends AbstractHydrator
|
||||
$this->_resultPointers =
|
||||
$this->_idTemplate = array();
|
||||
$this->_resultCounter = 0;
|
||||
|
||||
if (!isset($this->_hints['deferEagerLoad'])) {
|
||||
$this->_hints['deferEagerLoad'] = true;
|
||||
}
|
||||
|
||||
foreach ($this->_rsm->aliasMap as $dqlAlias => $className) {
|
||||
$this->_identifierMap[$dqlAlias] = array();
|
||||
$this->_idTemplate[$dqlAlias] = '';
|
||||
@@ -68,33 +72,29 @@ class ObjectHydrator extends AbstractHydrator
|
||||
if ( ! isset($this->_ce[$className])) {
|
||||
$this->_ce[$className] = $class;
|
||||
}
|
||||
|
||||
|
||||
// Remember which associations are "fetch joined", so that we know where to inject
|
||||
// collection stubs or proxies and where not.
|
||||
if (isset($this->_rsm->relationMap[$dqlAlias])) {
|
||||
if ( ! isset($this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]])) {
|
||||
throw HydrationException::parentObjectOfRelationNotFound($dqlAlias, $this->_rsm->parentAliasMap[$dqlAlias]);
|
||||
}
|
||||
|
||||
$sourceClassName = $this->_rsm->aliasMap[$this->_rsm->parentAliasMap[$dqlAlias]];
|
||||
$sourceClass = $this->_getClassMetadata($sourceClassName);
|
||||
$assoc = $sourceClass->associationMappings[$this->_rsm->relationMap[$dqlAlias]];
|
||||
$this->_hints['fetched'][$sourceClassName][$assoc['fieldName']] = true;
|
||||
if ($sourceClass->subClasses) {
|
||||
foreach ($sourceClass->subClasses as $sourceSubclassName) {
|
||||
$this->_hints['fetched'][$sourceSubclassName][$assoc['fieldName']] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$this->_hints['fetched'][$this->_rsm->parentAliasMap[$dqlAlias]][$assoc['fieldName']] = true;
|
||||
|
||||
if ($assoc['type'] != ClassMetadata::MANY_TO_MANY) {
|
||||
// Mark any non-collection opposite sides as fetched, too.
|
||||
if ($assoc['mappedBy']) {
|
||||
$this->_hints['fetched'][$className][$assoc['mappedBy']] = true;
|
||||
$this->_hints['fetched'][$dqlAlias][$assoc['mappedBy']] = true;
|
||||
} else {
|
||||
if ($assoc['inversedBy']) {
|
||||
$inverseAssoc = $class->associationMappings[$assoc['inversedBy']];
|
||||
if ($inverseAssoc['type'] & ClassMetadata::TO_ONE) {
|
||||
$this->_hints['fetched'][$className][$inverseAssoc['fieldName']] = true;
|
||||
if ($class->subClasses) {
|
||||
foreach ($class->subClasses as $targetSubclassName) {
|
||||
$this->_hints['fetched'][$targetSubclassName][$inverseAssoc['fieldName']] = true;
|
||||
}
|
||||
}
|
||||
$this->_hints['fetched'][$dqlAlias][$inverseAssoc['fieldName']] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,11 +108,17 @@ class ObjectHydrator extends AbstractHydrator
|
||||
*/
|
||||
protected function _cleanup()
|
||||
{
|
||||
$eagerLoad = (isset($this->_hints['deferEagerLoad'])) && $this->_hints['deferEagerLoad'] == true;
|
||||
|
||||
parent::_cleanup();
|
||||
$this->_identifierMap =
|
||||
$this->_initializedCollections =
|
||||
$this->_existingCollections =
|
||||
$this->_resultPointers = array();
|
||||
|
||||
if ($eagerLoad) {
|
||||
$this->_em->getUnitOfWork()->triggerEagerLoads();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,7 +147,7 @@ class ObjectHydrator extends AbstractHydrator
|
||||
* @param object $entity The entity to which the collection belongs.
|
||||
* @param string $name The name of the field on the entity that holds the collection.
|
||||
*/
|
||||
private function _initRelatedCollection($entity, $class, $fieldName)
|
||||
private function _initRelatedCollection($entity, $class, $fieldName, $parentDqlAlias)
|
||||
{
|
||||
$oid = spl_object_hash($entity);
|
||||
$relation = $class->associationMappings[$fieldName];
|
||||
@@ -162,7 +168,7 @@ class ObjectHydrator extends AbstractHydrator
|
||||
$this->_uow->setOriginalEntityProperty($oid, $fieldName, $value);
|
||||
$this->_initializedCollections[$oid . $fieldName] = $value;
|
||||
} else if (isset($this->_hints[Query::HINT_REFRESH]) ||
|
||||
isset($this->_hints['fetched'][$class->name][$fieldName]) &&
|
||||
isset($this->_hints['fetched'][$parentDqlAlias][$fieldName]) &&
|
||||
! $value->isInitialized()) {
|
||||
// Is already PersistentCollection, but either REFRESH or FETCH-JOIN and UNINITIALIZED!
|
||||
$value->setDirty(false);
|
||||
@@ -176,44 +182,59 @@ class ObjectHydrator extends AbstractHydrator
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets an entity instance.
|
||||
*
|
||||
*
|
||||
* @param $data The instance data.
|
||||
* @param $dqlAlias The DQL alias of the entity's class.
|
||||
* @return object The entity.
|
||||
*/
|
||||
private function _getEntity(array $data, $dqlAlias)
|
||||
{
|
||||
$className = $this->_rsm->aliasMap[$dqlAlias];
|
||||
$className = $this->_rsm->aliasMap[$dqlAlias];
|
||||
if (isset($this->_rsm->discriminatorColumns[$dqlAlias])) {
|
||||
$discrColumn = $this->_rsm->metaMappings[$this->_rsm->discriminatorColumns[$dqlAlias]];
|
||||
$className = $this->_ce[$className]->discriminatorMap[$data[$discrColumn]];
|
||||
unset($data[$discrColumn]);
|
||||
}
|
||||
|
||||
if (isset($this->_hints[Query::HINT_REFRESH_ENTITY]) && isset($this->_rootAliases[$dqlAlias])) {
|
||||
$class = $this->_ce[$className];
|
||||
$this->registerManaged($class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data);
|
||||
}
|
||||
|
||||
$this->_hints['fetchAlias'] = $dqlAlias;
|
||||
return $this->_uow->createEntity($className, $data, $this->_hints);
|
||||
}
|
||||
|
||||
|
||||
private function _getEntityFromIdentityMap($className, array $data)
|
||||
{
|
||||
// TODO: Abstract this code and UnitOfWork::createEntity() equivalent?
|
||||
$class = $this->_ce[$className];
|
||||
/* @var $class ClassMetadata */
|
||||
if ($class->isIdentifierComposite) {
|
||||
$idHash = '';
|
||||
foreach ($class->identifier as $fieldName) {
|
||||
$idHash .= $data[$fieldName] . ' ';
|
||||
if (isset($class->associationMappings[$fieldName])) {
|
||||
$idHash .= $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']] . ' ';
|
||||
} else {
|
||||
$idHash .= $data[$fieldName] . ' ';
|
||||
}
|
||||
}
|
||||
return $this->_uow->tryGetByIdHash(rtrim($idHash), $class->rootEntityName);
|
||||
} else if (isset($class->associationMappings[$class->identifier[0]])) {
|
||||
return $this->_uow->tryGetByIdHash($data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']], $class->rootEntityName);
|
||||
} else {
|
||||
return $this->_uow->tryGetByIdHash($data[$class->identifier[0]], $class->rootEntityName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets a ClassMetadata instance from the local cache.
|
||||
* If the instance is not yet in the local cache, it is loaded into the
|
||||
* local cache.
|
||||
*
|
||||
*
|
||||
* @param string $className The name of the class.
|
||||
* @return ClassMetadata
|
||||
*/
|
||||
@@ -227,21 +248,21 @@ class ObjectHydrator extends AbstractHydrator
|
||||
|
||||
/**
|
||||
* Hydrates a single row in an SQL result set.
|
||||
*
|
||||
*
|
||||
* @internal
|
||||
* First, the data of the row is split into chunks where each chunk contains data
|
||||
* that belongs to a particular component/class. Afterwards, all these chunks
|
||||
* are processed, one after the other. For each chunk of class data only one of the
|
||||
* following code paths is executed:
|
||||
*
|
||||
*
|
||||
* Path A: The data chunk belongs to a joined/associated object and the association
|
||||
* is collection-valued.
|
||||
* Path B: The data chunk belongs to a joined/associated object and the association
|
||||
* is single-valued.
|
||||
* Path C: The data chunk belongs to a root result element/object that appears in the topmost
|
||||
* level of the hydrated result. A typical example are the objects of the type
|
||||
* specified by the FROM clause in a DQL query.
|
||||
*
|
||||
* specified by the FROM clause in a DQL query.
|
||||
*
|
||||
* @param array $data The data of the row to process.
|
||||
* @param array $cache The cache to use.
|
||||
* @param array $result The result array to fill.
|
||||
@@ -266,7 +287,7 @@ class ObjectHydrator extends AbstractHydrator
|
||||
// Hydrate the data chunks
|
||||
foreach ($rowData as $dqlAlias => $data) {
|
||||
$entityName = $this->_rsm->aliasMap[$dqlAlias];
|
||||
|
||||
|
||||
if (isset($this->_rsm->parentAliasMap[$dqlAlias])) {
|
||||
// It's a joined result
|
||||
|
||||
@@ -275,10 +296,16 @@ class ObjectHydrator extends AbstractHydrator
|
||||
// seen for this parent-child relationship
|
||||
$path = $parentAlias . '.' . $dqlAlias;
|
||||
|
||||
// We have a RIGHT JOIN result here. Doctrine cannot hydrate RIGHT JOIN Object-Graphs
|
||||
if (!isset($nonemptyComponents[$parentAlias])) {
|
||||
// TODO: Add special case code where we hydrate the right join objects into identity map at least
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get a reference to the parent object to which the joined element belongs.
|
||||
if ($this->_rsm->isMixed && isset($this->_rootAliases[$parentAlias])) {
|
||||
$first = reset($this->_resultPointers);
|
||||
$parentObject = $this->_resultPointers[$parentAlias][key($first)];
|
||||
$first = reset($this->_resultPointers);
|
||||
$parentObject = $first[key($first)];
|
||||
} else if (isset($this->_resultPointers[$parentAlias])) {
|
||||
$parentObject = $this->_resultPointers[$parentAlias];
|
||||
} else {
|
||||
@@ -294,19 +321,20 @@ class ObjectHydrator extends AbstractHydrator
|
||||
|
||||
// Check the type of the relation (many or single-valued)
|
||||
if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) {
|
||||
$reflFieldValue = $reflField->getValue($parentObject);
|
||||
// PATH A: Collection-valued association
|
||||
if (isset($nonemptyComponents[$dqlAlias])) {
|
||||
$collKey = $oid . $relationField;
|
||||
if (isset($this->_initializedCollections[$collKey])) {
|
||||
$reflFieldValue = $this->_initializedCollections[$collKey];
|
||||
} else if ( ! isset($this->_existingCollections[$collKey])) {
|
||||
$reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField);
|
||||
$reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias);
|
||||
}
|
||||
|
||||
|
||||
$indexExists = isset($this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]]);
|
||||
$index = $indexExists ? $this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] : false;
|
||||
$indexIsValid = $index !== false ? isset($reflFieldValue[$index]) : false;
|
||||
|
||||
|
||||
if ( ! $indexExists || ! $indexIsValid) {
|
||||
if (isset($this->_existingCollections[$collKey])) {
|
||||
// Collection exists, only look for the element in the identity map.
|
||||
@@ -335,16 +363,18 @@ class ObjectHydrator extends AbstractHydrator
|
||||
// Update result pointer
|
||||
$this->_resultPointers[$dqlAlias] = $reflFieldValue[$index];
|
||||
}
|
||||
} else if ( ! $reflField->getValue($parentObject)) {
|
||||
$coll = new PersistentCollection($this->_em, $this->_ce[$entityName], new ArrayCollection);
|
||||
$coll->setOwner($parentObject, $relation);
|
||||
$reflField->setValue($parentObject, $coll);
|
||||
$this->_uow->setOriginalEntityProperty($oid, $relationField, $coll);
|
||||
} else if ( ! $reflFieldValue) {
|
||||
$reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias);
|
||||
} else if ($reflFieldValue instanceof PersistentCollection && $reflFieldValue->isInitialized() === false) {
|
||||
$reflFieldValue->setInitialized(true);
|
||||
}
|
||||
|
||||
} else {
|
||||
// PATH B: Single-valued association
|
||||
$reflFieldValue = $reflField->getValue($parentObject);
|
||||
if ( ! $reflFieldValue || isset($this->_hints[Query::HINT_REFRESH])) {
|
||||
if ( ! $reflFieldValue || isset($this->_hints[Query::HINT_REFRESH]) || ($reflFieldValue instanceof Proxy && !$reflFieldValue->__isInitialized__)) {
|
||||
// we only need to take action if this value is null,
|
||||
// we refresh the entity or its an unitialized proxy.
|
||||
if (isset($nonemptyComponents[$dqlAlias])) {
|
||||
$element = $this->_getEntity($data, $dqlAlias);
|
||||
$reflField->setValue($parentObject, $element);
|
||||
@@ -370,6 +400,8 @@ class ObjectHydrator extends AbstractHydrator
|
||||
}
|
||||
// Update result pointer
|
||||
$this->_resultPointers[$dqlAlias] = $element;
|
||||
} else {
|
||||
$this->_uow->setOriginalEntityProperty($oid, $relationField, null);
|
||||
}
|
||||
// else leave $reflFieldValue null for single-valued associations
|
||||
} else {
|
||||
@@ -381,6 +413,18 @@ class ObjectHydrator extends AbstractHydrator
|
||||
// PATH C: Its a root result element
|
||||
$this->_rootAliases[$dqlAlias] = true; // Mark as root alias
|
||||
|
||||
// if this row has a NULL value for the root result id then make it a null result.
|
||||
if ( ! isset($nonemptyComponents[$dqlAlias]) ) {
|
||||
if ($this->_rsm->isMixed) {
|
||||
$result[] = array(0 => null);
|
||||
} else {
|
||||
$result[] = null;
|
||||
}
|
||||
++$this->_resultCounter;
|
||||
continue;
|
||||
}
|
||||
|
||||
// check for existing result from the iterations before
|
||||
if ( ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
|
||||
$element = $this->_getEntity($rowData[$dqlAlias], $dqlAlias);
|
||||
if (isset($this->_rsm->indexByMap[$dqlAlias])) {
|
||||
@@ -395,6 +439,10 @@ class ObjectHydrator extends AbstractHydrator
|
||||
$result[$key] = $element;
|
||||
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $key;
|
||||
}
|
||||
|
||||
if (isset($this->_hints['collection'])) {
|
||||
$this->_hints['collection']->hydrateSet($key, $element);
|
||||
}
|
||||
} else {
|
||||
if ($this->_rsm->isMixed) {
|
||||
$element = array(0 => $element);
|
||||
@@ -402,6 +450,10 @@ class ObjectHydrator extends AbstractHydrator
|
||||
$result[] = $element;
|
||||
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $this->_resultCounter;
|
||||
++$this->_resultCounter;
|
||||
|
||||
if (isset($this->_hints['collection'])) {
|
||||
$this->_hints['collection']->hydrateAdd($element);
|
||||
}
|
||||
}
|
||||
|
||||
// Update result pointer
|
||||
|
||||
131
lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php
Normal file
131
lib/Doctrine/ORM/Internal/Hydration/SimpleObjectHydrator.php
Normal file
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
|
||||
namespace Doctrine\ORM\Internal\Hydration;
|
||||
|
||||
use \PDO;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\ORM\Query;
|
||||
|
||||
class SimpleObjectHydrator extends AbstractHydrator
|
||||
{
|
||||
/**
|
||||
* @var ClassMetadata
|
||||
*/
|
||||
private $class;
|
||||
|
||||
private $declaringClasses = array();
|
||||
|
||||
protected function _hydrateAll()
|
||||
{
|
||||
$result = array();
|
||||
$cache = array();
|
||||
|
||||
while ($row = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
$this->_hydrateRow($row, $cache, $result);
|
||||
}
|
||||
|
||||
$this->_em->getUnitOfWork()->triggerEagerLoads();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function _prepare()
|
||||
{
|
||||
if (count($this->_rsm->aliasMap) == 1) {
|
||||
$this->class = $this->_em->getClassMetadata(reset($this->_rsm->aliasMap));
|
||||
if ($this->class->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) {
|
||||
foreach ($this->_rsm->declaringClasses AS $column => $class) {
|
||||
$this->declaringClasses[$column] = $this->_em->getClassMetadata($class);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new \RuntimeException("Cannot use SimpleObjectHydrator with a ResultSetMapping not containing exactly one object result.");
|
||||
}
|
||||
if ($this->_rsm->scalarMappings) {
|
||||
throw new \RuntimeException("Cannot use SimpleObjectHydrator with a ResultSetMapping that contains scalar mappings.");
|
||||
}
|
||||
}
|
||||
|
||||
protected function _hydrateRow(array $sqlResult, array &$cache, array &$result)
|
||||
{
|
||||
$data = array();
|
||||
if ($this->class->inheritanceType == ClassMetadata::INHERITANCE_TYPE_NONE) {
|
||||
foreach ($sqlResult as $column => $value) {
|
||||
|
||||
if (!isset($cache[$column])) {
|
||||
if (isset($this->_rsm->fieldMappings[$column])) {
|
||||
$cache[$column]['name'] = $this->_rsm->fieldMappings[$column];
|
||||
$cache[$column]['field'] = true;
|
||||
} else {
|
||||
$cache[$column]['name'] = $this->_rsm->metaMappings[$column];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($cache[$column]['field'])) {
|
||||
$value = Type::getType($this->class->fieldMappings[$cache[$column]['name']]['type'])
|
||||
->convertToPHPValue($value, $this->_platform);
|
||||
}
|
||||
$data[$cache[$column]['name']] = $value;
|
||||
}
|
||||
$entityName = $this->class->name;
|
||||
} else {
|
||||
$discrColumnName = $this->_platform->getSQLResultCasing($this->class->discriminatorColumn['name']);
|
||||
$entityName = $this->class->discriminatorMap[$sqlResult[$discrColumnName]];
|
||||
unset($sqlResult[$discrColumnName]);
|
||||
foreach ($sqlResult as $column => $value) {
|
||||
if (!isset($cache[$column])) {
|
||||
if (isset($this->_rsm->fieldMappings[$column])) {
|
||||
$field = $this->_rsm->fieldMappings[$column];
|
||||
$class = $this->declaringClasses[$column];
|
||||
if ($class->name == $entityName || is_subclass_of($entityName, $class->name)) {
|
||||
$cache[$column]['name'] = $field;
|
||||
$cache[$column]['class'] = $class;
|
||||
}
|
||||
} else if (isset($this->_rsm->relationMap[$column])) {
|
||||
if ($this->_rsm->relationMap[$column] == $entityName || is_subclass_of($entityName, $this->_rsm->relationMap[$column])) {
|
||||
$cache[$column]['name'] = $field;
|
||||
}
|
||||
} else {
|
||||
$cache[$column]['name'] = $this->_rsm->metaMappings[$column];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($cache[$column]['class'])) {
|
||||
$value = Type::getType($cache[$column]['class']->fieldMappings[$cache[$column]['name']]['type'])
|
||||
->convertToPHPValue($value, $this->_platform);
|
||||
}
|
||||
|
||||
// the second and part is to prevent overwrites in case of multiple
|
||||
// inheritance classes using the same property name (See AbstractHydrator)
|
||||
if (isset($cache[$column]) && (!isset($data[$cache[$column]['name']]) || $value !== null)) {
|
||||
$data[$cache[$column]['name']] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->_hints[Query::HINT_REFRESH_ENTITY])) {
|
||||
$this->registerManaged($this->class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data);
|
||||
}
|
||||
|
||||
$result[] = $this->_em->getUnitOfWork()->createEntity($entityName, $data, $this->_hints);
|
||||
}
|
||||
}
|
||||
@@ -63,10 +63,10 @@ class ClassMetadata extends ClassMetadataInfo
|
||||
*/
|
||||
public function __construct($entityName)
|
||||
{
|
||||
parent::__construct($entityName);
|
||||
$this->reflClass = new ReflectionClass($entityName);
|
||||
$this->namespace = $this->reflClass->getNamespaceName();
|
||||
$this->table['name'] = $this->reflClass->getShortName();
|
||||
parent::__construct($this->reflClass->getName()); // do not use $entityName, possible case-problems
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -205,48 +205,6 @@ class ClassMetadata extends ClassMetadataInfo
|
||||
$this->reflFields[$sourceFieldName] = $refProp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the (possibly quoted) column name of a mapped field for safe use
|
||||
* in an SQL statement.
|
||||
*
|
||||
* @param string $field
|
||||
* @param AbstractPlatform $platform
|
||||
* @return string
|
||||
*/
|
||||
public function getQuotedColumnName($field, $platform)
|
||||
{
|
||||
return isset($this->fieldMappings[$field]['quoted']) ?
|
||||
$platform->quoteIdentifier($this->fieldMappings[$field]['columnName']) :
|
||||
$this->fieldMappings[$field]['columnName'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the (possibly quoted) primary table name of this class for safe use
|
||||
* in an SQL statement.
|
||||
*
|
||||
* @param AbstractPlatform $platform
|
||||
* @return string
|
||||
*/
|
||||
public function getQuotedTableName($platform)
|
||||
{
|
||||
return isset($this->table['quoted']) ?
|
||||
$platform->quoteIdentifier($this->table['name']) :
|
||||
$this->table['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the (possibly quoted) name of the join table.
|
||||
*
|
||||
* @param AbstractPlatform $platform
|
||||
* @return string
|
||||
*/
|
||||
public function getQuotedJoinTableName(array $assoc, $platform)
|
||||
{
|
||||
return isset($assoc['joinTable']['quoted'])
|
||||
? $platform->quoteIdentifier($assoc['joinTable']['name'])
|
||||
: $assoc['joinTable']['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a string representation of this instance.
|
||||
*
|
||||
@@ -317,6 +275,10 @@ class ClassMetadata extends ClassMetadataInfo
|
||||
$serialized[] = 'isMappedSuperclass';
|
||||
}
|
||||
|
||||
if ($this->containsForeignIdentifier) {
|
||||
$serialized[] = 'containsForeignIdentifier';
|
||||
}
|
||||
|
||||
if ($this->isVersioned) {
|
||||
$serialized[] = 'isVersioned';
|
||||
$serialized[] = 'versionField';
|
||||
@@ -326,6 +288,14 @@ class ClassMetadata extends ClassMetadataInfo
|
||||
$serialized[] = 'lifecycleCallbacks';
|
||||
}
|
||||
|
||||
if ($this->namedQueries) {
|
||||
$serialized[] = 'namedQueries';
|
||||
}
|
||||
|
||||
if ($this->isReadOnly) {
|
||||
$serialized[] = 'isReadOnly';
|
||||
}
|
||||
|
||||
return $serialized;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,8 @@ use ReflectionException,
|
||||
Doctrine\ORM\ORMException,
|
||||
Doctrine\ORM\EntityManager,
|
||||
Doctrine\DBAL\Platforms,
|
||||
Doctrine\ORM\Events;
|
||||
Doctrine\ORM\Events,
|
||||
Doctrine\Common\Persistence\Mapping\ClassMetadataFactory as ClassMetadataFactoryInterface;
|
||||
|
||||
/**
|
||||
* The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
|
||||
@@ -36,13 +37,13 @@ use ReflectionException,
|
||||
* @author Jonathan Wage <jonwage@gmail.com>
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
*/
|
||||
class ClassMetadataFactory
|
||||
class ClassMetadataFactory implements ClassMetadataFactoryInterface
|
||||
{
|
||||
/**
|
||||
* @var EntityManager
|
||||
*/
|
||||
private $em;
|
||||
|
||||
|
||||
/**
|
||||
* @var AbstractPlatform
|
||||
*/
|
||||
@@ -72,7 +73,7 @@ class ClassMetadataFactory
|
||||
* @var bool
|
||||
*/
|
||||
private $initialized = false;
|
||||
|
||||
|
||||
/**
|
||||
* @param EntityManager $$em
|
||||
*/
|
||||
@@ -84,7 +85,7 @@ class ClassMetadataFactory
|
||||
/**
|
||||
* Sets the cache driver used by the factory to cache ClassMetadata instances.
|
||||
*
|
||||
* @param Doctrine\Common\Cache\Cache $cacheDriver
|
||||
* @param \Doctrine\Common\Cache\Cache $cacheDriver
|
||||
*/
|
||||
public function setCacheDriver($cacheDriver)
|
||||
{
|
||||
@@ -94,22 +95,22 @@ class ClassMetadataFactory
|
||||
/**
|
||||
* Gets the cache driver used by the factory to cache ClassMetadata instances.
|
||||
*
|
||||
* @return Doctrine\Common\Cache\Cache
|
||||
* @return \Doctrine\Common\Cache\Cache
|
||||
*/
|
||||
public function getCacheDriver()
|
||||
{
|
||||
return $this->cacheDriver;
|
||||
}
|
||||
|
||||
|
||||
public function getLoadedMetadata()
|
||||
{
|
||||
return $this->loadedMetadata;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Forces the factory to load the metadata of all classes known to the underlying
|
||||
* mapping driver.
|
||||
*
|
||||
*
|
||||
* @return array The ClassMetadata instances of all mapped classes.
|
||||
*/
|
||||
public function getAllMetadata()
|
||||
@@ -142,7 +143,7 @@ class ClassMetadataFactory
|
||||
* Gets the class metadata descriptor for a class.
|
||||
*
|
||||
* @param string $className The name of the class.
|
||||
* @return Doctrine\ORM\Mapping\ClassMetadata
|
||||
* @return \Doctrine\ORM\Mapping\ClassMetadata
|
||||
*/
|
||||
public function getMetadataFor($className)
|
||||
{
|
||||
@@ -187,7 +188,7 @@ class ClassMetadataFactory
|
||||
|
||||
/**
|
||||
* Checks whether the factory has the metadata for a class loaded already.
|
||||
*
|
||||
*
|
||||
* @param string $className
|
||||
* @return boolean TRUE if the metadata of the class in question is already loaded, FALSE otherwise.
|
||||
*/
|
||||
@@ -198,7 +199,7 @@ class ClassMetadataFactory
|
||||
|
||||
/**
|
||||
* Sets the metadata descriptor for a specific class.
|
||||
*
|
||||
*
|
||||
* NOTE: This is only useful in very special cases, like when generating proxy classes.
|
||||
*
|
||||
* @param string $className
|
||||
@@ -247,11 +248,13 @@ class ClassMetadataFactory
|
||||
|
||||
// Move down the hierarchy of parent classes, starting from the topmost class
|
||||
$parent = null;
|
||||
$rootEntityFound = false;
|
||||
$visited = array();
|
||||
foreach ($parentClasses as $className) {
|
||||
if (isset($this->loadedMetadata[$className])) {
|
||||
$parent = $this->loadedMetadata[$className];
|
||||
if ( ! $parent->isMappedSuperclass) {
|
||||
$rootEntityFound = true;
|
||||
array_unshift($visited, $className);
|
||||
}
|
||||
continue;
|
||||
@@ -260,19 +263,15 @@ class ClassMetadataFactory
|
||||
$class = $this->newClassMetadataInstance($className);
|
||||
|
||||
if ($parent) {
|
||||
if (!$parent->isMappedSuperclass) {
|
||||
$class->setInheritanceType($parent->inheritanceType);
|
||||
$class->setDiscriminatorColumn($parent->discriminatorColumn);
|
||||
}
|
||||
$class->setInheritanceType($parent->inheritanceType);
|
||||
$class->setDiscriminatorColumn($parent->discriminatorColumn);
|
||||
$class->setIdGeneratorType($parent->generatorType);
|
||||
$this->addInheritedFields($class, $parent);
|
||||
$this->addInheritedRelations($class, $parent);
|
||||
$class->setIdentifier($parent->identifier);
|
||||
$class->setVersioned($parent->isVersioned);
|
||||
$class->setVersionField($parent->versionField);
|
||||
if (!$parent->isMappedSuperclass) {
|
||||
$class->setDiscriminatorMap($parent->discriminatorMap);
|
||||
}
|
||||
$class->setDiscriminatorMap($parent->discriminatorMap);
|
||||
$class->setLifecycleCallbacks($parent->lifecycleCallbacks);
|
||||
$class->setChangeTrackingPolicy($parent->changeTrackingPolicy);
|
||||
}
|
||||
@@ -284,11 +283,10 @@ class ClassMetadataFactory
|
||||
throw MappingException::reflectionFailure($className, $e);
|
||||
}
|
||||
|
||||
// Verify & complete identifier mapping
|
||||
if ( ! $class->identifier && ! $class->isMappedSuperclass) {
|
||||
throw MappingException::identifierRequired($className);
|
||||
}
|
||||
if ($parent && ! $parent->isMappedSuperclass) {
|
||||
// If this class has a parent the id generator strategy is inherited.
|
||||
// However this is only true if the hierachy of parents contains the root entity,
|
||||
// if it consinsts of mapped superclasses these don't necessarily include the id field.
|
||||
if ($parent && $rootEntityFound) {
|
||||
if ($parent->isIdGeneratorSequence()) {
|
||||
$class->setSequenceGeneratorDefinition($parent->sequenceGeneratorDefinition);
|
||||
} else if ($parent->isIdGeneratorTable()) {
|
||||
@@ -308,6 +306,10 @@ class ClassMetadataFactory
|
||||
$class->setPrimaryTable($parent->table);
|
||||
}
|
||||
|
||||
if ($parent && $parent->containsForeignIdentifier) {
|
||||
$class->containsForeignIdentifier = true;
|
||||
}
|
||||
|
||||
$class->setParentClasses($visited);
|
||||
|
||||
if ($this->evm->hasListeners(Events::loadClassMetadata)) {
|
||||
@@ -315,21 +317,14 @@ class ClassMetadataFactory
|
||||
$this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
|
||||
}
|
||||
|
||||
// verify inheritance
|
||||
if (!$parent && !$class->isMappedSuperclass && !$class->isInheritanceTypeNone()) {
|
||||
if (count($class->discriminatorMap) == 0) {
|
||||
throw MappingException::missingDiscriminatorMap($class->name);
|
||||
}
|
||||
if (!$class->discriminatorColumn) {
|
||||
throw MappingException::missingDiscriminatorColumn($class->name);
|
||||
}
|
||||
}
|
||||
$this->validateRuntimeMetadata($class, $parent);
|
||||
|
||||
$this->loadedMetadata[$className] = $class;
|
||||
|
||||
$parent = $class;
|
||||
|
||||
if ( ! $class->isMappedSuperclass) {
|
||||
$rootEntityFound = true;
|
||||
array_unshift($visited, $className);
|
||||
}
|
||||
|
||||
@@ -339,11 +334,43 @@ class ClassMetadataFactory
|
||||
return $loaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate runtime metadata is correctly defined.
|
||||
*
|
||||
* @param ClassMetadata $class
|
||||
* @param ClassMetadata $parent
|
||||
*/
|
||||
protected function validateRuntimeMetadata($class, $parent)
|
||||
{
|
||||
// Verify & complete identifier mapping
|
||||
if ( ! $class->identifier && ! $class->isMappedSuperclass) {
|
||||
throw MappingException::identifierRequired($class->name);
|
||||
}
|
||||
|
||||
// verify inheritance
|
||||
if (!$class->isMappedSuperclass && !$class->isInheritanceTypeNone()) {
|
||||
if (!$parent) {
|
||||
if (count($class->discriminatorMap) == 0) {
|
||||
throw MappingException::missingDiscriminatorMap($class->name);
|
||||
}
|
||||
if (!$class->discriminatorColumn) {
|
||||
throw MappingException::missingDiscriminatorColumn($class->name);
|
||||
}
|
||||
} else if ($parent && !$class->reflClass->isAbstract() && !in_array($class->name, array_values($class->discriminatorMap))) {
|
||||
// enforce discriminator map for all entities of an inheritance hierachy, otherwise problems will occur.
|
||||
throw MappingException::mappedClassNotPartOfDiscriminatorMap($class->name, $class->rootEntityName);
|
||||
}
|
||||
} else if ($class->isMappedSuperclass && $class->name == $class->rootEntityName && (count($class->discriminatorMap) || $class->discriminatorColumn)) {
|
||||
// second condition is necessary for mapped superclasses in the middle of an inheritance hierachy
|
||||
throw MappingException::noInheritanceOnMappedSuperClass($class->name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ClassMetadata instance for the given class name.
|
||||
*
|
||||
* @param string $className
|
||||
* @return Doctrine\ORM\Mapping\ClassMetadata
|
||||
* @return \Doctrine\ORM\Mapping\ClassMetadata
|
||||
*/
|
||||
protected function newClassMetadataInstance($className)
|
||||
{
|
||||
@@ -353,8 +380,8 @@ class ClassMetadataFactory
|
||||
/**
|
||||
* Adds inherited fields to the subclass mapping.
|
||||
*
|
||||
* @param Doctrine\ORM\Mapping\ClassMetadata $subClass
|
||||
* @param Doctrine\ORM\Mapping\ClassMetadata $parentClass
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $subClass
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $parentClass
|
||||
*/
|
||||
private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass)
|
||||
{
|
||||
@@ -375,13 +402,16 @@ class ClassMetadataFactory
|
||||
/**
|
||||
* Adds inherited association mappings to the subclass mapping.
|
||||
*
|
||||
* @param Doctrine\ORM\Mapping\ClassMetadata $subClass
|
||||
* @param Doctrine\ORM\Mapping\ClassMetadata $parentClass
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $subClass
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $parentClass
|
||||
*/
|
||||
private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass)
|
||||
{
|
||||
foreach ($parentClass->associationMappings as $field => $mapping) {
|
||||
if ($parentClass->isMappedSuperclass) {
|
||||
if ($mapping['type'] & ClassMetadata::TO_MANY && !$mapping['isOwningSide']) {
|
||||
throw MappingException::illegalToManyAssocationOnMappedSuperclass($parentClass->name, $field);
|
||||
}
|
||||
$mapping['sourceEntity'] = $subClass->name;
|
||||
}
|
||||
|
||||
@@ -400,7 +430,7 @@ class ClassMetadataFactory
|
||||
* Completes the ID generator mapping. If "auto" is specified we choose the generator
|
||||
* most appropriate for the targeted database platform.
|
||||
*
|
||||
* @param Doctrine\ORM\Mapping\ClassMetadata $class
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $class
|
||||
*/
|
||||
private function completeIdGeneratorMapping(ClassMetadataInfo $class)
|
||||
{
|
||||
@@ -452,4 +482,22 @@ class ClassMetadataFactory
|
||||
throw new ORMException("Unknown generator type: " . $class->generatorType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function isTransient($class)
|
||||
{
|
||||
if ( ! $this->initialized) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
// Check for namespace alias
|
||||
if (strpos($class, ':') !== false) {
|
||||
list($namespaceAlias, $simpleClassName) = explode(':', $class);
|
||||
$class = $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
|
||||
}
|
||||
|
||||
return $this->driver->isTransient($class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
|
||||
use ReflectionClass;
|
||||
|
||||
/**
|
||||
@@ -39,7 +40,7 @@ use ReflectionClass;
|
||||
* @author Jonathan H. Wage <jonwage@gmail.com>
|
||||
* @since 2.0
|
||||
*/
|
||||
class ClassMetadataInfo
|
||||
class ClassMetadataInfo implements ClassMetadata
|
||||
{
|
||||
/* The inheritance mapping types */
|
||||
/**
|
||||
@@ -118,9 +119,15 @@ class ClassMetadataInfo
|
||||
const FETCH_LAZY = 2;
|
||||
/**
|
||||
* Specifies that an association is to be fetched when the owner of the
|
||||
* association is fetched.
|
||||
* association is fetched.
|
||||
*/
|
||||
const FETCH_EAGER = 3;
|
||||
/**
|
||||
* Specifies that an association is to be fetched lazy (on first access) and that
|
||||
* commands such as Collection#count, Collection#slice are issued directly against
|
||||
* the database if the collection is not yet initialized.
|
||||
*/
|
||||
const FETCH_EXTRA_LAZY = 4;
|
||||
/**
|
||||
* Identifies a one-to-one association.
|
||||
*/
|
||||
@@ -197,6 +204,13 @@ class ClassMetadataInfo
|
||||
*/
|
||||
public $subClasses = array();
|
||||
|
||||
/**
|
||||
* READ-ONLY: The named queries allowed to be called directly from Repository.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $namedQueries = array();
|
||||
|
||||
/**
|
||||
* READ-ONLY: The field names of all fields that are part of the identifier/primary key
|
||||
* of the mapped entity class.
|
||||
@@ -255,7 +269,7 @@ class ClassMetadataInfo
|
||||
* - <b>scale</b> (integer, optional, schema-only)
|
||||
* The scale of a decimal column. Only valid if the column type is decimal.
|
||||
*
|
||||
* - <b>unique (string, optional, schema-only)</b>
|
||||
[* - <b>'unique'] (string, optional, schema-only)</b>
|
||||
* Whether a unique constraint should be generated for the column.
|
||||
*
|
||||
* @var array
|
||||
@@ -321,7 +335,6 @@ class ClassMetadataInfo
|
||||
* uniqueConstraints => array
|
||||
*
|
||||
* @var array
|
||||
* @todo Rename to just $table
|
||||
*/
|
||||
public $table;
|
||||
|
||||
@@ -348,7 +361,7 @@ class ClassMetadataInfo
|
||||
* - <b>mappedBy</b> (string, required for bidirectional associations)
|
||||
* The name of the field that completes the bidirectional association on the owning side.
|
||||
* This key must be specified on the inverse side of a bidirectional association.
|
||||
*
|
||||
*
|
||||
* - <b>inversedBy</b> (string, required for bidirectional associations)
|
||||
* The name of the field that completes the bidirectional association on the inverse side.
|
||||
* This key must be specified on the owning side of a bidirectional association.
|
||||
@@ -370,7 +383,12 @@ class ClassMetadataInfo
|
||||
* Only valid for many-to-many mappings. Note that one-to-many associations can be mapped
|
||||
* through a join table by simply mapping the association as many-to-many with a unique
|
||||
* constraint on the join table.
|
||||
*
|
||||
*
|
||||
* - <b>indexBy</b> (string, optional, to-many only)
|
||||
* Specification of a field on target-entity that is used to index the collection by.
|
||||
* This field HAS to be either the primary key or a unique column. Otherwise the collection
|
||||
* does not contain all the entities that are actually related.
|
||||
*
|
||||
* A join table definition has the following structure:
|
||||
* <pre>
|
||||
* array(
|
||||
@@ -392,6 +410,15 @@ class ClassMetadataInfo
|
||||
*/
|
||||
public $isIdentifierComposite = false;
|
||||
|
||||
/**
|
||||
* READ-ONLY: Flag indicating wheather the identifier/primary key contains at least one foreign key association.
|
||||
*
|
||||
* This flag is necessary because some code blocks require special treatment of this cases.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $containsForeignIdentifier = false;
|
||||
|
||||
/**
|
||||
* READ-ONLY: The ID generator used for generating IDs for this class.
|
||||
*
|
||||
@@ -403,7 +430,7 @@ class ClassMetadataInfo
|
||||
/**
|
||||
* READ-ONLY: The definition of the sequence generator of this class. Only used for the
|
||||
* SEQUENCE generation strategy.
|
||||
*
|
||||
*
|
||||
* The definition has the following structure:
|
||||
* <code>
|
||||
* array(
|
||||
@@ -456,6 +483,17 @@ class ClassMetadataInfo
|
||||
*/
|
||||
public $reflClass;
|
||||
|
||||
/**
|
||||
* Is this entity marked as "read-only"?
|
||||
*
|
||||
* That means it is never considered for change-tracking in the UnitOfWork. It is a very helpful performance
|
||||
* optimization for entities that are immutable, either in your domain or through the relation database
|
||||
* (coming from a view, or a history table for example).
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $isReadOnly = false;
|
||||
|
||||
/**
|
||||
* Initializes a new ClassMetadata instance that will hold the object-relational mapping
|
||||
* metadata of the class with the given name.
|
||||
@@ -634,6 +672,32 @@ class ClassMetadataInfo
|
||||
$this->fieldNames[$columnName] : $columnName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the named query.
|
||||
*
|
||||
* @see ClassMetadataInfo::$namedQueries
|
||||
* @throws MappingException
|
||||
* @param string $queryName The query name
|
||||
* @return string
|
||||
*/
|
||||
public function getNamedQuery($queryName)
|
||||
{
|
||||
if ( ! isset($this->namedQueries[$queryName])) {
|
||||
throw MappingException::queryNotFound($this->name, $queryName);
|
||||
}
|
||||
return $this->namedQueries[$queryName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all named queries of the class.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getNamedQueries()
|
||||
{
|
||||
return $this->namedQueries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates & completes the given field mapping.
|
||||
*
|
||||
@@ -643,8 +707,8 @@ class ClassMetadataInfo
|
||||
protected function _validateAndCompleteFieldMapping(array &$mapping)
|
||||
{
|
||||
// Check mandatory fields
|
||||
if ( ! isset($mapping['fieldName'])) {
|
||||
throw MappingException::missingFieldName($this->name, $mapping);
|
||||
if ( ! isset($mapping['fieldName']) || strlen($mapping['fieldName']) == 0) {
|
||||
throw MappingException::missingFieldName($this->name);
|
||||
}
|
||||
if ( ! isset($mapping['type'])) {
|
||||
// Default to string
|
||||
@@ -702,22 +766,54 @@ class ClassMetadataInfo
|
||||
}
|
||||
$mapping['isOwningSide'] = true; // assume owning side until we hit mappedBy
|
||||
|
||||
// unset optional indexBy attribute if its empty
|
||||
if (!isset($mapping['indexBy']) || !$mapping['indexBy']) {
|
||||
unset($mapping['indexBy']);
|
||||
}
|
||||
|
||||
// If targetEntity is unqualified, assume it is in the same namespace as
|
||||
// the sourceEntity.
|
||||
$mapping['sourceEntity'] = $this->name;
|
||||
if (isset($mapping['targetEntity']) && strpos($mapping['targetEntity'], '\\') === false
|
||||
&& strlen($this->namespace) > 0) {
|
||||
$mapping['targetEntity'] = $this->namespace . '\\' . $mapping['targetEntity'];
|
||||
|
||||
if (isset($mapping['targetEntity'])) {
|
||||
if (strlen($this->namespace) > 0 && strpos($mapping['targetEntity'], '\\') === false) {
|
||||
$mapping['targetEntity'] = $this->namespace . '\\' . $mapping['targetEntity'];
|
||||
}
|
||||
|
||||
$mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\');
|
||||
}
|
||||
|
||||
// Complete id mapping
|
||||
if (isset($mapping['id']) && $mapping['id'] === true) {
|
||||
if (isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'] == true) {
|
||||
throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->name, $mapping['fieldName']);
|
||||
}
|
||||
|
||||
if ( ! in_array($mapping['fieldName'], $this->identifier)) {
|
||||
if (count($mapping['joinColumns']) >= 2) {
|
||||
throw MappingException::cannotMapCompositePrimaryKeyEntitiesAsForeignId(
|
||||
$mapping['targetEntity'], $this->name, $mapping['fieldName']
|
||||
);
|
||||
}
|
||||
|
||||
$this->identifier[] = $mapping['fieldName'];
|
||||
$this->containsForeignIdentifier = true;
|
||||
}
|
||||
// Check for composite key
|
||||
if ( ! $this->isIdentifierComposite && count($this->identifier) > 1) {
|
||||
$this->isIdentifierComposite = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Mandatory attributes for both sides
|
||||
// Mandatory: fieldName, targetEntity
|
||||
if ( ! isset($mapping['fieldName'])) {
|
||||
throw MappingException::missingFieldName();
|
||||
if ( ! isset($mapping['fieldName']) || strlen($mapping['fieldName']) == 0) {
|
||||
throw MappingException::missingFieldName($this->name);
|
||||
}
|
||||
if ( ! isset($mapping['targetEntity'])) {
|
||||
throw MappingException::missingTargetEntity($mapping['fieldName']);
|
||||
}
|
||||
|
||||
|
||||
// Mandatory and optional attributes for either side
|
||||
if ( ! $mapping['mappedBy']) {
|
||||
if (isset($mapping['joinTable']) && $mapping['joinTable']) {
|
||||
@@ -729,14 +825,18 @@ class ClassMetadataInfo
|
||||
} else {
|
||||
$mapping['isOwningSide'] = false;
|
||||
}
|
||||
|
||||
|
||||
if (isset($mapping['id']) && $mapping['id'] === true && $mapping['type'] & self::TO_MANY) {
|
||||
throw MappingException::illegalToManyIdentifierAssoaction($this->name, $mapping['fieldName']);
|
||||
}
|
||||
|
||||
// Fetch mode. Default fetch mode to LAZY, if not set.
|
||||
if ( ! isset($mapping['fetch'])) {
|
||||
$mapping['fetch'] = self::FETCH_LAZY;
|
||||
}
|
||||
|
||||
// Cascades
|
||||
$cascades = isset($mapping['cascade']) ? $mapping['cascade'] : array();
|
||||
$cascades = isset($mapping['cascade']) ? array_map('strtolower', $mapping['cascade']) : array();
|
||||
if (in_array('all', $cascades)) {
|
||||
$cascades = array(
|
||||
'remove',
|
||||
@@ -752,7 +852,7 @@ class ClassMetadataInfo
|
||||
$mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
|
||||
$mapping['isCascadeMerge'] = in_array('merge', $cascades);
|
||||
$mapping['isCascadeDetach'] = in_array('detach', $cascades);
|
||||
|
||||
|
||||
return $mapping;
|
||||
}
|
||||
|
||||
@@ -766,11 +866,11 @@ class ClassMetadataInfo
|
||||
protected function _validateAndCompleteOneToOneMapping(array $mapping)
|
||||
{
|
||||
$mapping = $this->_validateAndCompleteAssociationMapping($mapping);
|
||||
|
||||
|
||||
if (isset($mapping['joinColumns']) && $mapping['joinColumns']) {
|
||||
$mapping['isOwningSide'] = true;
|
||||
}
|
||||
|
||||
|
||||
if ($mapping['isOwningSide']) {
|
||||
if ( ! isset($mapping['joinColumns']) || ! $mapping['joinColumns']) {
|
||||
// Apply default join column
|
||||
@@ -779,9 +879,17 @@ class ClassMetadataInfo
|
||||
'referencedColumnName' => 'id'
|
||||
));
|
||||
}
|
||||
|
||||
$uniqueContraintColumns = array();
|
||||
foreach ($mapping['joinColumns'] as $key => &$joinColumn) {
|
||||
if ($mapping['type'] === self::ONE_TO_ONE) {
|
||||
$joinColumn['unique'] = true;
|
||||
if (count($mapping['joinColumns']) == 1) {
|
||||
if (! isset($mapping['id']) || ! $mapping['id']) {
|
||||
$joinColumn['unique'] = true;
|
||||
}
|
||||
} else {
|
||||
$uniqueContraintColumns[] = $joinColumn['name'];
|
||||
}
|
||||
}
|
||||
if (empty($joinColumn['name'])) {
|
||||
$joinColumn['name'] = $mapping['fieldName'] . '_id';
|
||||
@@ -793,12 +901,25 @@ class ClassMetadataInfo
|
||||
$mapping['joinColumnFieldNames'][$joinColumn['name']] = isset($joinColumn['fieldName'])
|
||||
? $joinColumn['fieldName'] : $joinColumn['name'];
|
||||
}
|
||||
|
||||
if ($uniqueContraintColumns) {
|
||||
if (!$this->table) {
|
||||
throw new \RuntimeException("ClassMetadataInfo::setTable() has to be called before defining a one to one relationship.");
|
||||
}
|
||||
$this->table['uniqueConstraints'][$mapping['fieldName']."_uniq"] = array(
|
||||
'columns' => $uniqueContraintColumns
|
||||
);
|
||||
}
|
||||
|
||||
$mapping['targetToSourceKeyColumns'] = array_flip($mapping['sourceToTargetKeyColumns']);
|
||||
}
|
||||
|
||||
//TODO: if orphanRemoval, cascade=remove is implicit!
|
||||
$mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ?
|
||||
(bool) $mapping['orphanRemoval'] : false;
|
||||
$mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ? (bool) $mapping['orphanRemoval'] : false;
|
||||
$mapping['isCascadeRemove'] = $mapping['orphanRemoval'] ? true : $mapping['isCascadeRemove'];
|
||||
|
||||
if (isset($mapping['id']) && $mapping['id'] === true && !$mapping['isOwningSide']) {
|
||||
throw MappingException::illegalInverseIdentifierAssocation($this->name, $mapping['fieldName']);
|
||||
}
|
||||
|
||||
return $mapping;
|
||||
}
|
||||
@@ -818,17 +939,16 @@ class ClassMetadataInfo
|
||||
if ( ! isset($mapping['mappedBy'])) {
|
||||
throw MappingException::oneToManyRequiresMappedBy($mapping['fieldName']);
|
||||
}
|
||||
|
||||
//TODO: if orphanRemoval, cascade=remove is implicit!
|
||||
$mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ?
|
||||
(bool) $mapping['orphanRemoval'] : false;
|
||||
|
||||
$mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) ? (bool) $mapping['orphanRemoval'] : false;
|
||||
$mapping['isCascadeRemove'] = $mapping['orphanRemoval'] ? true : $mapping['isCascadeRemove'];
|
||||
|
||||
if (isset($mapping['orderBy'])) {
|
||||
if ( ! is_array($mapping['orderBy'])) {
|
||||
throw new \InvalidArgumentException("'orderBy' is expected to be an array, not ".gettype($mapping['orderBy']));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $mapping;
|
||||
}
|
||||
|
||||
@@ -836,8 +956,17 @@ class ClassMetadataInfo
|
||||
{
|
||||
$mapping = $this->_validateAndCompleteAssociationMapping($mapping);
|
||||
if ($mapping['isOwningSide']) {
|
||||
$sourceShortName = strtolower(substr($mapping['sourceEntity'], strrpos($mapping['sourceEntity'], '\\') + 1));
|
||||
$targetShortName = strtolower(substr($mapping['targetEntity'], strrpos($mapping['targetEntity'], '\\') + 1));
|
||||
if (strpos($mapping['sourceEntity'], '\\') !== false) {
|
||||
$sourceShortName = strtolower(substr($mapping['sourceEntity'], strrpos($mapping['sourceEntity'], '\\') + 1));
|
||||
} else {
|
||||
$sourceShortName = strtolower($mapping['sourceEntity']);
|
||||
}
|
||||
if (strpos($mapping['targetEntity'], '\\') !== false) {
|
||||
$targetShortName = strtolower(substr($mapping['targetEntity'], strrpos($mapping['targetEntity'], '\\') + 1));
|
||||
} else {
|
||||
$targetShortName = strtolower($mapping['targetEntity']);
|
||||
}
|
||||
|
||||
// owning side MUST have a join table
|
||||
if ( ! isset($mapping['joinTable']['name'])) {
|
||||
$mapping['joinTable']['name'] = $sourceShortName .'_' . $targetShortName;
|
||||
@@ -943,6 +1072,16 @@ class ClassMetadataInfo
|
||||
$this->isIdentifierComposite = (count($this->identifier) > 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the mapped identifier field of this class.
|
||||
*
|
||||
* @return string $identifier
|
||||
*/
|
||||
public function getIdentifier()
|
||||
{
|
||||
return $this->identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the class has a (mapped) field with a certain name.
|
||||
*
|
||||
@@ -978,15 +1117,23 @@ class ClassMetadataInfo
|
||||
*/
|
||||
public function getIdentifierColumnNames()
|
||||
{
|
||||
if ($this->isIdentifierComposite) {
|
||||
$columnNames = array();
|
||||
foreach ($this->identifier as $idField) {
|
||||
$columnNames[] = $this->fieldMappings[$idField]['columnName'];
|
||||
$columnNames = array();
|
||||
|
||||
foreach ($this->identifier as $idProperty) {
|
||||
if (isset($this->fieldMappings[$idProperty])) {
|
||||
$columnNames[] = $this->fieldMappings[$idProperty]['columnName'];
|
||||
|
||||
continue;
|
||||
}
|
||||
return $columnNames;
|
||||
} else {
|
||||
return array($this->fieldMappings[$this->identifier[0]]['columnName']);
|
||||
|
||||
// Association defined as Id field
|
||||
$joinColumns = $this->associationMappings[$idProperty]['joinColumns'];
|
||||
$assocColumnNames = array_map(function ($joinColumn) { return $joinColumn['name']; }, $joinColumns);
|
||||
|
||||
$columnNames = array_merge($columnNames, $assocColumnNames);
|
||||
}
|
||||
|
||||
return $columnNames;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1093,7 +1240,7 @@ class ClassMetadataInfo
|
||||
* Gets the type of a field.
|
||||
*
|
||||
* @param string $fieldName
|
||||
* @return Doctrine\DBAL\Types\Type
|
||||
* @return \Doctrine\DBAL\Types\Type
|
||||
*/
|
||||
public function getTypeOfField($fieldName)
|
||||
{
|
||||
@@ -1104,7 +1251,7 @@ class ClassMetadataInfo
|
||||
/**
|
||||
* Gets the type of a column.
|
||||
*
|
||||
* @return Doctrine\DBAL\Types\Type
|
||||
* @return \Doctrine\DBAL\Types\Type
|
||||
*/
|
||||
public function getTypeOfColumn($columnName)
|
||||
{
|
||||
@@ -1128,7 +1275,8 @@ class ClassMetadataInfo
|
||||
*/
|
||||
public function getTemporaryIdTableName()
|
||||
{
|
||||
return $this->table['name'] . '_id_tmp';
|
||||
// replace dots with underscores because PostgreSQL creates temporary tables in a special schema
|
||||
return str_replace('.', '_', $this->table['name'] . '_id_tmp');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1268,8 +1416,7 @@ class ClassMetadataInfo
|
||||
* Adds an association mapping without completing/validating it.
|
||||
* This is mainly used to add inherited association mappings to derived classes.
|
||||
*
|
||||
* @param AssociationMapping $mapping
|
||||
* @param string $owningClassName The name of the class that defined this mapping.
|
||||
* @param array $mapping
|
||||
*/
|
||||
public function addInheritedAssociationMapping(array $mapping/*, $owningClassName = null*/)
|
||||
{
|
||||
@@ -1285,7 +1432,6 @@ class ClassMetadataInfo
|
||||
* This is mainly used to add inherited field mappings to derived classes.
|
||||
*
|
||||
* @param array $mapping
|
||||
* @todo Rename: addInheritedFieldMapping
|
||||
*/
|
||||
public function addInheritedFieldMapping(array $fieldMapping)
|
||||
{
|
||||
@@ -1294,6 +1440,22 @@ class ClassMetadataInfo
|
||||
$this->fieldNames[$fieldMapping['columnName']] = $fieldMapping['fieldName'];
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL:
|
||||
* Adds a named query to this class.
|
||||
*
|
||||
* @throws MappingException
|
||||
* @param array $queryMapping
|
||||
*/
|
||||
public function addNamedQuery(array $queryMapping)
|
||||
{
|
||||
if (isset($this->namedQueries[$queryMapping['name']])) {
|
||||
throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']);
|
||||
}
|
||||
$query = str_replace('__CLASS__', $this->name, $queryMapping['query']);
|
||||
$this->namedQueries[$queryMapping['name']] = $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a one-to-one mapping.
|
||||
*
|
||||
@@ -1467,23 +1629,37 @@ class ClassMetadataInfo
|
||||
public function setDiscriminatorMap(array $map)
|
||||
{
|
||||
foreach ($map as $value => $className) {
|
||||
if (strpos($className, '\\') === false && strlen($this->namespace)) {
|
||||
if (strlen($this->namespace) > 0 && strpos($className, '\\') === false) {
|
||||
$className = $this->namespace . '\\' . $className;
|
||||
}
|
||||
|
||||
$className = ltrim($className, '\\');
|
||||
$this->discriminatorMap[$value] = $className;
|
||||
|
||||
if ($this->name == $className) {
|
||||
$this->discriminatorValue = $value;
|
||||
} else {
|
||||
if ( ! class_exists($className)) {
|
||||
throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
|
||||
}
|
||||
if (is_subclass_of($className, $this->name)) {
|
||||
if (is_subclass_of($className, $this->name) && ! in_array($className, $this->subClasses)) {
|
||||
$this->subClasses[] = $className;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the class has a named query with the given query name.
|
||||
*
|
||||
* @param string $fieldName
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasNamedQuery($queryName)
|
||||
{
|
||||
return isset($this->namedQueries[$queryName]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the class has a mapped association with the given field name.
|
||||
*
|
||||
@@ -1521,6 +1697,74 @@ class ClassMetadataInfo
|
||||
! ($this->associationMappings[$fieldName]['type'] & self::TO_ONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this an association that only has a single join column?
|
||||
*
|
||||
* @param string $fieldName
|
||||
* @return bool
|
||||
*/
|
||||
public function isAssociationWithSingleJoinColumn($fieldName)
|
||||
{
|
||||
return (
|
||||
isset($this->associationMappings[$fieldName]) &&
|
||||
isset($this->associationMappings[$fieldName]['joinColumns'][0]) &&
|
||||
!isset($this->associationMappings[$fieldName]['joinColumns'][1])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the single association join column (if any).
|
||||
*
|
||||
* @param string $fieldName
|
||||
* @return string
|
||||
*/
|
||||
public function getSingleAssociationJoinColumnName($fieldName)
|
||||
{
|
||||
if (!$this->isAssociationWithSingleJoinColumn($fieldName)) {
|
||||
throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName);
|
||||
}
|
||||
return $this->associationMappings[$fieldName]['joinColumns'][0]['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the single association referenced join column name (if any).
|
||||
*
|
||||
* @param string $fieldName
|
||||
* @return string
|
||||
*/
|
||||
public function getSingleAssociationReferencedJoinColumnName($fieldName)
|
||||
{
|
||||
if (!$this->isAssociationWithSingleJoinColumn($fieldName)) {
|
||||
throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName);
|
||||
}
|
||||
return $this->associationMappings[$fieldName]['joinColumns'][0]['referencedColumnName'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to retrieve a fieldname for either field or association from a given column,
|
||||
*
|
||||
* This method is used in foreign-key as primary-key contexts.
|
||||
*
|
||||
* @param string $columnName
|
||||
* @return string
|
||||
*/
|
||||
public function getFieldForColumn($columnName)
|
||||
{
|
||||
if (isset($this->fieldNames[$columnName])) {
|
||||
return $this->fieldNames[$columnName];
|
||||
} else {
|
||||
foreach ($this->associationMappings AS $assocName => $mapping) {
|
||||
if ($this->isAssociationWithSingleJoinColumn($assocName) &&
|
||||
$this->associationMappings[$assocName]['joinColumns'][0]['name'] == $columnName) {
|
||||
|
||||
return $assocName;
|
||||
}
|
||||
}
|
||||
|
||||
throw MappingException::noFieldNameFoundForColumn($this->name, $columnName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ID generator used to generate IDs for instances of this class.
|
||||
*
|
||||
@@ -1592,4 +1836,150 @@ class ClassMetadataInfo
|
||||
{
|
||||
$this->versionField = $versionField;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this class as read only, no change tracking is applied to it.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function markReadOnly()
|
||||
{
|
||||
$this->isReadOnly = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* A numerically indexed list of field names of this persistent class.
|
||||
*
|
||||
* This array includes identifier fields if present on this class.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFieldNames()
|
||||
{
|
||||
return array_keys($this->fieldMappings);
|
||||
}
|
||||
|
||||
/**
|
||||
* A numerically indexed list of association names of this persistent class.
|
||||
*
|
||||
* This array includes identifier associations if present on this class.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAssociationNames()
|
||||
{
|
||||
return array_keys($this->associationMappings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the target class name of the given association.
|
||||
*
|
||||
* @param string $assocName
|
||||
* @return string
|
||||
*/
|
||||
public function getAssociationTargetClass($assocName)
|
||||
{
|
||||
if (!isset($this->associationMappings[$assocName])) {
|
||||
throw new \InvalidArgumentException("Association name expected, '" . $assocName ."' is not an association.");
|
||||
}
|
||||
return $this->associationMappings[$assocName]['targetEntity'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fully-qualified class name of this persistent class.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the (possibly quoted) identifier column names for safe use in an SQL statement.
|
||||
*
|
||||
* @param AbstractPlatform $platform
|
||||
* @return array
|
||||
*/
|
||||
public function getQuotedIdentifierColumnNames($platform)
|
||||
{
|
||||
$quotedColumnNames = array();
|
||||
|
||||
foreach ($this->identifier as $idProperty) {
|
||||
if (isset($this->fieldMappings[$idProperty])) {
|
||||
$quotedColumnNames[] = isset($this->fieldMappings[$idProperty]['quoted'])
|
||||
? $platform->quoteIdentifier($this->fieldMappings[$idProperty]['columnName'])
|
||||
: $this->fieldMappings[$idProperty]['columnName'];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Association defined as Id field
|
||||
$joinColumns = $this->associationMappings[$idProperty]['joinColumns'];
|
||||
$assocQuotedColumnNames = array_map(
|
||||
function ($joinColumn) {
|
||||
return isset($joinColumn['quoted'])
|
||||
? $platform->quoteIdentifier($joinColumn['name'])
|
||||
: $joinColumn['name'];
|
||||
},
|
||||
$joinColumns
|
||||
);
|
||||
|
||||
$quotedColumnNames = array_merge($quotedColumnNames, $assocQuotedColumnNames);
|
||||
}
|
||||
|
||||
return $quotedColumnNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the (possibly quoted) column name of a mapped field for safe use
|
||||
* in an SQL statement.
|
||||
*
|
||||
* @param string $field
|
||||
* @param AbstractPlatform $platform
|
||||
* @return string
|
||||
*/
|
||||
public function getQuotedColumnName($field, $platform)
|
||||
{
|
||||
return isset($this->fieldMappings[$field]['quoted']) ?
|
||||
$platform->quoteIdentifier($this->fieldMappings[$field]['columnName']) :
|
||||
$this->fieldMappings[$field]['columnName'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the (possibly quoted) primary table name of this class for safe use
|
||||
* in an SQL statement.
|
||||
*
|
||||
* @param AbstractPlatform $platform
|
||||
* @return string
|
||||
*/
|
||||
public function getQuotedTableName($platform)
|
||||
{
|
||||
return isset($this->table['quoted']) ?
|
||||
$platform->quoteIdentifier($this->table['name']) :
|
||||
$this->table['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the (possibly quoted) name of the join table.
|
||||
*
|
||||
* @param AbstractPlatform $platform
|
||||
* @return string
|
||||
*/
|
||||
public function getQuotedJoinTableName(array $assoc, $platform)
|
||||
{
|
||||
return isset($assoc['joinTable']['quoted'])
|
||||
? $platform->quoteIdentifier($assoc['joinTable']['name'])
|
||||
: $assoc['joinTable']['name'];
|
||||
}
|
||||
|
||||
public function isAssociationInverseSide($fieldName)
|
||||
{
|
||||
return isset($this->associationMappings[$fieldName]) && !$this->associationMappings[$fieldName]['isOwningSide'];
|
||||
}
|
||||
|
||||
public function getAssociationMappedByTargetField($fieldName)
|
||||
{
|
||||
return $this->associationMappings[$fieldName]['mappedBy'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,10 @@ namespace Doctrine\ORM\Mapping\Driver;
|
||||
|
||||
use Doctrine\Common\Cache\ArrayCache,
|
||||
Doctrine\Common\Annotations\AnnotationReader,
|
||||
Doctrine\Common\Annotations\AnnotationRegistry,
|
||||
Doctrine\ORM\Mapping\ClassMetadataInfo,
|
||||
Doctrine\ORM\Mapping\MappingException;
|
||||
|
||||
require __DIR__ . '/DoctrineAnnotations.php';
|
||||
|
||||
/**
|
||||
* The AnnotationDriver reads the mapping metadata from docblock annotations.
|
||||
*
|
||||
@@ -42,7 +41,7 @@ class AnnotationDriver implements Driver
|
||||
*
|
||||
* @var AnnotationReader
|
||||
*/
|
||||
private $_reader;
|
||||
protected $_reader;
|
||||
|
||||
/**
|
||||
* The paths where to look for mapping files.
|
||||
@@ -62,22 +61,22 @@ class AnnotationDriver implements Driver
|
||||
* @param array
|
||||
*/
|
||||
protected $_classNames;
|
||||
|
||||
|
||||
/**
|
||||
* Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
|
||||
* docblock annotations.
|
||||
*
|
||||
* @param $reader The AnnotationReader to use.
|
||||
* @param string|array $paths One or multiple paths where mapping classes can be found.
|
||||
*
|
||||
* @param AnnotationReader $reader The AnnotationReader to use, duck-typed.
|
||||
* @param string|array $paths One or multiple paths where mapping classes can be found.
|
||||
*/
|
||||
public function __construct(AnnotationReader $reader, $paths = null)
|
||||
public function __construct($reader, $paths = null)
|
||||
{
|
||||
$this->_reader = $reader;
|
||||
if ($paths) {
|
||||
$this->addPaths((array) $paths);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Append lookup paths to metadata driver.
|
||||
*
|
||||
@@ -128,10 +127,21 @@ class AnnotationDriver implements Driver
|
||||
|
||||
$classAnnotations = $this->_reader->getClassAnnotations($class);
|
||||
|
||||
// Compatibility with Doctrine Common 3.x
|
||||
if ($classAnnotations && is_int(key($classAnnotations))) {
|
||||
foreach ($classAnnotations as $annot) {
|
||||
$classAnnotations[get_class($annot)] = $annot;
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate Entity annotation
|
||||
if (isset($classAnnotations['Doctrine\ORM\Mapping\Entity'])) {
|
||||
$entityAnnot = $classAnnotations['Doctrine\ORM\Mapping\Entity'];
|
||||
$metadata->setCustomRepositoryClass($entityAnnot->repositoryClass);
|
||||
|
||||
if ($entityAnnot->readOnly) {
|
||||
$metadata->markReadOnly();
|
||||
}
|
||||
} else if (isset($classAnnotations['Doctrine\ORM\Mapping\MappedSuperclass'])) {
|
||||
$metadata->isMappedSuperclass = true;
|
||||
} else {
|
||||
@@ -165,6 +175,18 @@ class AnnotationDriver implements Driver
|
||||
$metadata->setPrimaryTable($primaryTable);
|
||||
}
|
||||
|
||||
// Evaluate NamedQueries annotation
|
||||
if (isset($classAnnotations['Doctrine\ORM\Mapping\NamedQueries'])) {
|
||||
$namedQueriesAnnot = $classAnnotations['Doctrine\ORM\Mapping\NamedQueries'];
|
||||
|
||||
foreach ($namedQueriesAnnot->value as $namedQuery) {
|
||||
$metadata->addNamedQuery(array(
|
||||
'name' => $namedQuery->name,
|
||||
'query' => $namedQuery->query
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate InheritanceType annotation
|
||||
if (isset($classAnnotations['Doctrine\ORM\Mapping\InheritanceType'])) {
|
||||
$inheritanceTypeAnnot = $classAnnotations['Doctrine\ORM\Mapping\InheritanceType'];
|
||||
@@ -288,6 +310,10 @@ class AnnotationDriver implements Driver
|
||||
throw MappingException::tableIdGeneratorNotImplemented($className);
|
||||
}
|
||||
} else if ($oneToOneAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OneToOne')) {
|
||||
if ($idAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Id')) {
|
||||
$mapping['id'] = true;
|
||||
}
|
||||
|
||||
$mapping['targetEntity'] = $oneToOneAnnot->targetEntity;
|
||||
$mapping['joinColumns'] = $joinColumns;
|
||||
$mapping['mappedBy'] = $oneToOneAnnot->mappedBy;
|
||||
@@ -300,6 +326,7 @@ class AnnotationDriver implements Driver
|
||||
$mapping['mappedBy'] = $oneToManyAnnot->mappedBy;
|
||||
$mapping['targetEntity'] = $oneToManyAnnot->targetEntity;
|
||||
$mapping['cascade'] = $oneToManyAnnot->cascade;
|
||||
$mapping['indexBy'] = $oneToManyAnnot->indexBy;
|
||||
$mapping['orphanRemoval'] = $oneToManyAnnot->orphanRemoval;
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $oneToManyAnnot->fetch);
|
||||
|
||||
@@ -309,6 +336,10 @@ class AnnotationDriver implements Driver
|
||||
|
||||
$metadata->mapOneToMany($mapping);
|
||||
} else if ($manyToOneAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\ManyToOne')) {
|
||||
if ($idAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Id')) {
|
||||
$mapping['id'] = true;
|
||||
}
|
||||
|
||||
$mapping['joinColumns'] = $joinColumns;
|
||||
$mapping['cascade'] = $manyToOneAnnot->cascade;
|
||||
$mapping['inversedBy'] = $manyToOneAnnot->inversedBy;
|
||||
@@ -354,6 +385,7 @@ class AnnotationDriver implements Driver
|
||||
$mapping['mappedBy'] = $manyToManyAnnot->mappedBy;
|
||||
$mapping['inversedBy'] = $manyToManyAnnot->inversedBy;
|
||||
$mapping['cascade'] = $manyToManyAnnot->cascade;
|
||||
$mapping['indexBy'] = $manyToManyAnnot->indexBy;
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $manyToManyAnnot->fetch);
|
||||
|
||||
if ($orderByAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OrderBy')) {
|
||||
@@ -367,9 +399,17 @@ class AnnotationDriver implements Driver
|
||||
// Evaluate @HasLifecycleCallbacks annotation
|
||||
if (isset($classAnnotations['Doctrine\ORM\Mapping\HasLifecycleCallbacks'])) {
|
||||
foreach ($class->getMethods() as $method) {
|
||||
if ($method->isPublic()) {
|
||||
// filter for the declaring class only, callbacks from parents will already be registered.
|
||||
if ($method->isPublic() && $method->getDeclaringClass()->getName() == $class->name) {
|
||||
$annotations = $this->_reader->getMethodAnnotations($method);
|
||||
|
||||
// Compatibility with Doctrine Common 3.x
|
||||
if ($annotations && is_int(key($annotations))) {
|
||||
foreach ($annotations as $annot) {
|
||||
$annotations[get_class($annot)] = $annot;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($annotations['Doctrine\ORM\Mapping\PrePersist'])) {
|
||||
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::prePersist);
|
||||
}
|
||||
@@ -415,6 +455,20 @@ class AnnotationDriver implements Driver
|
||||
{
|
||||
$classAnnotations = $this->_reader->getClassAnnotations(new \ReflectionClass($className));
|
||||
|
||||
// Compatibility with Doctrine Common 3.x
|
||||
if ($classAnnotations && is_int(key($classAnnotations))) {
|
||||
foreach ($classAnnotations as $annot) {
|
||||
if ($annot instanceof \Doctrine\ORM\Mapping\Entity) {
|
||||
return false;
|
||||
}
|
||||
if ($annot instanceof \Doctrine\ORM\Mapping\MappedSuperclass) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return ! isset($classAnnotations['Doctrine\ORM\Mapping\Entity']) &&
|
||||
! isset($classAnnotations['Doctrine\ORM\Mapping\MappedSuperclass']);
|
||||
}
|
||||
@@ -440,18 +494,20 @@ class AnnotationDriver implements Driver
|
||||
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
|
||||
}
|
||||
|
||||
$iterator = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($path),
|
||||
\RecursiveIteratorIterator::LEAVES_ONLY
|
||||
$iterator = new \RegexIterator(
|
||||
new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS),
|
||||
\RecursiveIteratorIterator::LEAVES_ONLY
|
||||
),
|
||||
'/^.+\\' . $this->_fileExtension . '$/i',
|
||||
\RecursiveRegexIterator::GET_MATCH
|
||||
);
|
||||
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
if (($fileName = $file->getBasename($this->_fileExtension)) == $file->getBasename()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sourceFile = realpath($file->getPathName());
|
||||
$sourceFile = realpath($file[0]);
|
||||
|
||||
require_once $sourceFile;
|
||||
|
||||
$includedFiles[] = $sourceFile;
|
||||
}
|
||||
}
|
||||
@@ -473,7 +529,7 @@ class AnnotationDriver implements Driver
|
||||
|
||||
/**
|
||||
* Factory method for the Annotation Driver
|
||||
*
|
||||
*
|
||||
* @param array|string $paths
|
||||
* @param AnnotationReader $reader
|
||||
* @return AnnotationDriver
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Doctrine\ORM\Mapping\Driver;
|
||||
use Doctrine\Common\Cache\ArrayCache,
|
||||
Doctrine\Common\Annotations\AnnotationReader,
|
||||
Doctrine\DBAL\Schema\AbstractSchemaManager,
|
||||
Doctrine\DBAL\Schema\SchemaException,
|
||||
Doctrine\ORM\Mapping\ClassMetadataInfo,
|
||||
Doctrine\ORM\Mapping\MappingException,
|
||||
Doctrine\Common\Util\Inflector;
|
||||
@@ -54,7 +55,24 @@ class DatabaseDriver implements Driver
|
||||
* @var array
|
||||
*/
|
||||
private $manyToManyTables = array();
|
||||
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $classNamesForTables = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $fieldNamesForColumns = array();
|
||||
|
||||
/**
|
||||
* The namespace for the generated entities.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $namespace;
|
||||
|
||||
/**
|
||||
* Initializes a new AnnotationDriver that uses the given AnnotationReader for reading
|
||||
* docblock annotations.
|
||||
@@ -66,17 +84,39 @@ class DatabaseDriver implements Driver
|
||||
$this->_sm = $schemaManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set tables manually instead of relying on the reverse engeneering capabilities of SchemaManager.
|
||||
*
|
||||
* @param array $entityTables
|
||||
* @param array $manyToManyTables
|
||||
* @return void
|
||||
*/
|
||||
public function setTables($entityTables, $manyToManyTables)
|
||||
{
|
||||
$this->tables = $this->manyToManyTables = $this->classToTableNames = array();
|
||||
foreach ($entityTables AS $table) {
|
||||
$className = $this->getClassNameForTable($table->getName());
|
||||
$this->classToTableNames[$className] = $table->getName();
|
||||
$this->tables[$table->getName()] = $table;
|
||||
}
|
||||
foreach ($manyToManyTables AS $table) {
|
||||
$this->manyToManyTables[$table->getName()] = $table;
|
||||
}
|
||||
}
|
||||
|
||||
private function reverseEngineerMappingFromDatabase()
|
||||
{
|
||||
if ($this->tables !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tables = array();
|
||||
|
||||
foreach ($this->_sm->listTableNames() as $tableName) {
|
||||
$tables[$tableName] = $this->_sm->listTableDetails($tableName);
|
||||
}
|
||||
|
||||
$this->tables = array();
|
||||
$this->tables = $this->manyToManyTables = $this->classToTableNames = array();
|
||||
foreach ($tables AS $tableName => $table) {
|
||||
/* @var $table Table */
|
||||
if ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints()) {
|
||||
@@ -94,16 +134,12 @@ class DatabaseDriver implements Driver
|
||||
sort($pkColumns);
|
||||
sort($allForeignKeyColumns);
|
||||
|
||||
if ($pkColumns == $allForeignKeyColumns) {
|
||||
if (count($table->getForeignKeys()) > 2) {
|
||||
throw new \InvalidArgumentException("ManyToMany table '" . $tableName . "' with more or less than two foreign keys are not supported by the Database Reverese Engineering Driver.");
|
||||
}
|
||||
|
||||
if ($pkColumns == $allForeignKeyColumns && count($foreignKeys) == 2) {
|
||||
$this->manyToManyTables[$tableName] = $table;
|
||||
} else {
|
||||
// lower-casing is necessary because of Oracle Uppercase Tablenames,
|
||||
// assumption is lower-case + underscore separated.
|
||||
$className = Inflector::classify(strtolower($tableName));
|
||||
$className = $this->getClassNameForTable($tableName);
|
||||
$this->tables[$tableName] = $table;
|
||||
$this->classToTableNames[$className] = $tableName;
|
||||
}
|
||||
@@ -128,6 +164,11 @@ class DatabaseDriver implements Driver
|
||||
|
||||
$columns = $this->tables[$tableName]->getColumns();
|
||||
$indexes = $this->tables[$tableName]->getIndexes();
|
||||
try {
|
||||
$primaryKeyColumns = $this->tables[$tableName]->getPrimaryKey()->getColumns();
|
||||
} catch(SchemaException $e) {
|
||||
$primaryKeyColumns = array();
|
||||
}
|
||||
|
||||
if ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints()) {
|
||||
$foreignKeys = $this->tables[$tableName]->getForeignKeys();
|
||||
@@ -144,13 +185,14 @@ class DatabaseDriver implements Driver
|
||||
$fieldMappings = array();
|
||||
foreach ($columns as $column) {
|
||||
$fieldMapping = array();
|
||||
if (isset($indexes['primary']) && in_array($column->getName(), $indexes['primary']->getColumns())) {
|
||||
$fieldMapping['id'] = true;
|
||||
} else if (in_array($column->getName(), $allForeignKeyColumns)) {
|
||||
|
||||
if (in_array($column->getName(), $allForeignKeyColumns)) {
|
||||
continue;
|
||||
} else if ($primaryKeyColumns && in_array($column->getName(), $primaryKeyColumns)) {
|
||||
$fieldMapping['id'] = true;
|
||||
}
|
||||
|
||||
$fieldMapping['fieldName'] = Inflector::camelize(strtolower($column->getName()));
|
||||
|
||||
$fieldMapping['fieldName'] = $this->getFieldNameForColumn($tableName, $column->getName(), false);
|
||||
$fieldMapping['columnName'] = $column->getName();
|
||||
$fieldMapping['type'] = strtolower((string) $column->getType());
|
||||
|
||||
@@ -185,8 +227,10 @@ class DatabaseDriver implements Driver
|
||||
|
||||
foreach ($this->manyToManyTables AS $manyTable) {
|
||||
foreach ($manyTable->getForeignKeys() AS $foreignKey) {
|
||||
// foreign key maps to the table of the current entity, many to many association probably exists
|
||||
if (strtolower($tableName) == strtolower($foreignKey->getForeignTableName())) {
|
||||
$myFk = $foreignKey;
|
||||
$otherFk = null;
|
||||
foreach ($manyTable->getForeignKeys() AS $foreignKey) {
|
||||
if ($foreignKey != $myFk) {
|
||||
$otherFk = $foreignKey;
|
||||
@@ -194,12 +238,18 @@ class DatabaseDriver implements Driver
|
||||
}
|
||||
}
|
||||
|
||||
if (!$otherFk) {
|
||||
// the definition of this many to many table does not contain
|
||||
// enough foreign key information to continue reverse engeneering.
|
||||
continue;
|
||||
}
|
||||
|
||||
$localColumn = current($myFk->getColumns());
|
||||
$associationMapping = array();
|
||||
$associationMapping['fieldName'] = Inflector::camelize(str_replace('_id', '', strtolower(current($otherFk->getColumns()))));
|
||||
$associationMapping['targetEntity'] = Inflector::classify(strtolower($otherFk->getForeignTableName()));
|
||||
$associationMapping['fieldName'] = $this->getFieldNameForColumn($manyTable->getName(), current($otherFk->getColumns()), true);
|
||||
$associationMapping['targetEntity'] = $this->getClassNameForTable($otherFk->getForeignTableName());
|
||||
if (current($manyTable->getColumns())->getName() == $localColumn) {
|
||||
$associationMapping['inversedBy'] = Inflector::camelize(str_replace('_id', '', strtolower(current($myFk->getColumns()))));
|
||||
$associationMapping['inversedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true);
|
||||
$associationMapping['joinTable'] = array(
|
||||
'name' => strtolower($manyTable->getName()),
|
||||
'joinColumns' => array(),
|
||||
@@ -224,7 +274,7 @@ class DatabaseDriver implements Driver
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$associationMapping['mappedBy'] = Inflector::camelize(str_replace('_id', '', strtolower(current($myFk->getColumns()))));
|
||||
$associationMapping['mappedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true);
|
||||
}
|
||||
$metadata->mapManyToMany($associationMapping);
|
||||
break;
|
||||
@@ -239,8 +289,12 @@ class DatabaseDriver implements Driver
|
||||
|
||||
$localColumn = current($cols);
|
||||
$associationMapping = array();
|
||||
$associationMapping['fieldName'] = Inflector::camelize(str_replace('_id', '', strtolower($localColumn)));
|
||||
$associationMapping['targetEntity'] = Inflector::classify($foreignTable);
|
||||
$associationMapping['fieldName'] = $this->getFieldNameForColumn($tableName, $localColumn, true);
|
||||
$associationMapping['targetEntity'] = $this->getClassNameForTable($foreignTable);
|
||||
|
||||
if ($primaryKeyColumns && in_array($localColumn, $primaryKeyColumns)) {
|
||||
$associationMapping['id'] = true;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < count($cols); $i++) {
|
||||
$associationMapping['joinColumns'][] = array(
|
||||
@@ -248,7 +302,13 @@ class DatabaseDriver implements Driver
|
||||
'referencedColumnName' => $fkCols[$i],
|
||||
);
|
||||
}
|
||||
$metadata->mapManyToOne($associationMapping);
|
||||
|
||||
//Here we need to check if $cols are the same as $primaryKeyColums
|
||||
if (!array_diff($cols,$primaryKeyColumns)) {
|
||||
$metadata->mapOneToOne($associationMapping);
|
||||
} else {
|
||||
$metadata->mapManyToOne($associationMapping);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,4 +333,78 @@ class DatabaseDriver implements Driver
|
||||
|
||||
return array_keys($this->classToTableNames);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set class name for a table.
|
||||
*
|
||||
* @param string $tableName
|
||||
* @param string $className
|
||||
* @return void
|
||||
*/
|
||||
public function setClassNameForTable($tableName, $className)
|
||||
{
|
||||
$this->classNamesForTables[$tableName] = $className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set field name for a column on a specific table.
|
||||
*
|
||||
* @param string $tableName
|
||||
* @param string $columnName
|
||||
* @param string $fieldName
|
||||
* @return void
|
||||
*/
|
||||
public function setFieldNameForColumn($tableName, $columnName, $fieldName)
|
||||
{
|
||||
$this->fieldNamesForColumns[$tableName][$columnName] = $fieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mapped class name for a table if it exists. Otherwise return "classified" version.
|
||||
*
|
||||
* @param string $tableName
|
||||
* @return string
|
||||
*/
|
||||
private function getClassNameForTable($tableName)
|
||||
{
|
||||
if (isset($this->classNamesForTables[$tableName])) {
|
||||
return $this->namespace . $this->classNamesForTables[$tableName];
|
||||
}
|
||||
|
||||
return $this->namespace . Inflector::classify(strtolower($tableName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mapped field name for a column, if it exists. Otherwise return camelized version.
|
||||
*
|
||||
* @param string $tableName
|
||||
* @param string $columnName
|
||||
* @param boolean $fk Whether the column is a foreignkey or not.
|
||||
* @return string
|
||||
*/
|
||||
private function getFieldNameForColumn($tableName, $columnName, $fk = false)
|
||||
{
|
||||
if (isset($this->fieldNamesForColumns[$tableName]) && isset($this->fieldNamesForColumns[$tableName][$columnName])) {
|
||||
return $this->fieldNamesForColumns[$tableName][$columnName];
|
||||
}
|
||||
|
||||
$columnName = strtolower($columnName);
|
||||
|
||||
// Replace _id if it is a foreignkey column
|
||||
if ($fk) {
|
||||
$columnName = str_replace('_id', '', $columnName);
|
||||
}
|
||||
return Inflector::camelize($columnName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the namespace for the generated entities.
|
||||
*
|
||||
* @param string $namespace
|
||||
* @return void
|
||||
*/
|
||||
public function setNamespace($namespace)
|
||||
{
|
||||
$this->namespace = $namespace;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<?php
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* 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
|
||||
@@ -25,23 +23,41 @@ use Doctrine\Common\Annotations\Annotation;
|
||||
|
||||
/* Annotations */
|
||||
|
||||
/** @Annotation */
|
||||
final class Entity extends Annotation {
|
||||
public $repositoryClass;
|
||||
public $readOnly = false;
|
||||
}
|
||||
|
||||
/** @Annotation */
|
||||
final class MappedSuperclass extends Annotation {}
|
||||
|
||||
/** @Annotation */
|
||||
final class InheritanceType extends Annotation {}
|
||||
|
||||
/** @Annotation */
|
||||
final class DiscriminatorColumn extends Annotation {
|
||||
public $name;
|
||||
public $fieldName; // field name used in non-object hydration (array/scalar)
|
||||
public $type;
|
||||
public $length;
|
||||
}
|
||||
|
||||
/** @Annotation */
|
||||
final class DiscriminatorMap extends Annotation {}
|
||||
|
||||
/** @Annotation */
|
||||
final class Id extends Annotation {}
|
||||
|
||||
/** @Annotation */
|
||||
final class GeneratedValue extends Annotation {
|
||||
public $strategy = 'AUTO';
|
||||
}
|
||||
|
||||
/** @Annotation */
|
||||
final class Version extends Annotation {}
|
||||
|
||||
/** @Annotation */
|
||||
final class JoinColumn extends Annotation {
|
||||
public $name;
|
||||
public $fieldName; // field name used in non-object hydration (array/scalar)
|
||||
@@ -52,7 +68,11 @@ final class JoinColumn extends Annotation {
|
||||
public $onUpdate;
|
||||
public $columnDefinition;
|
||||
}
|
||||
|
||||
/** @Annotation */
|
||||
final class JoinColumns extends Annotation {}
|
||||
|
||||
/** @Annotation */
|
||||
final class Column extends Annotation {
|
||||
public $type = 'string';
|
||||
public $length;
|
||||
@@ -66,6 +86,8 @@ final class Column extends Annotation {
|
||||
public $options = array();
|
||||
public $columnDefinition;
|
||||
}
|
||||
|
||||
/** @Annotation */
|
||||
final class OneToOne extends Annotation {
|
||||
public $targetEntity;
|
||||
public $mappedBy;
|
||||
@@ -74,64 +96,111 @@ final class OneToOne extends Annotation {
|
||||
public $fetch = 'LAZY';
|
||||
public $orphanRemoval = false;
|
||||
}
|
||||
|
||||
/** @Annotation */
|
||||
final class OneToMany extends Annotation {
|
||||
public $mappedBy;
|
||||
public $targetEntity;
|
||||
public $cascade;
|
||||
public $fetch = 'LAZY';
|
||||
public $orphanRemoval = false;
|
||||
public $indexBy;
|
||||
}
|
||||
|
||||
/** @Annotation */
|
||||
final class ManyToOne extends Annotation {
|
||||
public $targetEntity;
|
||||
public $cascade;
|
||||
public $fetch = 'LAZY';
|
||||
public $inversedBy;
|
||||
}
|
||||
|
||||
/** @Annotation */
|
||||
final class ManyToMany extends Annotation {
|
||||
public $targetEntity;
|
||||
public $mappedBy;
|
||||
public $inversedBy;
|
||||
public $cascade;
|
||||
public $fetch = 'LAZY';
|
||||
public $indexBy;
|
||||
}
|
||||
|
||||
/** @Annotation */
|
||||
final class ElementCollection extends Annotation {
|
||||
public $tableName;
|
||||
}
|
||||
|
||||
/** @Annotation */
|
||||
final class Table extends Annotation {
|
||||
public $name;
|
||||
public $schema;
|
||||
public $indexes;
|
||||
public $uniqueConstraints;
|
||||
}
|
||||
|
||||
/** @Annotation */
|
||||
final class UniqueConstraint extends Annotation {
|
||||
public $name;
|
||||
public $columns;
|
||||
}
|
||||
|
||||
/** @Annotation */
|
||||
final class Index extends Annotation {
|
||||
public $name;
|
||||
public $columns;
|
||||
}
|
||||
|
||||
/** @Annotation */
|
||||
final class JoinTable extends Annotation {
|
||||
public $name;
|
||||
public $schema;
|
||||
public $joinColumns = array();
|
||||
public $inverseJoinColumns = array();
|
||||
}
|
||||
|
||||
/** @Annotation */
|
||||
final class SequenceGenerator extends Annotation {
|
||||
public $sequenceName;
|
||||
public $allocationSize = 1;
|
||||
public $initialValue = 1;
|
||||
}
|
||||
|
||||
/** @Annotation */
|
||||
final class ChangeTrackingPolicy extends Annotation {}
|
||||
|
||||
/** @Annotation */
|
||||
final class OrderBy extends Annotation {}
|
||||
|
||||
/* Annotations for lifecycle callbacks */
|
||||
final class HasLifecycleCallbacks extends Annotation {}
|
||||
final class PrePersist extends Annotation {}
|
||||
final class PostPersist extends Annotation {}
|
||||
final class PreUpdate extends Annotation {}
|
||||
final class PostUpdate extends Annotation {}
|
||||
final class PreRemove extends Annotation {}
|
||||
final class PostRemove extends Annotation {}
|
||||
final class PostLoad extends Annotation {}
|
||||
/** @Annotation */
|
||||
final class NamedQueries extends Annotation {}
|
||||
|
||||
/** @Annotation */
|
||||
final class NamedQuery extends Annotation {
|
||||
public $name;
|
||||
public $query;
|
||||
}
|
||||
|
||||
/* Annotations for lifecycle callbacks */
|
||||
/** @Annotation */
|
||||
final class HasLifecycleCallbacks extends Annotation {}
|
||||
|
||||
/** @Annotation */
|
||||
final class PrePersist extends Annotation {}
|
||||
|
||||
/** @Annotation */
|
||||
final class PostPersist extends Annotation {}
|
||||
|
||||
/** @Annotation */
|
||||
final class PreUpdate extends Annotation {}
|
||||
|
||||
/** @Annotation */
|
||||
final class PostUpdate extends Annotation {}
|
||||
|
||||
/** @Annotation */
|
||||
final class PreRemove extends Annotation {}
|
||||
|
||||
/** @Annotation */
|
||||
final class PostRemove extends Annotation {}
|
||||
|
||||
/** @Annotation */
|
||||
final class PostLoad extends Annotation {}
|
||||
|
||||
@@ -88,15 +88,20 @@ class DriverChain implements Driver
|
||||
public function getAllClassNames()
|
||||
{
|
||||
$classNames = array();
|
||||
$driverClasses = array();
|
||||
foreach ($this->_drivers AS $namespace => $driver) {
|
||||
$driverClasses = $driver->getAllClassNames();
|
||||
foreach ($driverClasses AS $className) {
|
||||
$oid = spl_object_hash($driver);
|
||||
if (!isset($driverClasses[$oid])) {
|
||||
$driverClasses[$oid] = $driver->getAllClassNames();
|
||||
}
|
||||
|
||||
foreach ($driverClasses[$oid] AS $className) {
|
||||
if (strpos($className, $namespace) === 0) {
|
||||
$classNames[] = $className;
|
||||
$classNames[$className] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return array_unique($classNames);
|
||||
return array_keys($classNames);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<?php
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* 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
|
||||
@@ -22,7 +20,7 @@
|
||||
namespace Doctrine\ORM\Mapping\Driver;
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadataInfo,
|
||||
Doctrine\ORM\Mapping\MappingException;
|
||||
Doctrine\ORM\Mapping\MappingException;
|
||||
|
||||
/**
|
||||
* The StaticPHPDriver calls a static loadMetadata() method on your entity
|
||||
@@ -31,15 +29,33 @@ use Doctrine\ORM\Mapping\ClassMetadataInfo,
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
* @since 2.0
|
||||
* @version $Revision$
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @author Jonathan H. Wage <jonwage@gmail.com>
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
*/
|
||||
class StaticPHPDriver implements Driver
|
||||
{
|
||||
/**
|
||||
* Paths of entity directories.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_paths = array();
|
||||
|
||||
/**
|
||||
* Map of all class names.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_classNames;
|
||||
|
||||
/**
|
||||
* The file extension of mapping documents.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_fileExtension = '.php';
|
||||
|
||||
public function __construct($paths)
|
||||
{
|
||||
@@ -58,7 +74,7 @@ class StaticPHPDriver implements Driver
|
||||
{
|
||||
call_user_func_array(array($className, 'loadMetadata'), array($metadata));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @todo Same code exists in AnnotationDriver, should we re-use it somehow or not worry about it?
|
||||
@@ -77,13 +93,13 @@ class StaticPHPDriver implements Driver
|
||||
$includedFiles = array();
|
||||
|
||||
foreach ($this->_paths as $path) {
|
||||
if ( ! is_dir($path)) {
|
||||
if (!is_dir($path)) {
|
||||
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
|
||||
}
|
||||
|
||||
$iterator = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($path),
|
||||
\RecursiveIteratorIterator::LEAVES_ONLY
|
||||
new \RecursiveDirectoryIterator($path),
|
||||
\RecursiveIteratorIterator::LEAVES_ONLY
|
||||
);
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
@@ -102,7 +118,7 @@ class StaticPHPDriver implements Driver
|
||||
foreach ($declared as $className) {
|
||||
$rc = new \ReflectionClass($className);
|
||||
$sourceFile = $rc->getFileName();
|
||||
if (in_array($sourceFile, $includedFiles) && ! $this->isTransient($className)) {
|
||||
if (in_array($sourceFile, $includedFiles) && !$this->isTransient($className)) {
|
||||
$classes[] = $className;
|
||||
}
|
||||
}
|
||||
@@ -119,4 +135,4 @@ class StaticPHPDriver implements Driver
|
||||
{
|
||||
return method_exists($className, 'loadMetadata') ? false : true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,9 @@ class XmlDriver extends AbstractFileDriver
|
||||
$metadata->setCustomRepositoryClass(
|
||||
isset($xmlRoot['repository-class']) ? (string)$xmlRoot['repository-class'] : null
|
||||
);
|
||||
if (isset($xmlRoot['read-only']) && $xmlRoot['read-only'] == "true") {
|
||||
$metadata->markReadOnly();
|
||||
}
|
||||
} else if ($xmlRoot->getName() == 'mapped-superclass') {
|
||||
$metadata->isMappedSuperclass = true;
|
||||
} else {
|
||||
@@ -69,11 +72,21 @@ class XmlDriver extends AbstractFileDriver
|
||||
|
||||
$metadata->setPrimaryTable($table);
|
||||
|
||||
// Evaluate named queries
|
||||
if (isset($xmlRoot['named-queries'])) {
|
||||
foreach ($xmlRoot->{'named-queries'}->{'named-query'} as $namedQueryElement) {
|
||||
$metadata->addNamedQuery(array(
|
||||
'name' => (string)$namedQueryElement['name'],
|
||||
'query' => (string)$namedQueryElement['query']
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/* not implemented specially anyway. use table = schema.table
|
||||
if (isset($xmlRoot['schema'])) {
|
||||
$metadata->table['schema'] = (string)$xmlRoot['schema'];
|
||||
}*/
|
||||
|
||||
|
||||
if (isset($xmlRoot['inheritance-type'])) {
|
||||
$inheritanceType = (string)$xmlRoot['inheritance-type'];
|
||||
$metadata->setInheritanceType(constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceType));
|
||||
@@ -194,7 +207,13 @@ class XmlDriver extends AbstractFileDriver
|
||||
}
|
||||
|
||||
// Evaluate <id ...> mappings
|
||||
$associationIds = array();
|
||||
foreach ($xmlRoot->id as $idElement) {
|
||||
if ((bool)$idElement['association-key'] == true) {
|
||||
$associationIds[(string)$idElement['name']] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
$mapping = array(
|
||||
'id' => true,
|
||||
'fieldName' => (string)$idElement['name'],
|
||||
@@ -235,6 +254,10 @@ class XmlDriver extends AbstractFileDriver
|
||||
'targetEntity' => (string)$oneToOneElement['target-entity']
|
||||
);
|
||||
|
||||
if (isset($associationIds[$mapping['fieldName']])) {
|
||||
$mapping['id'] = true;
|
||||
}
|
||||
|
||||
if (isset($oneToOneElement['fetch'])) {
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string)$oneToOneElement['fetch']);
|
||||
}
|
||||
@@ -262,8 +285,8 @@ class XmlDriver extends AbstractFileDriver
|
||||
$mapping['cascade'] = $this->_getCascadeMappings($oneToOneElement->cascade);
|
||||
}
|
||||
|
||||
if (isset($oneToOneElement->{'orphan-removal'})) {
|
||||
$mapping['orphanRemoval'] = (bool)$oneToOneElement->{'orphan-removal'};
|
||||
if (isset($oneToOneElement['orphan-removal'])) {
|
||||
$mapping['orphanRemoval'] = (bool)$oneToOneElement['orphan-removal'];
|
||||
}
|
||||
|
||||
$metadata->mapOneToOne($mapping);
|
||||
@@ -287,8 +310,8 @@ class XmlDriver extends AbstractFileDriver
|
||||
$mapping['cascade'] = $this->_getCascadeMappings($oneToManyElement->cascade);
|
||||
}
|
||||
|
||||
if (isset($oneToManyElement->{'orphan-removal'})) {
|
||||
$mapping['orphanRemoval'] = (bool)$oneToManyElement->{'orphan-removal'};
|
||||
if (isset($oneToManyElement['orphan-removal'])) {
|
||||
$mapping['orphanRemoval'] = (bool)$oneToManyElement['orphan-removal'];
|
||||
}
|
||||
|
||||
if (isset($oneToManyElement->{'order-by'})) {
|
||||
@@ -299,6 +322,12 @@ class XmlDriver extends AbstractFileDriver
|
||||
$mapping['orderBy'] = $orderBy;
|
||||
}
|
||||
|
||||
if (isset($oneToManyElement['index-by'])) {
|
||||
$mapping['indexBy'] = (string)$oneToManyElement['index-by'];
|
||||
} else if (isset($oneToManyElement->{'index-by'})) {
|
||||
throw new \InvalidArgumentException("<index-by /> is not a valid tag");
|
||||
}
|
||||
|
||||
$metadata->mapOneToMany($mapping);
|
||||
}
|
||||
}
|
||||
@@ -311,6 +340,10 @@ class XmlDriver extends AbstractFileDriver
|
||||
'targetEntity' => (string)$manyToOneElement['target-entity']
|
||||
);
|
||||
|
||||
if (isset($associationIds[$mapping['fieldName']])) {
|
||||
$mapping['id'] = true;
|
||||
}
|
||||
|
||||
if (isset($manyToOneElement['fetch'])) {
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string)$manyToOneElement['fetch']);
|
||||
}
|
||||
@@ -325,9 +358,6 @@ class XmlDriver extends AbstractFileDriver
|
||||
$joinColumns[] = $this->_getJoinColumnMapping($manyToOneElement->{'join-column'});
|
||||
} else if (isset($manyToOneElement->{'join-columns'})) {
|
||||
foreach ($manyToOneElement->{'join-columns'}->{'join-column'} as $joinColumnElement) {
|
||||
if (!isset($joinColumnElement['name'])) {
|
||||
$joinColumnElement['name'] = $name;
|
||||
}
|
||||
$joinColumns[] = $this->_getJoinColumnMapping($joinColumnElement);
|
||||
}
|
||||
}
|
||||
@@ -401,6 +431,12 @@ class XmlDriver extends AbstractFileDriver
|
||||
$mapping['orderBy'] = $orderBy;
|
||||
}
|
||||
|
||||
if (isset($manyToManyElement['index-by'])) {
|
||||
$mapping['indexBy'] = (string)$manyToManyElement['index-by'];
|
||||
} else if (isset($manyToManyElement->{'index-by'})) {
|
||||
throw new \InvalidArgumentException("<index-by /> is not a valid tag");
|
||||
}
|
||||
|
||||
$metadata->mapManyToMany($mapping);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,9 @@ class YamlDriver extends AbstractFileDriver
|
||||
$metadata->setCustomRepositoryClass(
|
||||
isset($element['repositoryClass']) ? $element['repositoryClass'] : null
|
||||
);
|
||||
if (isset($element['readOnly']) && $element['readOnly'] == true) {
|
||||
$metadata->markReadOnly();
|
||||
}
|
||||
} else if ($element['type'] == 'mappedSuperclass') {
|
||||
$metadata->isMappedSuperclass = true;
|
||||
} else {
|
||||
@@ -62,6 +65,21 @@ class YamlDriver extends AbstractFileDriver
|
||||
}
|
||||
$metadata->setPrimaryTable($table);
|
||||
|
||||
// Evaluate named queries
|
||||
if (isset($element['namedQueries'])) {
|
||||
foreach ($element['namedQueries'] as $name => $queryMapping) {
|
||||
if (is_string($queryMapping)) {
|
||||
$queryMapping = array('query' => $queryMapping);
|
||||
}
|
||||
|
||||
if ( ! isset($queryMapping['name'])) {
|
||||
$queryMapping['name'] = $name;
|
||||
}
|
||||
|
||||
$metadata->addNamedQuery($queryMapping);
|
||||
}
|
||||
}
|
||||
|
||||
/* not implemented specially anyway. use table = schema.table
|
||||
if (isset($element['schema'])) {
|
||||
$metadata->table['schema'] = $element['schema'];
|
||||
@@ -135,9 +153,15 @@ class YamlDriver extends AbstractFileDriver
|
||||
}
|
||||
}
|
||||
|
||||
$associationIds = array();
|
||||
if (isset($element['id'])) {
|
||||
// Evaluate identifier settings
|
||||
foreach ($element['id'] as $name => $idElement) {
|
||||
if (isset($idElement['associationKey']) && $idElement['associationKey'] == true) {
|
||||
$associationIds[$name] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($idElement['type'])) {
|
||||
throw MappingException::propertyTypeIsRequired($className, $name);
|
||||
}
|
||||
@@ -234,6 +258,10 @@ class YamlDriver extends AbstractFileDriver
|
||||
'targetEntity' => $oneToOneElement['targetEntity']
|
||||
);
|
||||
|
||||
if (isset($associationIds[$mapping['fieldName']])) {
|
||||
$mapping['id'] = true;
|
||||
}
|
||||
|
||||
if (isset($oneToOneElement['fetch'])) {
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $oneToOneElement['fetch']);
|
||||
}
|
||||
@@ -266,6 +294,10 @@ class YamlDriver extends AbstractFileDriver
|
||||
$mapping['cascade'] = $oneToOneElement['cascade'];
|
||||
}
|
||||
|
||||
if (isset($oneToOneElement['orphanRemoval'])) {
|
||||
$mapping['orphanRemoval'] = (bool)$oneToOneElement['orphanRemoval'];
|
||||
}
|
||||
|
||||
$metadata->mapOneToOne($mapping);
|
||||
}
|
||||
}
|
||||
@@ -287,10 +319,18 @@ class YamlDriver extends AbstractFileDriver
|
||||
$mapping['cascade'] = $oneToManyElement['cascade'];
|
||||
}
|
||||
|
||||
if (isset($oneToManyElement['orphanRemoval'])) {
|
||||
$mapping['orphanRemoval'] = (bool)$oneToManyElement['orphanRemoval'];
|
||||
}
|
||||
|
||||
if (isset($oneToManyElement['orderBy'])) {
|
||||
$mapping['orderBy'] = $oneToManyElement['orderBy'];
|
||||
}
|
||||
|
||||
if (isset($oneToManyElement['indexBy'])) {
|
||||
$mapping['indexBy'] = $oneToManyElement['indexBy'];
|
||||
}
|
||||
|
||||
$metadata->mapOneToMany($mapping);
|
||||
}
|
||||
}
|
||||
@@ -303,6 +343,10 @@ class YamlDriver extends AbstractFileDriver
|
||||
'targetEntity' => $manyToOneElement['targetEntity']
|
||||
);
|
||||
|
||||
if (isset($associationIds[$mapping['fieldName']])) {
|
||||
$mapping['id'] = true;
|
||||
}
|
||||
|
||||
if (isset($manyToOneElement['fetch'])) {
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $manyToOneElement['fetch']);
|
||||
}
|
||||
@@ -331,6 +375,10 @@ class YamlDriver extends AbstractFileDriver
|
||||
$mapping['cascade'] = $manyToOneElement['cascade'];
|
||||
}
|
||||
|
||||
if (isset($manyToOneElement['orphanRemoval'])) {
|
||||
$mapping['orphanRemoval'] = (bool)$manyToOneElement['orphanRemoval'];
|
||||
}
|
||||
|
||||
$metadata->mapManyToOne($mapping);
|
||||
}
|
||||
}
|
||||
@@ -386,10 +434,18 @@ class YamlDriver extends AbstractFileDriver
|
||||
$mapping['cascade'] = $manyToManyElement['cascade'];
|
||||
}
|
||||
|
||||
if (isset($manyToManyElement['orphanRemoval'])) {
|
||||
$mapping['orphanRemoval'] = (bool)$manyToManyElement['orphanRemoval'];
|
||||
}
|
||||
|
||||
if (isset($manyToManyElement['orderBy'])) {
|
||||
$mapping['orderBy'] = $manyToManyElement['orderBy'];
|
||||
}
|
||||
|
||||
if (isset($manyToManyElement['indexBy'])) {
|
||||
$mapping['indexBy'] = $manyToManyElement['indexBy'];
|
||||
}
|
||||
|
||||
$metadata->mapManyToMany($mapping);
|
||||
}
|
||||
}
|
||||
@@ -450,6 +506,6 @@ class YamlDriver extends AbstractFileDriver
|
||||
*/
|
||||
protected function _loadMappingFile($file)
|
||||
{
|
||||
return \Symfony\Component\Yaml\Yaml::load($file);
|
||||
return \Symfony\Component\Yaml\Yaml::parse($file);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,9 +48,9 @@ class MappingException extends \Doctrine\ORM\ORMException
|
||||
return new self("Id generators can't be used with a composite id.");
|
||||
}
|
||||
|
||||
public static function missingFieldName()
|
||||
public static function missingFieldName($entity)
|
||||
{
|
||||
return new self("The association mapping misses the 'fieldName' attribute.");
|
||||
return new self("The field or association mapping misses the 'fieldName' attribute in entity '$entity'.");
|
||||
}
|
||||
|
||||
public static function missingTargetEntity($fieldName)
|
||||
@@ -68,9 +68,14 @@ class MappingException extends \Doctrine\ORM\ORMException
|
||||
return new self("No mapping file found named '$fileName' for class '$entityName'.");
|
||||
}
|
||||
|
||||
public static function mappingNotFound($fieldName)
|
||||
public static function mappingNotFound($className, $fieldName)
|
||||
{
|
||||
return new self("No mapping found for field '$fieldName'.");
|
||||
return new self("No mapping found for field '$fieldName' on class '$className'.");
|
||||
}
|
||||
|
||||
public static function queryNotFound($className, $queryName)
|
||||
{
|
||||
return new self("No query found named '$queryName' on class '$className'.");
|
||||
}
|
||||
|
||||
public static function oneToManyRequiresMappedBy($fieldName)
|
||||
@@ -160,6 +165,10 @@ class MappingException extends \Doctrine\ORM\ORMException
|
||||
return new self('Property "'.$fieldName.'" in "'.$entity.'" was already declared, but it must be declared only once');
|
||||
}
|
||||
|
||||
public static function duplicateQueryMapping($entity, $queryName) {
|
||||
return new self('Query named "'.$queryName.'" in "'.$entity.'" was already declared, but it must be declared only once');
|
||||
}
|
||||
|
||||
public static function singleIdNotAllowedOnCompositePrimaryKey($entity) {
|
||||
return new self('Single id is not allowed on composite primary key in entity '.$entity);
|
||||
}
|
||||
@@ -227,4 +236,61 @@ class MappingException extends \Doctrine\ORM\ORMException
|
||||
return new self("Duplicate definition of column '".$columnName."' on entity '".$className."' in a field or discriminator column mapping.");
|
||||
}
|
||||
|
||||
public static function illegalToManyAssocationOnMappedSuperclass($className, $field)
|
||||
{
|
||||
return new self("It is illegal to put an inverse side one-to-many or many-to-many association on mapped superclass '".$className."#".$field."'.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
* @param string $targetEntity
|
||||
* @param string $targetField
|
||||
* @return self
|
||||
*/
|
||||
public static function cannotMapCompositePrimaryKeyEntitiesAsForeignId($className, $targetEntity, $targetField)
|
||||
{
|
||||
return new self("It is not possible to map entity '".$className."' with a composite primary key ".
|
||||
"as part of the primary key of another entity '".$targetEntity."#".$targetField."'.");
|
||||
}
|
||||
|
||||
public static function noSingleAssociationJoinColumnFound($className, $field)
|
||||
{
|
||||
return new self("'$className#$field' is not an association with a single join column.");
|
||||
}
|
||||
|
||||
public static function noFieldNameFoundForColumn($className, $column)
|
||||
{
|
||||
return new self("Cannot find a field on '$className' that is mapped to column '$column'. Either the ".
|
||||
"field does not exist or an association exists but it has multiple join columns.");
|
||||
}
|
||||
|
||||
public static function illegalOrphanRemovalOnIdentifierAssociation($className, $field)
|
||||
{
|
||||
return new self("The orphan removal option is not allowed on an association that is ".
|
||||
"part of the identifier in '$className#$field'.");
|
||||
}
|
||||
|
||||
public static function illegalInverseIdentifierAssocation($className, $field)
|
||||
{
|
||||
return new self("An inverse association is not allowed to be identifier in '$className#$field'.");
|
||||
}
|
||||
|
||||
public static function illegalToManyIdentifierAssoaction($className, $field)
|
||||
{
|
||||
return new self("Many-to-many or one-to-many associations are not allowed to be identifier in '$className#$field'.");
|
||||
}
|
||||
|
||||
public static function noInheritanceOnMappedSuperClass($className)
|
||||
{
|
||||
return new self("Its not supported to define inheritance information on a mapped superclass '" . $className . "'.");
|
||||
}
|
||||
|
||||
public static function mappedClassNotPartOfDiscriminatorMap($className, $rootClassName)
|
||||
{
|
||||
return new self(
|
||||
"Entity '" . $className . "' has to be part of the descriminator map of '" . $rootClassName . "' " .
|
||||
"to be properly mapped in the inheritance hierachy. Alternatively you can make '".$className."' an abstract class " .
|
||||
"to avoid this exception from occuring."
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -34,10 +34,25 @@ class ORMException extends Exception
|
||||
return new self("It's a requirement to specify a Metadata Driver and pass it ".
|
||||
"to Doctrine\ORM\Configuration::setMetadataDriverImpl().");
|
||||
}
|
||||
|
||||
public static function entityMissingForeignAssignedId($entity, $relatedEntity)
|
||||
{
|
||||
return new self(
|
||||
"Entity of type " . get_class($entity) . " has identity through a foreign entity " . get_class($relatedEntity) . ", " .
|
||||
"however this entity has no ientity itself. You have to call EntityManager#persist() on the related entity " .
|
||||
"and make sure it an identifier was generated before trying to persist '" . get_class($entity) . "'. In case " .
|
||||
"of Post Insert ID Generation (such as MySQL Auto-Increment or PostgreSQL SERIAL) this means you have to call " .
|
||||
"EntityManager#flush() between both persist operations."
|
||||
);
|
||||
}
|
||||
|
||||
public static function entityMissingAssignedId($entity)
|
||||
{
|
||||
return new self("Entity of type " . get_class($entity) . " is missing an assigned ID.");
|
||||
return new self("Entity of type " . get_class($entity) . " is missing an assigned ID. " .
|
||||
"The identifier generation strategy for this entity requires the ID field to be populated before ".
|
||||
"EntityManager#persist() is called. If you want automatically generated identifiers instead " .
|
||||
"you need to adjust the metadata mapping accordingly."
|
||||
);
|
||||
}
|
||||
|
||||
public static function unrecognizedField($field)
|
||||
@@ -45,6 +60,15 @@ class ORMException extends Exception
|
||||
return new self("Unrecognized field: $field");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
* @param string $field
|
||||
*/
|
||||
public static function invalidOrientation($className, $field)
|
||||
{
|
||||
return new self("Invalid order by orientation specified for " . $className . "#" . $field);
|
||||
}
|
||||
|
||||
public static function invalidFlushMode($mode)
|
||||
{
|
||||
return new self("'$mode' is an invalid flush mode.");
|
||||
|
||||
@@ -33,6 +33,7 @@ class OptimisticLockException extends ORMException
|
||||
|
||||
public function __construct($msg, $entity)
|
||||
{
|
||||
parent::__construct($msg);
|
||||
$this->entity = $entity;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,14 +59,14 @@ final class PersistentCollection implements Collection
|
||||
* The association mapping the collection belongs to.
|
||||
* This is currently either a OneToManyMapping or a ManyToManyMapping.
|
||||
*
|
||||
* @var Doctrine\ORM\Mapping\AssociationMapping
|
||||
* @var array
|
||||
*/
|
||||
private $association;
|
||||
|
||||
/**
|
||||
* The EntityManager that manages the persistence of the collection.
|
||||
*
|
||||
* @var Doctrine\ORM\EntityManager
|
||||
* @var \Doctrine\ORM\EntityManager
|
||||
*/
|
||||
private $em;
|
||||
|
||||
@@ -93,21 +93,21 @@ final class PersistentCollection implements Collection
|
||||
|
||||
/**
|
||||
* Whether the collection has already been initialized.
|
||||
*
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
private $initialized = true;
|
||||
|
||||
|
||||
/**
|
||||
* The wrapped Collection instance.
|
||||
*
|
||||
*
|
||||
* @var Collection
|
||||
*/
|
||||
private $coll;
|
||||
|
||||
/**
|
||||
* Creates a new persistent collection.
|
||||
*
|
||||
*
|
||||
* @param EntityManager $em The EntityManager the collection will be associated with.
|
||||
* @param ClassMetadata $class The class descriptor of the entity type of this collection.
|
||||
* @param array The collection elements.
|
||||
@@ -144,7 +144,7 @@ final class PersistentCollection implements Collection
|
||||
{
|
||||
return $this->owner;
|
||||
}
|
||||
|
||||
|
||||
public function getTypeClass()
|
||||
{
|
||||
return $this->typeClass;
|
||||
@@ -154,7 +154,7 @@ final class PersistentCollection implements Collection
|
||||
* INTERNAL:
|
||||
* Adds an element to a collection during hydration. This will automatically
|
||||
* complete bidirectional associations in the case of a one-to-many association.
|
||||
*
|
||||
*
|
||||
* @param mixed $element The element to add.
|
||||
*/
|
||||
public function hydrateAdd($element)
|
||||
@@ -172,7 +172,7 @@ final class PersistentCollection implements Collection
|
||||
$this->owner);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* INTERNAL:
|
||||
* Sets a keyed element in the collection during hydration.
|
||||
@@ -265,13 +265,13 @@ final class PersistentCollection implements Collection
|
||||
/**
|
||||
* INTERNAL: Gets the association mapping of the collection.
|
||||
*
|
||||
* @return Doctrine\ORM\Mapping\AssociationMapping
|
||||
* @return \Doctrine\ORM\Mapping\AssociationMapping
|
||||
*/
|
||||
public function getMapping()
|
||||
{
|
||||
return $this->association;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Marks this collection as changed/dirty.
|
||||
*/
|
||||
@@ -280,7 +280,7 @@ final class PersistentCollection implements Collection
|
||||
if ( ! $this->isDirty) {
|
||||
$this->isDirty = true;
|
||||
if ($this->association !== null && $this->association['isOwningSide'] && $this->association['type'] == ClassMetadata::MANY_TO_MANY &&
|
||||
$this->em->getClassMetadata(get_class($this->owner))->isChangeTrackingNotify()) {
|
||||
$this->owner && $this->em->getClassMetadata(get_class($this->owner))->isChangeTrackingNotify()) {
|
||||
$this->em->getUnitOfWork()->scheduleForDirtyCheck($this->owner);
|
||||
}
|
||||
}
|
||||
@@ -306,17 +306,17 @@ final class PersistentCollection implements Collection
|
||||
{
|
||||
$this->isDirty = $dirty;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the initialized flag of the collection, forcing it into that state.
|
||||
*
|
||||
*
|
||||
* @param boolean $bool
|
||||
*/
|
||||
public function setInitialized($bool)
|
||||
{
|
||||
$this->initialized = $bool;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether this collection has been initialized.
|
||||
*
|
||||
@@ -377,7 +377,7 @@ final class PersistentCollection implements Collection
|
||||
$this->em->getUnitOfWork()->getCollectionPersister($this->association)
|
||||
->deleteRows($this, $element);
|
||||
}*/
|
||||
|
||||
|
||||
$this->initialize();
|
||||
$removed = $this->coll->removeElement($element);
|
||||
if ($removed) {
|
||||
@@ -404,23 +404,13 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function contains($element)
|
||||
{
|
||||
/* DRAFT
|
||||
if ($this->initialized) {
|
||||
return $this->coll->contains($element);
|
||||
} else {
|
||||
if ($element is MANAGED) {
|
||||
if ($this->coll->contains($element)) {
|
||||
return true;
|
||||
}
|
||||
$exists = check db for existence;
|
||||
if ($exists) {
|
||||
$this->coll->add($element);
|
||||
}
|
||||
return $exists;
|
||||
}
|
||||
return false;
|
||||
}*/
|
||||
|
||||
if (!$this->initialized && $this->association['fetch'] == Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) {
|
||||
return $this->coll->contains($element) ||
|
||||
$this->em->getUnitOfWork()
|
||||
->getCollectionPersister($this->association)
|
||||
->contains($this, $element);
|
||||
}
|
||||
|
||||
$this->initialize();
|
||||
return $this->coll->contains($element);
|
||||
}
|
||||
@@ -475,6 +465,12 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
if (!$this->initialized && $this->association['fetch'] == Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) {
|
||||
return $this->em->getUnitOfWork()
|
||||
->getCollectionPersister($this->association)
|
||||
->count($this) + ($this->isDirty ? $this->coll->count() : 0);
|
||||
}
|
||||
|
||||
$this->initialize();
|
||||
return $this->coll->count();
|
||||
}
|
||||
@@ -507,7 +503,7 @@ final class PersistentCollection implements Collection
|
||||
$this->initialize();
|
||||
return $this->coll->isEmpty();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -534,7 +530,7 @@ final class PersistentCollection implements Collection
|
||||
$this->initialize();
|
||||
return $this->coll->filter($p);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -552,7 +548,7 @@ final class PersistentCollection implements Collection
|
||||
$this->initialize();
|
||||
return $this->coll->partition($p);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -571,18 +567,22 @@ final class PersistentCollection implements Collection
|
||||
return;
|
||||
}
|
||||
if ($this->association['type'] == ClassMetadata::ONE_TO_MANY && $this->association['orphanRemoval']) {
|
||||
// we need to initialize here, as orphan removal acts like implicit cascadeRemove,
|
||||
// hence for event listeners we need the objects in memory.
|
||||
$this->initialize();
|
||||
foreach ($this->coll as $element) {
|
||||
$this->em->getUnitOfWork()->scheduleOrphanRemoval($element);
|
||||
}
|
||||
}
|
||||
$this->coll->clear();
|
||||
$this->initialized = true; // direct call, {@link initialize()} is too expensive
|
||||
if ($this->association['isOwningSide']) {
|
||||
$this->changed();
|
||||
$this->em->getUnitOfWork()->scheduleCollectionDeletion($this);
|
||||
$this->takeSnapshot();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called by PHP when this collection is serialized. Ensures that only the
|
||||
* elements are properly serialized.
|
||||
@@ -594,7 +594,7 @@ final class PersistentCollection implements Collection
|
||||
{
|
||||
return array('coll', 'initialized');
|
||||
}
|
||||
|
||||
|
||||
/* ArrayAccess implementation */
|
||||
|
||||
/**
|
||||
@@ -632,12 +632,12 @@ final class PersistentCollection implements Collection
|
||||
{
|
||||
return $this->remove($offset);
|
||||
}
|
||||
|
||||
|
||||
public function key()
|
||||
{
|
||||
return $this->coll->key();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the element of the collection at the current iterator position.
|
||||
*/
|
||||
@@ -645,7 +645,7 @@ final class PersistentCollection implements Collection
|
||||
{
|
||||
return $this->coll->current();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Moves the internal iterator position to the next element.
|
||||
*/
|
||||
@@ -653,7 +653,7 @@ final class PersistentCollection implements Collection
|
||||
{
|
||||
return $this->coll->next();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the wrapped Collection instance.
|
||||
*/
|
||||
@@ -675,7 +675,40 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function slice($offset, $length = null)
|
||||
{
|
||||
if ( ! $this->initialized &&
|
||||
! $this->isDirty &&
|
||||
$this->association['fetch'] == Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) {
|
||||
|
||||
return $this->em->getUnitOfWork()
|
||||
->getCollectionPersister($this->association)
|
||||
->slice($this, $offset, $length);
|
||||
}
|
||||
|
||||
$this->initialize();
|
||||
return $this->coll->slice($offset, $length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup internal state of cloned persistent collection.
|
||||
*
|
||||
* The following problems have to be prevented:
|
||||
* 1. Added entities are added to old PC
|
||||
* 2. New collection is not dirty, if reused on other entity nothing
|
||||
* changes.
|
||||
* 3. Snapshot leads to invalid diffs being generated.
|
||||
* 4. Lazy loading grabs entities from old owner object.
|
||||
* 5. New collection is connected to old owner and leads to duplicate keys.
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
if (is_object($this->coll)) {
|
||||
$this->coll = clone $this->coll;
|
||||
}
|
||||
|
||||
$this->initialize();
|
||||
$this->owner = null;
|
||||
|
||||
$this->snapshot = array();
|
||||
$this->changed();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,19 +36,19 @@ abstract class AbstractCollectionPersister
|
||||
protected $_em;
|
||||
|
||||
/**
|
||||
* @var Doctrine\DBAL\Connection
|
||||
* @var \Doctrine\DBAL\Connection
|
||||
*/
|
||||
protected $_conn;
|
||||
|
||||
/**
|
||||
* @var Doctrine\ORM\UnitOfWork
|
||||
* @var \Doctrine\ORM\UnitOfWork
|
||||
*/
|
||||
protected $_uow;
|
||||
|
||||
/**
|
||||
* Initializes a new instance of a class derived from AbstractCollectionPersister.
|
||||
*
|
||||
* @param Doctrine\ORM\EntityManager $em
|
||||
* @param \Doctrine\ORM\EntityManager $em
|
||||
*/
|
||||
public function __construct(EntityManager $em)
|
||||
{
|
||||
@@ -65,9 +65,11 @@ abstract class AbstractCollectionPersister
|
||||
public function delete(PersistentCollection $coll)
|
||||
{
|
||||
$mapping = $coll->getMapping();
|
||||
|
||||
if ( ! $mapping['isOwningSide']) {
|
||||
return; // ignore inverse side
|
||||
}
|
||||
|
||||
$sql = $this->_getDeleteSQL($coll);
|
||||
$this->_conn->executeUpdate($sql, $this->_getDeleteSQLParameters($coll));
|
||||
}
|
||||
@@ -96,9 +98,11 @@ abstract class AbstractCollectionPersister
|
||||
public function update(PersistentCollection $coll)
|
||||
{
|
||||
$mapping = $coll->getMapping();
|
||||
|
||||
if ( ! $mapping['isOwningSide']) {
|
||||
return; // ignore inverse side
|
||||
}
|
||||
|
||||
$this->deleteRows($coll);
|
||||
//$this->updateRows($coll);
|
||||
$this->insertRows($coll);
|
||||
@@ -108,6 +112,7 @@ abstract class AbstractCollectionPersister
|
||||
{
|
||||
$deleteDiff = $coll->getDeleteDiff();
|
||||
$sql = $this->_getDeleteRowSQL($coll);
|
||||
|
||||
foreach ($deleteDiff as $element) {
|
||||
$this->_conn->executeUpdate($sql, $this->_getDeleteRowSQLParameters($coll, $element));
|
||||
}
|
||||
@@ -120,11 +125,37 @@ abstract class AbstractCollectionPersister
|
||||
{
|
||||
$insertDiff = $coll->getInsertDiff();
|
||||
$sql = $this->_getInsertRowSQL($coll);
|
||||
|
||||
foreach ($insertDiff as $element) {
|
||||
$this->_conn->executeUpdate($sql, $this->_getInsertRowSQLParameters($coll, $element));
|
||||
}
|
||||
}
|
||||
|
||||
public function count(PersistentCollection $coll)
|
||||
{
|
||||
throw new \BadMethodCallException("Counting the size of this persistent collection is not supported by this CollectionPersister.");
|
||||
}
|
||||
|
||||
public function slice(PersistentCollection $coll, $offset, $length = null)
|
||||
{
|
||||
throw new \BadMethodCallException("Slicing elements is not supported by this CollectionPersister.");
|
||||
}
|
||||
|
||||
public function contains(PersistentCollection $coll, $element)
|
||||
{
|
||||
throw new \BadMethodCallException("Checking for existance of an element is not supported by this CollectionPersister.");
|
||||
}
|
||||
|
||||
public function containsKey(PersistentCollection $coll, $key)
|
||||
{
|
||||
throw new \BadMethodCallException("Checking for existance of a key is not supported by this CollectionPersister.");
|
||||
}
|
||||
|
||||
public function get(PersistentCollection $coll, $index)
|
||||
{
|
||||
throw new \BadMethodCallException("Selecting a collection by index is not supported by this CollectionPersister.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the SQL statement used for deleting a row from the collection.
|
||||
*
|
||||
@@ -163,4 +194,4 @@ abstract class AbstractCollectionPersister
|
||||
* @param mixed $element
|
||||
*/
|
||||
abstract protected function _getInsertRowSQLParameters(PersistentCollection $coll, $element);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,27 +28,23 @@ use Doctrine\ORM\Mapping\ClassMetadata,
|
||||
* types in the hierarchy.
|
||||
*
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
* @since 2.0
|
||||
*/
|
||||
abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
|
||||
{
|
||||
/**
|
||||
* Map from column names to class names that declare the field the column is mapped to.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_declaringClassMap = array();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function _prepareInsertData($entity)
|
||||
{
|
||||
$data = parent::_prepareInsertData($entity);
|
||||
|
||||
// Populate the discriminator column
|
||||
$discColumn = $this->_class->discriminatorColumn;
|
||||
$this->_columnTypes[$discColumn['name']] = $discColumn['type'];
|
||||
$data[$this->_getDiscriminatorColumnTableName()][$discColumn['name']] = $this->_class->discriminatorValue;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
@@ -62,46 +58,22 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function _processSQLResult(array $sqlResult)
|
||||
{
|
||||
$data = array();
|
||||
$discrColumnName = $this->_platform->getSQLResultCasing($this->_class->discriminatorColumn['name']);
|
||||
$entityName = $this->_class->discriminatorMap[$sqlResult[$discrColumnName]];
|
||||
unset($sqlResult[$discrColumnName]);
|
||||
foreach ($sqlResult as $column => $value) {
|
||||
$realColumnName = $this->_resultColumnNames[$column];
|
||||
if (isset($this->_declaringClassMap[$column])) {
|
||||
$class = $this->_declaringClassMap[$column];
|
||||
if ($class->name == $entityName || is_subclass_of($entityName, $class->name)) {
|
||||
$field = $class->fieldNames[$realColumnName];
|
||||
if (isset($data[$field])) {
|
||||
$data[$realColumnName] = $value;
|
||||
} else {
|
||||
$data[$field] = Type::getType($class->fieldMappings[$field]['type'])
|
||||
->convertToPHPValue($value, $this->_platform);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$data[$realColumnName] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return array($entityName, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function _getSelectColumnSQL($field, ClassMetadata $class)
|
||||
protected function _getSelectColumnSQL($field, ClassMetadata $class, $alias = 'r')
|
||||
{
|
||||
$columnName = $class->columnNames[$field];
|
||||
$sql = $this->_getSQLTableAlias($class->name) . '.' . $class->getQuotedColumnName($field, $this->_platform);
|
||||
$sql = $this->_getSQLTableAlias($class->name, $alias == 'r' ? '' : $alias) . '.' . $class->getQuotedColumnName($field, $this->_platform);
|
||||
$columnAlias = $this->_platform->getSQLResultCasing($columnName . $this->_sqlAliasCounter++);
|
||||
if ( ! isset($this->_resultColumnNames[$columnAlias])) {
|
||||
$this->_resultColumnNames[$columnAlias] = $columnName;
|
||||
$this->_declaringClassMap[$columnAlias] = $class;
|
||||
}
|
||||
$this->_rsm->addFieldResult($alias, $columnAlias, $field, $class->name);
|
||||
|
||||
return "$sql AS $columnAlias";
|
||||
return $sql . ' AS ' . $columnAlias;
|
||||
}
|
||||
|
||||
protected function getSelectJoinColumnSQL($tableAlias, $joinColumnName, $className)
|
||||
{
|
||||
$columnAlias = $joinColumnName . $this->_sqlAliasCounter++;
|
||||
$resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
|
||||
$this->_rsm->addMetaResult('r', $resultColumnName, $joinColumnName);
|
||||
|
||||
return $tableAlias . '.' . $joinColumnName . ' AS ' . $columnAlias;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,13 +20,17 @@
|
||||
namespace Doctrine\ORM\Persisters;
|
||||
|
||||
use Doctrine\ORM\ORMException,
|
||||
Doctrine\ORM\Mapping\ClassMetadata;
|
||||
Doctrine\ORM\Mapping\ClassMetadata,
|
||||
Doctrine\DBAL\LockMode,
|
||||
Doctrine\DBAL\Types\Type,
|
||||
Doctrine\ORM\Query\ResultSetMapping;
|
||||
|
||||
/**
|
||||
* The joined subclass persister maps a single entity instance to several tables in the
|
||||
* database as it is defined by the <tt>Class Table Inheritance</tt> strategy.
|
||||
*
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
* @since 2.0
|
||||
* @see http://martinfowler.com/eaaCatalog/classTableInheritance.html
|
||||
*/
|
||||
@@ -63,7 +67,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
* This function finds the ClassMetadata instance in an inheritance hierarchy
|
||||
* that is responsible for enabling versioning.
|
||||
*
|
||||
* @return Doctrine\ORM\Mapping\ClassMetadata
|
||||
* @return \Doctrine\ORM\Mapping\ClassMetadata
|
||||
*/
|
||||
private function _getVersionedClassMetadata()
|
||||
{
|
||||
@@ -107,10 +111,6 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->_class->isVersioned) {
|
||||
$versionedClass = $this->_getVersionedClassMetadata();
|
||||
}
|
||||
|
||||
$postInsertIds = array();
|
||||
$idGen = $this->_class->idGenerator;
|
||||
$isPostInsertId = $idGen->isPostInsertGenerator();
|
||||
@@ -144,9 +144,11 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
|
||||
// Execute insert on root table
|
||||
$paramIndex = 1;
|
||||
|
||||
foreach ($insertData[$rootTableName] as $columnName => $value) {
|
||||
$rootTableStmt->bindValue($paramIndex++, $value, $this->_columnTypes[$columnName]);
|
||||
}
|
||||
|
||||
$rootTableStmt->execute();
|
||||
|
||||
if ($isPostInsertId) {
|
||||
@@ -161,12 +163,17 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
foreach ($subTableStmts as $tableName => $stmt) {
|
||||
$data = isset($insertData[$tableName]) ? $insertData[$tableName] : array();
|
||||
$paramIndex = 1;
|
||||
foreach ((array) $id as $idVal) {
|
||||
$stmt->bindValue($paramIndex++, $idVal);
|
||||
|
||||
foreach ((array) $id as $idName => $idVal) {
|
||||
$type = isset($this->_columnTypes[$idName]) ? $this->_columnTypes[$idName] : Type::STRING;
|
||||
|
||||
$stmt->bindValue($paramIndex++, $idVal, $type);
|
||||
}
|
||||
|
||||
foreach ($data as $columnName => $value) {
|
||||
$stmt->bindValue($paramIndex++, $value, $this->_columnTypes[$columnName]);
|
||||
}
|
||||
|
||||
$stmt->execute();
|
||||
}
|
||||
}
|
||||
@@ -176,8 +183,8 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
$stmt->closeCursor();
|
||||
}
|
||||
|
||||
if (isset($versionedClass)) {
|
||||
$this->_assignDefaultVersionValue($versionedClass, $entity, $id);
|
||||
if ($this->_class->isVersioned) {
|
||||
$this->assignDefaultVersionValue($entity, $id);
|
||||
}
|
||||
|
||||
$this->_queuedInserts = array();
|
||||
@@ -192,7 +199,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
{
|
||||
$updateData = $this->_prepareUpdateData($entity);
|
||||
|
||||
if ($isVersioned = $this->_class->isVersioned) {
|
||||
if (($isVersioned = $this->_class->isVersioned) != false) {
|
||||
$versionedClass = $this->_getVersionedClassMetadata();
|
||||
$versionedTable = $versionedClass->table['name'];
|
||||
}
|
||||
@@ -201,13 +208,14 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
foreach ($updateData as $tableName => $data) {
|
||||
$this->_updateTable($entity, $this->_quotedTableMap[$tableName], $data, $isVersioned && $versionedTable == $tableName);
|
||||
}
|
||||
|
||||
// Make sure the table with the version column is updated even if no columns on that
|
||||
// table were affected.
|
||||
if ($isVersioned && ! isset($updateData[$versionedTable])) {
|
||||
$this->_updateTable($entity, $versionedClass->getQuotedTableName($this->_platform), array(), true);
|
||||
|
||||
$id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity);
|
||||
$this->_assignDefaultVersionValue($this->_class, $entity, $id);
|
||||
$this->assignDefaultVersionValue($entity, $id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,6 +238,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
} else {
|
||||
// Delete from all tables individually, starting from this class' table up to the root table.
|
||||
$this->_conn->delete($this->_class->getQuotedTableName($this->_platform), $id);
|
||||
|
||||
foreach ($this->_class->parentClasses as $parentClass) {
|
||||
$this->_conn->delete($this->_em->getClassMetadata($parentClass)->getQuotedTableName($this->_platform), $id);
|
||||
}
|
||||
@@ -239,13 +248,17 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function _getSelectEntitiesSQL(array $criteria, $assoc = null, $lockMode = 0)
|
||||
protected function _getSelectEntitiesSQL(array $criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null)
|
||||
{
|
||||
$idColumns = $this->_class->getIdentifierColumnNames();
|
||||
$baseTableAlias = $this->_getSQLTableAlias($this->_class->name);
|
||||
|
||||
// Create the column list fragment only once
|
||||
if ($this->_selectColumnListSql === null) {
|
||||
|
||||
$this->_rsm = new ResultSetMapping();
|
||||
$this->_rsm->addEntityResult($this->_class->name, 'r');
|
||||
|
||||
// Add regular columns
|
||||
$columnList = '';
|
||||
foreach ($this->_class->fieldMappings as $fieldName => $mapping) {
|
||||
@@ -263,12 +276,10 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
$this->_getSQLTableAlias($assoc2['inherited'])
|
||||
: $baseTableAlias;
|
||||
foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) {
|
||||
$columnAlias = $srcColumn . $this->_sqlAliasCounter++;
|
||||
$columnList .= ", $tableAlias.$srcColumn AS $columnAlias";
|
||||
$resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
|
||||
if ( ! isset($this->_resultColumnNames[$resultColumnName])) {
|
||||
$this->_resultColumnNames[$resultColumnName] = $srcColumn;
|
||||
}
|
||||
if ($columnList != '') $columnList .= ', ';
|
||||
$columnList .= $this->getSelectJoinColumnSQL($tableAlias, $srcColumn,
|
||||
isset($assoc2['inherited']) ? $assoc2['inherited'] : $this->_class->name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,7 +294,8 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
}
|
||||
|
||||
$resultColumnName = $this->_platform->getSQLResultCasing($discrColumn);
|
||||
$this->_resultColumnNames[$resultColumnName] = $discrColumn;
|
||||
$this->_rsm->setDiscriminatorColumn('r', $resultColumnName);
|
||||
$this->_rsm->addMetaResult('r', $resultColumnName, $discrColumn);
|
||||
}
|
||||
|
||||
// INNER JOIN parent tables
|
||||
@@ -318,12 +330,10 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
if ($assoc2['isOwningSide'] && $assoc2['type'] & ClassMetadata::TO_ONE
|
||||
&& ! isset($assoc2['inherited'])) {
|
||||
foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) {
|
||||
$columnAlias = $srcColumn . $this->_sqlAliasCounter++;
|
||||
$columnList .= ', ' . $tableAlias . ".$srcColumn AS $columnAlias";
|
||||
$resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
|
||||
if ( ! isset($this->_resultColumnNames[$resultColumnName])) {
|
||||
$this->_resultColumnNames[$resultColumnName] = $srcColumn;
|
||||
}
|
||||
if ($columnList != '') $columnList .= ', ';
|
||||
$columnList .= $this->getSelectJoinColumnSQL($tableAlias, $srcColumn,
|
||||
isset($assoc2['inherited']) ? $assoc2['inherited'] : $subClass->name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -343,19 +353,25 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
|
||||
$conditionSql = $this->_getSelectConditionSQL($criteria, $assoc);
|
||||
|
||||
$orderBySql = '';
|
||||
if ($assoc != null && isset($assoc['orderBy'])) {
|
||||
$orderBySql = $this->_getCollectionOrderBySQL($assoc['orderBy'], $baseTableAlias);
|
||||
}
|
||||
$orderBy = ($assoc !== null && isset($assoc['orderBy'])) ? $assoc['orderBy'] : $orderBy;
|
||||
$orderBySql = $orderBy ? $this->_getOrderBySQL($orderBy, $baseTableAlias) : '';
|
||||
|
||||
if ($this->_selectColumnListSql === null) {
|
||||
$this->_selectColumnListSql = $columnList;
|
||||
}
|
||||
|
||||
return 'SELECT ' . $this->_selectColumnListSql
|
||||
$lockSql = '';
|
||||
if ($lockMode == LockMode::PESSIMISTIC_READ) {
|
||||
$lockSql = ' ' . $this->_platform->getReadLockSql();
|
||||
} else if ($lockMode == LockMode::PESSIMISTIC_WRITE) {
|
||||
$lockSql = ' ' . $this->_platform->getWriteLockSql();
|
||||
}
|
||||
|
||||
return $this->_platform->modifyLimitQuery('SELECT ' . $this->_selectColumnListSql
|
||||
. ' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' . $baseTableAlias
|
||||
. $joinSql
|
||||
. ($conditionSql != '' ? ' WHERE ' . $conditionSql : '') . $orderBySql;
|
||||
. ($conditionSql != '' ? ' WHERE ' . $conditionSql : '') . $orderBySql, $limit, $offset)
|
||||
. $lockSql;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -423,4 +439,13 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function assignDefaultVersionValue($entity, $id)
|
||||
{
|
||||
$value = $this->fetchVersionValue($this->_getVersionedClassMetadata(), $id);
|
||||
$this->_class->setFieldValue($entity, $this->_class->versionField, $value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@
|
||||
|
||||
namespace Doctrine\ORM\Persisters;
|
||||
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\PersistentCollection,
|
||||
Doctrine\ORM\UnitOfWork;
|
||||
|
||||
/**
|
||||
* Persister for many-to-many collections.
|
||||
@@ -38,9 +39,10 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
*/
|
||||
protected function _getDeleteRowSQL(PersistentCollection $coll)
|
||||
{
|
||||
$mapping = $coll->getMapping();
|
||||
$mapping = $coll->getMapping();
|
||||
$joinTable = $mapping['joinTable'];
|
||||
$columns = $mapping['joinTableColumns'];
|
||||
$columns = $mapping['joinTableColumns'];
|
||||
|
||||
return 'DELETE FROM ' . $joinTable['name'] . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?';
|
||||
}
|
||||
|
||||
@@ -117,13 +119,21 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
|
||||
if (isset($mapping['relationToSourceKeyColumns'][$joinTableColumn])) {
|
||||
if ($isComposite) {
|
||||
$params[] = $identifier1[$class1->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]];
|
||||
if ($class1->containsForeignIdentifier) {
|
||||
$params[] = $identifier1[$class1->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])];
|
||||
} else {
|
||||
$params[] = $identifier1[$class1->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]];
|
||||
}
|
||||
} else {
|
||||
$params[] = array_pop($identifier1);
|
||||
}
|
||||
} else {
|
||||
if ($isComposite) {
|
||||
$params[] = $identifier2[$class2->fieldNames[$mapping['relationToTargetKeyColumns'][$joinTableColumn]]];
|
||||
if ($class2->containsForeignIdentifier) {
|
||||
$params[] = $identifier2[$class2->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])];
|
||||
} else {
|
||||
$params[] = $identifier2[$class2->fieldNames[$mapping['relationToTargetKeyColumns'][$joinTableColumn]]];
|
||||
}
|
||||
} else {
|
||||
$params[] = array_pop($identifier2);
|
||||
}
|
||||
@@ -173,4 +183,119 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function count(PersistentCollection $coll)
|
||||
{
|
||||
$params = array();
|
||||
$mapping = $coll->getMapping();
|
||||
$class = $this->_em->getClassMetadata($mapping['sourceEntity']);
|
||||
$id = $this->_em->getUnitOfWork()->getEntityIdentifier($coll->getOwner());
|
||||
|
||||
if ($mapping['isOwningSide']) {
|
||||
$joinTable = $mapping['joinTable'];
|
||||
$joinColumns = $mapping['relationToSourceKeyColumns'];
|
||||
} else {
|
||||
$mapping = $this->_em->getClassMetadata($mapping['targetEntity'])->associationMappings[$mapping['mappedBy']];
|
||||
$joinTable = $mapping['joinTable'];
|
||||
$joinColumns = $mapping['relationToTargetKeyColumns'];
|
||||
}
|
||||
|
||||
$whereClause = '';
|
||||
foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
|
||||
if (isset($joinColumns[$joinTableColumn])) {
|
||||
if ($whereClause !== '') {
|
||||
$whereClause .= ' AND ';
|
||||
}
|
||||
$whereClause .= "$joinTableColumn = ?";
|
||||
|
||||
if ($class->containsForeignIdentifier) {
|
||||
$params[] = $id[$class->getFieldForColumn($joinColumns[$joinTableColumn])];
|
||||
} else {
|
||||
$params[] = $id[$class->fieldNames[$joinColumns[$joinTableColumn]]];
|
||||
}
|
||||
}
|
||||
}
|
||||
$sql = 'SELECT count(*) FROM ' . $joinTable['name'] . ' WHERE ' . $whereClause;
|
||||
|
||||
return $this->_conn->fetchColumn($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PersistentCollection $coll
|
||||
* @param int $offset
|
||||
* @param int $length
|
||||
* @return array
|
||||
*/
|
||||
public function slice(PersistentCollection $coll, $offset, $length = null)
|
||||
{
|
||||
$mapping = $coll->getMapping();
|
||||
return $this->_em->getUnitOfWork()
|
||||
->getEntityPersister($mapping['targetEntity'])
|
||||
->getManyToManyCollection($mapping, $coll->getOwner(), $offset, $length);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PersistentCollection $coll
|
||||
* @param object $element
|
||||
*/
|
||||
public function contains(PersistentCollection $coll, $element)
|
||||
{
|
||||
$uow = $this->_em->getUnitOfWork();
|
||||
|
||||
// shortcut for new entities
|
||||
if ($uow->getEntityState($element, UnitOfWork::STATE_NEW) == UnitOfWork::STATE_NEW) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$params = array();
|
||||
$mapping = $coll->getMapping();
|
||||
|
||||
if (!$mapping['isOwningSide']) {
|
||||
$sourceClass = $this->_em->getClassMetadata($mapping['targetEntity']);
|
||||
$targetClass = $this->_em->getClassMetadata($mapping['sourceEntity']);
|
||||
$sourceId = $uow->getEntityIdentifier($element);
|
||||
$targetId = $uow->getEntityIdentifier($coll->getOwner());
|
||||
|
||||
$mapping = $sourceClass->associationMappings[$mapping['mappedBy']];
|
||||
} else {
|
||||
$sourceClass = $this->_em->getClassMetadata($mapping['sourceEntity']);
|
||||
$targetClass = $this->_em->getClassMetadata($mapping['targetEntity']);
|
||||
$sourceId = $uow->getEntityIdentifier($coll->getOwner());
|
||||
$targetId = $uow->getEntityIdentifier($element);
|
||||
}
|
||||
$joinTable = $mapping['joinTable'];
|
||||
|
||||
$whereClause = '';
|
||||
foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
|
||||
if (isset($mapping['relationToTargetKeyColumns'][$joinTableColumn])) {
|
||||
if ($whereClause !== '') {
|
||||
$whereClause .= ' AND ';
|
||||
}
|
||||
$whereClause .= "$joinTableColumn = ?";
|
||||
|
||||
if ($targetClass->containsForeignIdentifier) {
|
||||
$params[] = $targetId[$targetClass->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])];
|
||||
} else {
|
||||
$params[] = $targetId[$targetClass->fieldNames[$mapping['relationToTargetKeyColumns'][$joinTableColumn]]];
|
||||
}
|
||||
} else if (isset($mapping['relationToSourceKeyColumns'][$joinTableColumn])) {
|
||||
if ($whereClause !== '') {
|
||||
$whereClause .= ' AND ';
|
||||
}
|
||||
$whereClause .= "$joinTableColumn = ?";
|
||||
|
||||
if ($sourceClass->containsForeignIdentifier) {
|
||||
$params[] = $sourceId[$sourceClass->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])];
|
||||
} else {
|
||||
$params[] = $sourceId[$sourceClass->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]];
|
||||
}
|
||||
}
|
||||
}
|
||||
$sql = 'SELECT 1 FROM ' . $joinTable['name'] . ' WHERE ' . $whereClause;
|
||||
|
||||
return (bool)$this->_conn->fetchColumn($sql, $params);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,8 @@
|
||||
|
||||
namespace Doctrine\ORM\Persisters;
|
||||
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\PersistentCollection,
|
||||
Doctrine\ORM\UnitOfWork;
|
||||
|
||||
/**
|
||||
* Persister for one-to-many collections.
|
||||
@@ -116,4 +117,69 @@ class OneToManyPersister extends AbstractCollectionPersister
|
||||
*/
|
||||
protected function _getDeleteRowSQLParameters(PersistentCollection $coll, $element)
|
||||
{}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function count(PersistentCollection $coll)
|
||||
{
|
||||
$mapping = $coll->getMapping();
|
||||
$targetClass = $this->_em->getClassMetadata($mapping['targetEntity']);
|
||||
$sourceClass = $this->_em->getClassMetadata($mapping['sourceEntity']);
|
||||
|
||||
$params = array();
|
||||
$id = $this->_em->getUnitOfWork()->getEntityIdentifier($coll->getOwner());
|
||||
|
||||
$where = '';
|
||||
foreach ($targetClass->associationMappings[$mapping['mappedBy']]['joinColumns'] AS $joinColumn) {
|
||||
if ($where != '') {
|
||||
$where .= ' AND ';
|
||||
}
|
||||
$where .= $joinColumn['name'] . " = ?";
|
||||
if ($targetClass->containsForeignIdentifier) {
|
||||
$params[] = $id[$sourceClass->getFieldForColumn($joinColumn['referencedColumnName'])];
|
||||
} else {
|
||||
$params[] = $id[$sourceClass->fieldNames[$joinColumn['referencedColumnName']]];
|
||||
}
|
||||
}
|
||||
|
||||
$sql = "SELECT count(*) FROM " . $targetClass->getQuotedTableName($this->_conn->getDatabasePlatform()) . " WHERE " . $where;
|
||||
return $this->_conn->fetchColumn($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PersistentCollection $coll
|
||||
* @param int $offset
|
||||
* @param int $length
|
||||
* @return \Doctrine\Common\Collections\ArrayCollection
|
||||
*/
|
||||
public function slice(PersistentCollection $coll, $offset, $length = null)
|
||||
{
|
||||
$mapping = $coll->getMapping();
|
||||
return $this->_em->getUnitOfWork()
|
||||
->getEntityPersister($mapping['targetEntity'])
|
||||
->getOneToManyCollection($mapping, $coll->getOwner(), $offset, $length);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PersistentCollection $coll
|
||||
* @param object $element
|
||||
*/
|
||||
public function contains(PersistentCollection $coll, $element)
|
||||
{
|
||||
$mapping = $coll->getMapping();
|
||||
$uow = $this->_em->getUnitOfWork();
|
||||
|
||||
// shortcut for new entities
|
||||
if ($uow->getEntityState($element, UnitOfWork::STATE_NEW) == UnitOfWork::STATE_NEW) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// only works with single id identifier entities. Will throw an exception in Entity Persisters
|
||||
// if that is not the case for the 'mappedBy' field.
|
||||
$id = current( $uow->getEntityIdentifier($coll->getOwner()) );
|
||||
|
||||
return $uow->getEntityPersister($mapping['targetEntity'])
|
||||
->exists($element, array($mapping['mappedBy'] => $id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
* SINGLE_TABLE strategy.
|
||||
*
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
* @since 2.0
|
||||
* @link http://martinfowler.com/eaaCatalog/singleTableInheritance.html
|
||||
*/
|
||||
@@ -40,6 +41,10 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
|
||||
/** {@inheritdoc} */
|
||||
protected function _getSelectColumnListSQL()
|
||||
{
|
||||
if ($this->_selectColumnListSql !== null) {
|
||||
return $this->_selectColumnListSql;
|
||||
}
|
||||
|
||||
$columnList = parent::_getSelectColumnListSQL();
|
||||
|
||||
// Append discriminator column
|
||||
@@ -48,7 +53,8 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
|
||||
$rootClass = $this->_em->getClassMetadata($this->_class->rootEntityName);
|
||||
$tableAlias = $this->_getSQLTableAlias($rootClass->name);
|
||||
$resultColumnName = $this->_platform->getSQLResultCasing($discrColumn);
|
||||
$this->_resultColumnNames[$resultColumnName] = $discrColumn;
|
||||
$this->_rsm->setDiscriminatorColumn('r', $resultColumnName);
|
||||
$this->_rsm->addMetaResult('r', $resultColumnName, $discrColumn);
|
||||
|
||||
// Append subclass columns
|
||||
foreach ($this->_class->subClasses as $subClassName) {
|
||||
@@ -63,18 +69,17 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
|
||||
foreach ($subClass->associationMappings as $assoc) {
|
||||
if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE && ! isset($assoc['inherited'])) {
|
||||
foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
|
||||
$columnAlias = $srcColumn . $this->_sqlAliasCounter++;
|
||||
$columnList .= ', ' . $tableAlias . ".$srcColumn AS $columnAlias";
|
||||
$resultColumnName = $this->_platform->getSQLResultCasing($columnAlias);
|
||||
if ( ! isset($this->_resultColumnNames[$resultColumnName])) {
|
||||
$this->_resultColumnNames[$resultColumnName] = $srcColumn;
|
||||
}
|
||||
if ($columnList != '') $columnList .= ', ';
|
||||
$columnList .= $this->getSelectJoinColumnSQL($tableAlias, $srcColumn,
|
||||
isset($assoc['inherited']) ? $assoc['inherited'] : $this->_class->name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $columnList;
|
||||
$this->_selectColumnListSql = $columnList;
|
||||
return $this->_selectColumnListSql;
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
@@ -88,9 +93,9 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
protected function _getSQLTableAlias($className)
|
||||
protected function _getSQLTableAlias($className, $assocName = '')
|
||||
{
|
||||
return parent::_getSQLTableAlias($this->_class->rootEntityName);
|
||||
return parent::_getSQLTableAlias($this->_class->rootEntityName, $assocName);
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
|
||||
@@ -130,11 +130,12 @@ class ProxyFactory
|
||||
{
|
||||
$methods = $this->_generateMethods($class);
|
||||
$sleepImpl = $this->_generateSleep($class);
|
||||
$cloneImpl = $class->reflClass->hasMethod('__clone') ? 'parent::__clone();' : ''; // hasMethod() checks case-insensitive
|
||||
|
||||
$placeholders = array(
|
||||
'<namespace>',
|
||||
'<proxyClassName>', '<className>',
|
||||
'<methods>', '<sleepImpl>'
|
||||
'<methods>', '<sleepImpl>', '<cloneImpl>'
|
||||
);
|
||||
|
||||
if(substr($class->name, 0, 1) == "\\") {
|
||||
@@ -146,7 +147,7 @@ class ProxyFactory
|
||||
$replacements = array(
|
||||
$this->_proxyNamespace,
|
||||
$proxyClassName, $className,
|
||||
$methods, $sleepImpl
|
||||
$methods, $sleepImpl, $cloneImpl
|
||||
);
|
||||
|
||||
$file = str_replace($placeholders, $replacements, $file);
|
||||
@@ -164,14 +165,16 @@ class ProxyFactory
|
||||
{
|
||||
$methods = '';
|
||||
|
||||
$methodNames = array();
|
||||
foreach ($class->reflClass->getMethods() as $method) {
|
||||
/* @var $method ReflectionMethod */
|
||||
if ($method->isConstructor() || strtolower($method->getName()) == "__sleep") {
|
||||
if ($method->isConstructor() || in_array(strtolower($method->getName()), array("__sleep", "__clone")) || isset($methodNames[$method->getName()])) {
|
||||
continue;
|
||||
}
|
||||
$methodNames[$method->getName()] = true;
|
||||
|
||||
if ($method->isPublic() && ! $method->isFinal() && ! $method->isStatic()) {
|
||||
$methods .= PHP_EOL . ' public function ';
|
||||
$methods .= "\n" . ' public function ';
|
||||
if ($method->returnsReference()) {
|
||||
$methods .= '&';
|
||||
}
|
||||
@@ -207,10 +210,10 @@ class ProxyFactory
|
||||
}
|
||||
|
||||
$methods .= $parameterString . ')';
|
||||
$methods .= PHP_EOL . ' {' . PHP_EOL;
|
||||
$methods .= ' $this->_load();' . PHP_EOL;
|
||||
$methods .= "\n" . ' {' . "\n";
|
||||
$methods .= ' $this->__load();' . "\n";
|
||||
$methods .= ' return parent::' . $method->getName() . '(' . $argumentString . ');';
|
||||
$methods .= PHP_EOL . ' }' . PHP_EOL;
|
||||
$methods .= "\n" . ' }' . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,22 +271,48 @@ class <proxyClassName> extends \<className> implements \Doctrine\ORM\Proxy\Proxy
|
||||
$this->_entityPersister = $entityPersister;
|
||||
$this->_identifier = $identifier;
|
||||
}
|
||||
private function _load()
|
||||
/** @private */
|
||||
public function __load()
|
||||
{
|
||||
if (!$this->__isInitialized__ && $this->_entityPersister) {
|
||||
$this->__isInitialized__ = true;
|
||||
|
||||
if (method_exists($this, "__wakeup")) {
|
||||
// call this after __isInitialized__to avoid infinite recursion
|
||||
// but before loading to emulate what ClassMetadata::newInstance()
|
||||
// provides.
|
||||
$this->__wakeup();
|
||||
}
|
||||
|
||||
if ($this->_entityPersister->load($this->_identifier, $this) === null) {
|
||||
throw new \Doctrine\ORM\EntityNotFoundException();
|
||||
}
|
||||
unset($this->_entityPersister, $this->_identifier);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
<methods>
|
||||
|
||||
public function __sleep()
|
||||
{
|
||||
<sleepImpl>
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
if (!$this->__isInitialized__ && $this->_entityPersister) {
|
||||
$this->__isInitialized__ = true;
|
||||
$class = $this->_entityPersister->getClassMetadata();
|
||||
$original = $this->_entityPersister->load($this->_identifier);
|
||||
if ($original === null) {
|
||||
throw new \Doctrine\ORM\EntityNotFoundException();
|
||||
}
|
||||
foreach ($class->reflFields AS $field => $reflProperty) {
|
||||
$reflProperty->setValue($this, $reflProperty->getValue($original));
|
||||
}
|
||||
unset($this->_entityPersister, $this->_identifier);
|
||||
}
|
||||
<cloneImpl>
|
||||
}
|
||||
}';
|
||||
}
|
||||
|
||||
@@ -44,19 +44,28 @@ final class Query extends AbstractQuery
|
||||
* is called.
|
||||
*/
|
||||
const STATE_DIRTY = 2;
|
||||
|
||||
|
||||
/* Query HINTS */
|
||||
/**
|
||||
* The refresh hint turns any query into a refresh query with the result that
|
||||
* any local changes in entities are overridden with the fetched values.
|
||||
*
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HINT_REFRESH = 'doctrine.refresh';
|
||||
|
||||
|
||||
/**
|
||||
* Internal hint: is set to the proxy entity that is currently triggered for loading
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HINT_REFRESH_ENTITY = 'doctrine.refresh.entity';
|
||||
|
||||
/**
|
||||
* The forcePartialLoad query hint forces a particular query to return
|
||||
* partial objects.
|
||||
*
|
||||
*
|
||||
* @var string
|
||||
* @todo Rename: HINT_OPTIMIZE
|
||||
*/
|
||||
@@ -64,15 +73,15 @@ final class Query extends AbstractQuery
|
||||
/**
|
||||
* The includeMetaColumns query hint causes meta columns like foreign keys and
|
||||
* discriminator columns to be selected and returned as part of the query result.
|
||||
*
|
||||
*
|
||||
* This hint does only apply to non-object queries.
|
||||
*
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const HINT_INCLUDE_META_COLUMNS = 'doctrine.includeMetaColumns';
|
||||
|
||||
/**
|
||||
* An array of class names that implement Doctrine\ORM\Query\TreeWalker and
|
||||
* An array of class names that implement \Doctrine\ORM\Query\TreeWalker and
|
||||
* are iterated and executed after the DQL has been parsed into an AST.
|
||||
*
|
||||
* @var string
|
||||
@@ -80,7 +89,7 @@ final class Query extends AbstractQuery
|
||||
const HINT_CUSTOM_TREE_WALKERS = 'doctrine.customTreeWalkers';
|
||||
|
||||
/**
|
||||
* A string with a class name that implements Doctrine\ORM\Query\TreeWalker
|
||||
* A string with a class name that implements \Doctrine\ORM\Query\TreeWalker
|
||||
* and is used for generating the target SQL from any DQL AST tree.
|
||||
*
|
||||
* @var string
|
||||
@@ -110,15 +119,15 @@ final class Query extends AbstractQuery
|
||||
private $_dql = null;
|
||||
|
||||
/**
|
||||
* @var Doctrine\ORM\Query\ParserResult The parser result that holds DQL => SQL information.
|
||||
* @var \Doctrine\ORM\Query\ParserResult The parser result that holds DQL => SQL information.
|
||||
*/
|
||||
private $_parserResult;
|
||||
|
||||
|
||||
/**
|
||||
* @var integer The first result to return (the "offset").
|
||||
*/
|
||||
private $_firstResult = null;
|
||||
|
||||
|
||||
/**
|
||||
* @var integer The maximum number of results to return (the "limit").
|
||||
*/
|
||||
@@ -138,7 +147,7 @@ final class Query extends AbstractQuery
|
||||
* @var int Query Cache lifetime.
|
||||
*/
|
||||
private $_queryCacheTTL;
|
||||
|
||||
|
||||
/**
|
||||
* @var boolean Whether to use a query cache, if available. Defaults to TRUE.
|
||||
*/
|
||||
@@ -149,7 +158,7 @@ final class Query extends AbstractQuery
|
||||
/**
|
||||
* Initializes a new Query instance.
|
||||
*
|
||||
* @param Doctrine\ORM\EntityManager $entityManager
|
||||
* @param \Doctrine\ORM\EntityManager $entityManager
|
||||
*/
|
||||
/*public function __construct(EntityManager $entityManager)
|
||||
{
|
||||
@@ -170,9 +179,9 @@ final class Query extends AbstractQuery
|
||||
/**
|
||||
* Returns the corresponding AST for this DQL query.
|
||||
*
|
||||
* @return Doctrine\ORM\Query\AST\SelectStatement |
|
||||
* Doctrine\ORM\Query\AST\UpdateStatement |
|
||||
* Doctrine\ORM\Query\AST\DeleteStatement
|
||||
* @return \Doctrine\ORM\Query\AST\SelectStatement |
|
||||
* \Doctrine\ORM\Query\AST\UpdateStatement |
|
||||
* \Doctrine\ORM\Query\AST\DeleteStatement
|
||||
*/
|
||||
public function getAST()
|
||||
{
|
||||
@@ -182,21 +191,22 @@ final class Query extends AbstractQuery
|
||||
|
||||
/**
|
||||
* Parses the DQL query, if necessary, and stores the parser result.
|
||||
*
|
||||
*
|
||||
* Note: Populates $this->_parserResult as a side-effect.
|
||||
*
|
||||
* @return Doctrine\ORM\Query\ParserResult
|
||||
* @return \Doctrine\ORM\Query\ParserResult
|
||||
*/
|
||||
private function _parse()
|
||||
{
|
||||
if ($this->_state === self::STATE_CLEAN) {
|
||||
return $this->_parserResult;
|
||||
}
|
||||
|
||||
|
||||
// Check query cache.
|
||||
if ($this->_useQueryCache && ($queryCache = $this->getQueryCacheDriver())) {
|
||||
$hash = $this->_getQueryCacheId();
|
||||
$cached = $this->_expireQueryCache ? false : $queryCache->fetch($hash);
|
||||
|
||||
if ($cached === false) {
|
||||
// Cache miss.
|
||||
$parser = new Parser($this);
|
||||
@@ -210,8 +220,9 @@ final class Query extends AbstractQuery
|
||||
$parser = new Parser($this);
|
||||
$this->_parserResult = $parser->parse();
|
||||
}
|
||||
|
||||
$this->_state = self::STATE_CLEAN;
|
||||
|
||||
|
||||
return $this->_parserResult;
|
||||
}
|
||||
|
||||
@@ -229,38 +240,7 @@ final class Query extends AbstractQuery
|
||||
throw QueryException::invalidParameterNumber();
|
||||
}
|
||||
|
||||
$sqlParams = $types = array();
|
||||
|
||||
foreach ($this->_params as $key => $value) {
|
||||
if ( ! isset($paramMappings[$key])) {
|
||||
throw QueryException::unknownParameter($key);
|
||||
}
|
||||
if (isset($this->_paramTypes[$key])) {
|
||||
foreach ($paramMappings[$key] as $position) {
|
||||
$types[$position] = $this->_paramTypes[$key];
|
||||
}
|
||||
}
|
||||
|
||||
if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value))) {
|
||||
if ($this->_em->getUnitOfWork()->getEntityState($value) == UnitOfWork::STATE_MANAGED) {
|
||||
$idValues = $this->_em->getUnitOfWork()->getEntityIdentifier($value);
|
||||
} else {
|
||||
$class = $this->_em->getClassMetadata(get_class($value));
|
||||
$idValues = $class->getIdentifierValues($value);
|
||||
}
|
||||
$sqlPositions = $paramMappings[$key];
|
||||
$sqlParams += array_combine((array)$sqlPositions, $idValues);
|
||||
} else {
|
||||
foreach ($paramMappings[$key] as $position) {
|
||||
$sqlParams[$position] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($sqlParams) {
|
||||
ksort($sqlParams);
|
||||
$sqlParams = array_values($sqlParams);
|
||||
}
|
||||
list($sqlParams, $types) = $this->processParameterMappings($paramMappings);
|
||||
|
||||
if ($this->_resultSetMapping === null) {
|
||||
$this->_resultSetMapping = $this->_parserResult->getResultSetMapping();
|
||||
@@ -269,6 +249,83 @@ final class Query extends AbstractQuery
|
||||
return $executor->execute($this->_em->getConnection(), $sqlParams, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes query parameter mappings
|
||||
*
|
||||
* @param array $paramMappings
|
||||
* @return array
|
||||
*/
|
||||
private function processParameterMappings($paramMappings)
|
||||
{
|
||||
$sqlParams = $types = array();
|
||||
|
||||
foreach ($this->_params as $key => $value) {
|
||||
if ( ! isset($paramMappings[$key])) {
|
||||
throw QueryException::unknownParameter($key);
|
||||
}
|
||||
|
||||
if (isset($this->_paramTypes[$key])) {
|
||||
foreach ($paramMappings[$key] as $position) {
|
||||
$types[$position] = $this->_paramTypes[$key];
|
||||
}
|
||||
}
|
||||
|
||||
$sqlPositions = $paramMappings[$key];
|
||||
$value = array_values($this->processParameterValue($value));
|
||||
$countValue = count($value);
|
||||
|
||||
for ($i = 0, $l = count($sqlPositions); $i < $l; $i++) {
|
||||
$sqlParams[$sqlPositions[$i]] = $value[($i % $countValue)];
|
||||
}
|
||||
}
|
||||
|
||||
if (count($sqlParams) != count($types)) {
|
||||
throw QueryException::parameterTypeMissmatch();
|
||||
}
|
||||
|
||||
if ($sqlParams) {
|
||||
ksort($sqlParams);
|
||||
$sqlParams = array_values($sqlParams);
|
||||
|
||||
ksort($types);
|
||||
$types = array_values($types);
|
||||
}
|
||||
|
||||
return array($sqlParams, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an individual parameter value
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return array
|
||||
*/
|
||||
private function processParameterValue($value)
|
||||
{
|
||||
if (is_array($value)) {
|
||||
for ($i = 0, $l = count($value); $i < $l; $i++) {
|
||||
$paramValue = $this->processParameterValue($value[$i]);
|
||||
|
||||
// TODO: What about Entities that have composite primary key?
|
||||
$value[$i] = is_array($paramValue) ? $paramValue[key($paramValue)] : $paramValue;
|
||||
}
|
||||
|
||||
return array($value);
|
||||
}
|
||||
|
||||
if ( ! (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value)))) {
|
||||
return array($value);
|
||||
}
|
||||
|
||||
if ($this->_em->getUnitOfWork()->getEntityState($value) === UnitOfWork::STATE_MANAGED) {
|
||||
return array_values($this->_em->getUnitOfWork()->getEntityIdentifier($value));
|
||||
}
|
||||
|
||||
$class = $this->_em->getClassMetadata(get_class($value));
|
||||
|
||||
return array_values($class->getIdentifierValues($value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a cache driver to be used for caching queries.
|
||||
*
|
||||
@@ -280,10 +337,10 @@ final class Query extends AbstractQuery
|
||||
$this->_queryCache = $queryCache;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Defines whether the query should make use of a query cache, if available.
|
||||
*
|
||||
*
|
||||
* @param boolean $bool
|
||||
* @return @return Query This query instance.
|
||||
*/
|
||||
@@ -371,7 +428,7 @@ final class Query extends AbstractQuery
|
||||
* Sets a DQL query string.
|
||||
*
|
||||
* @param string $dqlQuery DQL Query
|
||||
* @return Doctrine\ORM\AbstractQuery
|
||||
* @return \Doctrine\ORM\AbstractQuery
|
||||
*/
|
||||
public function setDQL($dqlQuery)
|
||||
{
|
||||
@@ -417,7 +474,7 @@ final class Query extends AbstractQuery
|
||||
{
|
||||
return stripos($this->getDQL(), $dql) === false ? false : true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the position of the first result to retrieve (the "offset").
|
||||
*
|
||||
@@ -430,21 +487,21 @@ final class Query extends AbstractQuery
|
||||
$this->_state = self::STATE_DIRTY;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the position of the first result the query object was set to retrieve (the "offset").
|
||||
* Returns NULL if {@link setFirstResult} was not applied to this query.
|
||||
*
|
||||
*
|
||||
* @return integer The position of the first result.
|
||||
*/
|
||||
public function getFirstResult()
|
||||
{
|
||||
return $this->_firstResult;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the maximum number of results to retrieve (the "limit").
|
||||
*
|
||||
*
|
||||
* @param integer $maxResults
|
||||
* @return Query This query object.
|
||||
*/
|
||||
@@ -454,11 +511,11 @@ final class Query extends AbstractQuery
|
||||
$this->_state = self::STATE_DIRTY;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the maximum number of results the query object was set to retrieve (the "limit").
|
||||
* Returns NULL if {@link setMaxResults} was not applied to this query.
|
||||
*
|
||||
*
|
||||
* @return integer Maximum number of results.
|
||||
*/
|
||||
public function getMaxResults()
|
||||
@@ -479,7 +536,7 @@ final class Query extends AbstractQuery
|
||||
$this->setHint(self::HINT_INTERNAL_ITERATION, true);
|
||||
return parent::iterate($params, $hydrationMode);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -488,7 +545,7 @@ final class Query extends AbstractQuery
|
||||
$this->_state = self::STATE_DIRTY;
|
||||
return parent::setHint($name, $value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -501,7 +558,7 @@ final class Query extends AbstractQuery
|
||||
/**
|
||||
* Set the lock mode for this Query.
|
||||
*
|
||||
* @see Doctrine\DBAL\LockMode
|
||||
* @see \Doctrine\DBAL\LockMode
|
||||
* @param int $lockMode
|
||||
* @return Query
|
||||
*/
|
||||
@@ -543,7 +600,7 @@ final class Query extends AbstractQuery
|
||||
ksort($this->_hints);
|
||||
|
||||
return md5(
|
||||
$this->getDql() . var_export($this->_hints, true) .
|
||||
$this->getDql() . var_export($this->_hints, true) .
|
||||
'&firstResult=' . $this->_firstResult . '&maxResult=' . $this->_maxResults .
|
||||
'&hydrationMode='.$this->_hydrationMode.'DOCTRINE_QUERY_CACHE_SALT'
|
||||
);
|
||||
@@ -559,4 +616,4 @@ final class Query extends AbstractQuery
|
||||
parent::__clone();
|
||||
$this->_state = self::STATE_DIRTY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
47
lib/Doctrine/ORM/Query/AST/CoalesceExpression.php
Normal file
47
lib/Doctrine/ORM/Query/AST/CoalesceExpression.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Query\AST;
|
||||
|
||||
/**
|
||||
* CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
|
||||
*
|
||||
* @since 2.1
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
* @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 CoalesceExpression extends Node
|
||||
{
|
||||
public $scalarExpressions = array();
|
||||
|
||||
|
||||
public function __construct(array $scalarExpressions)
|
||||
{
|
||||
$this->scalarExpressions = $scalarExpressions;
|
||||
}
|
||||
|
||||
public function dispatch($sqlWalker)
|
||||
{
|
||||
return $sqlWalker->walkCoalesceExpression($this);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
<?php
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* 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
|
||||
@@ -27,7 +25,6 @@ namespace Doctrine\ORM\Query\AST;
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
* @since 2.0
|
||||
* @version $Revision: 3938 $
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @author Jonathan Wage <jonwage@gmail.com>
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
|
||||
71
lib/Doctrine/ORM/Query/AST/Functions/DateAddFunction.php
Normal file
71
lib/Doctrine/ORM/Query/AST/Functions/DateAddFunction.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Query\AST\Functions;
|
||||
|
||||
use Doctrine\ORM\Query\Lexer;
|
||||
use Doctrine\ORM\Query\SqlWalker;
|
||||
use Doctrine\ORM\Query\Parser;
|
||||
use Doctrine\ORM\Query\QueryException;
|
||||
|
||||
/**
|
||||
* "DATE_ADD(date1, interval, unit)"
|
||||
*
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
* @since 2.0
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class DateAddFunction extends FunctionNode
|
||||
{
|
||||
public $firstDateExpression = null;
|
||||
public $intervalExpression = null;
|
||||
public $unit = null;
|
||||
|
||||
public function getSql(SqlWalker $sqlWalker)
|
||||
{
|
||||
$unit = strtolower($this->unit);
|
||||
if ($unit == "day") {
|
||||
return $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddDaysExpression(
|
||||
$this->firstDateExpression->dispatch($sqlWalker),
|
||||
$this->intervalExpression->dispatch($sqlWalker)
|
||||
);
|
||||
} else if ($unit == "month") {
|
||||
return $sqlWalker->getConnection()->getDatabasePlatform()->getDateAddMonthExpression(
|
||||
$this->firstDateExpression->dispatch($sqlWalker),
|
||||
$this->intervalExpression->dispatch($sqlWalker)
|
||||
);
|
||||
} else {
|
||||
throw QueryException::semanticalError('DATE_ADD() only supports units of type day and month.');
|
||||
}
|
||||
}
|
||||
|
||||
public function parse(Parser $parser)
|
||||
{
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
$this->firstDateExpression = $parser->ArithmeticPrimary();
|
||||
$parser->match(Lexer::T_COMMA);
|
||||
$this->intervalExpression = $parser->ArithmeticPrimary();
|
||||
$parser->match(Lexer::T_COMMA);
|
||||
$this->unit = $parser->StringPrimary();
|
||||
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
}
|
||||
}
|
||||
58
lib/Doctrine/ORM/Query/AST/Functions/DateDiffFunction.php
Normal file
58
lib/Doctrine/ORM/Query/AST/Functions/DateDiffFunction.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Query\AST\Functions;
|
||||
|
||||
use Doctrine\ORM\Query\Lexer;
|
||||
use Doctrine\ORM\Query\SqlWalker;
|
||||
use Doctrine\ORM\Query\Parser;
|
||||
|
||||
/**
|
||||
* "DATE_DIFF(date1, date2)"
|
||||
*
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
* @since 2.0
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class DateDiffFunction extends FunctionNode
|
||||
{
|
||||
public $date1;
|
||||
public $date2;
|
||||
|
||||
public function getSql(SqlWalker $sqlWalker)
|
||||
{
|
||||
return $sqlWalker->getConnection()->getDatabasePlatform()->getDateDiffExpression(
|
||||
$this->date1->dispatch($sqlWalker),
|
||||
$this->date2->dispatch($sqlWalker)
|
||||
);
|
||||
}
|
||||
|
||||
public function parse(Parser $parser)
|
||||
{
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
$this->date1 = $parser->ArithmeticPrimary();
|
||||
$parser->match(Lexer::T_COMMA);
|
||||
$this->date2 = $parser->ArithmeticPrimary();
|
||||
|
||||
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
}
|
||||
}
|
||||
58
lib/Doctrine/ORM/Query/AST/Functions/DateSubFunction.php
Normal file
58
lib/Doctrine/ORM/Query/AST/Functions/DateSubFunction.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Query\AST\Functions;
|
||||
|
||||
use Doctrine\ORM\Query\Lexer;
|
||||
use Doctrine\ORM\Query\SqlWalker;
|
||||
use Doctrine\ORM\Query\Parser;
|
||||
use Doctrine\ORM\Query\QueryException;
|
||||
|
||||
/**
|
||||
* "DATE_ADD(date1, interval, unit)"
|
||||
*
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
* @since 2.0
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class DateSubFunction extends DateAddFunction
|
||||
{
|
||||
public $firstDateExpression = null;
|
||||
public $intervalExpression = null;
|
||||
public $unit = null;
|
||||
|
||||
public function getSql(SqlWalker $sqlWalker)
|
||||
{
|
||||
$unit = strtolower($this->unit);
|
||||
if ($unit == "day") {
|
||||
return $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubDaysExpression(
|
||||
$this->firstDateExpression->dispatch($sqlWalker),
|
||||
$this->intervalExpression->dispatch($sqlWalker)
|
||||
);
|
||||
} else if ($unit == "month") {
|
||||
return $sqlWalker->getConnection()->getDatabasePlatform()->getDateSubMonthExpression(
|
||||
$this->firstDateExpression->dispatch($sqlWalker),
|
||||
$this->intervalExpression->dispatch($sqlWalker)
|
||||
);
|
||||
} else {
|
||||
throw QueryException::semanticalError('DATE_SUB() only supports units of type day and month.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,8 +53,8 @@ class SizeFunction extends FunctionNode
|
||||
|
||||
if ($assoc['type'] == \Doctrine\ORM\Mapping\ClassMetadata::ONE_TO_MANY) {
|
||||
$targetClass = $sqlWalker->getEntityManager()->getClassMetadata($assoc['targetEntity']);
|
||||
$targetTableAlias = $sqlWalker->getSqlTableAlias($targetClass->table['name']);
|
||||
$sourceTableAlias = $sqlWalker->getSqlTableAlias($class->table['name'], $dqlAlias);
|
||||
$targetTableAlias = $sqlWalker->getSQLTableAlias($targetClass->table['name']);
|
||||
$sourceTableAlias = $sqlWalker->getSQLTableAlias($class->table['name'], $dqlAlias);
|
||||
|
||||
$sql .= $targetClass->getQuotedTableName($platform) . ' ' . $targetTableAlias . ' WHERE ';
|
||||
|
||||
@@ -76,8 +76,8 @@ class SizeFunction extends FunctionNode
|
||||
$joinTable = $owningAssoc['joinTable'];
|
||||
|
||||
// SQL table aliases
|
||||
$joinTableAlias = $sqlWalker->getSqlTableAlias($joinTable['name']);
|
||||
$sourceTableAlias = $sqlWalker->getSqlTableAlias($class->table['name'], $dqlAlias);
|
||||
$joinTableAlias = $sqlWalker->getSQLTableAlias($joinTable['name']);
|
||||
$sourceTableAlias = $sqlWalker->getSQLTableAlias($class->table['name'], $dqlAlias);
|
||||
|
||||
// join to target table
|
||||
$sql .= $targetClass->getQuotedJoinTableName($owningAssoc, $platform) . ' ' . $joinTableAlias . ' WHERE ';
|
||||
|
||||
49
lib/Doctrine/ORM/Query/AST/NullIfExpression.php
Normal file
49
lib/Doctrine/ORM/Query/AST/NullIfExpression.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Query\AST;
|
||||
|
||||
/**
|
||||
* NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
|
||||
*
|
||||
* @since 2.1
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
* @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 NullIfExpression extends Node
|
||||
{
|
||||
public $firstExpression;
|
||||
|
||||
public $secondExpression;
|
||||
|
||||
public function __construct($firstExpression, $secondExpression)
|
||||
{
|
||||
$this->firstExpression = $firstExpression;
|
||||
$this->secondExpression = $secondExpression;
|
||||
}
|
||||
|
||||
public function dispatch($sqlWalker)
|
||||
{
|
||||
return $sqlWalker->walkNullIfExpression($this);
|
||||
}
|
||||
}
|
||||
@@ -49,9 +49,9 @@ abstract class AbstractSqlExecutor
|
||||
/**
|
||||
* Executes all sql statements.
|
||||
*
|
||||
* @param Doctrine\DBAL\Connection $conn The database connection that is used to execute the queries.
|
||||
* @param \Doctrine\DBAL\Connection $conn The database connection that is used to execute the queries.
|
||||
* @param array $params The parameters.
|
||||
* @return Doctrine\DBAL\Driver\Statement
|
||||
* @return \Doctrine\DBAL\Driver\Statement
|
||||
*/
|
||||
abstract public function execute(Connection $conn, array $params, array $types);
|
||||
}
|
||||
@@ -63,9 +63,11 @@ class MultiTableDeleteExecutor extends AbstractSqlExecutor
|
||||
$idColumnList = implode(', ', $idColumnNames);
|
||||
|
||||
// 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause()
|
||||
$sqlWalker->setSQLTableAlias($primaryClass->table['name'], 't0', $primaryDqlAlias);
|
||||
|
||||
$this->_insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')'
|
||||
. ' SELECT t0.' . implode(', t0.', $idColumnNames);
|
||||
$sqlWalker->setSqlTableAlias($primaryClass->table['name'] . $primaryDqlAlias, 't0');
|
||||
|
||||
$rangeDecl = new AST\RangeVariableDeclaration($primaryClass->name, $primaryDqlAlias);
|
||||
$fromClause = new AST\FromClause(array(new AST\IdentificationVariableDeclaration($rangeDecl, null, array())));
|
||||
$this->_insertSql .= $sqlWalker->walkFromClause($fromClause);
|
||||
@@ -96,13 +98,13 @@ class MultiTableDeleteExecutor extends AbstractSqlExecutor
|
||||
}
|
||||
$this->_createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' ('
|
||||
. $platform->getColumnDeclarationListSQL($columnDefinitions) . ')';
|
||||
$this->_dropTempTableSql = 'DROP TABLE ' . $tempTable;
|
||||
$this->_dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes all SQL statements.
|
||||
*
|
||||
* @param Doctrine\DBAL\Connection $conn The database connection that is used to execute the queries.
|
||||
* @param \Doctrine\DBAL\Connection $conn The database connection that is used to execute the queries.
|
||||
* @param array $params The parameters.
|
||||
* @override
|
||||
*/
|
||||
|
||||
@@ -51,7 +51,7 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor
|
||||
$em = $sqlWalker->getEntityManager();
|
||||
$conn = $em->getConnection();
|
||||
$platform = $conn->getDatabasePlatform();
|
||||
|
||||
|
||||
$updateClause = $AST->updateClause;
|
||||
|
||||
$primaryClass = $sqlWalker->getEntityManager()->getClassMetadata($updateClause->abstractSchemaName);
|
||||
@@ -64,11 +64,14 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor
|
||||
$idColumnList = implode(', ', $idColumnNames);
|
||||
|
||||
// 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause()
|
||||
$sqlWalker->setSQLTableAlias($primaryClass->table['name'], 't0', $updateClause->aliasIdentificationVariable);
|
||||
|
||||
$this->_insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')'
|
||||
. ' SELECT t0.' . implode(', t0.', $idColumnNames);
|
||||
$sqlWalker->setSqlTableAlias($primaryClass->table['name'] . $updateClause->aliasIdentificationVariable, 't0');
|
||||
|
||||
$rangeDecl = new AST\RangeVariableDeclaration($primaryClass->name, $updateClause->aliasIdentificationVariable);
|
||||
$fromClause = new AST\FromClause(array(new AST\IdentificationVariableDeclaration($rangeDecl, null, array())));
|
||||
|
||||
$this->_insertSql .= $sqlWalker->walkFromClause($fromClause);
|
||||
|
||||
// 2. Create ID subselect statement used in UPDATE ... WHERE ... IN (subselect)
|
||||
@@ -85,8 +88,9 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor
|
||||
|
||||
foreach ($updateItems as $updateItem) {
|
||||
$field = $updateItem->pathExpression->field;
|
||||
|
||||
if (isset($class->fieldMappings[$field]) && ! isset($class->fieldMappings[$field]['inherited']) ||
|
||||
isset($class->associationMappings[$field]) && ! isset($class->associationMappings[$field]['inherited'])) {
|
||||
isset($class->associationMappings[$field]) && ! isset($class->associationMappings[$field]['inherited'])) {
|
||||
$newValue = $updateItem->newValue;
|
||||
|
||||
if ( ! $affected) {
|
||||
@@ -102,7 +106,9 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor
|
||||
//FIXME (URGENT): With query cache the parameter is out of date. Move to execute() stage.
|
||||
if ($newValue instanceof AST\InputParameter) {
|
||||
$paramKey = $newValue->name;
|
||||
$this->_sqlParameters[$i][] = $sqlWalker->getQuery()->getParameter($paramKey);
|
||||
$this->_sqlParameters[$i]['parameters'][] = $sqlWalker->getQuery()->getParameter($paramKey);
|
||||
$this->_sqlParameters[$i]['types'][] = $sqlWalker->getQuery()->getParameterType($paramKey);
|
||||
|
||||
++$this->_numParametersInUpdateClause;
|
||||
}
|
||||
}
|
||||
@@ -120,15 +126,18 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor
|
||||
|
||||
// 4. Store DDL for temporary identifier table.
|
||||
$columnDefinitions = array();
|
||||
|
||||
foreach ($idColumnNames as $idColumnName) {
|
||||
$columnDefinitions[$idColumnName] = array(
|
||||
'notnull' => true,
|
||||
'type' => Type::getType($rootClass->getTypeOfColumn($idColumnName))
|
||||
);
|
||||
}
|
||||
|
||||
$this->_createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' ('
|
||||
. $platform->getColumnDeclarationListSQL($columnDefinitions) . ')';
|
||||
$this->_dropTempTableSql = 'DROP TABLE ' . $tempTable;
|
||||
|
||||
$this->_dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,11 +155,23 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor
|
||||
$conn->executeUpdate($this->_createTempTableSql);
|
||||
|
||||
// Insert identifiers. Parameters from the update clause are cut off.
|
||||
$numUpdated = $conn->executeUpdate($this->_insertSql, array_slice($params, $this->_numParametersInUpdateClause), $types);
|
||||
$numUpdated = $conn->executeUpdate(
|
||||
$this->_insertSql,
|
||||
array_slice($params, $this->_numParametersInUpdateClause),
|
||||
array_slice($types, $this->_numParametersInUpdateClause)
|
||||
);
|
||||
|
||||
// Execute UPDATE statements
|
||||
for ($i=0, $count=count($this->_sqlStatements); $i<$count; ++$i) {
|
||||
$conn->executeUpdate($this->_sqlStatements[$i], isset($this->_sqlParameters[$i]) ? $this->_sqlParameters[$i] : array());
|
||||
$parameters = array();
|
||||
$types = array();
|
||||
|
||||
if (isset($this->_sqlParameters[$i])) {
|
||||
$parameters = isset($this->_sqlParameters[$i]['parameters']) ? $this->_sqlParameters[$i]['parameters'] : array();
|
||||
$types = isset($this->_sqlParameters[$i]['types']) ? $this->_sqlParameters[$i]['types'] : array();
|
||||
}
|
||||
|
||||
$conn->executeUpdate($this->_sqlStatements[$i], $parameters, $types);
|
||||
}
|
||||
|
||||
// Drop temporary table
|
||||
|
||||
@@ -73,7 +73,7 @@ class Expr
|
||||
* Creates an ASCending order expression.
|
||||
*
|
||||
* @param $sort
|
||||
* @return OrderBy
|
||||
* @return Expr\OrderBy
|
||||
*/
|
||||
public function asc($expr)
|
||||
{
|
||||
@@ -84,7 +84,7 @@ class Expr
|
||||
* Creates a DESCending order expression.
|
||||
*
|
||||
* @param $sort
|
||||
* @return OrderBy
|
||||
* @return Expr\OrderBy
|
||||
*/
|
||||
public function desc($expr)
|
||||
{
|
||||
@@ -443,6 +443,28 @@ class Expr
|
||||
return new Expr\Func($x . ' NOT IN', (array) $y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an IS NULL expression with the given arguments.
|
||||
*
|
||||
* @param string $x Field in string format to be restricted by IS NULL
|
||||
* @return string
|
||||
*/
|
||||
public function isNull($x)
|
||||
{
|
||||
return $x . ' IS NULL';
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an IS NOT NULL expression with the given arguments.
|
||||
*
|
||||
* @param string $x Field in string format to be restricted by IS NOT NULL
|
||||
* @return string
|
||||
*/
|
||||
public function isNotNull($x)
|
||||
{
|
||||
return $x . ' IS NOT NULL';
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a LIKE() comparison expression with the given arguments.
|
||||
*
|
||||
@@ -538,6 +560,8 @@ class Expr
|
||||
{
|
||||
if (is_numeric($literal) && !is_string($literal)) {
|
||||
return (string) $literal;
|
||||
} else if (is_bool($literal)) {
|
||||
return $literal ? "true" : "false";
|
||||
} else {
|
||||
return "'" . str_replace("'", "''", $literal) . "'";
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@ namespace Doctrine\ORM\Query\Expr;
|
||||
* @author Jonathan Wage <jonwage@gmail.com>
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
*/
|
||||
class Andx extends Base
|
||||
class Andx extends Composite
|
||||
{
|
||||
protected $_separator = ') AND (';
|
||||
protected $_separator = ' AND ';
|
||||
protected $_allowedClasses = array(
|
||||
'Doctrine\ORM\Query\Expr\Comparison',
|
||||
'Doctrine\ORM\Query\Expr\Func',
|
||||
|
||||
@@ -39,7 +39,7 @@ abstract class Base
|
||||
protected $_postSeparator = ')';
|
||||
protected $_allowedClasses = array();
|
||||
|
||||
private $_parts = array();
|
||||
protected $_parts = array();
|
||||
|
||||
public function __construct($args = array())
|
||||
{
|
||||
@@ -55,7 +55,7 @@ abstract class Base
|
||||
|
||||
public function add($arg)
|
||||
{
|
||||
if ( ! empty($arg) || ($arg instanceof self && $arg->count() > 0)) {
|
||||
if ( $arg !== null || ($arg instanceof self && $arg->count() > 0)) {
|
||||
// If we decide to keep Expr\Base instances, we can use this check
|
||||
if ( ! is_string($arg)) {
|
||||
$class = get_class($arg);
|
||||
|
||||
68
lib/Doctrine/ORM/Query/Expr/Composite.php
Normal file
68
lib/Doctrine/ORM/Query/Expr/Composite.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Query\Expr;
|
||||
|
||||
/**
|
||||
* Expression class for building DQL and parts
|
||||
*
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
* @since 2.0
|
||||
* @version $Revision$
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @author Jonathan Wage <jonwage@gmail.com>
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
*/
|
||||
class Composite extends Base
|
||||
{
|
||||
public function __toString()
|
||||
{
|
||||
if ($this->count() === 1) {
|
||||
return (string) $this->_parts[0];
|
||||
}
|
||||
|
||||
$components = array();
|
||||
|
||||
foreach ($this->_parts as $part) {
|
||||
$components[] = $this->processQueryPart($part);
|
||||
}
|
||||
|
||||
return implode($this->_separator, $components);
|
||||
}
|
||||
|
||||
|
||||
private function processQueryPart($part)
|
||||
{
|
||||
$queryPart = (string) $part;
|
||||
|
||||
if (is_object($part) && $part instanceof self && $part->count() > 1) {
|
||||
return $this->_preSeparator . $queryPart . $this->_postSeparator;
|
||||
}
|
||||
|
||||
// Fixes DDC-1237: User may have added a where item containing nested expression (with "OR" or "AND")
|
||||
if (stripos($queryPart, ' OR ') !== false || stripos($queryPart, ' AND ') !== false) {
|
||||
return $this->_preSeparator . $queryPart . $this->_postSeparator;
|
||||
}
|
||||
|
||||
return $queryPart;
|
||||
}
|
||||
}
|
||||
@@ -45,20 +45,23 @@ class Join
|
||||
private $_alias;
|
||||
private $_conditionType;
|
||||
private $_condition;
|
||||
private $_indexBy;
|
||||
|
||||
public function __construct($joinType, $join, $alias = null, $conditionType = null, $condition = null)
|
||||
public function __construct($joinType, $join, $alias = null, $conditionType = null, $condition = null, $indexBy = null)
|
||||
{
|
||||
$this->_joinType = $joinType;
|
||||
$this->_join = $join;
|
||||
$this->_alias = $alias;
|
||||
$this->_joinType = $joinType;
|
||||
$this->_join = $join;
|
||||
$this->_alias = $alias;
|
||||
$this->_conditionType = $conditionType;
|
||||
$this->_condition = $condition;
|
||||
$this->_condition = $condition;
|
||||
$this->_indexBy = $indexBy;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return strtoupper($this->_joinType) . ' JOIN ' . $this->_join
|
||||
. ($this->_alias ? ' ' . $this->_alias : '')
|
||||
. ($this->_condition ? ' ' . strtoupper($this->_conditionType) . ' ' . $this->_condition : '');
|
||||
. ($this->_condition ? ' ' . strtoupper($this->_conditionType) . ' ' . $this->_condition : '')
|
||||
. ($this->_indexBy ? ' INDEX BY ' . $this->_indexBy : '');
|
||||
}
|
||||
}
|
||||
@@ -32,9 +32,9 @@ namespace Doctrine\ORM\Query\Expr;
|
||||
* @author Jonathan Wage <jonwage@gmail.com>
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
*/
|
||||
class Orx extends Base
|
||||
class Orx extends Composite
|
||||
{
|
||||
protected $_separator = ') OR (';
|
||||
protected $_separator = ' OR ';
|
||||
protected $_allowedClasses = array(
|
||||
'Doctrine\ORM\Query\Expr\Andx',
|
||||
'Doctrine\ORM\Query\Expr\Comparison',
|
||||
|
||||
@@ -126,7 +126,7 @@ class Lexer extends \Doctrine\Common\Lexer
|
||||
'[a-z_\\\][a-z0-9_\:\\\]*[a-z0-9_]{1}',
|
||||
'(?:[0-9]+(?:[\.][0-9]+)*)(?:e[+-]?[0-9]+)?',
|
||||
"'(?:[^']|'')*'",
|
||||
'\?[1-9][0-9]*|:[a-z][a-z0-9_]+'
|
||||
'\?[0-9]*|:[a-z]{1}[a-z0-9_]{0,}'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
72
lib/Doctrine/ORM/Query/ParameterTypeInferer.php
Normal file
72
lib/Doctrine/ORM/Query/ParameterTypeInferer.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Query;
|
||||
|
||||
use Doctrine\DBAL\Connection,
|
||||
Doctrine\DBAL\Types\Type;
|
||||
|
||||
/**
|
||||
* Provides an enclosed support for parameter infering.
|
||||
*
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
* @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 ParameterTypeInferer
|
||||
{
|
||||
/**
|
||||
* Infer type of a given value, returning a compatible constant:
|
||||
* - Type (\Doctrine\DBAL\Types\Type::*)
|
||||
* - Connection (\Doctrine\DBAL\Connection::PARAM_*)
|
||||
*
|
||||
* @param mixed $value Parameter value
|
||||
*
|
||||
* @return mixed Parameter type constant
|
||||
*/
|
||||
public static function inferType($value)
|
||||
{
|
||||
switch (true) {
|
||||
case is_integer($value):
|
||||
return Type::INTEGER;
|
||||
|
||||
case ($value instanceof \DateTime):
|
||||
return Type::DATETIME;
|
||||
|
||||
case is_array($value):
|
||||
$key = key($value);
|
||||
|
||||
if (is_integer($value[$key])) {
|
||||
return Connection::PARAM_INT_ARRAY;
|
||||
}
|
||||
|
||||
return Connection::PARAM_STR_ARRAY;
|
||||
|
||||
default:
|
||||
// Do nothing
|
||||
break;
|
||||
}
|
||||
|
||||
return \PDO::PARAM_STR;
|
||||
}
|
||||
}
|
||||
@@ -45,19 +45,22 @@ class Parser
|
||||
|
||||
/** READ-ONLY: Maps BUILT-IN numeric function names to AST class names. */
|
||||
private static $_NUMERIC_FUNCTIONS = array(
|
||||
'length' => 'Doctrine\ORM\Query\AST\Functions\LengthFunction',
|
||||
'locate' => 'Doctrine\ORM\Query\AST\Functions\LocateFunction',
|
||||
'abs' => 'Doctrine\ORM\Query\AST\Functions\AbsFunction',
|
||||
'sqrt' => 'Doctrine\ORM\Query\AST\Functions\SqrtFunction',
|
||||
'mod' => 'Doctrine\ORM\Query\AST\Functions\ModFunction',
|
||||
'size' => 'Doctrine\ORM\Query\AST\Functions\SizeFunction'
|
||||
'length' => 'Doctrine\ORM\Query\AST\Functions\LengthFunction',
|
||||
'locate' => 'Doctrine\ORM\Query\AST\Functions\LocateFunction',
|
||||
'abs' => 'Doctrine\ORM\Query\AST\Functions\AbsFunction',
|
||||
'sqrt' => 'Doctrine\ORM\Query\AST\Functions\SqrtFunction',
|
||||
'mod' => 'Doctrine\ORM\Query\AST\Functions\ModFunction',
|
||||
'size' => 'Doctrine\ORM\Query\AST\Functions\SizeFunction',
|
||||
'date_diff' => 'Doctrine\ORM\Query\AST\Functions\DateDiffFunction',
|
||||
);
|
||||
|
||||
/** READ-ONLY: Maps BUILT-IN datetime function names to AST class names. */
|
||||
private static $_DATETIME_FUNCTIONS = array(
|
||||
'current_date' => 'Doctrine\ORM\Query\AST\Functions\CurrentDateFunction',
|
||||
'current_time' => 'Doctrine\ORM\Query\AST\Functions\CurrentTimeFunction',
|
||||
'current_timestamp' => 'Doctrine\ORM\Query\AST\Functions\CurrentTimestampFunction'
|
||||
'current_timestamp' => 'Doctrine\ORM\Query\AST\Functions\CurrentTimestampFunction',
|
||||
'date_add' => 'Doctrine\ORM\Query\AST\Functions\DateAddFunction',
|
||||
'date_sub' => 'Doctrine\ORM\Query\AST\Functions\DateSubFunction',
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -72,14 +75,14 @@ class Parser
|
||||
/**
|
||||
* The lexer.
|
||||
*
|
||||
* @var Doctrine\ORM\Query\Lexer
|
||||
* @var \Doctrine\ORM\Query\Lexer
|
||||
*/
|
||||
private $_lexer;
|
||||
|
||||
/**
|
||||
* The parser result.
|
||||
*
|
||||
* @var Doctrine\ORM\Query\ParserResult
|
||||
* @var \Doctrine\ORM\Query\ParserResult
|
||||
*/
|
||||
private $_parserResult;
|
||||
|
||||
@@ -167,7 +170,7 @@ class Parser
|
||||
/**
|
||||
* Gets the lexer used by the parser.
|
||||
*
|
||||
* @return Doctrine\ORM\Query\Lexer
|
||||
* @return \Doctrine\ORM\Query\Lexer
|
||||
*/
|
||||
public function getLexer()
|
||||
{
|
||||
@@ -177,7 +180,7 @@ class Parser
|
||||
/**
|
||||
* Gets the ParserResult that is being filled with information during parsing.
|
||||
*
|
||||
* @return Doctrine\ORM\Query\ParserResult
|
||||
* @return \Doctrine\ORM\Query\ParserResult
|
||||
*/
|
||||
public function getParserResult()
|
||||
{
|
||||
@@ -231,7 +234,7 @@ class Parser
|
||||
* If they match, updates the lookahead token; otherwise raises a syntax
|
||||
* error.
|
||||
*
|
||||
* @param int|string token type or value
|
||||
* @param int token type
|
||||
* @return void
|
||||
* @throws QueryException If the tokens dont match.
|
||||
*/
|
||||
@@ -277,20 +280,8 @@ class Parser
|
||||
{
|
||||
$AST = $this->getAST();
|
||||
|
||||
// Fix order of identification variables.
|
||||
// They have to appear in the select clause in the same order as the
|
||||
// declarations (from ... x join ... y join ... z ...) appear in the query
|
||||
// as the hydration process relies on that order for proper operation.
|
||||
if ( count($this->_identVariableExpressions) > 1) {
|
||||
foreach ($this->_queryComponents as $dqlAlias => $qComp) {
|
||||
if (isset($this->_identVariableExpressions[$dqlAlias])) {
|
||||
$expr = $this->_identVariableExpressions[$dqlAlias];
|
||||
$key = array_search($expr, $AST->selectClause->selectExpressions);
|
||||
unset($AST->selectClause->selectExpressions[$key]);
|
||||
$AST->selectClause->selectExpressions[] = $expr;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->fixIdentificationVariableOrder($AST);
|
||||
$this->assertSelectEntityRootAliasRequirement();
|
||||
|
||||
if (($customWalkers = $this->_query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) !== false) {
|
||||
$this->_customTreeWalkers = $customWalkers;
|
||||
@@ -332,6 +323,46 @@ class Parser
|
||||
|
||||
return $this->_parserResult;
|
||||
}
|
||||
|
||||
private function assertSelectEntityRootAliasRequirement()
|
||||
{
|
||||
if ( count($this->_identVariableExpressions) > 0) {
|
||||
$foundRootEntity = false;
|
||||
foreach ($this->_identVariableExpressions AS $dqlAlias => $expr) {
|
||||
if (isset($this->_queryComponents[$dqlAlias]) && $this->_queryComponents[$dqlAlias]['parent'] === null) {
|
||||
$foundRootEntity = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$foundRootEntity) {
|
||||
$this->semanticalError('Cannot select entity through identification variables without choosing at least one root entity alias.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix order of identification variables.
|
||||
*
|
||||
* They have to appear in the select clause in the same order as the
|
||||
* declarations (from ... x join ... y join ... z ...) appear in the query
|
||||
* as the hydration process relies on that order for proper operation.
|
||||
*
|
||||
* @param AST\SelectStatement|AST\DeleteStatement|AST\UpdateStatement $AST
|
||||
* @return void
|
||||
*/
|
||||
private function fixIdentificationVariableOrder($AST)
|
||||
{
|
||||
if ( count($this->_identVariableExpressions) > 1) {
|
||||
foreach ($this->_queryComponents as $dqlAlias => $qComp) {
|
||||
if (isset($this->_identVariableExpressions[$dqlAlias])) {
|
||||
$expr = $this->_identVariableExpressions[$dqlAlias];
|
||||
$key = array_search($expr, $AST->selectClause->selectExpressions);
|
||||
unset($AST->selectClause->selectExpressions[$key]);
|
||||
$AST->selectClause->selectExpressions[] = $expr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new syntax error.
|
||||
@@ -895,6 +926,10 @@ class Parser
|
||||
$token = $this->_lexer->lookahead;
|
||||
$identVariable = $this->IdentificationVariable();
|
||||
|
||||
if (!isset($this->_queryComponents[$identVariable])) {
|
||||
$this->semanticalError('Identification Variable ' . $identVariable .' used in join path expression but was not defined before.');
|
||||
}
|
||||
|
||||
$this->match(Lexer::T_DOT);
|
||||
$this->match(Lexer::T_IDENTIFIER);
|
||||
|
||||
@@ -1296,6 +1331,10 @@ class Parser
|
||||
$token = $this->_lexer->lookahead;
|
||||
$identVariable = $this->IdentificationVariable();
|
||||
|
||||
if (!isset($this->_queryComponents[$identVariable])) {
|
||||
$this->semanticalError('Cannot group by undefined identification variable.');
|
||||
}
|
||||
|
||||
return $identVariable;
|
||||
}
|
||||
|
||||
@@ -1427,7 +1466,7 @@ class Parser
|
||||
/**
|
||||
* RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
|
||||
*
|
||||
* @return Doctrine\ORM\Query\AST\RangeVariableDeclaration
|
||||
* @return \Doctrine\ORM\Query\AST\RangeVariableDeclaration
|
||||
*/
|
||||
public function RangeVariableDeclaration()
|
||||
{
|
||||
@@ -1498,7 +1537,7 @@ class Parser
|
||||
* Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression
|
||||
* ["AS"] AliasIdentificationVariable ["WITH" ConditionalExpression]
|
||||
*
|
||||
* @return Doctrine\ORM\Query\AST\Join
|
||||
* @return \Doctrine\ORM\Query\AST\Join
|
||||
*/
|
||||
public function Join()
|
||||
{
|
||||
@@ -1568,7 +1607,7 @@ class Parser
|
||||
/**
|
||||
* IndexBy ::= "INDEX" "BY" StateFieldPathExpression
|
||||
*
|
||||
* @return Doctrine\ORM\Query\AST\IndexBy
|
||||
* @return \Doctrine\ORM\Query\AST\IndexBy
|
||||
*/
|
||||
public function IndexBy()
|
||||
{
|
||||
@@ -1605,7 +1644,11 @@ class Parser
|
||||
return $this->StateFieldPathExpression();
|
||||
} else if ($lookahead == Lexer::T_INTEGER || $lookahead == Lexer::T_FLOAT) {
|
||||
return $this->SimpleArithmeticExpression();
|
||||
} else if ($this->_isFunction()) {
|
||||
} else if ($lookahead == Lexer::T_CASE || $lookahead == Lexer::T_COALESCE || $lookahead == Lexer::T_NULLIF) {
|
||||
// Since NULLIF and COALESCE can be identified as a function,
|
||||
// we need to check if before check for FunctionDeclaration
|
||||
return $this->CaseExpression();
|
||||
} else if ($this->_isFunction() || $this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
|
||||
// We may be in an ArithmeticExpression (find the matching ")" and inspect for Math operator)
|
||||
$this->_lexer->peek(); // "("
|
||||
$peek = $this->_peekBeyondClosingParenthesis();
|
||||
@@ -1613,8 +1656,12 @@ class Parser
|
||||
if ($this->_isMathOperator($peek)) {
|
||||
return $this->SimpleArithmeticExpression();
|
||||
}
|
||||
|
||||
return $this->FunctionDeclaration();
|
||||
|
||||
if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
|
||||
return $this->AggregateExpression();
|
||||
} else {
|
||||
return $this->FunctionDeclaration();
|
||||
}
|
||||
} else if ($lookahead == Lexer::T_STRING) {
|
||||
return $this->StringPrimary();
|
||||
} else if ($lookahead == Lexer::T_INPUT_PARAMETER) {
|
||||
@@ -1622,8 +1669,6 @@ class Parser
|
||||
} else if ($lookahead == Lexer::T_TRUE || $lookahead == Lexer::T_FALSE) {
|
||||
$this->match($lookahead);
|
||||
return new AST\Literal(AST\Literal::BOOLEAN, $this->_lexer->token['value']);
|
||||
} else if ($lookahead == Lexer::T_CASE || $lookahead == Lexer::T_COALESCE || $lookahead == Lexer::T_NULLIF) {
|
||||
return $this->CaseExpression();
|
||||
} else {
|
||||
$this->syntaxError();
|
||||
}
|
||||
@@ -1631,11 +1676,66 @@ class Parser
|
||||
|
||||
public function CaseExpression()
|
||||
{
|
||||
$lookahead = $this->_lexer->lookahead['type'];
|
||||
|
||||
// if "CASE" "WHEN" => GeneralCaseExpression
|
||||
// else if "CASE" => SimpleCaseExpression
|
||||
// else if "COALESCE" => CoalesceExpression
|
||||
// else if "NULLIF" => NullifExpression
|
||||
$this->semanticalError('CaseExpression not yet supported.');
|
||||
// [DONE] else if "COALESCE" => CoalesceExpression
|
||||
// [DONE] else if "NULLIF" => NullifExpression
|
||||
switch ($lookahead) {
|
||||
case Lexer::T_NULLIF:
|
||||
return $this->NullIfExpression();
|
||||
|
||||
case Lexer::T_COALESCE:
|
||||
return $this->CoalesceExpression();
|
||||
|
||||
default:
|
||||
$this->semanticalError('CaseExpression not yet supported.');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
|
||||
*
|
||||
* @return \Doctrine\ORM\Query\AST\CoalesceExpression
|
||||
*/
|
||||
public function CoalesceExpression()
|
||||
{
|
||||
$this->match(Lexer::T_COALESCE);
|
||||
$this->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
// Process ScalarExpressions (1..N)
|
||||
$scalarExpressions = array();
|
||||
$scalarExpressions[] = $this->ScalarExpression();
|
||||
|
||||
while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
|
||||
$this->match(Lexer::T_COMMA);
|
||||
$scalarExpressions[] = $this->ScalarExpression();
|
||||
}
|
||||
|
||||
$this->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
|
||||
return new AST\CoalesceExpression($scalarExpressions);
|
||||
}
|
||||
|
||||
/**
|
||||
* NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
|
||||
*
|
||||
* @return \Doctrine\ORM\Query\AST\ExistsExpression
|
||||
*/
|
||||
public function NullIfExpression()
|
||||
{
|
||||
$this->match(Lexer::T_NULLIF);
|
||||
$this->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
$firstExpression = $this->ScalarExpression();
|
||||
$this->match(Lexer::T_COMMA);
|
||||
$secondExpression = $this->ScalarExpression();
|
||||
|
||||
$this->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
|
||||
return new AST\NullIfExpression($firstExpression, $secondExpression);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1643,7 +1743,7 @@ class Parser
|
||||
* IdentificationVariable | StateFieldPathExpression |
|
||||
* (AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable]
|
||||
*
|
||||
* @return Doctrine\ORM\Query\AST\SelectExpression
|
||||
* @return \Doctrine\ORM\Query\AST\SelectExpression
|
||||
*/
|
||||
public function SelectExpression()
|
||||
{
|
||||
@@ -1674,12 +1774,16 @@ class Parser
|
||||
}
|
||||
} else if ($this->_isFunction()) {
|
||||
$this->_lexer->peek(); // "("
|
||||
$beyond = $this->_peekBeyondClosingParenthesis();
|
||||
|
||||
|
||||
$lookaheadType = $this->_lexer->lookahead['type'];
|
||||
$beyond = $this->_peekBeyondClosingParenthesis();
|
||||
|
||||
if ($this->_isMathOperator($beyond)) {
|
||||
$expression = $this->ScalarExpression();
|
||||
} else if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
|
||||
$expression = $this->AggregateExpression();
|
||||
} else if (in_array ($lookaheadType, array(Lexer::T_CASE, Lexer::T_COALESCE, Lexer::T_NULLIF))) {
|
||||
$expression = $this->CaseExpression();
|
||||
} else {
|
||||
// Shortcut: ScalarExpression => Function
|
||||
$expression = $this->FunctionDeclaration();
|
||||
@@ -1689,7 +1793,8 @@ class Parser
|
||||
$expression = $this->PartialObjectExpression();
|
||||
$identVariable = $expression->identificationVariable;
|
||||
} else if ($this->_lexer->lookahead['type'] == Lexer::T_INTEGER ||
|
||||
$this->_lexer->lookahead['type'] == Lexer::T_FLOAT) {
|
||||
$this->_lexer->lookahead['type'] == Lexer::T_FLOAT ||
|
||||
$this->_lexer->lookahead['type'] == Lexer::T_STRING) {
|
||||
// Shortcut: ScalarExpression => SimpleArithmeticExpression
|
||||
$expression = $this->SimpleArithmeticExpression();
|
||||
} else {
|
||||
@@ -1758,15 +1863,8 @@ class Parser
|
||||
}
|
||||
|
||||
$this->_lexer->peek();
|
||||
$beyond = $this->_peekBeyondClosingParenthesis();
|
||||
|
||||
if ($this->_isMathOperator($beyond)) {
|
||||
$expression = $this->ScalarExpression();
|
||||
} else if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
|
||||
$expression = $this->AggregateExpression();
|
||||
} else {
|
||||
$expression = $this->FunctionDeclaration();
|
||||
}
|
||||
$expression = $this->ScalarExpression();
|
||||
|
||||
$expr = new AST\SimpleSelectExpression($expression);
|
||||
|
||||
@@ -1869,7 +1967,7 @@ class Parser
|
||||
/**
|
||||
* ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")"
|
||||
*
|
||||
* @return Doctrine\ORM\Query\AST\ConditionalPrimary
|
||||
* @return \Doctrine\ORM\Query\AST\ConditionalPrimary
|
||||
*/
|
||||
public function ConditionalPrimary()
|
||||
{
|
||||
@@ -2192,7 +2290,7 @@ class Parser
|
||||
/**
|
||||
* ArithmeticPrimary ::= SingleValuedPathExpression | Literal | "(" SimpleArithmeticExpression ")"
|
||||
* | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings
|
||||
* | FunctionsReturningDatetime | IdentificationVariable
|
||||
* | FunctionsReturningDatetime | IdentificationVariable | ResultVariable
|
||||
*/
|
||||
public function ArithmeticPrimary()
|
||||
{
|
||||
@@ -2216,7 +2314,11 @@ class Parser
|
||||
if ($peek['value'] == '.') {
|
||||
return $this->SingleValuedPathExpression();
|
||||
}
|
||||
|
||||
|
||||
if (isset($this->_queryComponents[$this->_lexer->lookahead['value']]['resultVariable'])) {
|
||||
return $this->ResultVariable();
|
||||
}
|
||||
|
||||
return $this->StateFieldPathExpression();
|
||||
|
||||
case Lexer::T_INPUT_PARAMETER:
|
||||
@@ -2271,7 +2373,8 @@ class Parser
|
||||
if ($peek['value'] == '.') {
|
||||
return $this->StateFieldPathExpression();
|
||||
} else if ($peek['value'] == '(') {
|
||||
return $this->FunctionsReturningStrings();
|
||||
// do NOT directly go to FunctionsReturningString() because it doesnt check for custom functions.
|
||||
return $this->FunctionDeclaration();
|
||||
} else {
|
||||
$this->syntaxError("'.' or '('");
|
||||
}
|
||||
@@ -2358,7 +2461,7 @@ class Parser
|
||||
|
||||
$functionName = $this->_lexer->token['value'];
|
||||
$this->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
$pathExp = $this->StateFieldPathExpression();
|
||||
$pathExp = $this->SimpleArithmeticExpression();
|
||||
$this->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
}
|
||||
|
||||
|
||||
@@ -72,11 +72,15 @@ class QueryException extends \Doctrine\ORM\ORMException
|
||||
return new self("Invalid parameter: token ".$key." is not defined in the query.");
|
||||
}
|
||||
|
||||
public static function parameterTypeMissmatch()
|
||||
{
|
||||
return new self("DQL Query parameter and type numbers missmatch, but have to be exactly equal.");
|
||||
}
|
||||
|
||||
public static function invalidPathExpression($pathExpr)
|
||||
{
|
||||
return new self(
|
||||
"Invalid PathExpression '" . $pathExpr->identificationVariable .
|
||||
"." . implode('.', $pathExpr->parts) . "'."
|
||||
"Invalid PathExpression '" . $pathExpr->identificationVariable . "." . $pathExpr->field . "'."
|
||||
);
|
||||
}
|
||||
|
||||
@@ -85,7 +89,7 @@ class QueryException extends \Doctrine\ORM\ORMException
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Doctrine\ORM\Mapping\AssociationMapping $assoc
|
||||
* @param \Doctrine\ORM\Mapping\AssociationMapping $assoc
|
||||
*/
|
||||
public static function iterateWithFetchJoinCollectionNotAllowed($assoc)
|
||||
{
|
||||
@@ -136,4 +140,10 @@ class QueryException extends \Doctrine\ORM\ORMException
|
||||
"in the query."
|
||||
);
|
||||
}
|
||||
|
||||
public static function instanceOfUnrelatedClass($className, $rootClass)
|
||||
{
|
||||
return new self("Cannot check if a child of '" . $rootClass . "' is instanceof '" . $className . "', " .
|
||||
"inheritance hierachy exists between these two classes.");
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
<?php
|
||||
/*
|
||||
* $Id$
|
||||
*
|
||||
* 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
|
||||
@@ -114,6 +112,13 @@ class ResultSetMapping
|
||||
* @var array
|
||||
*/
|
||||
public $declaringClasses = array();
|
||||
|
||||
/**
|
||||
* This is necessary to hydrate derivate foreign keys correctly.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $isIdentifierColumn = array();
|
||||
|
||||
/**
|
||||
* Adds an entity result to this ResultSetMapping.
|
||||
@@ -383,14 +388,17 @@ class ResultSetMapping
|
||||
/**
|
||||
* Adds a meta column (foreign key or discriminator column) to the result set.
|
||||
*
|
||||
* @param $alias
|
||||
* @param $columnName
|
||||
* @param $fieldName
|
||||
* @param string $alias
|
||||
* @param string $columnName
|
||||
* @param string $fieldName
|
||||
* @param bool
|
||||
*/
|
||||
public function addMetaResult($alias, $columnName, $fieldName)
|
||||
public function addMetaResult($alias, $columnName, $fieldName, $isIdentifierColumn = false)
|
||||
{
|
||||
$this->metaMappings[$columnName] = $fieldName;
|
||||
$this->columnOwnerMap[$columnName] = $alias;
|
||||
if ($isIdentifierColumn) {
|
||||
$this->isIdentifierColumn[$alias][$columnName] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
107
lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php
Normal file
107
lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Query;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||
|
||||
/**
|
||||
* A ResultSetMappingBuilder uses the EntityManager to automatically populate entity fields
|
||||
*
|
||||
* @author Michael Ridgway <mcridgway@gmail.com>
|
||||
* @since 2.1
|
||||
*/
|
||||
class ResultSetMappingBuilder extends ResultSetMapping
|
||||
{
|
||||
/**
|
||||
* @var EntityManager
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* @param EntityManager
|
||||
*/
|
||||
public function __construct(EntityManager $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a root entity and all of its fields to the result set.
|
||||
*
|
||||
* @param string $class The class name of the root entity.
|
||||
* @param string $alias The unique alias to use for the root entity.
|
||||
* @param array $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName)
|
||||
*/
|
||||
public function addRootEntityFromClassMetadata($class, $alias, $renamedColumns = array())
|
||||
{
|
||||
$this->addEntityResult($class, $alias);
|
||||
$this->addAllClassFields($class, $alias, $renamedColumns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a joined entity and all of its fields to the result set.
|
||||
*
|
||||
* @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.
|
||||
* @param array $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName)
|
||||
*/
|
||||
public function addJoinedEntityFromClassMetadata($class, $alias, $parentAlias, $relation, $renamedColumns = array())
|
||||
{
|
||||
$this->addJoinedEntityResult($class, $alias, $parentAlias, $relation);
|
||||
$this->addAllClassFields($class, $alias, $renamedColumns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all fields of the given class to the result set mapping (columns and meta fields)
|
||||
*/
|
||||
protected function addAllClassFields($class, $alias, $renamedColumns = array())
|
||||
{
|
||||
$classMetadata = $this->em->getClassMetadata($class);
|
||||
if ($classMetadata->isInheritanceTypeSingleTable() || $classMetadata->isInheritanceTypeJoined()) {
|
||||
throw new \InvalidArgumentException('ResultSetMapping builder does not currently support inheritance.');
|
||||
}
|
||||
$platform = $this->em->getConnection()->getDatabasePlatform();
|
||||
foreach ($classMetadata->getColumnNames() AS $columnName) {
|
||||
$propertyName = $classMetadata->getFieldName($columnName);
|
||||
if (isset($renamedColumns[$columnName])) {
|
||||
$columnName = $renamedColumns[$columnName];
|
||||
}
|
||||
if (isset($this->fieldMappings[$columnName])) {
|
||||
throw new \InvalidArgumentException("The column '$columnName' conflicts with another column in the mapper.");
|
||||
}
|
||||
$this->addFieldResult($alias, $platform->getSQLResultCasing($columnName), $propertyName);
|
||||
}
|
||||
foreach ($classMetadata->associationMappings AS $associationMapping) {
|
||||
if ($associationMapping['isOwningSide'] && $associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
|
||||
foreach ($associationMapping['joinColumns'] AS $joinColumn) {
|
||||
$columnName = $joinColumn['name'];
|
||||
$renamedColumnName = isset($renamedColumns[$columnName]) ? $renamedColumns[$columnName] : $columnName;
|
||||
if (isset($this->metaMappings[$renamedColumnName])) {
|
||||
throw new \InvalidArgumentException("The column '$renamedColumnName' conflicts with another column in the mapper.");
|
||||
}
|
||||
$this->addMetaResult($alias, $platform->getSQLResultCasing($renamedColumnName), $platform->getSQLResultCasing($columnName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -55,7 +55,7 @@ abstract class TreeWalkerAdapter implements TreeWalker
|
||||
/**
|
||||
* Retrieve Query Instance reponsible for the current walkers execution.
|
||||
*
|
||||
* @return Doctrine\ORM\Query
|
||||
* @return \Doctrine\ORM\Query
|
||||
*/
|
||||
protected function _getQuery()
|
||||
{
|
||||
@@ -65,7 +65,7 @@ abstract class TreeWalkerAdapter implements TreeWalker
|
||||
/**
|
||||
* Retrieve ParserResult
|
||||
*
|
||||
* @return Doctrine\ORM\Query\ParserResult
|
||||
* @return \Doctrine\ORM\Query\ParserResult
|
||||
*/
|
||||
protected function _getParserResult()
|
||||
{
|
||||
|
||||
@@ -50,6 +50,7 @@ class QueryBuilder
|
||||
* @var array The array of DQL parts collected.
|
||||
*/
|
||||
private $_dqlParts = array(
|
||||
'distinct' => false,
|
||||
'select' => array(),
|
||||
'from' => array(),
|
||||
'join' => array(),
|
||||
@@ -119,7 +120,7 @@ class QueryBuilder
|
||||
* For more complex expression construction, consider storing the expression
|
||||
* builder object in a local variable.
|
||||
*
|
||||
* @return Expr
|
||||
* @return Query\Expr
|
||||
*/
|
||||
public function expr()
|
||||
{
|
||||
@@ -217,9 +218,30 @@ class QueryBuilder
|
||||
->setFirstResult($this->_firstResult)
|
||||
->setMaxResults($this->_maxResults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the FIRST root alias of the query. This is the first entity alias involved
|
||||
* in the construction of the query.
|
||||
*
|
||||
* <code>
|
||||
* $qb = $em->createQueryBuilder()
|
||||
* ->select('u')
|
||||
* ->from('User', 'u');
|
||||
*
|
||||
* echo $qb->getRootAlias(); // u
|
||||
* </code>
|
||||
*
|
||||
* @deprecated Please use $qb->getRootAliases() instead.
|
||||
* @return string $rootAlias
|
||||
*/
|
||||
public function getRootAlias()
|
||||
{
|
||||
$aliases = $this->getRootAliases();
|
||||
return $aliases[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the root alias of the query. This is the first entity alias involved
|
||||
* Gets the root aliases of the query. This is the entity aliases involved
|
||||
* in the construction of the query.
|
||||
*
|
||||
* <code>
|
||||
@@ -227,15 +249,61 @@ class QueryBuilder
|
||||
* ->select('u')
|
||||
* ->from('User', 'u');
|
||||
*
|
||||
* echo $qb->getRootAlias(); // u
|
||||
* $qb->getRootAliases(); // array('u')
|
||||
* </code>
|
||||
*
|
||||
* @return string $rootAlias
|
||||
* @todo Rename/Refactor: getRootAliases(), there can be multiple roots!
|
||||
* @return array $rootAliases
|
||||
*/
|
||||
public function getRootAlias()
|
||||
public function getRootAliases()
|
||||
{
|
||||
return $this->_dqlParts['from'][0]->getAlias();
|
||||
$aliases = array();
|
||||
|
||||
foreach ($this->_dqlParts['from'] as &$fromClause) {
|
||||
if (is_string($fromClause)) {
|
||||
$spacePos = strrpos($fromClause, ' ');
|
||||
$from = substr($fromClause, 0, $spacePos);
|
||||
$alias = substr($fromClause, $spacePos + 1);
|
||||
|
||||
$fromClause = new Query\Expr\From($from, $alias);
|
||||
}
|
||||
|
||||
$aliases[] = $fromClause->getAlias();
|
||||
}
|
||||
|
||||
return $aliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the root entities of the query. This is the entity aliases involved
|
||||
* in the construction of the query.
|
||||
*
|
||||
* <code>
|
||||
* $qb = $em->createQueryBuilder()
|
||||
* ->select('u')
|
||||
* ->from('User', 'u');
|
||||
*
|
||||
* $qb->getRootEntities(); // array('User')
|
||||
* </code>
|
||||
*
|
||||
* @return array $rootEntities
|
||||
*/
|
||||
public function getRootEntities()
|
||||
{
|
||||
$entities = array();
|
||||
|
||||
foreach ($this->_dqlParts['from'] as &$fromClause) {
|
||||
if (is_string($fromClause)) {
|
||||
$spacePos = strrpos($fromClause, ' ');
|
||||
$from = substr($fromClause, 0, $spacePos);
|
||||
$alias = substr($fromClause, $spacePos + 1);
|
||||
|
||||
$fromClause = new Query\Expr\From($from, $alias);
|
||||
}
|
||||
|
||||
$entities[] = $fromClause->getFrom();
|
||||
}
|
||||
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -256,10 +324,13 @@ class QueryBuilder
|
||||
*/
|
||||
public function setParameter($key, $value, $type = null)
|
||||
{
|
||||
if ($type !== null) {
|
||||
$this->_paramTypes[$key] = $type;
|
||||
if ($type === null) {
|
||||
$type = Query\ParameterTypeInferer::inferType($value);
|
||||
}
|
||||
|
||||
$this->_paramTypes[$key] = $type;
|
||||
$this->_params[$key] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -272,8 +343,8 @@ class QueryBuilder
|
||||
* ->from('User', 'u')
|
||||
* ->where('u.id = :user_id1 OR u.id = :user_id2')
|
||||
* ->setParameters(array(
|
||||
* ':user_id1' => 1,
|
||||
* ':user_id2' => 2
|
||||
* 'user_id1' => 1,
|
||||
* 'user_id2' => 2
|
||||
* ));
|
||||
* </code>
|
||||
*
|
||||
@@ -282,8 +353,13 @@ class QueryBuilder
|
||||
*/
|
||||
public function setParameters(array $params, array $types = array())
|
||||
{
|
||||
$this->_paramTypes = $types;
|
||||
$this->_params = $params;
|
||||
foreach ($params as $key => $value) {
|
||||
if (isset($types[$key])) {
|
||||
$this->setParameter($key, $value, $types[$key]);
|
||||
} else {
|
||||
$this->setParameter($key, $value);
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -368,9 +444,29 @@ class QueryBuilder
|
||||
public function add($dqlPartName, $dqlPart, $append = false)
|
||||
{
|
||||
$isMultiple = is_array($this->_dqlParts[$dqlPartName]);
|
||||
|
||||
// This is introduced for backwards compatibility reasons.
|
||||
// TODO: Remove for 3.0
|
||||
if ($dqlPartName == 'join') {
|
||||
$newDqlPart = array();
|
||||
foreach ($dqlPart AS $k => $v) {
|
||||
if (is_numeric($k)) {
|
||||
$newDqlPart[$this->getRootAlias()] = $v;
|
||||
} else {
|
||||
$newDqlPart[$k] = $v;
|
||||
}
|
||||
}
|
||||
$dqlPart = $newDqlPart;
|
||||
}
|
||||
|
||||
if ($append && $isMultiple) {
|
||||
$this->_dqlParts[$dqlPartName][] = $dqlPart;
|
||||
if (is_array($dqlPart)) {
|
||||
$key = key($dqlPart);
|
||||
|
||||
$this->_dqlParts[$dqlPartName][$key][] = $dqlPart[$key];
|
||||
} else {
|
||||
$this->_dqlParts[$dqlPartName][] = $dqlPart;
|
||||
}
|
||||
} else {
|
||||
$this->_dqlParts[$dqlPartName] = ($isMultiple) ? array($dqlPart) : $dqlPart;
|
||||
}
|
||||
@@ -407,6 +503,25 @@ class QueryBuilder
|
||||
return $this->add('select', new Expr\Select($selects), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a DISTINCT flag to this query.
|
||||
*
|
||||
* <code>
|
||||
* $qb = $em->createQueryBuilder()
|
||||
* ->select('u')
|
||||
* ->distinct()
|
||||
* ->from('User', 'u');
|
||||
* </code>
|
||||
*
|
||||
* @param bool
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function distinct($flag = true)
|
||||
{
|
||||
$this->_dqlParts['distinct'] = (bool) $flag;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an item that is to be returned in the query result.
|
||||
*
|
||||
@@ -523,11 +638,12 @@ class QueryBuilder
|
||||
* @param string $alias The alias of the join
|
||||
* @param string $conditionType The condition type constant. Either ON or WITH.
|
||||
* @param string $condition The condition for the join
|
||||
* @param string $indexBy The index for the join
|
||||
* @return QueryBuilder This QueryBuilder instance.
|
||||
*/
|
||||
public function join($join, $alias, $conditionType = null, $condition = null)
|
||||
public function join($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
|
||||
{
|
||||
return $this->innerJoin($join, $alias, $conditionType, $condition);
|
||||
return $this->innerJoin($join, $alias, $conditionType, $condition, $indexBy);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -547,12 +663,18 @@ class QueryBuilder
|
||||
* @param string $alias The alias of the join
|
||||
* @param string $conditionType The condition type constant. Either ON or WITH.
|
||||
* @param string $condition The condition for the join
|
||||
* @param string $indexBy The index for the join
|
||||
* @return QueryBuilder This QueryBuilder instance.
|
||||
*/
|
||||
public function innerJoin($join, $alias, $conditionType = null, $condition = null)
|
||||
public function innerJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
|
||||
{
|
||||
return $this->add('join', new Expr\Join(
|
||||
Expr\Join::INNER_JOIN, $join, $alias, $conditionType, $condition
|
||||
$rootAlias = substr($join, 0, strpos($join, '.'));
|
||||
if (!in_array($rootAlias, $this->getRootAliases())) {
|
||||
$rootAlias = $this->getRootAlias();
|
||||
}
|
||||
|
||||
return $this->add('join', array(
|
||||
$rootAlias => new Expr\Join(Expr\Join::INNER_JOIN, $join, $alias, $conditionType, $condition, $indexBy)
|
||||
), true);
|
||||
}
|
||||
|
||||
@@ -574,12 +696,18 @@ class QueryBuilder
|
||||
* @param string $alias The alias of the join
|
||||
* @param string $conditionType The condition type constant. Either ON or WITH.
|
||||
* @param string $condition The condition for the join
|
||||
* @param string $indexBy The index for the join
|
||||
* @return QueryBuilder This QueryBuilder instance.
|
||||
*/
|
||||
public function leftJoin($join, $alias, $conditionType = null, $condition = null)
|
||||
public function leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
|
||||
{
|
||||
return $this->add('join', new Expr\Join(
|
||||
Expr\Join::LEFT_JOIN, $join, $alias, $conditionType, $condition
|
||||
$rootAlias = substr($join, 0, strpos($join, '.'));
|
||||
if (!in_array($rootAlias, $this->getRootAliases())) {
|
||||
$rootAlias = $this->getRootAlias();
|
||||
}
|
||||
|
||||
return $this->add('join', array(
|
||||
$rootAlias => new Expr\Join(Expr\Join::LEFT_JOIN, $join, $alias, $conditionType, $condition, $indexBy)
|
||||
), true);
|
||||
}
|
||||
|
||||
@@ -629,7 +757,7 @@ class QueryBuilder
|
||||
*/
|
||||
public function where($predicates)
|
||||
{
|
||||
if ( ! (func_num_args() == 1 && ($predicates instanceof Expr\Andx || $predicates instanceof Expr\Orx))) {
|
||||
if ( ! (func_num_args() == 1 && $predicates instanceof Expr\Composite)) {
|
||||
$predicates = new Expr\Andx(func_get_args());
|
||||
}
|
||||
|
||||
@@ -865,14 +993,38 @@ class QueryBuilder
|
||||
|
||||
private function _getDQLForSelect()
|
||||
{
|
||||
return 'SELECT'
|
||||
. $this->_getReducedDQLQueryPart('select', array('pre' => ' ', 'separator' => ', '))
|
||||
. $this->_getReducedDQLQueryPart('from', array('pre' => ' FROM ', 'separator' => ', '))
|
||||
. $this->_getReducedDQLQueryPart('join', array('pre' => ' ', 'separator' => ' '))
|
||||
$dql = 'SELECT'
|
||||
. ($this->_dqlParts['distinct']===true ? ' DISTINCT' : '')
|
||||
. $this->_getReducedDQLQueryPart('select', array('pre' => ' ', 'separator' => ', '));
|
||||
|
||||
$fromParts = $this->getDQLPart('from');
|
||||
$joinParts = $this->getDQLPart('join');
|
||||
$fromClauses = array();
|
||||
|
||||
// Loop through all FROM clauses
|
||||
if ( ! empty($fromParts)) {
|
||||
$dql .= ' FROM ';
|
||||
|
||||
foreach ($fromParts as $from) {
|
||||
$fromClause = (string) $from;
|
||||
|
||||
if ($from instanceof Expr\From && isset($joinParts[$from->getAlias()])) {
|
||||
foreach ($joinParts[$from->getAlias()] as $join) {
|
||||
$fromClause .= ' ' . ((string) $join);
|
||||
}
|
||||
}
|
||||
|
||||
$fromClauses[] = $fromClause;
|
||||
}
|
||||
}
|
||||
|
||||
$dql .= implode(', ', $fromClauses)
|
||||
. $this->_getReducedDQLQueryPart('where', array('pre' => ' WHERE '))
|
||||
. $this->_getReducedDQLQueryPart('groupBy', array('pre' => ' GROUP BY ', 'separator' => ', '))
|
||||
. $this->_getReducedDQLQueryPart('having', array('pre' => ' HAVING '))
|
||||
. $this->_getReducedDQLQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', '));
|
||||
|
||||
return $dql;
|
||||
}
|
||||
|
||||
private function _getReducedDQLQueryPart($queryPartName, $options = array())
|
||||
@@ -952,4 +1104,4 @@ class QueryBuilder
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,10 @@ class ConvertMappingCommand extends Console\Command\Command
|
||||
'dest-path', InputArgument::REQUIRED,
|
||||
'The path to generate your entities classes.'
|
||||
),
|
||||
new InputOption(
|
||||
'force', null, InputOption::VALUE_NONE,
|
||||
'Force to overwrite existing mapping files.'
|
||||
),
|
||||
new InputOption(
|
||||
'from-database', null, null, 'Whether or not to convert mapping information from existing database.'
|
||||
),
|
||||
@@ -73,10 +77,28 @@ class ConvertMappingCommand extends Console\Command\Command
|
||||
new InputOption(
|
||||
'num-spaces', null, InputOption::VALUE_OPTIONAL,
|
||||
'Defines the number of indentation spaces', 4
|
||||
)
|
||||
),
|
||||
new InputOption(
|
||||
'namespace', null, InputOption::VALUE_OPTIONAL,
|
||||
'Defines a namespace for the generated entity classes, if converted from database.'
|
||||
),
|
||||
))
|
||||
->setHelp(<<<EOT
|
||||
Convert mapping information between supported formats.
|
||||
|
||||
This is an execute <info>one-time</info> command. It should not be necessary for
|
||||
you to call this method multiple times, escpecially when using the <comment>--from-database</comment>
|
||||
flag.
|
||||
|
||||
Converting an existing databsae 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.
|
||||
|
||||
<comment>Hint:</comment> 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.
|
||||
EOT
|
||||
);
|
||||
}
|
||||
@@ -89,11 +111,17 @@ EOT
|
||||
$em = $this->getHelper('em')->getEntityManager();
|
||||
|
||||
if ($input->getOption('from-database') === true) {
|
||||
$em->getConfiguration()->setMetadataDriverImpl(
|
||||
new \Doctrine\ORM\Mapping\Driver\DatabaseDriver(
|
||||
$em->getConnection()->getSchemaManager()
|
||||
)
|
||||
$databaseDriver = new \Doctrine\ORM\Mapping\Driver\DatabaseDriver(
|
||||
$em->getConnection()->getSchemaManager()
|
||||
);
|
||||
|
||||
$em->getConfiguration()->setMetadataDriverImpl(
|
||||
$databaseDriver
|
||||
);
|
||||
|
||||
if (($namespace = $input->getOption('namespace')) !== null) {
|
||||
$databaseDriver->setNamespace($namespace);
|
||||
}
|
||||
}
|
||||
|
||||
$cmf = new DisconnectedClassMetadataFactory();
|
||||
@@ -119,8 +147,8 @@ EOT
|
||||
|
||||
$toType = strtolower($input->getArgument('to-type'));
|
||||
|
||||
$cme = new ClassMetadataExporter();
|
||||
$exporter = $cme->getExporter($toType, $destPath);
|
||||
$exporter = $this->getExporter($toType, $destPath);
|
||||
$exporter->setOverwriteExistingFiles( ($input->getOption('force') !== false) );
|
||||
|
||||
if ($toType == 'annotation') {
|
||||
$entityGenerator = new EntityGenerator();
|
||||
@@ -148,4 +176,11 @@ EOT
|
||||
$output->write('No Metadata Classes to process.' . PHP_EOL);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getExporter($toType, $destPath)
|
||||
{
|
||||
$cme = new ClassMetadataExporter();
|
||||
|
||||
return $cme->getExporter($toType, $destPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +85,23 @@ class GenerateEntitiesCommand extends Console\Command\Command
|
||||
))
|
||||
->setHelp(<<<EOT
|
||||
Generate entity classes and method stubs from your mapping information.
|
||||
|
||||
If you use the <comment>--update-entities</comment> or <comment>--regenerate-entities</comment> flags your exisiting
|
||||
code gets overwritten. 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 dont 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.
|
||||
|
||||
<error>Important:</error> 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!
|
||||
EOT
|
||||
);
|
||||
}
|
||||
|
||||
80
lib/Doctrine/ORM/Tools/Console/Command/InfoCommand.php
Normal file
80
lib/Doctrine/ORM/Tools/Console/Command/InfoCommand.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Tools\Console\Command;
|
||||
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
||||
/**
|
||||
* Show information about mapped entities
|
||||
*
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
* @since 2.1
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class InfoCommand extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('orm:info')
|
||||
->setDescription('Show basic information about all mapped entities')
|
||||
->setHelp(<<<EOT
|
||||
The <info>doctrine:mapping:info</info> shows basic information about which
|
||||
entities exist and possibly if their mapping information contains errors or
|
||||
not.
|
||||
EOT
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
/* @var $entityManager \Doctrine\ORM\EntityManager */
|
||||
$entityManager = $this->getHelper('em')->getEntityManager();
|
||||
|
||||
$entityClassNames = $entityManager->getConfiguration()
|
||||
->getMetadataDriverImpl()
|
||||
->getAllClassNames();
|
||||
|
||||
if (!$entityClassNames) {
|
||||
throw new \Exception(
|
||||
'You do not have any mapped Doctrine ORM entities according to the current configuration. '.
|
||||
'If you have entities or mapping files you should check your mapping configuration for errors.'
|
||||
);
|
||||
}
|
||||
|
||||
$output->writeln(sprintf("Found <info>%d</info> mapped entities:", count($entityClassNames)));
|
||||
|
||||
foreach ($entityClassNames as $entityClassName) {
|
||||
try {
|
||||
$cm = $entityManager->getClassMetadata($entityClassName);
|
||||
$output->writeln(sprintf("<info>[OK]</info> %s", $entityClassName));
|
||||
} catch (MappingException $e) {
|
||||
$output->writeln("<error>[FAIL]</error> ".$entityClassName);
|
||||
$output->writeln(sprintf("<comment>%s</comment>", $e->getMessage()));
|
||||
$output->writeln('');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,7 @@ EOT
|
||||
|
||||
protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas)
|
||||
{
|
||||
$output->write('ATTENTION: This operation should not be executed in an production enviroment.' . PHP_EOL . PHP_EOL);
|
||||
$output->write('ATTENTION: This operation should not be executed in a production environment.' . PHP_EOL . PHP_EOL);
|
||||
|
||||
if ($input->getOption('dump-sql') === true) {
|
||||
$sqls = $schemaTool->getCreateSchemaSql($metadatas);
|
||||
|
||||
@@ -92,7 +92,7 @@ EOT
|
||||
}
|
||||
$output->write('Database schema dropped successfully!' . PHP_EOL);
|
||||
} else {
|
||||
$output->write('ATTENTION: This operation should not be executed in an production enviroment.' . PHP_EOL . PHP_EOL);
|
||||
$output->write('ATTENTION: This operation should not be executed in a production environment.' . PHP_EOL . PHP_EOL);
|
||||
|
||||
if ($isFullDatabaseDrop) {
|
||||
$sqls = $schemaTool->getDropDatabaseSQL();
|
||||
|
||||
@@ -28,7 +28,8 @@ use Symfony\Component\Console\Input\InputArgument,
|
||||
Doctrine\ORM\Tools\SchemaTool;
|
||||
|
||||
/**
|
||||
* Command to update the database schema for a set of classes based on their mappings.
|
||||
* Command to generate the SQL needed to update the database schema to match
|
||||
* the current mapping information.
|
||||
*
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
@@ -38,37 +39,58 @@ use Symfony\Component\Console\Input\InputArgument,
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @author Jonathan Wage <jonwage@gmail.com>
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
* @author Ryan Weaver <ryan@thatsquality.com>
|
||||
*/
|
||||
class UpdateCommand extends AbstractCommand
|
||||
{
|
||||
protected $name = 'orm:schema-tool:update';
|
||||
|
||||
/**
|
||||
* @see Console\Command\Command
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('orm:schema-tool:update')
|
||||
->setName($this->name)
|
||||
->setDescription(
|
||||
'Processes the schema and either update the database schema of EntityManager Storage Connection or generate the SQL output.'
|
||||
'Executes (or dumps) the SQL needed to update the database schema to match the current mapping metadata.'
|
||||
)
|
||||
->setDefinition(array(
|
||||
new InputOption(
|
||||
'complete', null, InputOption::VALUE_NONE,
|
||||
'If defined, all assets of the database which are not relevant to the current metadata will be dropped.'
|
||||
),
|
||||
|
||||
new InputOption(
|
||||
'dump-sql', null, InputOption::VALUE_NONE,
|
||||
'Instead of try to apply generated SQLs into EntityManager Storage Connection, output them.'
|
||||
'Dumps the generated SQL statements to the screen (does not execute them).'
|
||||
),
|
||||
new InputOption(
|
||||
'force', null, InputOption::VALUE_NONE,
|
||||
"Don't ask for the incremental update of the database, but force the operation to run."
|
||||
'Causes the generated SQL statements to be physically executed against your database.'
|
||||
),
|
||||
))
|
||||
->setHelp(<<<EOT
|
||||
Processes the schema and either update the database schema of EntityManager Storage Connection or generate the SQL output.
|
||||
Beware that if --complete is not defined, it will do a save update, which does not delete any tables, sequences or affected foreign keys.
|
||||
If defined, all assets of the database which are not relevant to the current metadata are dropped by this command.
|
||||
));
|
||||
|
||||
$fullName = $this->getName();
|
||||
$this->setHelp(<<<EOT
|
||||
The <info>$fullName</info> command generates the SQL needed to
|
||||
synchronize the database schema with the current mapping metadata of the
|
||||
default entity manager.
|
||||
|
||||
For example, if you add metadata for a new column to an entity, this command
|
||||
would generate and output the SQL needed to add the new column to the database:
|
||||
|
||||
<info>$fullName --dump-sql</info>
|
||||
|
||||
Alternatively, you can execute the generated queries:
|
||||
|
||||
<info>$fullName --force</info>
|
||||
|
||||
Finally, be aware that if the <info>--complete</info> option is passed, this
|
||||
task will drop all database assets (e.g. tables, etc) that are *not* described
|
||||
by the current metadata. In other words, without this option, this task leaves
|
||||
untouched any "extra" tables that exist in the database, but which aren't
|
||||
described by any metadata.
|
||||
EOT
|
||||
);
|
||||
}
|
||||
@@ -78,26 +100,36 @@ EOT
|
||||
// Defining if update is complete or not (--complete not defined means $saveMode = true)
|
||||
$saveMode = ($input->getOption('complete') !== true);
|
||||
|
||||
if ($input->getOption('dump-sql') === true) {
|
||||
$sqls = $schemaTool->getUpdateSchemaSql($metadatas, $saveMode);
|
||||
$output->write(implode(';' . PHP_EOL, $sqls) . PHP_EOL);
|
||||
} else if ($input->getOption('force') === true) {
|
||||
$output->write('Updating database schema...' . PHP_EOL);
|
||||
$sqls = $schemaTool->getUpdateSchemaSql($metadatas, $saveMode);
|
||||
if (0 == count($sqls)) {
|
||||
$output->writeln('Nothing to update - your database is already in sync with the current entity metadata.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$dumpSql = (true === $input->getOption('dump-sql'));
|
||||
$force = (true === $input->getOption('force'));
|
||||
if ($dumpSql && $force) {
|
||||
throw new \InvalidArgumentException('You can pass either the --dump-sql or the --force option (but not both simultaneously).');
|
||||
}
|
||||
|
||||
if ($dumpSql) {
|
||||
$output->writeln(implode(';' . PHP_EOL, $sqls));
|
||||
} else if ($force) {
|
||||
$output->writeln('Updating database schema...');
|
||||
$schemaTool->updateSchema($metadatas, $saveMode);
|
||||
$output->write('Database schema updated successfully!' . PHP_EOL);
|
||||
$output->writeln(sprintf('Database schema updated successfully! "<info>%s</info>" queries were executed', count($sqls)));
|
||||
} else {
|
||||
$output->write('ATTENTION: This operation should not be executed in an production enviroment.' . PHP_EOL);
|
||||
$output->write('Use the incremental update to detect changes during development and use' . PHP_EOL);
|
||||
$output->write('this SQL DDL to manually update your database in production.' . PHP_EOL . PHP_EOL);
|
||||
$output->writeln('<comment>ATTENTION</comment>: This operation should not be executed in a production environment.');
|
||||
$output->writeln(' Use the incremental update to detect changes during development and use');
|
||||
$output->writeln(' the SQL DDL provided to manually update your database in production.');
|
||||
$output->writeln('');
|
||||
|
||||
$sqls = $schemaTool->getUpdateSchemaSql($metadatas, $saveMode);
|
||||
|
||||
if (count($sqls)) {
|
||||
$output->write('Schema-Tool would execute ' . count($sqls) . ' queries to update the database.' . PHP_EOL);
|
||||
$output->write('Please run the operation with --force to execute these queries or use --dump-sql to see them.' . PHP_EOL);
|
||||
} else {
|
||||
$output->write('Nothing to update. The database is in sync with the current entity metadata.' . PHP_EOL);
|
||||
}
|
||||
$output->writeln(sprintf('The Schema-Tool would execute <info>"%s"</info> queries to update the database.', count($sqls)));
|
||||
$output->writeln('Please run the operation by passing one of the following options:');
|
||||
|
||||
$output->writeln(sprintf(' <info>%s --force</info> to execute the command', $this->getName()));
|
||||
$output->writeln(sprintf(' <info>%s --dump-sql</info> to dump the SQL statements to the screen', $this->getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ class ConsoleRunner
|
||||
new \Doctrine\ORM\Tools\Console\Command\ConvertMappingCommand(),
|
||||
new \Doctrine\ORM\Tools\Console\Command\RunDqlCommand(),
|
||||
new \Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand(),
|
||||
new \Doctrine\ORM\Tools\Console\Command\InfoCommand()
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -70,10 +70,10 @@ class ConvertDoctrine1Schema
|
||||
if (is_dir($path)) {
|
||||
$files = glob($path . '/*.yml');
|
||||
foreach ($files as $file) {
|
||||
$schema = array_merge($schema, (array) \Symfony\Component\Yaml\Yaml::load($file));
|
||||
$schema = array_merge($schema, (array) \Symfony\Component\Yaml\Yaml::parse($file));
|
||||
}
|
||||
} else {
|
||||
$schema = array_merge($schema, (array) \Symfony\Component\Yaml\Yaml::load($path));
|
||||
$schema = array_merge($schema, (array) \Symfony\Component\Yaml\Yaml::parse($path));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +188,6 @@ class ConvertDoctrine1Schema
|
||||
$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO);
|
||||
} else if (isset($column['sequence'])) {
|
||||
$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE);
|
||||
$metadata->setSequenceGeneratorDefinition($definition);
|
||||
$definition = array(
|
||||
'sequenceName' => is_array($column['sequence']) ? $column['sequence']['name']:$column['sequence']
|
||||
);
|
||||
@@ -198,6 +197,7 @@ class ConvertDoctrine1Schema
|
||||
if (isset($column['sequence']['value'])) {
|
||||
$definition['initialValue'] = $column['sequence']['value'];
|
||||
}
|
||||
$metadata->setSequenceGeneratorDefinition($definition);
|
||||
}
|
||||
return $fieldMapping;
|
||||
}
|
||||
|
||||
@@ -52,6 +52,17 @@ class DisconnectedClassMetadataFactory extends ClassMetadataFactory
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate runtime metadata is correctly defined.
|
||||
*
|
||||
* @param ClassMetadata $class
|
||||
* @param ClassMetadata $parent
|
||||
*/
|
||||
protected function validateRuntimeMetadata($class, $parent)
|
||||
{
|
||||
// validate nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
|
||||
@@ -47,14 +47,18 @@ use Doctrine\ORM\Mapping\ClassMetadataInfo,
|
||||
*/
|
||||
class EntityGenerator
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $_backupExisting = true;
|
||||
|
||||
/** The extension to use for written php files */
|
||||
private $_extension = '.php';
|
||||
|
||||
/** Whether or not the current ClassMetadataInfo instance is new or old */
|
||||
private $_isNew = true;
|
||||
|
||||
/** If isNew is false then this variable contains instance of ReflectionClass for current entity */
|
||||
private $_reflection;
|
||||
private $_staticReflection = array();
|
||||
|
||||
/** Number of spaces to use for indention in generated code */
|
||||
private $_numSpaces = 4;
|
||||
@@ -68,6 +72,11 @@ class EntityGenerator
|
||||
/** Whether or not to generation annotations */
|
||||
private $_generateAnnotations = false;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $_annotationsPrefix = '';
|
||||
|
||||
/** Whether or not to generated sub methods */
|
||||
private $_generateEntityStubMethods = false;
|
||||
|
||||
@@ -82,6 +91,8 @@ class EntityGenerator
|
||||
|
||||
<namespace>
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
<entityAnnotation>
|
||||
<entityClassName>
|
||||
{
|
||||
@@ -92,7 +103,7 @@ class EntityGenerator
|
||||
'/**
|
||||
* <description>
|
||||
*
|
||||
* @return <variableType>$<variableName>
|
||||
* @return <variableType>
|
||||
*/
|
||||
public function <methodName>()
|
||||
{
|
||||
@@ -130,11 +141,25 @@ public function <methodName>()
|
||||
<spaces>// Add your code here
|
||||
}';
|
||||
|
||||
private static $_constructorMethodTemplate =
|
||||
'public function __construct()
|
||||
{
|
||||
<spaces><collections>
|
||||
}
|
||||
';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (version_compare(\Doctrine\Common\Version::VERSION, '3.0.0-DEV', '>=')) {
|
||||
$this->_annotationsPrefix = 'ORM\\';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate and write entity classes for the given array of ClassMetadataInfo instances
|
||||
*
|
||||
* @param array $metadatas
|
||||
* @param string $outputDirectory
|
||||
* @param string $outputDirectory
|
||||
* @return void
|
||||
*/
|
||||
public function generate(array $metadatas, $outputDirectory)
|
||||
@@ -148,7 +173,7 @@ public function <methodName>()
|
||||
* Generated and write entity class to disk for the given ClassMetadataInfo instance
|
||||
*
|
||||
* @param ClassMetadataInfo $metadata
|
||||
* @param string $outputDirectory
|
||||
* @param string $outputDirectory
|
||||
* @return void
|
||||
*/
|
||||
public function writeEntityClass(ClassMetadataInfo $metadata, $outputDirectory)
|
||||
@@ -160,16 +185,23 @@ public function <methodName>()
|
||||
mkdir($dir, 0777, true);
|
||||
}
|
||||
|
||||
$this->_isNew = ! file_exists($path);
|
||||
$this->_isNew = !file_exists($path) || (file_exists($path) && $this->_regenerateEntityIfExists);
|
||||
|
||||
if ( ! $this->_isNew) {
|
||||
require_once $path;
|
||||
$this->_parseTokensInEntityFile(file_get_contents($path));
|
||||
} else {
|
||||
$this->_staticReflection[$metadata->name] = array('properties' => array(), 'methods' => array());
|
||||
}
|
||||
|
||||
$this->_reflection = new \ReflectionClass($metadata->name);
|
||||
if ($this->_backupExisting && file_exists($path)) {
|
||||
$backupPath = dirname($path) . DIRECTORY_SEPARATOR . basename($path) . "~";
|
||||
if (!copy($path, $backupPath)) {
|
||||
throw new \RuntimeException("Attempt to backup overwritten entitiy file but copy operation failed.");
|
||||
}
|
||||
}
|
||||
|
||||
// If entity doesn't exist or we're re-generating the entities entirely
|
||||
if ($this->_isNew || ( ! $this->_isNew && $this->_regenerateEntityIfExists)) {
|
||||
if ($this->_isNew) {
|
||||
file_put_contents($path, $this->generateEntityClass($metadata));
|
||||
// If entity exists and we're allowed to update the entity class
|
||||
} else if ( ! $this->_isNew && $this->_updateEntityIfExists) {
|
||||
@@ -180,7 +212,7 @@ public function <methodName>()
|
||||
/**
|
||||
* Generate a PHP5 Doctrine 2 entity class from the given ClassMetadataInfo instance
|
||||
*
|
||||
* @param ClassMetadataInfo $metadata
|
||||
* @param ClassMetadataInfo $metadata
|
||||
* @return string $code
|
||||
*/
|
||||
public function generateEntityClass(ClassMetadataInfo $metadata)
|
||||
@@ -206,8 +238,8 @@ public function <methodName>()
|
||||
/**
|
||||
* Generate the updated code for the given ClassMetadataInfo and entity at path
|
||||
*
|
||||
* @param ClassMetadataInfo $metadata
|
||||
* @param string $path
|
||||
* @param ClassMetadataInfo $metadata
|
||||
* @param string $path
|
||||
* @return string $code;
|
||||
*/
|
||||
public function generateUpdatedEntityClass(ClassMetadataInfo $metadata, $path)
|
||||
@@ -224,7 +256,7 @@ public function <methodName>()
|
||||
/**
|
||||
* Set the number of spaces the exported class should have
|
||||
*
|
||||
* @param integer $numSpaces
|
||||
* @param integer $numSpaces
|
||||
* @return void
|
||||
*/
|
||||
public function setNumSpaces($numSpaces)
|
||||
@@ -236,7 +268,7 @@ public function <methodName>()
|
||||
/**
|
||||
* Set the extension to use when writing php files to disk
|
||||
*
|
||||
* @param string $extension
|
||||
* @param string $extension
|
||||
* @return void
|
||||
*/
|
||||
public function setExtension($extension)
|
||||
@@ -257,7 +289,7 @@ public function <methodName>()
|
||||
/**
|
||||
* Set whether or not to generate annotations for the entity
|
||||
*
|
||||
* @param bool $bool
|
||||
* @param bool $bool
|
||||
* @return void
|
||||
*/
|
||||
public function setGenerateAnnotations($bool)
|
||||
@@ -265,10 +297,23 @@ public function <methodName>()
|
||||
$this->_generateAnnotations = $bool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an annotation prefix.
|
||||
*
|
||||
* @param string $prefix
|
||||
*/
|
||||
public function setAnnotationPrefix($prefix)
|
||||
{
|
||||
if (version_compare(\Doctrine\Common\Version::VERSION, '3.0.0-DEV', '>=')) {
|
||||
return;
|
||||
}
|
||||
$this->_annotationsPrefix = $prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not to try and update the entity if it already exists
|
||||
*
|
||||
* @param bool $bool
|
||||
* @param bool $bool
|
||||
* @return void
|
||||
*/
|
||||
public function setUpdateEntityIfExists($bool)
|
||||
@@ -298,6 +343,14 @@ public function <methodName>()
|
||||
$this->_generateEntityStubMethods = $bool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should an existing entity be backed up if it already exists?
|
||||
*/
|
||||
public function setBackupExisting($bool)
|
||||
{
|
||||
$this->_backupExisting = $bool;
|
||||
}
|
||||
|
||||
private function _generateEntityNamespace(ClassMetadataInfo $metadata)
|
||||
{
|
||||
if ($this->_hasNamespace($metadata)) {
|
||||
@@ -313,7 +366,7 @@ public function <methodName>()
|
||||
|
||||
private function _generateEntityBody(ClassMetadataInfo $metadata)
|
||||
{
|
||||
$fieldMappingProperties = $this->_generateEntityFieldMappingProperties($metadata);
|
||||
$fieldMappingProperties = $this->_generateEntityFieldMappingProperties($metadata);
|
||||
$associationMappingProperties = $this->_generateEntityAssociationMappingProperties($metadata);
|
||||
$stubMethods = $this->_generateEntityStubMethods ? $this->_generateEntityStubMethods($metadata) : null;
|
||||
$lifecycleCallbackMethods = $this->_generateEntityLifecycleCallbackMethods($metadata);
|
||||
@@ -328,6 +381,8 @@ public function <methodName>()
|
||||
$code[] = $associationMappingProperties;
|
||||
}
|
||||
|
||||
$code[] = $this->_generateEntityConstructor($metadata);
|
||||
|
||||
if ($stubMethods) {
|
||||
$code[] = $stubMethods;
|
||||
}
|
||||
@@ -339,14 +394,104 @@ public function <methodName>()
|
||||
return implode("\n", $code);
|
||||
}
|
||||
|
||||
private function _generateEntityConstructor(ClassMetadataInfo $metadata)
|
||||
{
|
||||
if ($this->_hasMethod('__construct', $metadata)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$collections = array();
|
||||
foreach ($metadata->associationMappings AS $mapping) {
|
||||
if ($mapping['type'] & ClassMetadataInfo::TO_MANY) {
|
||||
$collections[] = '$this->'.$mapping['fieldName'].' = new \Doctrine\Common\Collections\ArrayCollection();';
|
||||
}
|
||||
}
|
||||
if ($collections) {
|
||||
return $this->_prefixCodeWithSpaces(str_replace("<collections>", implode("\n", $collections), self::$_constructorMethodTemplate));
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo this won't work if there is a namespace in brackets and a class outside of it.
|
||||
* @param string $src
|
||||
*/
|
||||
private function _parseTokensInEntityFile($src)
|
||||
{
|
||||
$tokens = token_get_all($src);
|
||||
$lastSeenNamespace = "";
|
||||
$lastSeenClass = false;
|
||||
|
||||
$inNamespace = false;
|
||||
$inClass = false;
|
||||
for ($i = 0; $i < count($tokens); $i++) {
|
||||
$token = $tokens[$i];
|
||||
if (in_array($token[0], array(T_WHITESPACE, T_COMMENT, T_DOC_COMMENT))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($inNamespace) {
|
||||
if ($token[0] == T_NS_SEPARATOR || $token[0] == T_STRING) {
|
||||
$lastSeenNamespace .= $token[1];
|
||||
} else if (is_string($token) && in_array($token, array(';', '{'))) {
|
||||
$inNamespace = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($inClass) {
|
||||
$inClass = false;
|
||||
$lastSeenClass = $lastSeenNamespace . ($lastSeenNamespace ? '\\' : '') . $token[1];
|
||||
$this->_staticReflection[$lastSeenClass]['properties'] = array();
|
||||
$this->_staticReflection[$lastSeenClass]['methods'] = array();
|
||||
}
|
||||
|
||||
if ($token[0] == T_NAMESPACE) {
|
||||
$lastSeenNamespace = "";
|
||||
$inNamespace = true;
|
||||
} else if ($token[0] == T_CLASS) {
|
||||
$inClass = true;
|
||||
} else if ($token[0] == T_FUNCTION) {
|
||||
if ($tokens[$i+2][0] == T_STRING) {
|
||||
$this->_staticReflection[$lastSeenClass]['methods'][] = $tokens[$i+2][1];
|
||||
} else if ($tokens[$i+2] == "&" && $tokens[$i+3][0] == T_STRING) {
|
||||
$this->_staticReflection[$lastSeenClass]['methods'][] = $tokens[$i+3][1];
|
||||
}
|
||||
} else if (in_array($token[0], array(T_VAR, T_PUBLIC, T_PRIVATE, T_PROTECTED)) && $tokens[$i+2][0] != T_FUNCTION) {
|
||||
$this->_staticReflection[$lastSeenClass]['properties'][] = substr($tokens[$i+2][1], 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function _hasProperty($property, ClassMetadataInfo $metadata)
|
||||
{
|
||||
return ($this->_isNew) ? false : $this->_reflection->hasProperty($property);
|
||||
if ($this->_extendsClass()) {
|
||||
// don't generate property if its already on the base class.
|
||||
$reflClass = new \ReflectionClass($this->_getClassToExtend());
|
||||
if ($reflClass->hasProperty($property)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
isset($this->_staticReflection[$metadata->name]) &&
|
||||
in_array($property, $this->_staticReflection[$metadata->name]['properties'])
|
||||
);
|
||||
}
|
||||
|
||||
private function _hasMethod($method, ClassMetadataInfo $metadata)
|
||||
{
|
||||
return ($this->_isNew) ? false : $this->_reflection->hasMethod($method);
|
||||
if ($this->_extendsClass()) {
|
||||
// don't generate method if its already on the base class.
|
||||
$reflClass = new \ReflectionClass($this->_getClassToExtend());
|
||||
if ($reflClass->hasMethod($method)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
isset($this->_staticReflection[$metadata->name]) &&
|
||||
in_array($method, $this->_staticReflection[$metadata->name]['methods'])
|
||||
);
|
||||
}
|
||||
|
||||
private function _hasNamespace(ClassMetadataInfo $metadata)
|
||||
@@ -405,9 +550,9 @@ public function <methodName>()
|
||||
}
|
||||
|
||||
if ($metadata->isMappedSuperclass) {
|
||||
$lines[] = ' * @MappedSupperClass';
|
||||
$lines[] = ' * @' . $this->_annotationsPrefix . 'MappedSuperClass';
|
||||
} else {
|
||||
$lines[] = ' * @Entity';
|
||||
$lines[] = ' * @' . $this->_annotationsPrefix . 'Entity';
|
||||
}
|
||||
|
||||
if ($metadata->customRepositoryClassName) {
|
||||
@@ -415,7 +560,7 @@ public function <methodName>()
|
||||
}
|
||||
|
||||
if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) {
|
||||
$lines[] = ' * @HasLifecycleCallbacks';
|
||||
$lines[] = ' * @' . $this->_annotationsPrefix . 'HasLifecycleCallbacks';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,17 +576,13 @@ public function <methodName>()
|
||||
$table[] = 'name="' . $metadata->table['name'] . '"';
|
||||
}
|
||||
|
||||
if (isset($metadata->table['schema'])) {
|
||||
$table[] = 'schema="' . $metadata->table['schema'] . '"';
|
||||
}
|
||||
|
||||
return '@Table(' . implode(', ', $table) . ')';
|
||||
return '@' . $this->_annotationsPrefix . 'Table(' . implode(', ', $table) . ')';
|
||||
}
|
||||
|
||||
private function _generateInheritanceAnnotation($metadata)
|
||||
{
|
||||
if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
|
||||
return '@InheritanceType("'.$this->_getInheritanceTypeString($metadata->inheritanceType).'")';
|
||||
return '@' . $this->_annotationsPrefix . 'InheritanceType("'.$this->_getInheritanceTypeString($metadata->inheritanceType).'")';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,7 +594,7 @@ public function <methodName>()
|
||||
. '", type="' . $discrColumn['type']
|
||||
. '", length=' . $discrColumn['length'];
|
||||
|
||||
return '@DiscriminatorColumn(' . $columnDefinition . ')';
|
||||
return '@' . $this->_annotationsPrefix . 'DiscriminatorColumn(' . $columnDefinition . ')';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,7 +607,7 @@ public function <methodName>()
|
||||
$inheritanceClassMap[] .= '"' . $type . '" = "' . $class . '"';
|
||||
}
|
||||
|
||||
return '@DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})';
|
||||
return '@' . $this->_annotationsPrefix . 'DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,7 +616,7 @@ public function <methodName>()
|
||||
$methods = array();
|
||||
|
||||
foreach ($metadata->fieldMappings as $fieldMapping) {
|
||||
if ( ! isset($fieldMapping['id']) || ! $fieldMapping['id']) {
|
||||
if ( ! isset($fieldMapping['id']) || ! $fieldMapping['id'] || $metadata->generatorType == ClassMetadataInfo::GENERATOR_TYPE_NONE) {
|
||||
if ($code = $this->_generateEntityStubMethod($metadata, 'set', $fieldMapping['fieldName'], $fieldMapping['type'])) {
|
||||
$methods[] = $code;
|
||||
}
|
||||
@@ -494,23 +635,7 @@ public function <methodName>()
|
||||
if ($code = $this->_generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
|
||||
$methods[] = $code;
|
||||
}
|
||||
} else if ($associationMapping['type'] == ClassMetadataInfo::ONE_TO_MANY) {
|
||||
if ($associationMapping['isOwningSide']) {
|
||||
if ($code = $this->_generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
|
||||
$methods[] = $code;
|
||||
}
|
||||
if ($code = $this->_generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
|
||||
$methods[] = $code;
|
||||
}
|
||||
} else {
|
||||
if ($code = $this->_generateEntityStubMethod($metadata, 'add', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
|
||||
$methods[] = $code;
|
||||
}
|
||||
if ($code = $this->_generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], 'Doctrine\Common\Collections\Collection')) {
|
||||
$methods[] = $code;
|
||||
}
|
||||
}
|
||||
} else if ($associationMapping['type'] == ClassMetadataInfo::MANY_TO_MANY) {
|
||||
} else if ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) {
|
||||
if ($code = $this->_generateEntityStubMethod($metadata, 'add', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
|
||||
$methods[] = $code;
|
||||
}
|
||||
@@ -536,8 +661,10 @@ public function <methodName>()
|
||||
}
|
||||
}
|
||||
|
||||
return implode('', $methods);
|
||||
return implode("\n\n", $methods);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private function _generateEntityAssociationMappingProperties(ClassMetadataInfo $metadata)
|
||||
@@ -562,7 +689,8 @@ public function <methodName>()
|
||||
$lines = array();
|
||||
|
||||
foreach ($metadata->fieldMappings as $fieldMapping) {
|
||||
if ($this->_hasProperty($fieldMapping['fieldName'], $metadata)) {
|
||||
if ($this->_hasProperty($fieldMapping['fieldName'], $metadata) ||
|
||||
$metadata->isInheritedField($fieldMapping['fieldName'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -576,11 +704,18 @@ public function <methodName>()
|
||||
|
||||
private function _generateEntityStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null)
|
||||
{
|
||||
$methodName = $type . Inflector::classify($fieldName);
|
||||
if ($type == "add") {
|
||||
$addMethod = explode("\\", $typeHint);
|
||||
$addMethod = end($addMethod);
|
||||
$methodName = $type . $addMethod;
|
||||
} else {
|
||||
$methodName = $type . Inflector::classify($fieldName);
|
||||
}
|
||||
|
||||
if ($this->_hasMethod($methodName, $metadata)) {
|
||||
return;
|
||||
}
|
||||
$this->_staticReflection[$metadata->name]['methods'][] = $methodName;
|
||||
|
||||
$var = sprintf('_%sMethodTemplate', $type);
|
||||
$template = self::$$var;
|
||||
@@ -613,9 +748,10 @@ public function <methodName>()
|
||||
if ($this->_hasMethod($methodName, $metadata)) {
|
||||
return;
|
||||
}
|
||||
$this->_staticReflection[$metadata->name]['methods'][] = $methodName;
|
||||
|
||||
$replacements = array(
|
||||
'<name>' => $name,
|
||||
'<name>' => $this->_annotationsPrefix . ucfirst($name),
|
||||
'<methodName>' => $methodName,
|
||||
);
|
||||
|
||||
@@ -649,7 +785,7 @@ public function <methodName>()
|
||||
}
|
||||
|
||||
if (isset($joinColumn['onDelete'])) {
|
||||
$joinColumnAnnot[] = 'onDelete=' . ($joinColumn['onDelete'] ? 'true' : 'false');
|
||||
$joinColumnAnnot[] = 'onDelete="' . ($joinColumn['onDelete'] . '"');
|
||||
}
|
||||
|
||||
if (isset($joinColumn['onUpdate'])) {
|
||||
@@ -660,7 +796,7 @@ public function <methodName>()
|
||||
$joinColumnAnnot[] = 'columnDefinition="' . $joinColumn['columnDefinition'] . '"';
|
||||
}
|
||||
|
||||
return '@JoinColumn(' . implode(', ', $joinColumnAnnot) . ')';
|
||||
return '@' . $this->_annotationsPrefix . 'JoinColumn(' . implode(', ', $joinColumnAnnot) . ')';
|
||||
}
|
||||
|
||||
private function _generateAssociationMappingPropertyDocBlock(array $associationMapping, ClassMetadataInfo $metadata)
|
||||
@@ -671,6 +807,14 @@ public function <methodName>()
|
||||
|
||||
if ($this->_generateAnnotations) {
|
||||
$lines[] = $this->_spaces . ' *';
|
||||
|
||||
if (isset($associationMapping['id']) && $associationMapping['id']) {
|
||||
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'Id';
|
||||
|
||||
if ($generatorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) {
|
||||
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")';
|
||||
}
|
||||
}
|
||||
|
||||
$type = null;
|
||||
switch ($associationMapping['type']) {
|
||||
@@ -710,17 +854,17 @@ public function <methodName>()
|
||||
if ($associationMapping['isCascadeMerge']) $cascades[] = '"merge"';
|
||||
if ($associationMapping['isCascadeRefresh']) $cascades[] = '"refresh"';
|
||||
|
||||
$typeOptions[] = 'cascade={' . implode(',', $cascades) . '}';
|
||||
$typeOptions[] = 'cascade={' . implode(',', $cascades) . '}';
|
||||
}
|
||||
|
||||
if (isset($associationMapping['orphanRemoval']) && $associationMapping['orphanRemoval']) {
|
||||
$typeOptions[] = 'orphanRemoval=' . ($associationMapping['orphanRemoval'] ? 'true' : 'false');
|
||||
}
|
||||
|
||||
$lines[] = $this->_spaces . ' * @' . $type . '(' . implode(', ', $typeOptions) . ')';
|
||||
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . '' . $type . '(' . implode(', ', $typeOptions) . ')';
|
||||
|
||||
if (isset($associationMapping['joinColumns']) && $associationMapping['joinColumns']) {
|
||||
$lines[] = $this->_spaces . ' * @JoinColumns({';
|
||||
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'JoinColumns({';
|
||||
|
||||
$joinColumnsLines = array();
|
||||
|
||||
@@ -742,7 +886,7 @@ public function <methodName>()
|
||||
$joinTable[] = 'schema="' . $associationMapping['joinTable']['schema'] . '"';
|
||||
}
|
||||
|
||||
$lines[] = $this->_spaces . ' * @JoinTable(' . implode(', ', $joinTable) . ',';
|
||||
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'JoinTable(' . implode(', ', $joinTable) . ',';
|
||||
$lines[] = $this->_spaces . ' * joinColumns={';
|
||||
|
||||
foreach ($associationMapping['joinTable']['joinColumns'] as $joinColumn) {
|
||||
@@ -761,10 +905,10 @@ public function <methodName>()
|
||||
}
|
||||
|
||||
if (isset($associationMapping['orderBy'])) {
|
||||
$lines[] = $this->_spaces . ' * @OrderBy({';
|
||||
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'OrderBy({';
|
||||
|
||||
foreach ($associationMapping['orderBy'] as $name => $direction) {
|
||||
$lines[] = $this->_spaces . ' * "' . $name . '"="' . $direction . '",';
|
||||
$lines[] = $this->_spaces . ' * "' . $name . '"="' . $direction . '",';
|
||||
}
|
||||
|
||||
$lines[count($lines) - 1] = substr($lines[count($lines) - 1], 0, strlen($lines[count($lines) - 1]) - 1);
|
||||
@@ -815,31 +959,17 @@ public function <methodName>()
|
||||
$column[] = 'columnDefinition="' . $fieldMapping['columnDefinition'] . '"';
|
||||
}
|
||||
|
||||
if (isset($fieldMapping['options'])) {
|
||||
$options = array();
|
||||
|
||||
foreach ($fieldMapping['options'] as $key => $value) {
|
||||
$value = var_export($value, true);
|
||||
$value = str_replace("'", '"', $value);
|
||||
$options[] = ! is_numeric($key) ? $key . '=' . $value:$value;
|
||||
}
|
||||
|
||||
if ($options) {
|
||||
$column[] = 'options={' . implode(', ', $options) . '}';
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($fieldMapping['unique'])) {
|
||||
$column[] = 'unique=' . var_export($fieldMapping['unique'], true);
|
||||
}
|
||||
|
||||
$lines[] = $this->_spaces . ' * @Column(' . implode(', ', $column) . ')';
|
||||
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'Column(' . implode(', ', $column) . ')';
|
||||
|
||||
if (isset($fieldMapping['id']) && $fieldMapping['id']) {
|
||||
$lines[] = $this->_spaces . ' * @Id';
|
||||
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'Id';
|
||||
|
||||
if ($generatorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) {
|
||||
$lines[] = $this->_spaces.' * @GeneratedValue(strategy="' . $generatorType . '")';
|
||||
$lines[] = $this->_spaces.' * @' . $this->_annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")';
|
||||
}
|
||||
|
||||
if ($metadata->sequenceGeneratorDefinition) {
|
||||
@@ -857,12 +987,12 @@ public function <methodName>()
|
||||
$sequenceGenerator[] = 'initialValue="' . $metadata->sequenceGeneratorDefinition['initialValue'] . '"';
|
||||
}
|
||||
|
||||
$lines[] = $this->_spaces . ' * @SequenceGenerator(' . implode(', ', $sequenceGenerator) . ')';
|
||||
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'SequenceGenerator(' . implode(', ', $sequenceGenerator) . ')';
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($fieldMapping['version']) && $fieldMapping['version']) {
|
||||
$lines[] = $this->_spaces . ' * @Version';
|
||||
$lines[] = $this->_spaces . ' * @' . $this->_annotationsPrefix . 'Version';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -941,4 +1071,4 @@ public function <methodName>()
|
||||
throw new \InvalidArgumentException('Invalid provided IdGeneratorType: ' . $type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,10 +23,11 @@
|
||||
namespace Doctrine\ORM\Tools\Export\Driver;
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||
use Doctrine\ORM\Tools\Export\ExportException;
|
||||
|
||||
/**
|
||||
* Abstract base class which is to be used for the Exporter drivers
|
||||
* which can be found in Doctrine\ORM\Tools\Export\Driver
|
||||
* which can be found in \Doctrine\ORM\Tools\Export\Driver
|
||||
*
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
@@ -39,12 +40,18 @@ abstract class AbstractExporter
|
||||
protected $_metadata = array();
|
||||
protected $_outputDir;
|
||||
protected $_extension;
|
||||
protected $_overwriteExistingFiles = false;
|
||||
|
||||
public function __construct($dir = null)
|
||||
{
|
||||
$this->_outputDir = $dir;
|
||||
}
|
||||
|
||||
public function setOverwriteExistingFiles($overwrite)
|
||||
{
|
||||
$this->_overwriteExistingFiles = $overwrite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a single ClassMetadata instance to the exported format
|
||||
* and returns it
|
||||
@@ -110,6 +117,9 @@ abstract class AbstractExporter
|
||||
if ( ! is_dir($dir)) {
|
||||
mkdir($dir, 0777, true);
|
||||
}
|
||||
if (file_exists($path) && !$this->_overwriteExistingFiles) {
|
||||
throw ExportException::attemptOverwriteExistingFile($path);
|
||||
}
|
||||
file_put_contents($path, $output);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,22 +120,30 @@ class PhpExporter extends AbstractExporter
|
||||
|
||||
$associationMappingArray = array_merge($associationMappingArray, $oneToOneMappingArray);
|
||||
} else if ($associationMapping['type'] == ClassMetadataInfo::ONE_TO_MANY) {
|
||||
$method = 'mapOneToMany';
|
||||
$oneToManyMappingArray = array(
|
||||
'mappedBy' => $associationMapping['mappedBy'],
|
||||
'orphanRemoval' => $associationMapping['orphanRemoval'],
|
||||
'orderBy' => $associationMapping['orderBy']
|
||||
$method = 'mapOneToMany';
|
||||
$potentialAssociationMappingIndexes = array(
|
||||
'mappedBy',
|
||||
'orphanRemoval',
|
||||
'orderBy',
|
||||
);
|
||||
|
||||
foreach ($potentialAssociationMappingIndexes as $index) {
|
||||
if (isset($associationMapping[$index])) {
|
||||
$oneToManyMappingArray[$index] = $associationMapping[$index];
|
||||
}
|
||||
}
|
||||
$associationMappingArray = array_merge($associationMappingArray, $oneToManyMappingArray);
|
||||
} else if ($associationMapping['type'] == ClassMetadataInfo::MANY_TO_MANY) {
|
||||
$method = 'mapManyToMany';
|
||||
$manyToManyMappingArray = array(
|
||||
'mappedBy' => $associationMapping['mappedBy'],
|
||||
'joinTable' => $associationMapping['joinTable'],
|
||||
'orderBy' => $associationMapping['orderBy']
|
||||
$method = 'mapManyToMany';
|
||||
$potentialAssociationMappingIndexes = array(
|
||||
'mappedBy',
|
||||
'joinTable',
|
||||
'orderBy',
|
||||
);
|
||||
|
||||
foreach ($potentialAssociationMappingIndexes as $index) {
|
||||
if (isset($associationMapping[$index])) {
|
||||
$manyToManyMappingArray[$index] = $associationMapping[$index];
|
||||
}
|
||||
}
|
||||
$associationMappingArray = array_merge($associationMappingArray, $manyToManyMappingArray);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ class YamlExporter extends AbstractExporter
|
||||
*
|
||||
* TODO: Should this code be pulled out in to a toArray() method in ClassMetadata
|
||||
*
|
||||
* @param ClassMetadataInfo $metadata
|
||||
* @param ClassMetadataInfo $metadata
|
||||
* @return mixed $exported
|
||||
*/
|
||||
public function exportClassMetadata(ClassMetadataInfo $metadata)
|
||||
@@ -84,9 +84,9 @@ class YamlExporter extends AbstractExporter
|
||||
if (isset($metadata->table['uniqueConstraints'])) {
|
||||
$array['uniqueConstraints'] = $metadata->table['uniqueConstraints'];
|
||||
}
|
||||
|
||||
|
||||
$fieldMappings = $metadata->fieldMappings;
|
||||
|
||||
|
||||
$ids = array();
|
||||
foreach ($fieldMappings as $name => $fieldMapping) {
|
||||
$fieldMapping['column'] = $fieldMapping['columnName'];
|
||||
@@ -94,7 +94,7 @@ class YamlExporter extends AbstractExporter
|
||||
$fieldMapping['columnName'],
|
||||
$fieldMapping['fieldName']
|
||||
);
|
||||
|
||||
|
||||
if ($fieldMapping['column'] == $name) {
|
||||
unset($fieldMapping['column']);
|
||||
}
|
||||
@@ -111,7 +111,7 @@ class YamlExporter extends AbstractExporter
|
||||
if ($idGeneratorType = $this->_getIdGeneratorTypeString($metadata->generatorType)) {
|
||||
$ids[$metadata->getSingleIdentifierFieldName()]['generator']['strategy'] = $this->_getIdGeneratorTypeString($metadata->generatorType);
|
||||
}
|
||||
|
||||
|
||||
if ($ids) {
|
||||
$array['fields'] = $ids;
|
||||
}
|
||||
@@ -145,7 +145,7 @@ class YamlExporter extends AbstractExporter
|
||||
'targetEntity' => $associationMapping['targetEntity'],
|
||||
'cascade' => $cascade,
|
||||
);
|
||||
|
||||
|
||||
if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
|
||||
$joinColumns = $associationMapping['joinColumns'];
|
||||
$newJoinColumns = array();
|
||||
@@ -164,15 +164,21 @@ class YamlExporter extends AbstractExporter
|
||||
'joinColumns' => $newJoinColumns,
|
||||
'orphanRemoval' => $associationMapping['orphanRemoval'],
|
||||
);
|
||||
|
||||
|
||||
$associationMappingArray = array_merge($associationMappingArray, $oneToOneMappingArray);
|
||||
$array['oneToOne'][$name] = $associationMappingArray;
|
||||
|
||||
if ($associationMapping['type'] & ClassMetadataInfo::ONE_TO_ONE) {
|
||||
$array['oneToOne'][$name] = $associationMappingArray;
|
||||
} else {
|
||||
$array['manyToOne'][$name] = $associationMappingArray;
|
||||
}
|
||||
|
||||
} else if ($associationMapping['type'] == ClassMetadataInfo::ONE_TO_MANY) {
|
||||
$oneToManyMappingArray = array(
|
||||
'mappedBy' => $associationMapping['mappedBy'],
|
||||
'inversedBy' => $associationMapping['inversedBy'],
|
||||
'orphanRemoval' => $associationMapping['orphanRemoval'],
|
||||
'orderBy' => $associationMapping['orderBy']
|
||||
'orderBy' => isset($associationMapping['orderBy']) ? $associationMapping['orderBy'] : null
|
||||
);
|
||||
|
||||
$associationMappingArray = array_merge($associationMappingArray, $oneToManyMappingArray);
|
||||
@@ -181,10 +187,10 @@ class YamlExporter extends AbstractExporter
|
||||
$manyToManyMappingArray = array(
|
||||
'mappedBy' => $associationMapping['mappedBy'],
|
||||
'inversedBy' => $associationMapping['inversedBy'],
|
||||
'joinTable' => $associationMapping['joinTable'],
|
||||
'orderBy' => isset($associationMapping['orderBy']) ? $associationMapping['orderBy'] : null
|
||||
'joinTable' => isset($associationMapping['joinTable']) ? $associationMapping['joinTable'] : null,
|
||||
'orderBy' => isset($associationMapping['orderBy']) ? $associationMapping['orderBy'] : null
|
||||
);
|
||||
|
||||
|
||||
$associationMappingArray = array_merge($associationMappingArray, $manyToManyMappingArray);
|
||||
$array['manyToMany'][$name] = $associationMappingArray;
|
||||
}
|
||||
|
||||
@@ -15,4 +15,9 @@ class ExportException extends ORMException
|
||||
{
|
||||
return new self("The mapping driver '$type' does not exist");
|
||||
}
|
||||
|
||||
public static function attemptOverwriteExistingFile($file)
|
||||
{
|
||||
return new self("Attempting to overwrite an existing file '".$file."'.");
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,7 @@ class SchemaTool
|
||||
* Initializes a new SchemaTool instance that uses the connection of the
|
||||
* provided EntityManager.
|
||||
*
|
||||
* @param Doctrine\ORM\EntityManager $em
|
||||
* @param \Doctrine\ORM\EntityManager $em
|
||||
*/
|
||||
public function __construct(EntityManager $em)
|
||||
{
|
||||
@@ -174,18 +174,18 @@ class SchemaTool
|
||||
$discrColumnDef = $this->_getDiscriminatorColumnDefinition($class, $table);
|
||||
} else {
|
||||
// Add an ID FK column to child tables
|
||||
/* @var Doctrine\ORM\Mapping\ClassMetadata $class */
|
||||
/* @var \Doctrine\ORM\Mapping\ClassMetadata $class */
|
||||
$idMapping = $class->fieldMappings[$class->identifier[0]];
|
||||
$this->_gatherColumn($class, $idMapping, $table);
|
||||
$columnName = $class->getQuotedColumnName($class->identifier[0], $this->_platform);
|
||||
// TODO: This seems rather hackish, can we optimize it?
|
||||
$table->getColumn($class->identifier[0])->setAutoincrement(false);
|
||||
$table->getColumn($columnName)->setAutoincrement(false);
|
||||
|
||||
$pkColumns[] = $columnName;
|
||||
|
||||
// Add a FK constraint on the ID column
|
||||
$table->addUnnamedForeignKeyConstraint(
|
||||
$this->_em->getClassMetadata($class->rootEntityName)->getTableName(),
|
||||
$this->_em->getClassMetadata($class->rootEntityName)->getQuotedTableName($this->_platform),
|
||||
array($columnName), array($columnName), array('onDelete' => 'CASCADE')
|
||||
);
|
||||
}
|
||||
@@ -199,15 +199,31 @@ class SchemaTool
|
||||
$this->_gatherRelationsSql($class, $table, $schema);
|
||||
}
|
||||
|
||||
$pkColumns = array();
|
||||
foreach ($class->identifier AS $identifierField) {
|
||||
if (isset($class->fieldMappings[$identifierField])) {
|
||||
$pkColumns[] = $class->getQuotedColumnName($identifierField, $this->_platform);
|
||||
} else if (isset($class->associationMappings[$identifierField])) {
|
||||
/* @var $assoc \Doctrine\ORM\Mapping\OneToOne */
|
||||
$assoc = $class->associationMappings[$identifierField];
|
||||
foreach ($assoc['joinColumns'] AS $joinColumn) {
|
||||
$pkColumns[] = $joinColumn['name'];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$table->hasIndex('primary')) {
|
||||
$table->setPrimaryKey($pkColumns);
|
||||
}
|
||||
|
||||
if (isset($class->table['indexes'])) {
|
||||
foreach ($class->table['indexes'] AS $indexName => $indexData) {
|
||||
$table->addIndex($indexData['columns'], $indexName);
|
||||
$table->addIndex($indexData['columns'], is_numeric($indexName) ? null : $indexName);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($class->table['uniqueConstraints'])) {
|
||||
foreach ($class->table['uniqueConstraints'] AS $indexName => $indexData) {
|
||||
$table->addUniqueIndex($indexData['columns'], $indexName);
|
||||
$table->addUniqueIndex($indexData['columns'], is_numeric($indexName) ? null : $indexName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,16 +291,21 @@ class SchemaTool
|
||||
$pkColumns = array();
|
||||
|
||||
foreach ($class->fieldMappings as $fieldName => $mapping) {
|
||||
if ($class->isInheritanceTypeSingleTable() && isset($mapping['inherited'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$column = $this->_gatherColumn($class, $mapping, $table);
|
||||
|
||||
if ($class->isIdentifier($mapping['fieldName'])) {
|
||||
$pkColumns[] = $class->getQuotedColumnName($mapping['fieldName'], $this->_platform);
|
||||
}
|
||||
}
|
||||
|
||||
// For now, this is a hack required for single table inheritence, since this method is called
|
||||
// twice by single table inheritence relations
|
||||
if(!$table->hasIndex('primary')) {
|
||||
$table->setPrimaryKey($pkColumns);
|
||||
//$table->setPrimaryKey($pkColumns);
|
||||
}
|
||||
|
||||
return $columns;
|
||||
@@ -405,13 +426,47 @@ class SchemaTool
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class metadata that is responsible for the definition of the referenced column name.
|
||||
*
|
||||
* Previously this was a simple task, but with DDC-117 this problem is actually recursive. If its
|
||||
* not a simple field, go through all identifier field names that are associations recursivly and
|
||||
* find that referenced column name.
|
||||
*
|
||||
* TODO: Is there any way to make this code more pleasing?
|
||||
*
|
||||
* @param ClassMetadata $class
|
||||
* @param string $referencedColumnName
|
||||
* @return array(ClassMetadata, referencedFieldName)
|
||||
*/
|
||||
private function getDefiningClass($class, $referencedColumnName)
|
||||
{
|
||||
$referencedFieldName = $class->getFieldName($referencedColumnName);
|
||||
|
||||
if ($class->hasField($referencedFieldName)) {
|
||||
return array($class, $referencedFieldName);
|
||||
} else if (in_array($referencedColumnName, $class->getIdentifierColumnNames())) {
|
||||
// it seems to be an entity as foreign key
|
||||
foreach ($class->getIdentifierFieldNames() AS $fieldName) {
|
||||
if ($class->hasAssociation($fieldName) && $class->getSingleAssociationJoinColumnName($fieldName) == $referencedColumnName) {
|
||||
return $this->getDefiningClass(
|
||||
$this->_em->getClassMetadata($class->associationMappings[$fieldName]['targetEntity']),
|
||||
$class->getSingleAssociationReferencedJoinColumnName($fieldName)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather columns and fk constraints that are required for one part of relationship.
|
||||
*
|
||||
* @param array $joinColumns
|
||||
* @param \Doctrine\DBAL\Schema\Table $theJoinTable
|
||||
* @param ClassMetadata $class
|
||||
* @param \Doctrine\ORM\Mapping\AssociationMapping $mapping
|
||||
* @param array $mapping
|
||||
* @param array $primaryKeyColumns
|
||||
* @param array $uniqueConstraints
|
||||
*/
|
||||
@@ -420,12 +475,13 @@ class SchemaTool
|
||||
$localColumns = array();
|
||||
$foreignColumns = array();
|
||||
$fkOptions = array();
|
||||
$foreignTableName = $class->getQuotedTableName($this->_platform);
|
||||
|
||||
foreach ($joinColumns as $joinColumn) {
|
||||
$columnName = $joinColumn['name'];
|
||||
$referencedFieldName = $class->getFieldName($joinColumn['referencedColumnName']);
|
||||
list($definingClass, $referencedFieldName) = $this->getDefiningClass($class, $joinColumn['referencedColumnName']);
|
||||
|
||||
if ( ! $class->hasField($referencedFieldName)) {
|
||||
if (!$definingClass) {
|
||||
throw new \Doctrine\ORM\ORMException(
|
||||
"Column name `".$joinColumn['referencedColumnName']."` referenced for relation from ".
|
||||
$mapping['sourceEntity'] . " towards ". $mapping['targetEntity'] . " does not exist."
|
||||
@@ -441,7 +497,7 @@ class SchemaTool
|
||||
// It might exist already if the foreign key is mapped into a regular
|
||||
// property as well.
|
||||
|
||||
$fieldMapping = $class->getFieldMapping($referencedFieldName);
|
||||
$fieldMapping = $definingClass->getFieldMapping($referencedFieldName);
|
||||
|
||||
$columnDef = null;
|
||||
if (isset($joinColumn['columnDefinition'])) {
|
||||
@@ -453,16 +509,14 @@ class SchemaTool
|
||||
if (isset($joinColumn['nullable'])) {
|
||||
$columnOptions['notnull'] = !$joinColumn['nullable'];
|
||||
}
|
||||
if ($fieldMapping['type'] == "string") {
|
||||
if ($fieldMapping['type'] == "string" && isset($fieldMapping['length'])) {
|
||||
$columnOptions['length'] = $fieldMapping['length'];
|
||||
} else if ($fieldMapping['type'] == "decimal") {
|
||||
$columnOptions['scale'] = $fieldMapping['scale'];
|
||||
$columnOptions['precision'] = $fieldMapping['precision'];
|
||||
}
|
||||
|
||||
$theJoinTable->addColumn(
|
||||
$columnName, $class->getTypeOfColumn($joinColumn['referencedColumnName']), $columnOptions
|
||||
);
|
||||
$theJoinTable->addColumn($columnName, $fieldMapping['type'], $columnOptions);
|
||||
}
|
||||
|
||||
if (isset($joinColumn['unique']) && $joinColumn['unique'] == true) {
|
||||
@@ -479,7 +533,7 @@ class SchemaTool
|
||||
}
|
||||
|
||||
$theJoinTable->addUnnamedForeignKeyConstraint(
|
||||
$class->getTableName(), $localColumns, $foreignColumns, $fkOptions
|
||||
$foreignTableName, $localColumns, $foreignColumns, $fkOptions
|
||||
);
|
||||
}
|
||||
|
||||
@@ -498,7 +552,11 @@ class SchemaTool
|
||||
$conn = $this->_em->getConnection();
|
||||
|
||||
foreach ($dropSchemaSql as $sql) {
|
||||
$conn->executeQuery($sql);
|
||||
try {
|
||||
$conn->executeQuery($sql);
|
||||
} catch(\Exception $e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,65 +592,62 @@ class SchemaTool
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Get SQL to drop the tables defined by the passed classes.
|
||||
*
|
||||
* @param array $classes
|
||||
* @return array
|
||||
*/
|
||||
public function getDropSchemaSQL(array $classes)
|
||||
{
|
||||
$visitor = new \Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector($this->_platform);
|
||||
$schema = $this->getSchemaFromMetadata($classes);
|
||||
|
||||
$sm = $this->_em->getConnection()->getSchemaManager();
|
||||
$fullSchema = $sm->createSchema();
|
||||
foreach ($fullSchema->getTables() AS $table) {
|
||||
if (!$schema->hasTable($table->getName())) {
|
||||
foreach ($table->getForeignKeys() AS $foreignKey) {
|
||||
/* @var $foreignKey \Doctrine\DBAL\Schema\ForeignKeyConstraint */
|
||||
if ($schema->hasTable($foreignKey->getForeignTableName())) {
|
||||
$visitor->acceptForeignKey($table, $foreignKey);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$visitor->acceptTable($table);
|
||||
foreach ($table->getForeignKeys() AS $foreignKey) {
|
||||
$visitor->acceptForeignKey($table, $foreignKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$sql = array();
|
||||
$orderedTables = array();
|
||||
|
||||
foreach ($classes AS $class) {
|
||||
if ($class->isIdGeneratorSequence() && !$class->isMappedSuperclass && $class->name == $class->rootEntityName && $this->_platform->supportsSequences()) {
|
||||
$sql[] = $this->_platform->getDropSequenceSQL($class->sequenceGeneratorDefinition['sequenceName']);
|
||||
if ($this->_platform->supportsSequences()) {
|
||||
foreach ($schema->getSequences() AS $sequence) {
|
||||
$visitor->acceptSequence($sequence);
|
||||
}
|
||||
foreach ($schema->getTables() AS $table) {
|
||||
/* @var $sequence Table */
|
||||
if ($table->hasPrimaryKey()) {
|
||||
$columns = $table->getPrimaryKey()->getColumns();
|
||||
if (count($columns) == 1) {
|
||||
$checkSequence = $table->getName() . "_" . $columns[0] . "_seq";
|
||||
if ($fullSchema->hasSequence($checkSequence)) {
|
||||
$visitor->acceptSequence($fullSchema->getSequence($checkSequence));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$commitOrder = $this->_getCommitOrder($classes);
|
||||
$associationTables = $this->_getAssociationTables($commitOrder);
|
||||
|
||||
// Drop association tables first
|
||||
foreach ($associationTables as $associationTable) {
|
||||
if (!in_array($associationTable, $orderedTables)) {
|
||||
$orderedTables[] = $associationTable;
|
||||
}
|
||||
}
|
||||
|
||||
// Drop tables in reverse commit order
|
||||
for ($i = count($commitOrder) - 1; $i >= 0; --$i) {
|
||||
$class = $commitOrder[$i];
|
||||
|
||||
if (($class->isInheritanceTypeSingleTable() && $class->name != $class->rootEntityName)
|
||||
|| $class->isMappedSuperclass) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!in_array($class->getTableName(), $orderedTables)) {
|
||||
$orderedTables[] = $class->getTableName();
|
||||
}
|
||||
}
|
||||
|
||||
$dropTablesSql = array();
|
||||
foreach ($orderedTables AS $tableName) {
|
||||
/* @var $sm \Doctrine\DBAL\Schema\AbstractSchemaManager */
|
||||
$foreignKeys = $sm->listTableForeignKeys($tableName);
|
||||
foreach ($foreignKeys AS $foreignKey) {
|
||||
$sql[] = $this->_platform->getDropForeignKeySQL($foreignKey, $tableName);
|
||||
}
|
||||
$dropTablesSql[] = $this->_platform->getDropTableSQL($tableName);
|
||||
}
|
||||
|
||||
return array_merge($sql, $dropTablesSql);
|
||||
return $visitor->getQueries();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the database schema of the given classes by comparing the ClassMetadata
|
||||
* ins$tableNametances to the current database schema that is inspected.
|
||||
* instances to the current database schema that is inspected. If $saveMode is set
|
||||
* to true the command is executed in the Database, else SQL is returned.
|
||||
*
|
||||
* @param array $classes
|
||||
* @param boolean $saveMode
|
||||
* @return void
|
||||
*/
|
||||
public function updateSchema(array $classes, $saveMode=false)
|
||||
@@ -608,8 +663,11 @@ class SchemaTool
|
||||
/**
|
||||
* Gets the sequence of SQL statements that need to be performed in order
|
||||
* to bring the given class mappings in-synch with the relational schema.
|
||||
* If $saveMode is set to true the command is executed in the Database,
|
||||
* else SQL is returned.
|
||||
*
|
||||
* @param array $classes The classes to consider.
|
||||
* @param boolean $saveMode True for writing to DB, false for SQL string
|
||||
* @return array The sequence of SQL statements.
|
||||
*/
|
||||
public function getUpdateSchemaSql(array $classes, $saveMode=false)
|
||||
@@ -628,44 +686,4 @@ class SchemaTool
|
||||
return $schemaDiff->toSql($this->_platform);
|
||||
}
|
||||
}
|
||||
|
||||
private function _getCommitOrder(array $classes)
|
||||
{
|
||||
$calc = new CommitOrderCalculator;
|
||||
|
||||
// Calculate dependencies
|
||||
foreach ($classes as $class) {
|
||||
$calc->addClass($class);
|
||||
|
||||
foreach ($class->associationMappings as $assoc) {
|
||||
if ($assoc['isOwningSide']) {
|
||||
$targetClass = $this->_em->getClassMetadata($assoc['targetEntity']);
|
||||
|
||||
if ( ! $calc->hasClass($targetClass->name)) {
|
||||
$calc->addClass($targetClass);
|
||||
}
|
||||
|
||||
// add dependency ($targetClass before $class)
|
||||
$calc->addDependency($targetClass, $class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $calc->getCommitOrder();
|
||||
}
|
||||
|
||||
private function _getAssociationTables(array $classes)
|
||||
{
|
||||
$associationTables = array();
|
||||
|
||||
foreach ($classes as $class) {
|
||||
foreach ($class->associationMappings as $assoc) {
|
||||
if ($assoc['isOwningSide'] && $assoc['type'] == ClassMetadata::MANY_TO_MANY) {
|
||||
$associationTables[] = $assoc['joinTable']['name'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $associationTables;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,6 +214,6 @@ class SchemaValidator
|
||||
$schemaTool = new SchemaTool($this->em);
|
||||
|
||||
$allMetadata = $this->em->getMetadataFactory()->getAllMetadata();
|
||||
return (count($schemaTool->getUpdateSchemaSql($allMetadata, false)) == 0);
|
||||
return (count($schemaTool->getUpdateSchemaSql($allMetadata, true)) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
194
lib/Doctrine/ORM/Tools/Setup.php
Normal file
194
lib/Doctrine/ORM/Tools/Setup.php
Normal file
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Tools;
|
||||
|
||||
use Doctrine\Common\ClassLoader;
|
||||
use Doctrine\Common\Cache\Cache;
|
||||
use Doctrine\Common\Cache\ArrayCache;
|
||||
use Doctrine\ORM\Configuration;
|
||||
use Doctrine\ORM\Mapping\Driver\XmlDriver;
|
||||
use Doctrine\ORM\Mapping\Driver\YamlDriver;
|
||||
|
||||
/**
|
||||
* Convenience class for setting up Doctrine from different installations and configurations.
|
||||
*
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class Setup
|
||||
{
|
||||
/**
|
||||
* Use this method to register all autoloaders for a setup where Doctrine is checked out from
|
||||
* its github repository at {@link http://github.com/doctrine/doctrine2}
|
||||
*
|
||||
* @param string $gitCheckoutRootPath
|
||||
* @return void
|
||||
*/
|
||||
static public function registerAutoloadGit($gitCheckoutRootPath)
|
||||
{
|
||||
if (!class_exists('Doctrine\Common\ClassLoader', false)) {
|
||||
require_once $gitCheckoutRootPath . "/lib/vendor/doctrine-common/lib/Doctrine/Common/ClassLoader.php";
|
||||
}
|
||||
|
||||
$loader = new ClassLoader("Doctrine\Common", $gitCheckoutRootPath . "/lib/vendor/doctrine-common/lib");
|
||||
$loader->register();
|
||||
|
||||
$loader = new ClassLoader("Doctrine\DBAL", $gitCheckoutRootPath . "/lib/vendor/doctrine-dbal/lib");
|
||||
$loader->register();
|
||||
|
||||
$loader = new ClassLoader("Doctrine\ORM", $gitCheckoutRootPath . "/lib");
|
||||
$loader->register();
|
||||
|
||||
$loader = new ClassLoader("Symfony\Component", $gitCheckoutRootPath . "/lib/vendor");
|
||||
$loader->register();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method to register all autoloaders for a setup where Doctrine is installed
|
||||
* though {@link http://pear.doctrine-project.org}.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
static public function registerAutoloadPEAR()
|
||||
{
|
||||
if (!class_exists('Doctrine\Common\ClassLoader', false)) {
|
||||
require_once "Doctrine/Common/ClassLoader.php";
|
||||
}
|
||||
|
||||
$loader = new ClassLoader("Doctrine");
|
||||
$loader->register();
|
||||
|
||||
$parts = explode(PATH_SEPARATOR, get_include_path());
|
||||
|
||||
foreach ($parts AS $includePath) {
|
||||
if ($includePath != "." && file_exists($includePath . "/Doctrine")) {
|
||||
$loader = new ClassLoader("Symfony\Component", $includePath . "/Doctrine");
|
||||
$loader->register();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method to register all autoloads for a downloaded Doctrine library.
|
||||
* Pick the directory the library was uncompressed into.
|
||||
*
|
||||
* @param string $directory
|
||||
*/
|
||||
static public function registerAutoloadDirectory($directory)
|
||||
{
|
||||
if (!class_exists('Doctrine\Common\ClassLoader', false)) {
|
||||
require_once $directory . "/Doctrine/Common/ClassLoader.php";
|
||||
}
|
||||
|
||||
$loader = new ClassLoader("Doctrine", $directory);
|
||||
$loader->register();
|
||||
|
||||
$loader = new ClassLoader("Symfony\Component", $directory . "/Doctrine");
|
||||
$loader->register();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a configuration with an annotation metadata driver.
|
||||
*
|
||||
* @param array $paths
|
||||
* @param boolean $isDevMode
|
||||
* @param string $proxyDir
|
||||
* @param Cache $cache
|
||||
* @return Configuration
|
||||
*/
|
||||
static public function createAnnotationMetadataConfiguration(array $paths, $isDevMode = false, $proxyDir = null, Cache $cache = null)
|
||||
{
|
||||
$config = self::createConfiguration($isDevMode, $proxyDir, $cache);
|
||||
$config->setMetadataDriverImpl($config->newDefaultAnnotationDriver($paths));
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a configuration with an annotation metadata driver.
|
||||
*
|
||||
* @param array $paths
|
||||
* @param boolean $isDevMode
|
||||
* @param string $proxyDir
|
||||
* @param Cache $cache
|
||||
* @return Configuration
|
||||
*/
|
||||
static public function createXMLMetadataConfiguration(array $paths, $isDevMode = false, $proxyDir = null, Cache $cache = null)
|
||||
{
|
||||
$config = self::createConfiguration($isDevMode, $proxyDir, $cache);
|
||||
$config->setMetadataDriverImpl(new XmlDriver($paths));
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a configuration with an annotation metadata driver.
|
||||
*
|
||||
* @param array $paths
|
||||
* @param boolean $isDevMode
|
||||
* @param string $proxyDir
|
||||
* @param Cache $cache
|
||||
* @return Configuration
|
||||
*/
|
||||
static public function createYAMLMetadataConfiguration(array $paths, $isDevMode = false, $proxyDir = null, Cache $cache = null)
|
||||
{
|
||||
$config = self::createConfiguration($isDevMode, $proxyDir, $cache);
|
||||
$config->setMetadataDriverImpl(new YamlDriver($paths));
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a configuration without a metadata driver.
|
||||
*
|
||||
* @param bool $isDevMode
|
||||
* @param string $proxyDir
|
||||
* @param Cache $cache
|
||||
* @return Configuration
|
||||
*/
|
||||
static public function createConfiguration($isDevMode = false, $proxyDir = null, Cache $cache = null)
|
||||
{
|
||||
$proxyDir = $proxyDir ?: sys_get_temp_dir();
|
||||
if ($isDevMode === false && $cache === null) {
|
||||
if (extension_loaded('apc')) {
|
||||
$cache = new \Doctrine\Common\Cache\ApcCache;
|
||||
} else if (extension_loaded('xcache')) {
|
||||
$cache = new \Doctrine\Common\Cache\XcacheCache;
|
||||
} else if (extension_loaded('memcache')) {
|
||||
$memcache = new \Memcache();
|
||||
$memcache->connect('127.0.0.1');
|
||||
$cache = new \Doctrine\Common\Cache\MemcacheCache();
|
||||
$cache->setMemcache($memcache);
|
||||
} else {
|
||||
$cache = new ArrayCache;
|
||||
}
|
||||
} else if ($cache === null) {
|
||||
$cache = new ArrayCache;
|
||||
}
|
||||
$cache->setNamespace("dc2_" . md5($proxyDir) . "_"); // to avoid collisions
|
||||
|
||||
$config = new Configuration();
|
||||
$config->setMetadataCacheImpl($cache);
|
||||
$config->setQueryCacheImpl($cache);
|
||||
$config->setResultCacheImpl($cache);
|
||||
$config->setProxyDir( $proxyDir );
|
||||
$config->setProxyNamespace('DoctrineProxies');
|
||||
$config->setAutoGenerateProxyClasses($isDevMode);
|
||||
|
||||
return $config;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -36,7 +36,7 @@ class Version
|
||||
/**
|
||||
* Current Doctrine Version
|
||||
*/
|
||||
const VERSION = '2.0.0-DEV';
|
||||
const VERSION = '2.1.7';
|
||||
|
||||
/**
|
||||
* Compares a Doctrine version with the current one.
|
||||
|
||||
1
lib/vendor/Symfony/Component/Console
vendored
Submodule
1
lib/vendor/Symfony/Component/Console
vendored
Submodule
Submodule lib/vendor/Symfony/Component/Console added at 3762cec59a
743
lib/vendor/Symfony/Component/Console/Application.php
vendored
743
lib/vendor/Symfony/Component/Console/Application.php
vendored
@@ -1,743 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Console;
|
||||
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Input\InputDefinition;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Output\Output;
|
||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Command\HelpCommand;
|
||||
use Symfony\Component\Console\Command\ListCommand;
|
||||
use Symfony\Component\Console\Helper\HelperSet;
|
||||
use Symfony\Component\Console\Helper\FormatterHelper;
|
||||
use Symfony\Component\Console\Helper\DialogHelper;
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony framework.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* An Application is the container for a collection of commands.
|
||||
*
|
||||
* It is the main entry point of a Console application.
|
||||
*
|
||||
* This class is optimized for a standard CLI environment.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* $app = new Application('myapp', '1.0 (stable)');
|
||||
* $app->add(new SimpleCommand());
|
||||
* $app->run();
|
||||
*
|
||||
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*/
|
||||
class Application
|
||||
{
|
||||
protected $commands;
|
||||
protected $aliases;
|
||||
protected $wantHelps = false;
|
||||
protected $runningCommand;
|
||||
protected $name;
|
||||
protected $version;
|
||||
protected $catchExceptions;
|
||||
protected $autoExit;
|
||||
protected $definition;
|
||||
protected $helperSet;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $name The name of the application
|
||||
* @param string $version The version of the application
|
||||
*/
|
||||
public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->version = $version;
|
||||
$this->catchExceptions = true;
|
||||
$this->autoExit = true;
|
||||
$this->commands = array();
|
||||
$this->aliases = array();
|
||||
$this->helperSet = new HelperSet(array(
|
||||
new FormatterHelper(),
|
||||
new DialogHelper(),
|
||||
));
|
||||
|
||||
$this->add(new HelpCommand());
|
||||
$this->add(new ListCommand());
|
||||
|
||||
$this->definition = new InputDefinition(array(
|
||||
new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
|
||||
|
||||
new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message.'),
|
||||
new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message.'),
|
||||
new InputOption('--verbose', '-v', InputOption::VALUE_NONE, 'Increase verbosity of messages.'),
|
||||
new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this program version.'),
|
||||
new InputOption('--ansi', '-a', InputOption::VALUE_NONE, 'Force ANSI output.'),
|
||||
new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question.'),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the current application.
|
||||
*
|
||||
* @param InputInterface $input An Input instance
|
||||
* @param OutputInterface $output An Output instance
|
||||
*
|
||||
* @return integer 0 if everything went fine, or an error code
|
||||
*
|
||||
* @throws \Exception When doRun returns Exception
|
||||
*/
|
||||
public function run(InputInterface $input = null, OutputInterface $output = null)
|
||||
{
|
||||
if (null === $input) {
|
||||
$input = new ArgvInput();
|
||||
}
|
||||
|
||||
if (null === $output) {
|
||||
$output = new ConsoleOutput();
|
||||
}
|
||||
|
||||
try {
|
||||
$statusCode = $this->doRun($input, $output);
|
||||
} catch (\Exception $e) {
|
||||
if (!$this->catchExceptions) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->renderException($e, $output);
|
||||
$statusCode = $e->getCode();
|
||||
|
||||
$statusCode = is_numeric($statusCode) && $statusCode ? $statusCode : 1;
|
||||
}
|
||||
|
||||
if ($this->autoExit) {
|
||||
if ($statusCode > 255) {
|
||||
$statusCode = 255;
|
||||
}
|
||||
// @codeCoverageIgnoreStart
|
||||
exit($statusCode);
|
||||
// @codeCoverageIgnoreEnd
|
||||
} else {
|
||||
return $statusCode;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the current application.
|
||||
*
|
||||
* @param InputInterface $input An Input instance
|
||||
* @param OutputInterface $output An Output instance
|
||||
*
|
||||
* @return integer 0 if everything went fine, or an error code
|
||||
*/
|
||||
public function doRun(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$name = $this->getCommandName($input);
|
||||
|
||||
if (true === $input->hasParameterOption(array('--ansi', '-a'))) {
|
||||
$output->setDecorated(true);
|
||||
}
|
||||
|
||||
if (true === $input->hasParameterOption(array('--help', '-h'))) {
|
||||
if (!$name) {
|
||||
$name = 'help';
|
||||
$input = new ArrayInput(array('command' => 'help'));
|
||||
} else {
|
||||
$this->wantHelps = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (true === $input->hasParameterOption(array('--no-interaction', '-n'))) {
|
||||
$input->setInteractive(false);
|
||||
}
|
||||
|
||||
if (true === $input->hasParameterOption(array('--quiet', '-q'))) {
|
||||
$output->setVerbosity(Output::VERBOSITY_QUIET);
|
||||
} elseif (true === $input->hasParameterOption(array('--verbose', '-v'))) {
|
||||
$output->setVerbosity(Output::VERBOSITY_VERBOSE);
|
||||
}
|
||||
|
||||
if (true === $input->hasParameterOption(array('--version', '-V'))) {
|
||||
$output->writeln($this->getLongVersion());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!$name) {
|
||||
$name = 'list';
|
||||
$input = new ArrayInput(array('command' => 'list'));
|
||||
}
|
||||
|
||||
// the command name MUST be the first element of the input
|
||||
$command = $this->find($name);
|
||||
|
||||
$this->runningCommand = $command;
|
||||
$statusCode = $command->run($input, $output);
|
||||
$this->runningCommand = null;
|
||||
|
||||
return is_numeric($statusCode) ? $statusCode : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a helper set to be used with the command.
|
||||
*
|
||||
* @param HelperSet $helperSet The helper set
|
||||
*/
|
||||
public function setHelperSet(HelperSet $helperSet)
|
||||
{
|
||||
$this->helperSet = $helperSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the helper set associated with the command
|
||||
*
|
||||
* @return HelperSet The HelperSet instance associated with this command
|
||||
*/
|
||||
public function getHelperSet()
|
||||
{
|
||||
return $this->helperSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the InputDefinition related to this Application.
|
||||
*
|
||||
* @return InputDefinition The InputDefinition instance
|
||||
*/
|
||||
public function getDefinition()
|
||||
{
|
||||
return $this->definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the help message.
|
||||
*
|
||||
* @return string A help message.
|
||||
*/
|
||||
public function getHelp()
|
||||
{
|
||||
$messages = array(
|
||||
$this->getLongVersion(),
|
||||
'',
|
||||
'<comment>Usage:</comment>',
|
||||
sprintf(" [options] command [arguments]\n"),
|
||||
'<comment>Options:</comment>',
|
||||
);
|
||||
|
||||
foreach ($this->definition->getOptions() as $option) {
|
||||
$messages[] = sprintf(' %-29s %s %s',
|
||||
'<info>--'.$option->getName().'</info>',
|
||||
$option->getShortcut() ? '<info>-'.$option->getShortcut().'</info>' : ' ',
|
||||
$option->getDescription()
|
||||
);
|
||||
}
|
||||
|
||||
return implode("\n", $messages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to catch exceptions or not during commands execution.
|
||||
*
|
||||
* @param Boolean $boolean Whether to catch exceptions or not during commands execution
|
||||
*/
|
||||
public function setCatchExceptions($boolean)
|
||||
{
|
||||
$this->catchExceptions = (Boolean) $boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to automatically exit after a command execution or not.
|
||||
*
|
||||
* @param Boolean $boolean Whether to automatically exit after a command execution or not
|
||||
*/
|
||||
public function setAutoExit($boolean)
|
||||
{
|
||||
$this->autoExit = (Boolean) $boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the application.
|
||||
*
|
||||
* @return string The application name
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the application name.
|
||||
*
|
||||
* @param string $name The application name
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the application version.
|
||||
*
|
||||
* @return string The application version
|
||||
*/
|
||||
public function getVersion()
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the application version.
|
||||
*
|
||||
* @param string $version The application version
|
||||
*/
|
||||
public function setVersion($version)
|
||||
{
|
||||
$this->version = $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the long version of the application.
|
||||
*
|
||||
* @return string The long application version
|
||||
*/
|
||||
public function getLongVersion()
|
||||
{
|
||||
if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) {
|
||||
return sprintf('<info>%s</info> version <comment>%s</comment>', $this->getName(), $this->getVersion());
|
||||
} else {
|
||||
return '<info>Console Tool</info>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new command.
|
||||
*
|
||||
* @param string $name The command name
|
||||
*
|
||||
* @return Command The newly created command
|
||||
*/
|
||||
public function register($name)
|
||||
{
|
||||
return $this->add(new Command($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an array of command objects.
|
||||
*
|
||||
* @param Command[] $commands An array of commands
|
||||
*/
|
||||
public function addCommands(array $commands)
|
||||
{
|
||||
foreach ($commands as $command) {
|
||||
$this->add($command);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a command object.
|
||||
*
|
||||
* If a command with the same name already exists, it will be overridden.
|
||||
*
|
||||
* @param Command $command A Command object
|
||||
*
|
||||
* @return Command The registered command
|
||||
*/
|
||||
public function add(Command $command)
|
||||
{
|
||||
$command->setApplication($this);
|
||||
|
||||
$this->commands[$command->getFullName()] = $command;
|
||||
|
||||
foreach ($command->getAliases() as $alias) {
|
||||
$this->aliases[$alias] = $command;
|
||||
}
|
||||
|
||||
return $command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a registered command by name or alias.
|
||||
*
|
||||
* @param string $name The command name or alias
|
||||
*
|
||||
* @return Command A Command object
|
||||
*
|
||||
* @throws \InvalidArgumentException When command name given does not exist
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) {
|
||||
throw new \InvalidArgumentException(sprintf('The command "%s" does not exist.', $name));
|
||||
}
|
||||
|
||||
$command = isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name];
|
||||
|
||||
if ($this->wantHelps) {
|
||||
$this->wantHelps = false;
|
||||
|
||||
$helpCommand = $this->get('help');
|
||||
$helpCommand->setCommand($command);
|
||||
|
||||
return $helpCommand;
|
||||
}
|
||||
|
||||
return $command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the command exists, false otherwise
|
||||
*
|
||||
* @param string $name The command name or alias
|
||||
*
|
||||
* @return Boolean true if the command exists, false otherwise
|
||||
*/
|
||||
public function has($name)
|
||||
{
|
||||
return isset($this->commands[$name]) || isset($this->aliases[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all unique namespaces used by currently registered commands.
|
||||
*
|
||||
* It does not returns the global namespace which always exists.
|
||||
*
|
||||
* @return array An array of namespaces
|
||||
*/
|
||||
public function getNamespaces()
|
||||
{
|
||||
$namespaces = array();
|
||||
foreach ($this->commands as $command) {
|
||||
if ($command->getNamespace()) {
|
||||
$namespaces[$command->getNamespace()] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return array_keys($namespaces);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a registered namespace by a name or an abbreviation.
|
||||
*
|
||||
* @return string A registered namespace
|
||||
*
|
||||
* @throws \InvalidArgumentException When namespace is incorrect or ambiguous
|
||||
*/
|
||||
public function findNamespace($namespace)
|
||||
{
|
||||
$abbrevs = static::getAbbreviations($this->getNamespaces());
|
||||
|
||||
if (!isset($abbrevs[$namespace])) {
|
||||
throw new \InvalidArgumentException(sprintf('There are no commands defined in the "%s" namespace.', $namespace));
|
||||
}
|
||||
|
||||
if (count($abbrevs[$namespace]) > 1) {
|
||||
throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions($abbrevs[$namespace])));
|
||||
}
|
||||
|
||||
return $abbrevs[$namespace][0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a command by name or alias.
|
||||
*
|
||||
* Contrary to get, this command tries to find the best
|
||||
* match if you give it an abbreviation of a name or alias.
|
||||
*
|
||||
* @param string $name A command name or a command alias
|
||||
*
|
||||
* @return Command A Command instance
|
||||
*
|
||||
* @throws \InvalidArgumentException When command name is incorrect or ambiguous
|
||||
*/
|
||||
public function find($name)
|
||||
{
|
||||
// namespace
|
||||
$namespace = '';
|
||||
if (false !== $pos = strrpos($name, ':')) {
|
||||
$namespace = $this->findNamespace(substr($name, 0, $pos));
|
||||
$name = substr($name, $pos + 1);
|
||||
}
|
||||
|
||||
$fullName = $namespace ? $namespace.':'.$name : $name;
|
||||
|
||||
// name
|
||||
$commands = array();
|
||||
foreach ($this->commands as $command) {
|
||||
if ($command->getNamespace() == $namespace) {
|
||||
$commands[] = $command->getName();
|
||||
}
|
||||
}
|
||||
|
||||
$abbrevs = static::getAbbreviations($commands);
|
||||
if (isset($abbrevs[$name]) && 1 == count($abbrevs[$name])) {
|
||||
return $this->get($namespace ? $namespace.':'.$abbrevs[$name][0] : $abbrevs[$name][0]);
|
||||
}
|
||||
|
||||
if (isset($abbrevs[$name]) && count($abbrevs[$name]) > 1) {
|
||||
$suggestions = $this->getAbbreviationSuggestions(array_map(function ($command) use ($namespace) { return $namespace.':'.$command; }, $abbrevs[$name]));
|
||||
|
||||
throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $fullName, $suggestions));
|
||||
}
|
||||
|
||||
// aliases
|
||||
$abbrevs = static::getAbbreviations(array_keys($this->aliases));
|
||||
if (!isset($abbrevs[$fullName])) {
|
||||
throw new \InvalidArgumentException(sprintf('Command "%s" is not defined.', $fullName));
|
||||
}
|
||||
|
||||
if (count($abbrevs[$fullName]) > 1) {
|
||||
throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $fullName, $this->getAbbreviationSuggestions($abbrevs[$fullName])));
|
||||
}
|
||||
|
||||
return $this->get($abbrevs[$fullName][0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the commands (registered in the given namespace if provided).
|
||||
*
|
||||
* The array keys are the full names and the values the command instances.
|
||||
*
|
||||
* @param string $namespace A namespace name
|
||||
*
|
||||
* @return array An array of Command instances
|
||||
*/
|
||||
public function all($namespace = null)
|
||||
{
|
||||
if (null === $namespace) {
|
||||
return $this->commands;
|
||||
}
|
||||
|
||||
$commands = array();
|
||||
foreach ($this->commands as $name => $command) {
|
||||
if ($namespace === $command->getNamespace()) {
|
||||
$commands[$name] = $command;
|
||||
}
|
||||
}
|
||||
|
||||
return $commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of possible abbreviations given a set of names.
|
||||
*
|
||||
* @param array $names An array of names
|
||||
*
|
||||
* @return array An array of abbreviations
|
||||
*/
|
||||
static public function getAbbreviations($names)
|
||||
{
|
||||
$abbrevs = array();
|
||||
foreach ($names as $name) {
|
||||
for ($len = strlen($name) - 1; $len > 0; --$len) {
|
||||
$abbrev = substr($name, 0, $len);
|
||||
if (!isset($abbrevs[$abbrev])) {
|
||||
$abbrevs[$abbrev] = array($name);
|
||||
} else {
|
||||
$abbrevs[$abbrev][] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Non-abbreviations always get entered, even if they aren't unique
|
||||
foreach ($names as $name) {
|
||||
$abbrevs[$name] = array($name);
|
||||
}
|
||||
|
||||
return $abbrevs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a text representation of the Application.
|
||||
*
|
||||
* @param string $namespace An optional namespace name
|
||||
*
|
||||
* @return string A string representing the Application
|
||||
*/
|
||||
public function asText($namespace = null)
|
||||
{
|
||||
$commands = $namespace ? $this->all($this->findNamespace($namespace)) : $this->commands;
|
||||
|
||||
$messages = array($this->getHelp(), '');
|
||||
if ($namespace) {
|
||||
$messages[] = sprintf("<comment>Available commands for the \"%s\" namespace:</comment>", $namespace);
|
||||
} else {
|
||||
$messages[] = '<comment>Available commands:</comment>';
|
||||
}
|
||||
|
||||
$width = 0;
|
||||
foreach ($commands as $command) {
|
||||
$width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width;
|
||||
}
|
||||
$width += 2;
|
||||
|
||||
// add commands by namespace
|
||||
foreach ($this->sortCommands($commands) as $space => $commands) {
|
||||
if (!$namespace && '_global' !== $space) {
|
||||
$messages[] = '<comment>'.$space.'</comment>';
|
||||
}
|
||||
|
||||
foreach ($commands as $command) {
|
||||
$aliases = $command->getAliases() ? '<comment> ('.implode(', ', $command->getAliases()).')</comment>' : '';
|
||||
|
||||
$messages[] = sprintf(" <info>%-${width}s</info> %s%s", ($command->getNamespace() ? ':' : '').$command->getName(), $command->getDescription(), $aliases);
|
||||
}
|
||||
}
|
||||
|
||||
return implode("\n", $messages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an XML representation of the Application.
|
||||
*
|
||||
* @param string $namespace An optional namespace name
|
||||
* @param Boolean $asDom Whether to return a DOM or an XML string
|
||||
*
|
||||
* @return string|DOMDocument An XML string representing the Application
|
||||
*/
|
||||
public function asXml($namespace = null, $asDom = false)
|
||||
{
|
||||
$commands = $namespace ? $this->all($this->findNamespace($namespace)) : $this->commands;
|
||||
|
||||
$dom = new \DOMDocument('1.0', 'UTF-8');
|
||||
$dom->formatOutput = true;
|
||||
$dom->appendChild($xml = $dom->createElement('symfony'));
|
||||
|
||||
$xml->appendChild($commandsXML = $dom->createElement('commands'));
|
||||
|
||||
if ($namespace) {
|
||||
$commandsXML->setAttribute('namespace', $namespace);
|
||||
} else {
|
||||
$xml->appendChild($namespacesXML = $dom->createElement('namespaces'));
|
||||
}
|
||||
|
||||
// add commands by namespace
|
||||
foreach ($this->sortCommands($commands) as $space => $commands) {
|
||||
if (!$namespace) {
|
||||
$namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace'));
|
||||
$namespaceArrayXML->setAttribute('id', $space);
|
||||
}
|
||||
|
||||
foreach ($commands as $command) {
|
||||
if (!$namespace) {
|
||||
$namespaceArrayXML->appendChild($commandXML = $dom->createElement('command'));
|
||||
$commandXML->appendChild($dom->createTextNode($command->getName()));
|
||||
}
|
||||
|
||||
$node = $command->asXml(true)->getElementsByTagName('command')->item(0);
|
||||
$node = $dom->importNode($node, true);
|
||||
|
||||
$commandsXML->appendChild($node);
|
||||
}
|
||||
}
|
||||
|
||||
return $asDom ? $dom : $dom->saveXml();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a catched exception.
|
||||
*
|
||||
* @param Exception $e An exception instance
|
||||
* @param OutputInterface $output An OutputInterface instance
|
||||
*/
|
||||
public function renderException($e, $output)
|
||||
{
|
||||
$strlen = function ($string)
|
||||
{
|
||||
return function_exists('mb_strlen') ? mb_strlen($string) : strlen($string);
|
||||
};
|
||||
|
||||
$title = sprintf(' [%s] ', get_class($e));
|
||||
$len = $strlen($title);
|
||||
$lines = array();
|
||||
foreach (explode("\n", $e->getMessage()) as $line) {
|
||||
$lines[] = sprintf(' %s ', $line);
|
||||
$len = max($strlen($line) + 4, $len);
|
||||
}
|
||||
|
||||
$messages = array(str_repeat(' ', $len), $title.str_repeat(' ', $len - $strlen($title)));
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$messages[] = $line.str_repeat(' ', $len - $strlen($line));
|
||||
}
|
||||
|
||||
$messages[] = str_repeat(' ', $len);
|
||||
|
||||
$output->writeln("\n");
|
||||
foreach ($messages as $message) {
|
||||
$output->writeln('<error>'.$message.'</error>');
|
||||
}
|
||||
$output->writeln("\n");
|
||||
|
||||
if (null !== $this->runningCommand) {
|
||||
$output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())));
|
||||
$output->writeln("\n");
|
||||
}
|
||||
|
||||
if (Output::VERBOSITY_VERBOSE === $output->getVerbosity()) {
|
||||
$output->writeln('</comment>Exception trace:</comment>');
|
||||
|
||||
// exception related properties
|
||||
$trace = $e->getTrace();
|
||||
array_unshift($trace, array(
|
||||
'function' => '',
|
||||
'file' => $e->getFile() != null ? $e->getFile() : 'n/a',
|
||||
'line' => $e->getLine() != null ? $e->getLine() : 'n/a',
|
||||
'args' => array(),
|
||||
));
|
||||
|
||||
for ($i = 0, $count = count($trace); $i < $count; $i++) {
|
||||
$class = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
|
||||
$type = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
|
||||
$function = $trace[$i]['function'];
|
||||
$file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a';
|
||||
$line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';
|
||||
|
||||
$output->writeln(sprintf(' %s%s%s() at <info>%s:%s</info>', $class, $type, $function, $file, $line));
|
||||
}
|
||||
|
||||
$output->writeln("\n");
|
||||
}
|
||||
}
|
||||
|
||||
protected function getCommandName(InputInterface $input)
|
||||
{
|
||||
return $input->getFirstArgument('command');
|
||||
}
|
||||
|
||||
protected function sortCommands($commands)
|
||||
{
|
||||
$namespacedCommands = array();
|
||||
foreach ($commands as $name => $command) {
|
||||
$key = $command->getNamespace() ? $command->getNamespace() : '_global';
|
||||
|
||||
if (!isset($namespacedCommands[$key])) {
|
||||
$namespacedCommands[$key] = array();
|
||||
}
|
||||
|
||||
$namespacedCommands[$key][$name] = $command;
|
||||
}
|
||||
ksort($namespacedCommands);
|
||||
|
||||
foreach ($namespacedCommands as $name => &$commands) {
|
||||
ksort($commands);
|
||||
}
|
||||
|
||||
return $namespacedCommands;
|
||||
}
|
||||
|
||||
protected function getAbbreviationSuggestions($abbrevs)
|
||||
{
|
||||
return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '');
|
||||
}
|
||||
}
|
||||
@@ -1,512 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Console\Command;
|
||||
|
||||
use Symfony\Component\Console\Input\InputDefinition;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Application;
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony framework.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base class for all commands.
|
||||
*
|
||||
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*/
|
||||
class Command
|
||||
{
|
||||
protected $name;
|
||||
protected $namespace;
|
||||
protected $aliases;
|
||||
protected $definition;
|
||||
protected $help;
|
||||
protected $application;
|
||||
protected $description;
|
||||
protected $ignoreValidationErrors;
|
||||
protected $applicationDefinitionMerged;
|
||||
protected $code;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $name The name of the command
|
||||
*
|
||||
* @throws \LogicException When the command name is empty
|
||||
*/
|
||||
public function __construct($name = null)
|
||||
{
|
||||
$this->definition = new InputDefinition();
|
||||
$this->ignoreValidationErrors = false;
|
||||
$this->applicationDefinitionMerged = false;
|
||||
$this->aliases = array();
|
||||
|
||||
if (null !== $name) {
|
||||
$this->setName($name);
|
||||
}
|
||||
|
||||
$this->configure();
|
||||
|
||||
if (!$this->name) {
|
||||
throw new \LogicException('The command name cannot be empty.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the application instance for this command.
|
||||
*
|
||||
* @param Application $application An Application instance
|
||||
*/
|
||||
public function setApplication(Application $application = null)
|
||||
{
|
||||
$this->application = $application;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the current command.
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the current command.
|
||||
*
|
||||
* @param InputInterface $input An InputInterface instance
|
||||
* @param OutputInterface $output An OutputInterface instance
|
||||
*
|
||||
* @return integer 0 if everything went fine, or an error code
|
||||
*
|
||||
* @throws \LogicException When this abstract class is not implemented
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
throw new \LogicException('You must override the execute() method in the concrete command class.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Interacts with the user.
|
||||
*
|
||||
* @param InputInterface $input An InputInterface instance
|
||||
* @param OutputInterface $output An OutputInterface instance
|
||||
*/
|
||||
protected function interact(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the command just after the input has been validated.
|
||||
*
|
||||
* This is mainly useful when a lot of commands extends one main command
|
||||
* where some things need to be initialized based on the input arguments and options.
|
||||
*
|
||||
* @param InputInterface $input An InputInterface instance
|
||||
* @param OutputInterface $output An OutputInterface instance
|
||||
*/
|
||||
protected function initialize(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the command.
|
||||
*
|
||||
* @param InputInterface $input An InputInterface instance
|
||||
* @param OutputInterface $output An OutputInterface instance
|
||||
*/
|
||||
public function run(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
// add the application arguments and options
|
||||
$this->mergeApplicationDefinition();
|
||||
|
||||
// bind the input against the command specific arguments/options
|
||||
try {
|
||||
$input->bind($this->definition);
|
||||
} catch (\Exception $e) {
|
||||
if (!$this->ignoreValidationErrors) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
$this->initialize($input, $output);
|
||||
|
||||
if ($input->isInteractive()) {
|
||||
$this->interact($input, $output);
|
||||
}
|
||||
|
||||
$input->validate();
|
||||
|
||||
if ($this->code) {
|
||||
return call_user_func($this->code, $input, $output);
|
||||
} else {
|
||||
return $this->execute($input, $output);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the code to execute when running this command.
|
||||
*
|
||||
* @param \Closure $code A \Closure
|
||||
*
|
||||
* @return Command The current instance
|
||||
*/
|
||||
public function setCode(\Closure $code)
|
||||
{
|
||||
$this->code = $code;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the application definition with the command definition.
|
||||
*/
|
||||
protected function mergeApplicationDefinition()
|
||||
{
|
||||
if (null === $this->application || true === $this->applicationDefinitionMerged) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->definition->setArguments(array_merge(
|
||||
$this->application->getDefinition()->getArguments(),
|
||||
$this->definition->getArguments()
|
||||
));
|
||||
|
||||
$this->definition->addOptions($this->application->getDefinition()->getOptions());
|
||||
|
||||
$this->applicationDefinitionMerged = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an array of argument and option instances.
|
||||
*
|
||||
* @param array|Definition $definition An array of argument and option instances or a definition instance
|
||||
*
|
||||
* @return Command The current instance
|
||||
*/
|
||||
public function setDefinition($definition)
|
||||
{
|
||||
if ($definition instanceof InputDefinition) {
|
||||
$this->definition = $definition;
|
||||
} else {
|
||||
$this->definition->setDefinition($definition);
|
||||
}
|
||||
|
||||
$this->applicationDefinitionMerged = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the InputDefinition attached to this Command.
|
||||
*
|
||||
* @return InputDefinition An InputDefinition instance
|
||||
*/
|
||||
public function getDefinition()
|
||||
{
|
||||
return $this->definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an argument.
|
||||
*
|
||||
* @param string $name The argument name
|
||||
* @param integer $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
|
||||
* @param string $description A description text
|
||||
* @param mixed $default The default value (for InputArgument::OPTIONAL mode only)
|
||||
*
|
||||
* @return Command The current instance
|
||||
*/
|
||||
public function addArgument($name, $mode = null, $description = '', $default = null)
|
||||
{
|
||||
$this->definition->addArgument(new InputArgument($name, $mode, $description, $default));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an option.
|
||||
*
|
||||
* @param string $name The option name
|
||||
* @param string $shortcut The shortcut (can be null)
|
||||
* @param integer $mode The option mode: One of the InputOption::VALUE_* constants
|
||||
* @param string $description A description text
|
||||
* @param mixed $default The default value (must be null for InputOption::VALUE_REQUIRED or self::VALUE_NONE)
|
||||
*
|
||||
* @return Command The current instance
|
||||
*/
|
||||
public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null)
|
||||
{
|
||||
$this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the command.
|
||||
*
|
||||
* This method can set both the namespace and the name if
|
||||
* you separate them by a colon (:)
|
||||
*
|
||||
* $command->setName('foo:bar');
|
||||
*
|
||||
* @param string $name The command name
|
||||
*
|
||||
* @return Command The current instance
|
||||
*
|
||||
* @throws \InvalidArgumentException When command name given is empty
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
if (false !== $pos = strrpos($name, ':')) {
|
||||
$namespace = substr($name, 0, $pos);
|
||||
$name = substr($name, $pos + 1);
|
||||
} else {
|
||||
$namespace = $this->namespace;
|
||||
}
|
||||
|
||||
if (!$name) {
|
||||
throw new \InvalidArgumentException('A command name cannot be empty.');
|
||||
}
|
||||
|
||||
$this->namespace = $namespace;
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the command namespace.
|
||||
*
|
||||
* @return string The command namespace
|
||||
*/
|
||||
public function getNamespace()
|
||||
{
|
||||
return $this->namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the command name
|
||||
*
|
||||
* @return string The command name
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fully qualified command name.
|
||||
*
|
||||
* @return string The fully qualified command name
|
||||
*/
|
||||
public function getFullName()
|
||||
{
|
||||
return $this->getNamespace() ? $this->getNamespace().':'.$this->getName() : $this->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description for the command.
|
||||
*
|
||||
* @param string $description The description for the command
|
||||
*
|
||||
* @return Command The current instance
|
||||
*/
|
||||
public function setDescription($description)
|
||||
{
|
||||
$this->description = $description;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the description for the command.
|
||||
*
|
||||
* @return string The description for the command
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the help for the command.
|
||||
*
|
||||
* @param string $help The help for the command
|
||||
*
|
||||
* @return Command The current instance
|
||||
*/
|
||||
public function setHelp($help)
|
||||
{
|
||||
$this->help = $help;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the help for the command.
|
||||
*
|
||||
* @return string The help for the command
|
||||
*/
|
||||
public function getHelp()
|
||||
{
|
||||
return $this->help;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the processed help for the command replacing the %command.name% and
|
||||
* %command.full_name% patterns with the real values dynamically.
|
||||
*
|
||||
* @return string The processed help for the command
|
||||
*/
|
||||
public function getProcessedHelp()
|
||||
{
|
||||
$name = $this->namespace.':'.$this->name;
|
||||
|
||||
$placeholders = array(
|
||||
'%command.name%',
|
||||
'%command.full_name%'
|
||||
);
|
||||
$replacements = array(
|
||||
$name,
|
||||
$_SERVER['PHP_SELF'].' '.$name
|
||||
);
|
||||
|
||||
return str_replace($placeholders, $replacements, $this->getHelp());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the aliases for the command.
|
||||
*
|
||||
* @param array $aliases An array of aliases for the command
|
||||
*
|
||||
* @return Command The current instance
|
||||
*/
|
||||
public function setAliases($aliases)
|
||||
{
|
||||
$this->aliases = $aliases;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the aliases for the command.
|
||||
*
|
||||
* @return array An array of aliases for the command
|
||||
*/
|
||||
public function getAliases()
|
||||
{
|
||||
return $this->aliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the synopsis for the command.
|
||||
*
|
||||
* @return string The synopsis
|
||||
*/
|
||||
public function getSynopsis()
|
||||
{
|
||||
return sprintf('%s %s', $this->getFullName(), $this->definition->getSynopsis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a helper instance by name.
|
||||
*
|
||||
* @param string $name The helper name
|
||||
*
|
||||
* @return mixed The helper value
|
||||
*
|
||||
* @throws \InvalidArgumentException if the helper is not defined
|
||||
*/
|
||||
protected function getHelper($name)
|
||||
{
|
||||
return $this->application->getHelperSet()->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a helper instance by name.
|
||||
*
|
||||
* @param string $name The helper name
|
||||
*
|
||||
* @return mixed The helper value
|
||||
*
|
||||
* @throws \InvalidArgumentException if the helper is not defined
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
return $this->application->getHelperSet()->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a text representation of the command.
|
||||
*
|
||||
* @return string A string representing the command
|
||||
*/
|
||||
public function asText()
|
||||
{
|
||||
$messages = array(
|
||||
'<comment>Usage:</comment>',
|
||||
' '.$this->getSynopsis(),
|
||||
'',
|
||||
);
|
||||
|
||||
if ($this->getAliases()) {
|
||||
$messages[] = '<comment>Aliases:</comment> <info>'.implode(', ', $this->getAliases()).'</info>';
|
||||
}
|
||||
|
||||
$messages[] = $this->definition->asText();
|
||||
|
||||
if ($help = $this->getProcessedHelp()) {
|
||||
$messages[] = '<comment>Help:</comment>';
|
||||
$messages[] = ' '.implode("\n ", explode("\n", $help))."\n";
|
||||
}
|
||||
|
||||
return implode("\n", $messages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an XML representation of the command.
|
||||
*
|
||||
* @param Boolean $asDom Whether to return a DOM or an XML string
|
||||
*
|
||||
* @return string|DOMDocument An XML string representing the command
|
||||
*/
|
||||
public function asXml($asDom = false)
|
||||
{
|
||||
$dom = new \DOMDocument('1.0', 'UTF-8');
|
||||
$dom->formatOutput = true;
|
||||
$dom->appendChild($commandXML = $dom->createElement('command'));
|
||||
$commandXML->setAttribute('id', $this->getFullName());
|
||||
$commandXML->setAttribute('namespace', $this->getNamespace() ? $this->getNamespace() : '_global');
|
||||
$commandXML->setAttribute('name', $this->getName());
|
||||
|
||||
$commandXML->appendChild($usageXML = $dom->createElement('usage'));
|
||||
$usageXML->appendChild($dom->createTextNode(sprintf($this->getSynopsis(), '')));
|
||||
|
||||
$commandXML->appendChild($descriptionXML = $dom->createElement('description'));
|
||||
$descriptionXML->appendChild($dom->createTextNode(implode("\n ", explode("\n", $this->getDescription()))));
|
||||
|
||||
$commandXML->appendChild($helpXML = $dom->createElement('help'));
|
||||
$help = $this->help;
|
||||
$helpXML->appendChild($dom->createTextNode(implode("\n ", explode("\n", $help))));
|
||||
|
||||
$commandXML->appendChild($aliasesXML = $dom->createElement('aliases'));
|
||||
foreach ($this->getAliases() as $alias) {
|
||||
$aliasesXML->appendChild($aliasXML = $dom->createElement('alias'));
|
||||
$aliasXML->appendChild($dom->createTextNode($alias));
|
||||
}
|
||||
|
||||
$definition = $this->definition->asXml(true);
|
||||
$commandXML->appendChild($dom->importNode($definition->getElementsByTagName('arguments')->item(0), true));
|
||||
$commandXML->appendChild($dom->importNode($definition->getElementsByTagName('options')->item(0), true));
|
||||
|
||||
return $asDom ? $dom : $dom->saveXml();
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Console\Command;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Output\Output;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony framework.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* HelpCommand displays the help for a given command.
|
||||
*
|
||||
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*/
|
||||
class HelpCommand extends Command
|
||||
{
|
||||
protected $command;
|
||||
|
||||
/**
|
||||
* @see Command
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->ignoreValidationErrors = true;
|
||||
|
||||
$this
|
||||
->setDefinition(array(
|
||||
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'),
|
||||
new InputOption('xml', null, InputOption::VALUE_NONE, 'To output help as XML'),
|
||||
))
|
||||
->setName('help')
|
||||
->setAliases(array('?'))
|
||||
->setDescription('Displays help for a command')
|
||||
->setHelp(<<<EOF
|
||||
The <info>help</info> command displays help for a given command:
|
||||
|
||||
<info>./symfony help list</info>
|
||||
|
||||
You can also output the help as XML by using the <comment>--xml</comment> option:
|
||||
|
||||
<info>./symfony help --xml list</info>
|
||||
EOF
|
||||
);
|
||||
}
|
||||
|
||||
public function setCommand(Command $command)
|
||||
{
|
||||
$this->command = $command;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Command
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
if (null === $this->command) {
|
||||
$this->command = $this->application->get($input->getArgument('command_name'));
|
||||
}
|
||||
|
||||
if ($input->getOption('xml')) {
|
||||
$output->writeln($this->command->asXml(), Output::OUTPUT_RAW);
|
||||
} else {
|
||||
$output->writeln($this->command->asText());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Console\Command;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Output\Output;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony framework.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* ListCommand displays the list of all available commands for the application.
|
||||
*
|
||||
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*/
|
||||
class ListCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @see Command
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setDefinition(array(
|
||||
new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'),
|
||||
new InputOption('xml', null, InputOption::VALUE_NONE, 'To output help as XML'),
|
||||
))
|
||||
->setName('list')
|
||||
->setDescription('Lists commands')
|
||||
->setHelp(<<<EOF
|
||||
The <info>list</info> command lists all commands:
|
||||
|
||||
<info>./symfony list</info>
|
||||
|
||||
You can also display the commands for a specific namespace:
|
||||
|
||||
<info>./symfony list test</info>
|
||||
|
||||
You can also output the information as XML by using the <comment>--xml</comment> option:
|
||||
|
||||
<info>./symfony list --xml</info>
|
||||
EOF
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Command
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
if ($input->getOption('xml')) {
|
||||
$output->writeln($this->application->asXml($input->getArgument('namespace')), Output::OUTPUT_RAW);
|
||||
} else {
|
||||
$output->writeln($this->application->asText($input->getArgument('namespace')));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Console\Helper;
|
||||
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony framework.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The Dialog class provides helpers to interact with the user.
|
||||
*
|
||||
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*/
|
||||
class DialogHelper extends Helper
|
||||
{
|
||||
/**
|
||||
* Asks a question to the user.
|
||||
*
|
||||
* @param OutputInterface $output
|
||||
* @param string|array $question The question to ask
|
||||
* @param string $default The default answer if none is given by the user
|
||||
*
|
||||
* @return string The user answer
|
||||
*/
|
||||
public function ask(OutputInterface $output, $question, $default = null)
|
||||
{
|
||||
// @codeCoverageIgnoreStart
|
||||
$output->writeln($question);
|
||||
|
||||
$ret = trim(fgets(STDIN));
|
||||
|
||||
return $ret ? $ret : $default;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks a confirmation to the user.
|
||||
*
|
||||
* The question will be asked until the user answer by nothing, yes, or no.
|
||||
*
|
||||
* @param OutputInterface $output
|
||||
* @param string|array $question The question to ask
|
||||
* @param Boolean $default The default answer if the user enters nothing
|
||||
*
|
||||
* @return Boolean true if the user has confirmed, false otherwise
|
||||
*/
|
||||
public function askConfirmation(OutputInterface $output, $question, $default = true)
|
||||
{
|
||||
// @codeCoverageIgnoreStart
|
||||
$answer = 'z';
|
||||
while ($answer && !in_array(strtolower($answer[0]), array('y', 'n'))) {
|
||||
$answer = $this->ask($output, $question);
|
||||
}
|
||||
|
||||
if (false === $default) {
|
||||
return $answer && 'y' == strtolower($answer[0]);
|
||||
} else {
|
||||
return !$answer || 'y' == strtolower($answer[0]);
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks for a value and validates the response.
|
||||
*
|
||||
* @param OutputInterface $output
|
||||
* @param string|array $question
|
||||
* @param Closure $validator
|
||||
* @param integer $attempts Max number of times to ask before giving up (false by default, which means infinite)
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Exception When any of the validator returns an error
|
||||
*/
|
||||
public function askAndValidate(OutputInterface $output, $question, \Closure $validator, $attempts = false)
|
||||
{
|
||||
// @codeCoverageIgnoreStart
|
||||
$error = null;
|
||||
while (false === $attempts || $attempts--) {
|
||||
if (null !== $error) {
|
||||
$output->writeln($this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'));
|
||||
}
|
||||
|
||||
$value = $this->ask($output, $question, null);
|
||||
|
||||
try {
|
||||
return $validator($value);
|
||||
} catch (\Exception $error) {
|
||||
}
|
||||
}
|
||||
|
||||
throw $error;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the helper's canonical name
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'dialog';
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Console\Helper;
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony framework.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The Formatter class provides helpers to format messages.
|
||||
*
|
||||
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*/
|
||||
class FormatterHelper extends Helper
|
||||
{
|
||||
/**
|
||||
* Formats a message within a section.
|
||||
*
|
||||
* @param string $section The section name
|
||||
* @param string $message The message
|
||||
* @param string $style The style to apply to the section
|
||||
*/
|
||||
public function formatSection($section, $message, $style = 'info')
|
||||
{
|
||||
return sprintf('<%s>[%s]</%s> %s', $style, $section, $style, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a message as a block of text.
|
||||
*
|
||||
* @param string|array $messages The message to write in the block
|
||||
* @param string $style The style to apply to the whole block
|
||||
* @param Boolean $large Whether to return a large block
|
||||
*
|
||||
* @return string The formatter message
|
||||
*/
|
||||
public function formatBlock($messages, $style, $large = false)
|
||||
{
|
||||
if (!is_array($messages)) {
|
||||
$messages = array($messages);
|
||||
}
|
||||
|
||||
$len = 0;
|
||||
$lines = array();
|
||||
foreach ($messages as $message) {
|
||||
$lines[] = sprintf($large ? ' %s ' : ' %s ', $message);
|
||||
$len = max($this->strlen($message) + ($large ? 4 : 2), $len);
|
||||
}
|
||||
|
||||
$messages = $large ? array(str_repeat(' ', $len)) : array();
|
||||
foreach ($lines as $line) {
|
||||
$messages[] = $line.str_repeat(' ', $len - $this->strlen($line));
|
||||
}
|
||||
if ($large) {
|
||||
$messages[] = str_repeat(' ', $len);
|
||||
}
|
||||
|
||||
foreach ($messages as &$message) {
|
||||
$message = sprintf('<%s>%s</%s>', $style, $message, $style);
|
||||
}
|
||||
|
||||
return implode("\n", $messages);
|
||||
}
|
||||
|
||||
protected function strlen($string)
|
||||
{
|
||||
return function_exists('mb_strlen') ? mb_strlen($string) : strlen($string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the helper's canonical name
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'formatter';
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Console\Helper;
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony framework.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helper is the base class for all helper classes.
|
||||
*
|
||||
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*/
|
||||
abstract class Helper implements HelperInterface
|
||||
{
|
||||
protected $helperSet = null;
|
||||
|
||||
/**
|
||||
* Sets the helper set associated with this helper.
|
||||
*
|
||||
* @param HelperSet $helperSet A HelperSet instance
|
||||
*/
|
||||
public function setHelperSet(HelperSet $helperSet = null)
|
||||
{
|
||||
$this->helperSet = $helperSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the helper set associated with this helper.
|
||||
*
|
||||
* @return HelperSet A HelperSet instance
|
||||
*/
|
||||
public function getHelperSet()
|
||||
{
|
||||
return $this->helperSet;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Console\Helper;
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony framework.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* HelperInterface is the interface all helpers must implement.
|
||||
*
|
||||
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*/
|
||||
interface HelperInterface
|
||||
{
|
||||
/**
|
||||
* Sets the helper set associated with this helper.
|
||||
*
|
||||
* @param HelperSet $helperSet A HelperSet instance
|
||||
*/
|
||||
function setHelperSet(HelperSet $helperSet = null);
|
||||
|
||||
/**
|
||||
* Gets the helper set associated with this helper.
|
||||
*
|
||||
* @return HelperSet A HelperSet instance
|
||||
*/
|
||||
function getHelperSet();
|
||||
|
||||
/**
|
||||
* Returns the canonical name of this helper.
|
||||
*
|
||||
* @return string The canonical name
|
||||
*/
|
||||
function getName();
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Console\Helper;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony framework.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*
|
||||
* This source file is subject to the MIT license that is bundled
|
||||
* with this source code in the file LICENSE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* HelperSet represents a set of helpers to be used with a command.
|
||||
*
|
||||
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||
*/
|
||||
class HelperSet
|
||||
{
|
||||
protected $helpers;
|
||||
protected $command;
|
||||
|
||||
/**
|
||||
* @param Helper[] $helpers An array of helper.
|
||||
*/
|
||||
public function __construct(array $helpers = array())
|
||||
{
|
||||
$this->helpers = array();
|
||||
foreach ($helpers as $alias => $helper) {
|
||||
$this->set($helper, is_int($alias) ? null : $alias);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a helper.
|
||||
*
|
||||
* @param HelperInterface $value The helper instance
|
||||
* @param string $alias An alias
|
||||
*/
|
||||
public function set(HelperInterface $helper, $alias = null)
|
||||
{
|
||||
$this->helpers[$helper->getName()] = $helper;
|
||||
if (null !== $alias) {
|
||||
$this->helpers[$alias] = $helper;
|
||||
}
|
||||
|
||||
$helper->setHelperSet($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the helper if defined.
|
||||
*
|
||||
* @param string $name The helper name
|
||||
*
|
||||
* @return Boolean true if the helper is defined, false otherwise
|
||||
*/
|
||||
public function has($name)
|
||||
{
|
||||
return isset($this->helpers[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a helper value.
|
||||
*
|
||||
* @param string $name The helper name
|
||||
*
|
||||
* @return HelperInterface The helper instance
|
||||
*
|
||||
* @throws \InvalidArgumentException if the helper is not defined
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
if (!$this->has($name)) {
|
||||
throw new \InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name));
|
||||
}
|
||||
|
||||
return $this->helpers[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the command associated with this helper set.
|
||||
*
|
||||
* @param Command $command A Command instance
|
||||
*/
|
||||
public function setCommand(Command $command = null)
|
||||
{
|
||||
$this->command = $command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the command associated with this helper set.
|
||||
*
|
||||
* @return Command A Command instance
|
||||
*/
|
||||
public function getCommand()
|
||||
{
|
||||
return $this->command;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user