mirror of
https://github.com/doctrine/orm.git
synced 2026-04-26 07:58:09 +02:00
Compare commits
626 Commits
2.0.0-BETA2
...
2.1.0RC1
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 7def30f283 | |||
| 22ffbe7488 | |||
| 988d229c07 | |||
| 43c63765db | |||
| b736c4216c | |||
| c03fc00f45 | |||
| 6ddfd06efe | |||
| d87391e40c | |||
| 2648c1a6ca | |||
| 6c9eeb6127 | |||
| 6c26af069c | |||
| 3c0f92f4c7 | |||
| 4f154b6aa1 | |||
| 06326918a5 | |||
| 5e788a0b84 | |||
| 1daf658ec6 | |||
| aa2501eb96 | |||
| ef50d940de | |||
| ad50327744 | |||
| 9a68015ccf | |||
| 72ba369dbb | |||
| 5b20838aec | |||
| 8654d060c6 | |||
| 2ba9d5a597 | |||
| ca682b3840 | |||
| 687548cde2 | |||
| 054f26c0a7 | |||
| c6a6aaf493 | |||
| 8e4197adc5 | |||
| 0ba9321f06 | |||
| 892eec2f26 | |||
| 796b62cd2c | |||
| dff5dae416 | |||
| 7196999b69 | |||
| 386b7e26d6 | |||
| 51922a1ff0 | |||
| e4f74d8290 | |||
| 6cd0fefef2 | |||
| e142bd1f30 | |||
| 97b80d69f1 | |||
| 154176516e | |||
| a2cbb8f72f | |||
| ec50125568 | |||
| 831b40e093 | |||
| b6da2e0e42 | |||
| 494bfc8966 | |||
| c1661dd53e | |||
| ae76b2ab8d | |||
| 85a579febc | |||
| e62fb0b48e | |||
| ece27e39c4 | |||
| d3d3032759 | |||
| ae9080aa98 | |||
| 4ea3277c28 | |||
| e4280cf82e | |||
| ac85584e9b | |||
| 53e8b8f32d | |||
| a966cb6cc7 | |||
| 31efc9a149 | |||
| 6a904a2d67 | |||
| 634aa0b291 | |||
| 4f71c3e6a3 | |||
| 9211bc2f4e | |||
| 692c35e7e2 | |||
| 4804f623c1 | |||
| 008601f2ea | |||
| 23795605fc | |||
| 97eeb437b2 | |||
| 3936f179e9 | |||
| 515ef33665 | |||
| b5c5ec3c69 | |||
| aa2a80f3ff | |||
| 338476805d | |||
| bf79168952 | |||
| 0a8ff7a030 | |||
| 6006979fc6 | |||
| 35860d9a94 | |||
| 89d0a52c4f | |||
| 07016f6da5 | |||
| b4aabf0ba6 | |||
| 7551bb3762 | |||
| 394c67d482 | |||
| b05e1ad7ad | |||
| 638c3df3a6 | |||
| de236e0456 | |||
| 3ad429a5aa | |||
| 394469d4b7 | |||
| 84bd843eed | |||
| 140ddf5098 | |||
| d2630ff54e | |||
| 039293c27a | |||
| 8f80c94923 | |||
| 8b766b6c92 | |||
| 01ffa2dc9e | |||
| 75e5c40a50 | |||
| 6390653df7 | |||
| 13047aa12e | |||
| 39f732ab91 | |||
| 7dc8ef1db9 | |||
| 62a8e2aad5 | |||
| 6d20d7d5ed | |||
| c70f32f4c9 | |||
| 72f65c3665 | |||
| 25f5ab6557 | |||
| 9fa8ff86f8 | |||
| 8a92bf075b | |||
| d3419780f9 | |||
| 810a129a32 | |||
| 97e572e2d8 | |||
| 2e3c1506fb | |||
| 4845745337 | |||
| da63bad9c8 | |||
| ee9158ffb4 | |||
| 4727489134 | |||
| 13da816f4e | |||
| 24c6bb3f46 | |||
| f415fa7174 | |||
| 20af9d6d9f | |||
| 207d624f5f | |||
| 7ff9976b3c | |||
| 803e338365 | |||
| 0b5c694a7e | |||
| 0904bc5cc5 | |||
| 33d0bb454b | |||
| c77a12ac83 | |||
| 2a2936fde5 | |||
| 5bd8ffa53c | |||
| 43f8398fbb | |||
| 187d9289ad | |||
| d115f7af4f | |||
| 797d9f1be5 | |||
| 8a21ab4755 | |||
| 506973a92e | |||
| 241e4d2f53 | |||
| d0717ee458 | |||
| aa5826b69a | |||
| 4a9f36800e | |||
| 1496250833 | |||
| 2f00db08e1 | |||
| dd5574162a | |||
| 6f4f8f8cb1 | |||
| 772e592489 | |||
| fb44fa6b5a | |||
| 5799e391c6 | |||
| d56d118458 | |||
| e45c52b024 | |||
| 5719f8523b | |||
| 496a34a4d2 | |||
| c3064336ab | |||
| 84b9eda17c | |||
| 98785122d3 | |||
| ca1931de81 | |||
| 575858774d | |||
| a812dab4d4 | |||
| 139f8b52ab | |||
| dd7be5b13a | |||
| 4826739824 | |||
| 5178f4b7d6 | |||
| 8d3e0e61ea | |||
| da809fdeda | |||
| db936035e0 | |||
| 8ea1d3825f | |||
| 21753c71c9 | |||
| ea954e8123 | |||
| 37a1a35b75 | |||
| a705b81d9b | |||
| 0424d87099 | |||
| dcebc241b4 | |||
| 7b07a17886 | |||
| a1bf4dc4f6 | |||
| 34262aeae1 | |||
| 3630e06b84 | |||
| 69e9fd3145 | |||
| eaa78b981b | |||
| f2715c9af4 | |||
| 9347263a43 | |||
| bd28cb1b12 | |||
| 251247c16f | |||
| e3a4c8ddeb | |||
| 013262a9b7 | |||
| 10f47389ae | |||
| c697a2d47f | |||
| 11b25422d6 | |||
| 0bc22a240b | |||
| 92a79df156 | |||
| 29bf4adac7 | |||
| 35af98260a | |||
| 623c02c7dc | |||
| 0c07b31136 | |||
| a25101add1 | |||
| 69073c4b37 | |||
| 954a8c3935 | |||
| e69c7c7c60 | |||
| c1fec32f58 | |||
| af59a581f0 | |||
| 5bebf13d58 | |||
| 104dd7aa77 | |||
| d6a1075b53 | |||
| 56b3c0e8b0 | |||
| 553e93ae27 | |||
| 86e24d373b | |||
| d2740f0e77 | |||
| 2a7a72b3f9 | |||
| b477211b4b | |||
| 17528224b9 | |||
| ad115f0ac8 | |||
| 75fa640bfe | |||
| c217b33c45 | |||
| 5f109c5d6c | |||
| d50b3c42e2 | |||
| 7a5ab94780 | |||
| 0be6b3fd85 | |||
| c2ee1d2439 | |||
| 6007084324 | |||
| 913e58e385 | |||
| 2c28872af8 | |||
| a05003016b | |||
| d86c2f6709 | |||
| e7ac35ed95 | |||
| d288e99a34 | |||
| 722d4a38f4 | |||
| bbaec5bf36 | |||
| ede6205204 | |||
| b0e4d06c40 | |||
| 1794127d51 | |||
| c0d26b97dd | |||
| 233b3cd0b9 | |||
| aa6edb7903 | |||
| a1c13b58d3 | |||
| 4212b88edc | |||
| 9b03a1ecdd | |||
| 54476882c7 | |||
| 88b0813536 | |||
| 26d8b4dafd | |||
| c45bd0edec | |||
| d4de420349 | |||
| aa5a2a049d | |||
| 552865a867 | |||
| fdf1f1b29e | |||
| ae1c40636c | |||
| d922631efe | |||
| b56e1e31af | |||
| fbaa4e3215 | |||
| c5656eb039 | |||
| 6479fcec68 | |||
| 92daf7f43e | |||
| f7e8109d07 | |||
| 13affb2eb2 | |||
| 6ba4fa002b | |||
| 178f35aaa1 | |||
| 1f059b7f99 | |||
| c1091485b0 | |||
| afd4121116 | |||
| 321fc2506d | |||
| fc7224f73e | |||
| 1d5d092453 | |||
| 206710f91a | |||
| cd978fb8c9 | |||
| 51e6681934 | |||
| 440a255e5a | |||
| bead9b8b4a | |||
| d27733f690 | |||
| 20c1ff3146 | |||
| e6f465ec80 | |||
| 438de30aa3 | |||
| 4d27b40423 | |||
| 8639735e91 | |||
| 4705c1cb03 | |||
| 190d115dce | |||
| 7018509126 | |||
| 7c2fac64fb | |||
| c69b2d2b54 | |||
| 434325ed4f | |||
| c8e20aa217 | |||
| b7db8df7ef | |||
| 45ec58fc7e | |||
| 773f56bef2 | |||
| 64309398e2 | |||
| 3fc9971e98 | |||
| b63afb6ce4 | |||
| c2d2e45859 | |||
| 4bf3058ab5 | |||
| 02e582e00e | |||
| e50f77a780 |
@@ -4,3 +4,9 @@
|
||||
[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
|
||||
|
||||
@@ -1,3 +1,73 @@
|
||||
# Update from 2.0-BETA3 to 2.0-BETA4
|
||||
|
||||
## XML Driver <change-tracking-policy /> element demoted to attribute
|
||||
|
||||
We changed how the XML Driver allows to define the change-tracking-policy. The working case is now:
|
||||
|
||||
<entity change-tracking-policy="DEFERRED_IMPLICT" />
|
||||
|
||||
# Update from 2.0-BETA2 to 2.0-BETA3
|
||||
|
||||
## Serialization of Uninitialized Proxies
|
||||
|
||||
As of Beta3 you can now serialize uninitialized proxies, an exception will only be thrown when
|
||||
trying to access methods on the unserialized proxy as long as it has not been re-attached to the
|
||||
EntityManager using `EntityManager#merge()`. See this example:
|
||||
|
||||
$proxy = $em->getReference('User', 1);
|
||||
|
||||
$serializedProxy = serialize($proxy);
|
||||
$detachedProxy = unserialized($serializedProxy);
|
||||
|
||||
echo $em->contains($detachedProxy); // FALSE
|
||||
|
||||
try {
|
||||
$detachedProxy->getId(); // uninitialized detached proxy
|
||||
} catch(Exception $e) {
|
||||
|
||||
}
|
||||
$attachedProxy = $em->merge($detachedProxy);
|
||||
echo $attackedProxy->getId(); // works!
|
||||
|
||||
## Changed SQL implementation of Postgres and Oracle DateTime types
|
||||
|
||||
The DBAL Type "datetime" included the Timezone Offset in both Postgres and Oracle. As of this version they are now
|
||||
generated without Timezone (TIMESTAMP WITHOUT TIME ZONE instead of TIMESTAMP WITH TIME ZONE).
|
||||
See [this comment to Ticket DBAL-22](http://www.doctrine-project.org/jira/browse/DBAL-22?focusedCommentId=13396&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#action_13396)
|
||||
for more details as well as migration issues for PostgreSQL and Oracle.
|
||||
|
||||
Both Postgres and Oracle will throw Exceptions during hydration of Objects with "DateTime" fields unless migration steps are taken!
|
||||
|
||||
## Removed multi-dot/deep-path expressions in DQL
|
||||
|
||||
The support for implicit joins in DQL through the multi-dot/Deep Path Expressions
|
||||
was dropped. For example:
|
||||
|
||||
SELECT u FROM User u WHERE u.group.name = ?1
|
||||
|
||||
See the "u.group.id" here is using multi dots (deep expression) to walk
|
||||
through the graph of objects and properties. Internally the DQL parser
|
||||
would rewrite these queries to:
|
||||
|
||||
SELECT u FROM User u JOIN u.group g WHERE g.name = ?1
|
||||
|
||||
This explicit notation will be the only supported notation as of now. The internal
|
||||
handling of multi-dots in the DQL Parser was very complex, error prone in edge cases
|
||||
and required special treatment for several features we added. Additionally
|
||||
it had edge cases that could not be solved without making the DQL Parser
|
||||
even much more complex. For this reason we will drop the support for the
|
||||
deep path expressions to increase maintainability and overall performance
|
||||
of the DQL parsing process. This will benefit any DQL query being parsed,
|
||||
even those not using deep path expressions.
|
||||
|
||||
Note that the generated SQL of both notations is exactly the same! You
|
||||
don't loose anything through this.
|
||||
|
||||
## Default Allocation Size for Sequences
|
||||
|
||||
The default allocation size for sequences has been changed from 10 to 1. This step was made
|
||||
to not cause confusion with users and also because it is partly some kind of premature optimization.
|
||||
|
||||
# Update from 2.0-BETA1 to 2.0-BETA2
|
||||
|
||||
There are no backwards incompatible changes in this release.
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
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:
|
||||
|
||||
$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.
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
@echo off
|
||||
|
||||
if "%PHPBIN%" == "" set PHPBIN=@php_bin@
|
||||
if not exist "%PHPBIN%" if "%PHP_PEAR_PHP_BIN%" neq "" goto USE_PEAR_PATH
|
||||
GOTO RUN
|
||||
:USE_PEAR_PATH
|
||||
set PHPBIN=%PHP_PEAR_PHP_BIN%
|
||||
:RUN
|
||||
"%PHPBIN%" "@bin_dir@\doctrine" %*
|
||||
+21
-29
@@ -1,4 +1,21 @@
|
||||
<?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>.
|
||||
*/
|
||||
|
||||
require_once 'Doctrine/Common/ClassLoader.php';
|
||||
|
||||
@@ -19,40 +36,15 @@ if (file_exists($configFile)) {
|
||||
}
|
||||
|
||||
require $configFile;
|
||||
|
||||
|
||||
foreach ($GLOBALS as $helperSetCandidate) {
|
||||
if ($helperSetCandidate instanceof \Symfony\Components\Console\Helper\HelperSet) {
|
||||
if ($helperSetCandidate instanceof \Symfony\Component\Console\Helper\HelperSet) {
|
||||
$helperSet = $helperSetCandidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$helperSet = ($helperSet) ?: new \Symfony\Components\Console\Helper\HelperSet();
|
||||
$helperSet = ($helperSet) ?: new \Symfony\Component\Console\Helper\HelperSet();
|
||||
|
||||
$cli = new \Symfony\Components\Console\Application('Doctrine Command Line Interface', Doctrine\ORM\Version::VERSION);
|
||||
$cli->setCatchExceptions(true);
|
||||
$cli->setHelperSet($helperSet);
|
||||
$cli->addCommands(array(
|
||||
// DBAL Commands
|
||||
new \Doctrine\DBAL\Tools\Console\Command\RunSqlCommand(),
|
||||
new \Doctrine\DBAL\Tools\Console\Command\ImportCommand(),
|
||||
|
||||
// ORM Commands
|
||||
new \Doctrine\ORM\Tools\Console\Command\ClearCache\MetadataCommand(),
|
||||
new \Doctrine\ORM\Tools\Console\Command\ClearCache\ResultCommand(),
|
||||
new \Doctrine\ORM\Tools\Console\Command\ClearCache\QueryCommand(),
|
||||
new \Doctrine\ORM\Tools\Console\Command\SchemaTool\CreateCommand(),
|
||||
new \Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand(),
|
||||
new \Doctrine\ORM\Tools\Console\Command\SchemaTool\DropCommand(),
|
||||
new \Doctrine\ORM\Tools\Console\Command\EnsureProductionSettingsCommand(),
|
||||
new \Doctrine\ORM\Tools\Console\Command\ConvertDoctrine1SchemaCommand(),
|
||||
new \Doctrine\ORM\Tools\Console\Command\GenerateRepositoriesCommand(),
|
||||
new \Doctrine\ORM\Tools\Console\Command\GenerateEntitiesCommand(),
|
||||
new \Doctrine\ORM\Tools\Console\Command\GenerateProxiesCommand(),
|
||||
new \Doctrine\ORM\Tools\Console\Command\ConvertMappingCommand(),
|
||||
new \Doctrine\ORM\Tools\Console\Command\RunDqlCommand(),
|
||||
new \Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand(),
|
||||
|
||||
));
|
||||
$cli->run();
|
||||
\Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet);
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
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
|
||||
svn.path=/usr/bin/svn
|
||||
project.pirum_dir=
|
||||
project.download_dir=
|
||||
test.phpunit_configuration_file=
|
||||
test.phpunit_generate_coverage=0
|
||||
test.pmd_reports=0
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
-->
|
||||
|
||||
<project name="Doctrine2" default="build" basedir=".">
|
||||
|
||||
<taskdef classname="NativePhpunitTask" classpath="./tests/" name="nativephpunit" />
|
||||
<taskdef classname="phing.tasks.ext.d51PearPkg2Task" name="d51pearpkg2" />
|
||||
|
||||
<property file="build.properties" />
|
||||
@@ -24,6 +22,7 @@
|
||||
<fileset id="bin-scripts" dir="./bin">
|
||||
<include name="doctrine"/>
|
||||
<include name="doctrine.php"/>
|
||||
<include name="doctrine.bat"/>
|
||||
</fileset>
|
||||
|
||||
<!--
|
||||
@@ -51,21 +50,7 @@
|
||||
Fileset for source of the Symfony YAML and Console components.
|
||||
-->
|
||||
<fileset id="symfony-sources" dir="./lib/vendor">
|
||||
<include name="Symfony/Components/**"/>
|
||||
</fileset>
|
||||
|
||||
<!--
|
||||
Fileset for the Doctrine ORM tools + sandbox.
|
||||
-->
|
||||
<fileset id="orm-tools" dir=".">
|
||||
<include name="tools/sandbox/Entities"/>
|
||||
<include name="tools/sandbox/xml"/>
|
||||
<include name="tools/sandbox/yaml"/>
|
||||
<include name="tools/sandbox/cli-config.php"/>
|
||||
<include name="tools/sandbox/config.php"/>
|
||||
<include name="tools/sandbox/doctrine"/>
|
||||
<include name="tools/sandbox/doctrine.php"/>
|
||||
<include name="tools/sandbox/index.php"/>
|
||||
<include name="Symfony/Component/**"/>
|
||||
</fileset>
|
||||
|
||||
<!--
|
||||
@@ -97,24 +82,27 @@
|
||||
<!--
|
||||
Builds ORM package, preparing it for distribution.
|
||||
-->
|
||||
<target name="build-orm" depends="test">
|
||||
<copy todir="${build.dir}/orm">
|
||||
<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">
|
||||
<fileset refid="shared-artifacts"/>
|
||||
<fileset refid="orm-tools"/>
|
||||
</copy>
|
||||
<copy todir="${build.dir}/orm">
|
||||
<copy todir="${build.dir}/doctrine-orm">
|
||||
<fileset refid="common-sources"/>
|
||||
<fileset refid="dbal-sources"/>
|
||||
<fileset refid="orm-sources"/>
|
||||
</copy>
|
||||
<copy todir="${build.dir}/orm/Doctrine">
|
||||
<copy todir="${build.dir}/doctrine-orm/Doctrine">
|
||||
<fileset refid="symfony-sources"/>
|
||||
</copy>
|
||||
<copy todir="${build.dir}/orm/bin">
|
||||
<copy todir="${build.dir}/doctrine-orm/bin">
|
||||
<fileset refid="bin-scripts"/>
|
||||
</copy>
|
||||
<exec command="sed 's/${version}-DEV/${version}/' ${build.dir}/orm/Doctrine/ORM/Version.php > ${build.dir}/orm/Doctrine/ORM/Version2.php" passthru="true" />
|
||||
<exec command="mv ${build.dir}/orm/Doctrine/ORM/Version2.php ${build.dir}/orm/Doctrine/ORM/Version.php" passthru="true" />
|
||||
<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" />
|
||||
<delete dir="${build.dir}/doctrine-orm/Doctrine/Symfony/Component/Yaml/.git" includeemptydirs="true"/>
|
||||
<delete dir="${build.dir}/doctrine-orm/Doctrine/Symfony/Component/Console/.git" includeemptydirs="true"/>
|
||||
|
||||
</target>
|
||||
|
||||
<target name="build" depends="test, build-orm"/>
|
||||
@@ -156,8 +144,8 @@
|
||||
<!--
|
||||
Builds distributable PEAR packages.
|
||||
-->
|
||||
<target name="build-packages" depends="build">
|
||||
<d51pearpkg2 baseinstalldir="/" dir="${build.dir}/orm">
|
||||
<target name="build-packages" depends="build-orm">
|
||||
<d51pearpkg2 baseinstalldir="/" dir="${build.dir}/doctrine-orm">
|
||||
<name>DoctrineORM</name>
|
||||
<summary>Doctrine Object Relational Mapper</summary>
|
||||
<channel>pear.doctrine-project.org</channel>
|
||||
@@ -165,6 +153,7 @@
|
||||
<lead user="jwage" name="Jonathan H. Wage" email="jonwage@gmail.com" />
|
||||
<lead user="guilhermeblanco" name="Guilherme Blanco" email="guilhermeblanco@gmail.com" />
|
||||
<lead user="romanb" name="Roman Borschel" email="roman@code-factory.org" />
|
||||
<lead user="beberlei" name="Benjamin Eberlei" email="kontakt@beberlei.de" />
|
||||
<license>LGPL</license>
|
||||
<version release="${version}" api="${version}" />
|
||||
<stability release="${stability}" api="${stability}" />
|
||||
@@ -172,17 +161,117 @@
|
||||
<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="DoctrineSymfonyConsole" channel="pear.doctrine-project.org" minimum_version="2.0.0" />
|
||||
<package name="DoctrineSymfonyYaml" channel="pear.doctrine-project.org" minimum_version="2.0.0" />
|
||||
</dependencies>
|
||||
<dirroles key="bin">script</dirroles>
|
||||
<replacement path="bin/doctrine" type="pear-config" from="@php_bin@" to="php_bin" />
|
||||
<replacement path="bin/doctrine.php" type="pear-config" from="@php_bin@" to="php_bin" />
|
||||
<replacement path="bin/doctrine.php" type="pear-config" from="@bin_dir@" to="bin_dir" />
|
||||
<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.bat" type="pear-config" from="@bin_dir@" to="bin_dir" />
|
||||
</d51pearpkg2>
|
||||
<exec command="pear package" dir="${build.dir}/orm" passthru="true" />
|
||||
<exec command="mv DoctrineORM-${version}.tgz ../../dist" dir="${build.dir}/orm" passthru="true" />
|
||||
<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>
|
||||
</project>
|
||||
|
||||
<target name="git-tag">
|
||||
<exec command="grep '${version}-DEV' ${project.basedir}/lib/Doctrine/ORM/Version.php" checkreturn="true"/>
|
||||
<exec command="sed 's/${version}-DEV/${version}/' ${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 'Release ${version}'" />
|
||||
<exec command="git tag -m 'Tag ${version}' -a ${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}/${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" />
|
||||
</target>
|
||||
|
||||
<target name="release" depends="git-tag,build-packages,distribute-download,pirum-release,update-dev-version" />
|
||||
|
||||
<!--
|
||||
Builds distributable PEAR packages for the Symfony Dependencies
|
||||
-->
|
||||
<target name="release-symfony-dependencies" depends="build-orm">
|
||||
<d51pearpkg2 baseinstalldir="/" dir="${build.dir}/doctrine-orm">
|
||||
<name>DoctrineSymfonyConsole</name>
|
||||
<summary>Symfony Console Component</summary>
|
||||
<channel>pear.doctrine-project.org</channel>
|
||||
<description>A command line interface tool from the Symfony project. Packaged for shipping with Doctrine projects using ORM version numbers.</description>
|
||||
<lead user="fabpot" name="Fabien Potencier" email="fabien.potencier@symfony-project.com" />
|
||||
<license>NewBSD License</license>
|
||||
<version release="${version}" api="${version}" />
|
||||
<stability release="${stability}" api="${stability}" />
|
||||
<notes>-</notes>
|
||||
<dependencies>
|
||||
<php minimum_version="5.3.0" />
|
||||
<pear minimum_version="1.6.0" recommended_version="1.6.1" />
|
||||
</dependencies>
|
||||
<ignore>bin/</ignore>
|
||||
<ignore>Doctrine/Common/</ignore>
|
||||
<ignore>Doctrine/DBAL/</ignore>
|
||||
<ignore>Doctrine/ORM/</ignore>
|
||||
<ignore>Symfony/Component/Yaml/</ignore>
|
||||
</d51pearpkg2>
|
||||
<exec command="pear package" dir="${build.dir}/doctrine-orm" passthru="true" />
|
||||
<exec command="mv DoctrineSymfonyConsole-${version}.tgz ../../dist" dir="${build.dir}/doctrine-orm" passthru="true" />
|
||||
|
||||
<d51pearpkg2 baseinstalldir="/" dir="${build.dir}/doctrine-orm">
|
||||
<name>DoctrineSymfonyYaml</name>
|
||||
<summary>Symfony Yaml Component</summary>
|
||||
<channel>pear.doctrine-project.org</channel>
|
||||
<description>A YAML Parser from the Symfony project. Packaged for shipping with Doctrine projects using ORM version numbers.</description>
|
||||
<lead user="fabpot" name="Fabien Potencier" email="fabien.potencier@symfony-project.com" />
|
||||
<license>NewBSD License</license>
|
||||
<version release="${version}" api="${version}" />
|
||||
<stability release="${stability}" api="${stability}" />
|
||||
<notes>-</notes>
|
||||
<dependencies>
|
||||
<php minimum_version="5.3.0" />
|
||||
<pear minimum_version="1.6.0" recommended_version="1.6.1" />
|
||||
</dependencies>
|
||||
<ignore>bin/</ignore>
|
||||
<ignore>Doctrine/Common/</ignore>
|
||||
<ignore>Doctrine/DBAL/</ignore>
|
||||
<ignore>Doctrine/ORM/</ignore>
|
||||
<ignore>Symfony/Component/Console/</ignore>
|
||||
</d51pearpkg2>
|
||||
<exec command="pear package" dir="${build.dir}/doctrine-orm" passthru="true" />
|
||||
<exec command="mv DoctrineSymfonyYaml-${version}.tgz ../../dist" dir="${build.dir}/doctrine-orm" passthru="true" />
|
||||
|
||||
<exec command="sudo pirum add ${project.pirum_dir} ${project.basedir}/dist/DoctrineSymfonyConsole-${version}.tgz" dir="." passthru="true" />
|
||||
<exec command="sudo pirum add ${project.pirum_dir} ${project.basedir}/dist/DoctrineSymfonyYaml-${version}.tgz" dir="." passthru="true" />
|
||||
<exec command="sudo pirum build ${project.pirum_dir}" passthru="true" />
|
||||
</target>
|
||||
</project>
|
||||
|
||||
|
||||
+126
-14
@@ -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,25 +91,43 @@
|
||||
<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" />
|
||||
<xs:element name="named-queries" type="orm:named-queries" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="id" type="orm:id" minOccurs="0" maxOccurs="1" />
|
||||
<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" />
|
||||
<xs:attribute name="schema" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="repository-class" type="xs:NMTOKEN"/>
|
||||
<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>
|
||||
|
||||
<xs:simpleType name="change-tracking-policy">
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:enumeration value="DEFERRED_IMPLICIT"/>
|
||||
<xs:enumeration value="DEFERRED_EXPLICIT"/>
|
||||
<xs:enumeration value="NOTIFY"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="inheritance-type">
|
||||
<xs:restriction base="xs:token">
|
||||
@@ -112,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" />
|
||||
@@ -126,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" />
|
||||
@@ -202,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">
|
||||
@@ -242,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:NMTOKEN" use="required" />
|
||||
<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:NMTOKEN" use="required" />
|
||||
<xs:attribute name="target-entity" type="xs:string" use="required" />
|
||||
<xs:attribute name="mapped-by" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="index-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="many-to-one">
|
||||
@@ -268,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:NMTOKEN" use="required" />
|
||||
<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">
|
||||
@@ -283,14 +392,17 @@
|
||||
<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:NMTOKEN" use="required" />
|
||||
<xs:attribute name="target-entity" type="xs:string" use="required" />
|
||||
<xs:attribute name="mapped-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
</xs:schema>
|
||||
</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.
|
||||
*/
|
||||
@@ -138,10 +141,16 @@ abstract class AbstractQuery
|
||||
|
||||
/**
|
||||
* Frees the resources used by the query object.
|
||||
*
|
||||
* Resets Parameters, Parameter Types and Query Hints.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function free()
|
||||
{
|
||||
$this->_params = array();
|
||||
$this->_paramTypes = array();
|
||||
$this->_hints = array();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,10 +195,13 @@ abstract class AbstractQuery
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -263,7 +275,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)
|
||||
{
|
||||
@@ -325,6 +337,26 @@ 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.
|
||||
*
|
||||
@@ -384,6 +416,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.
|
||||
*
|
||||
@@ -452,6 +509,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.
|
||||
@@ -460,31 +527,8 @@ 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)
|
||||
{
|
||||
return $this->_em->newHydrator($this->_hydrationMode)->iterate(
|
||||
$this->_doExecute($params, $hydrationMode), $this->_resultSetMapping, $this->_hints
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the query.
|
||||
*
|
||||
* @param string $params Any additional query parameters.
|
||||
* @param integer $hydrationMode Processing mode to be used during the hydration process.
|
||||
* @return mixed
|
||||
*/
|
||||
public function execute($params = array(), $hydrationMode = null)
|
||||
{
|
||||
// If there are still pending insertions in the UnitOfWork we need to flush
|
||||
// in order to guarantee a correct result.
|
||||
//TODO: Think this over. Its tricky. Not doing this can lead to strange results
|
||||
// potentially, but doing it could result in endless loops when querying during
|
||||
// a flush, i.e. inside an event listener.
|
||||
if ($this->_em->getUnitOfWork()->hasPendingInsertions()) {
|
||||
$this->_em->flush();
|
||||
}
|
||||
|
||||
if ($hydrationMode !== null) {
|
||||
$this->setHydrationMode($hydrationMode);
|
||||
}
|
||||
@@ -493,29 +537,49 @@ abstract class AbstractQuery
|
||||
$this->setParameters($params);
|
||||
}
|
||||
|
||||
if (isset($this->_params[0])) {
|
||||
throw QueryException::invalidParameterPosition(0);
|
||||
$stmt = $this->_doExecute();
|
||||
|
||||
return $this->_em->newHydrator($this->_hydrationMode)->iterate(
|
||||
$stmt, $this->_resultSetMapping, $this->_hints
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the query.
|
||||
*
|
||||
* @param array $params Any additional query parameters.
|
||||
* @param integer $hydrationMode Processing mode to be used during the hydration process.
|
||||
* @return mixed
|
||||
*/
|
||||
public function execute($params = array(), $hydrationMode = null)
|
||||
{
|
||||
if ($hydrationMode !== null) {
|
||||
$this->setHydrationMode($hydrationMode);
|
||||
}
|
||||
|
||||
if ($params) {
|
||||
$this->setParameters($params);
|
||||
}
|
||||
|
||||
// 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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,17 +613,33 @@ 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) {
|
||||
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);
|
||||
}
|
||||
$params[$key] = $idValues;
|
||||
} else {
|
||||
$params[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$sql = $this->getSql();
|
||||
ksort($this->_hints);
|
||||
return md5(implode(";", (array)$sql) . var_export($this->_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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -569,4 +649,16 @@ abstract class AbstractQuery
|
||||
* @return Doctrine\DBAL\Driver\Statement The executed database statement that holds the results.
|
||||
*/
|
||||
abstract protected function _doExecute();
|
||||
|
||||
/**
|
||||
* Cleanup Query resource when clone is called.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
$this->_params = array();
|
||||
$this->_paramTypes = array();
|
||||
$this->_hints = array();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use Doctrine\Common\Cache\Cache,
|
||||
Doctrine\ORM\Mapping\Driver\Driver;
|
||||
Doctrine\ORM\Mapping\Driver\Driver,
|
||||
Doctrine\Common\Cache\ArrayCache;
|
||||
|
||||
/**
|
||||
* Configuration container for all configuration options of Doctrine.
|
||||
@@ -120,9 +121,21 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function newDefaultAnnotationDriver($paths = array())
|
||||
{
|
||||
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
|
||||
$reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\');
|
||||
|
||||
if (version_compare(\Doctrine\Common\Version::VERSION, '3.0.0-DEV', '>=')) {
|
||||
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
|
||||
$reader = new \Doctrine\Common\Annotations\CachedReader($reader, new ArrayCache());
|
||||
} else if (version_compare(\Doctrine\Common\Version::VERSION, '2.1.0-BETA3-DEV', '>=')) {
|
||||
$reader = new \Doctrine\Common\Annotations\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 \Doctrine\Common\Annotations\AnnotationReader();
|
||||
$reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\');
|
||||
}
|
||||
return new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($reader, (array)$paths);
|
||||
}
|
||||
|
||||
@@ -163,6 +176,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.
|
||||
@@ -239,29 +262,6 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
$this->_attributes['metadataCacheImpl'] = $cacheImpl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a boolean flag that indicates whether Doctrine should make use of the
|
||||
* C extension.
|
||||
*
|
||||
* @return boolean TRUE if Doctrine is configured to use the C extension, FALSE otherwise.
|
||||
*/
|
||||
public function getUseCExtension()
|
||||
{
|
||||
return isset($this->_attributes['useCExtension']) ?
|
||||
$this->_attributes['useCExtension'] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a boolean flag that indicates whether Doctrine should make use of the
|
||||
* C extension.
|
||||
*
|
||||
* @param boolean $boolean Whether to make use of the C extension or not.
|
||||
*/
|
||||
public function setUseCExtension($boolean)
|
||||
{
|
||||
$this->_attributes['useCExtension'] = $boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a named DQL query to the configuration.
|
||||
*
|
||||
@@ -323,13 +323,13 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function ensureProductionSettings()
|
||||
{
|
||||
if ( ! $this->_attributes['queryCacheImpl']) {
|
||||
if ( !$this->getQueryCacheImpl()) {
|
||||
throw ORMException::queryCacheNotConfigured();
|
||||
}
|
||||
if ( ! $this->_attributes['metadataCacheImpl']) {
|
||||
if ( !$this->getMetadataCacheImpl()) {
|
||||
throw ORMException::metadataCacheNotConfigured();
|
||||
}
|
||||
if ($this->_attributes['autoGenerateProxyClasses']) {
|
||||
if ($this->getAutoGenerateProxyClasses()) {
|
||||
throw ORMException::proxyClassesAlwaysRegenerating();
|
||||
}
|
||||
}
|
||||
@@ -485,4 +485,25 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
{
|
||||
$this->_attributes['customHydrationModes'][$modeName] = $hydrator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a class metadata factory.
|
||||
*
|
||||
* @param string $cmf
|
||||
*/
|
||||
public function setClassMetadataFactoryName($cmfName)
|
||||
{
|
||||
$this->_attributes['classMetadataFactoryName'] = $cmfName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getClassMetadataFactoryName()
|
||||
{
|
||||
if (!isset($this->_attributes['classMetadataFactoryName'])) {
|
||||
$this->_attributes['classMetadataFactoryName'] = 'Doctrine\ORM\Mapping\ClassMetadataFactory';
|
||||
}
|
||||
return $this->_attributes['classMetadataFactoryName'];
|
||||
}
|
||||
}
|
||||
@@ -21,10 +21,12 @@ namespace Doctrine\ORM;
|
||||
|
||||
use Closure, Exception,
|
||||
Doctrine\Common\EventManager,
|
||||
Doctrine\Common\Persistence\ObjectManager,
|
||||
Doctrine\DBAL\Connection,
|
||||
Doctrine\DBAL\LockMode,
|
||||
Doctrine\ORM\Mapping\ClassMetadata,
|
||||
Doctrine\ORM\Mapping\ClassMetadataFactory,
|
||||
Doctrine\ORM\Query\ResultSetMapping,
|
||||
Doctrine\ORM\Proxy\ProxyFactory;
|
||||
|
||||
/**
|
||||
@@ -36,73 +38,77 @@ 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
|
||||
*/
|
||||
private $_config;
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* The database connection used by the EntityManager.
|
||||
*
|
||||
* @var Doctrine\DBAL\Connection
|
||||
*/
|
||||
private $_conn;
|
||||
private $conn;
|
||||
|
||||
/**
|
||||
* The metadata factory, used to retrieve the ORM metadata of entity classes.
|
||||
*
|
||||
* @var Doctrine\ORM\Mapping\ClassMetadataFactory
|
||||
*/
|
||||
private $_metadataFactory;
|
||||
private $metadataFactory;
|
||||
|
||||
/**
|
||||
* The EntityRepository instances.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_repositories = array();
|
||||
private $repositories = array();
|
||||
|
||||
/**
|
||||
* The UnitOfWork used to coordinate object-level transactions.
|
||||
*
|
||||
* @var Doctrine\ORM\UnitOfWork
|
||||
*/
|
||||
private $_unitOfWork;
|
||||
private $unitOfWork;
|
||||
|
||||
/**
|
||||
* The event manager that is the central point of the event system.
|
||||
*
|
||||
* @var Doctrine\Common\EventManager
|
||||
*/
|
||||
private $_eventManager;
|
||||
private $eventManager;
|
||||
|
||||
/**
|
||||
* The maintained (cached) hydrators. One instance per type.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_hydrators = array();
|
||||
private $hydrators = array();
|
||||
|
||||
/**
|
||||
* The proxy factory used to create dynamic proxies.
|
||||
*
|
||||
* @var Doctrine\ORM\Proxy\ProxyFactory
|
||||
*/
|
||||
private $_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;
|
||||
private $expressionBuilder;
|
||||
|
||||
/**
|
||||
* Whether the EntityManager is closed or not.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $_closed = false;
|
||||
private $closed = false;
|
||||
|
||||
/**
|
||||
* Creates a new EntityManager that operates on the given database connection
|
||||
@@ -114,13 +120,17 @@ class EntityManager
|
||||
*/
|
||||
protected function __construct(Connection $conn, Configuration $config, EventManager $eventManager)
|
||||
{
|
||||
$this->_conn = $conn;
|
||||
$this->_config = $config;
|
||||
$this->_eventManager = $eventManager;
|
||||
$this->_metadataFactory = new ClassMetadataFactory($this);
|
||||
$this->_metadataFactory->setCacheDriver($this->_config->getMetadataCacheImpl());
|
||||
$this->_unitOfWork = new UnitOfWork($this);
|
||||
$this->_proxyFactory = new ProxyFactory($this,
|
||||
$this->conn = $conn;
|
||||
$this->config = $config;
|
||||
$this->eventManager = $eventManager;
|
||||
|
||||
$metadataFactoryClassName = $config->getClassMetadataFactoryName();
|
||||
$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(),
|
||||
$config->getProxyNamespace(),
|
||||
$config->getAutoGenerateProxyClasses());
|
||||
@@ -133,7 +143,7 @@ class EntityManager
|
||||
*/
|
||||
public function getConnection()
|
||||
{
|
||||
return $this->_conn;
|
||||
return $this->conn;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,7 +153,7 @@ class EntityManager
|
||||
*/
|
||||
public function getMetadataFactory()
|
||||
{
|
||||
return $this->_metadataFactory;
|
||||
return $this->metadataFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,14 +168,14 @@ 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()
|
||||
{
|
||||
if ($this->_expressionBuilder === null) {
|
||||
$this->_expressionBuilder = new Query\Expr;
|
||||
if ($this->expressionBuilder === null) {
|
||||
$this->expressionBuilder = new Query\Expr;
|
||||
}
|
||||
return $this->_expressionBuilder;
|
||||
return $this->expressionBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,7 +185,7 @@ class EntityManager
|
||||
*/
|
||||
public function beginTransaction()
|
||||
{
|
||||
$this->_conn->beginTransaction();
|
||||
$this->conn->beginTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,14 +202,19 @@ class EntityManager
|
||||
*/
|
||||
public function transactional(Closure $func)
|
||||
{
|
||||
$this->_conn->beginTransaction();
|
||||
$this->conn->beginTransaction();
|
||||
|
||||
try {
|
||||
$func($this);
|
||||
$return = $func($this);
|
||||
|
||||
$this->flush();
|
||||
$this->_conn->commit();
|
||||
$this->conn->commit();
|
||||
|
||||
return $return ?: true;
|
||||
} catch (Exception $e) {
|
||||
$this->close();
|
||||
$this->_conn->rollback();
|
||||
$this->conn->rollback();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
@@ -211,7 +226,7 @@ class EntityManager
|
||||
*/
|
||||
public function commit()
|
||||
{
|
||||
$this->_conn->commit();
|
||||
$this->conn->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -221,18 +236,25 @@ class EntityManager
|
||||
*/
|
||||
public function rollback()
|
||||
{
|
||||
$this->_conn->rollback();
|
||||
$this->conn->rollback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the metadata for a class.
|
||||
* Returns the ORM metadata descriptor for a class.
|
||||
*
|
||||
* 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
|
||||
* @internal Performance-sensitive method.
|
||||
*/
|
||||
public function getClassMetadata($className)
|
||||
{
|
||||
return $this->_metadataFactory->getMetadataFor($className);
|
||||
return $this->metadataFactory->getMetadataFor($className);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -258,7 +280,7 @@ class EntityManager
|
||||
*/
|
||||
public function createNamedQuery($name)
|
||||
{
|
||||
return $this->createQuery($this->_config->getNamedQuery($name));
|
||||
return $this->createQuery($this->config->getNamedQuery($name));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -268,7 +290,7 @@ class EntityManager
|
||||
* @param ResultSetMapping $rsm The ResultSetMapping to use.
|
||||
* @return NativeQuery
|
||||
*/
|
||||
public function createNativeQuery($sql, \Doctrine\ORM\Query\ResultSetMapping $rsm)
|
||||
public function createNativeQuery($sql, ResultSetMapping $rsm)
|
||||
{
|
||||
$query = new NativeQuery($this);
|
||||
$query->setSql($sql);
|
||||
@@ -284,7 +306,7 @@ class EntityManager
|
||||
*/
|
||||
public function createNamedNativeQuery($name)
|
||||
{
|
||||
list($sql, $rsm) = $this->_config->getNamedNativeQuery($name);
|
||||
list($sql, $rsm) = $this->config->getNamedNativeQuery($name);
|
||||
return $this->createNativeQuery($sql, $rsm);
|
||||
}
|
||||
|
||||
@@ -308,8 +330,8 @@ class EntityManager
|
||||
*/
|
||||
public function flush()
|
||||
{
|
||||
$this->_errorIfClosed();
|
||||
$this->_unitOfWork->commit();
|
||||
$this->errorIfClosed();
|
||||
$this->unitOfWork->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -330,27 +352,67 @@ class EntityManager
|
||||
|
||||
/**
|
||||
* Gets a reference to the entity identified by the given type and identifier
|
||||
* without actually loading it.
|
||||
*
|
||||
* If partial objects are allowed, this method will return a partial object that only
|
||||
* has its identifier populated. Otherwise a proxy is returned that automatically
|
||||
* loads itself on first access.
|
||||
* without actually loading it, if the entity is not yet loaded.
|
||||
*
|
||||
* @param string $entityName The name of the entity type.
|
||||
* @param mixed $identifier The entity identifier.
|
||||
* @return object The entity reference.
|
||||
*/
|
||||
public function getReference($entityName, $identifier)
|
||||
{
|
||||
$class = $this->_metadataFactory->getMetadataFor($entityName);
|
||||
$class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
|
||||
|
||||
// Check identity map first, if its already in there just return it.
|
||||
if ($entity = $this->_unitOfWork->tryGetById($identifier, $class->rootEntityName)) {
|
||||
return $entity;
|
||||
if ($entity = $this->unitOfWork->tryGetById($identifier, $class->rootEntityName)) {
|
||||
return ($entity instanceof $class->name) ? $entity : null;
|
||||
}
|
||||
if ($class->subClasses) {
|
||||
$entity = $this->find($entityName, $identifier);
|
||||
} else {
|
||||
if ( ! is_array($identifier)) {
|
||||
$identifier = array($class->identifier[0] => $identifier);
|
||||
}
|
||||
$entity = $this->proxyFactory->getProxy($class->name, $identifier);
|
||||
$this->unitOfWork->registerManaged($entity, $identifier, array());
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a partial reference to the entity identified by the given type and identifier
|
||||
* without actually loading it, if the entity is not yet loaded.
|
||||
*
|
||||
* The returned reference may be a partial object if the entity is not yet loaded/managed.
|
||||
* If it is a partial object it will not initialize the rest of the entity state on access.
|
||||
* Thus you can only ever safely access the identifier of an entity obtained through
|
||||
* this method.
|
||||
*
|
||||
* The use-cases for partial references involve maintaining bidirectional associations
|
||||
* without loading one side of the association or to update an entity without loading it.
|
||||
* Note, however, that in the latter case the original (persistent) entity data will
|
||||
* never be visible to the application (especially not event listeners) as it will
|
||||
* never be loaded in the first place.
|
||||
*
|
||||
* @param string $entityName The name of the entity type.
|
||||
* @param mixed $identifier The entity identifier.
|
||||
* @return object The (partial) entity reference.
|
||||
*/
|
||||
public function getPartialReference($entityName, $identifier)
|
||||
{
|
||||
$class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
|
||||
|
||||
// Check identity map first, if its already in there just return it.
|
||||
if ($entity = $this->unitOfWork->tryGetById($identifier, $class->rootEntityName)) {
|
||||
return ($entity instanceof $class->name) ? $entity : null;
|
||||
}
|
||||
if ( ! is_array($identifier)) {
|
||||
$identifier = array($class->identifier[0] => $identifier);
|
||||
}
|
||||
$entity = $this->_proxyFactory->getProxy($class->name, $identifier);
|
||||
$this->_unitOfWork->registerManaged($entity, $identifier, array());
|
||||
|
||||
$entity = $class->newInstance();
|
||||
$class->setIdentifierValues($entity, $identifier);
|
||||
$this->unitOfWork->registerManaged($entity, $identifier, array());
|
||||
|
||||
return $entity;
|
||||
}
|
||||
@@ -364,7 +426,7 @@ class EntityManager
|
||||
public function clear($entityName = null)
|
||||
{
|
||||
if ($entityName === null) {
|
||||
$this->_unitOfWork->clear();
|
||||
$this->unitOfWork->clear();
|
||||
} else {
|
||||
//TODO
|
||||
throw new ORMException("EntityManager#clear(\$entityName) not yet implemented.");
|
||||
@@ -379,7 +441,7 @@ class EntityManager
|
||||
public function close()
|
||||
{
|
||||
$this->clear();
|
||||
$this->_closed = true;
|
||||
$this->closed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -398,8 +460,8 @@ class EntityManager
|
||||
if ( ! is_object($entity)) {
|
||||
throw new \InvalidArgumentException(gettype($entity));
|
||||
}
|
||||
$this->_errorIfClosed();
|
||||
$this->_unitOfWork->persist($entity);
|
||||
$this->errorIfClosed();
|
||||
$this->unitOfWork->persist($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -415,8 +477,8 @@ class EntityManager
|
||||
if ( ! is_object($entity)) {
|
||||
throw new \InvalidArgumentException(gettype($entity));
|
||||
}
|
||||
$this->_errorIfClosed();
|
||||
$this->_unitOfWork->remove($entity);
|
||||
$this->errorIfClosed();
|
||||
$this->unitOfWork->remove($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -430,8 +492,8 @@ class EntityManager
|
||||
if ( ! is_object($entity)) {
|
||||
throw new \InvalidArgumentException(gettype($entity));
|
||||
}
|
||||
$this->_errorIfClosed();
|
||||
$this->_unitOfWork->refresh($entity);
|
||||
$this->errorIfClosed();
|
||||
$this->unitOfWork->refresh($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -448,7 +510,7 @@ class EntityManager
|
||||
if ( ! is_object($entity)) {
|
||||
throw new \InvalidArgumentException(gettype($entity));
|
||||
}
|
||||
$this->_unitOfWork->detach($entity);
|
||||
$this->unitOfWork->detach($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -464,8 +526,8 @@ class EntityManager
|
||||
if ( ! is_object($entity)) {
|
||||
throw new \InvalidArgumentException(gettype($entity));
|
||||
}
|
||||
$this->_errorIfClosed();
|
||||
return $this->_unitOfWork->merge($entity);
|
||||
$this->errorIfClosed();
|
||||
return $this->unitOfWork->merge($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -492,19 +554,20 @@ class EntityManager
|
||||
*/
|
||||
public function lock($entity, $lockMode, $lockVersion = null)
|
||||
{
|
||||
$this->_unitOfWork->lock($entity, $lockMode, $lockVersion);
|
||||
$this->unitOfWork->lock($entity, $lockMode, $lockVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the repository for an entity class.
|
||||
*
|
||||
* @param string $entityName The name of the Entity.
|
||||
* @return EntityRepository The repository.
|
||||
* @param string $entityName The name of the entity.
|
||||
* @return EntityRepository The repository class.
|
||||
*/
|
||||
public function getRepository($entityName)
|
||||
{
|
||||
if (isset($this->_repositories[$entityName])) {
|
||||
return $this->_repositories[$entityName];
|
||||
$entityName = ltrim($entityName, '\\');
|
||||
if (isset($this->repositories[$entityName])) {
|
||||
return $this->repositories[$entityName];
|
||||
}
|
||||
|
||||
$metadata = $this->getClassMetadata($entityName);
|
||||
@@ -516,7 +579,7 @@ class EntityManager
|
||||
$repository = new EntityRepository($this, $metadata);
|
||||
}
|
||||
|
||||
$this->_repositories[$entityName] = $repository;
|
||||
$this->repositories[$entityName] = $repository;
|
||||
|
||||
return $repository;
|
||||
}
|
||||
@@ -529,9 +592,9 @@ class EntityManager
|
||||
*/
|
||||
public function contains($entity)
|
||||
{
|
||||
return $this->_unitOfWork->isScheduledForInsert($entity) ||
|
||||
$this->_unitOfWork->isInIdentityMap($entity) &&
|
||||
! $this->_unitOfWork->isScheduledForDelete($entity);
|
||||
return $this->unitOfWork->isScheduledForInsert($entity) ||
|
||||
$this->unitOfWork->isInIdentityMap($entity) &&
|
||||
! $this->unitOfWork->isScheduledForDelete($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -541,7 +604,7 @@ class EntityManager
|
||||
*/
|
||||
public function getEventManager()
|
||||
{
|
||||
return $this->_eventManager;
|
||||
return $this->eventManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -551,7 +614,7 @@ class EntityManager
|
||||
*/
|
||||
public function getConfiguration()
|
||||
{
|
||||
return $this->_config;
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -559,13 +622,23 @@ class EntityManager
|
||||
*
|
||||
* @throws ORMException If the EntityManager is closed.
|
||||
*/
|
||||
private function _errorIfClosed()
|
||||
private function errorIfClosed()
|
||||
{
|
||||
if ($this->_closed) {
|
||||
if ($this->closed) {
|
||||
throw ORMException::entityManagerClosed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the Entity manager is open or closed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isOpen()
|
||||
{
|
||||
return (!$this->closed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the UnitOfWork used by the EntityManager to coordinate operations.
|
||||
*
|
||||
@@ -573,7 +646,7 @@ class EntityManager
|
||||
*/
|
||||
public function getUnitOfWork()
|
||||
{
|
||||
return $this->_unitOfWork;
|
||||
return $this->unitOfWork;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -587,11 +660,11 @@ class EntityManager
|
||||
*/
|
||||
public function getHydrator($hydrationMode)
|
||||
{
|
||||
if ( ! isset($this->_hydrators[$hydrationMode])) {
|
||||
$this->_hydrators[$hydrationMode] = $this->newHydrator($hydrationMode);
|
||||
if ( ! isset($this->hydrators[$hydrationMode])) {
|
||||
$this->hydrators[$hydrationMode] = $this->newHydrator($hydrationMode);
|
||||
}
|
||||
|
||||
return $this->_hydrators[$hydrationMode];
|
||||
return $this->hydrators[$hydrationMode];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -615,8 +688,11 @@ 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)) {
|
||||
if ($class = $this->config->getCustomHydrationMode($hydrationMode)) {
|
||||
$hydrator = new $class($this);
|
||||
break;
|
||||
}
|
||||
@@ -633,7 +709,7 @@ class EntityManager
|
||||
*/
|
||||
public function getProxyFactory()
|
||||
{
|
||||
return $this->_proxyFactory;
|
||||
return $this->proxyFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -133,7 +149,6 @@ class EntityRepository
|
||||
/**
|
||||
* Finds all entities in the repository.
|
||||
*
|
||||
* @param int $hydrationMode
|
||||
* @return array The entities.
|
||||
*/
|
||||
public function findAll()
|
||||
@@ -144,20 +159,21 @@ class EntityRepository
|
||||
/**
|
||||
* Finds entities by a set of criteria.
|
||||
*
|
||||
* @param string $column
|
||||
* @param string $value
|
||||
* @return array
|
||||
* @param array $criteria
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a single entity by a set of criteria.
|
||||
*
|
||||
* @param string $column
|
||||
* @param string $value
|
||||
* @param array $criteria
|
||||
* @return object
|
||||
*/
|
||||
public function findOneBy(array $criteria)
|
||||
@@ -188,13 +204,14 @@ class EntityRepository
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! isset($arguments[0])) {
|
||||
if ( !isset($arguments[0])) {
|
||||
// we dont even want to allow null at this point, because we cannot (yet) transform it into IS NULL.
|
||||
throw ORMException::findByRequiresParameter($method.$by);
|
||||
}
|
||||
|
||||
$fieldName = lcfirst(\Doctrine\Common\Util\Inflector::classify($by));
|
||||
|
||||
if ($this->_class->hasField($fieldName)) {
|
||||
if ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName)) {
|
||||
return $this->$method(array($fieldName => $arguments[0]));
|
||||
} else {
|
||||
throw ORMException::invalidFindByCall($this->_entityName, $fieldName, $method.$by);
|
||||
|
||||
@@ -4,6 +4,9 @@ namespace Doctrine\ORM\Event;
|
||||
|
||||
use Doctrine\Common\EventArgs;
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
|
||||
/**
|
||||
* Class that holds event arguments for a loadMetadata event.
|
||||
*
|
||||
@@ -12,16 +15,40 @@ use Doctrine\Common\EventArgs;
|
||||
*/
|
||||
class LoadClassMetadataEventArgs extends EventArgs
|
||||
{
|
||||
private $_classMetadata;
|
||||
/**
|
||||
* @var ClassMetadata
|
||||
*/
|
||||
private $classMetadata;
|
||||
|
||||
public function __construct(\Doctrine\ORM\Mapping\ClassMetadata $classMetadata)
|
||||
/**
|
||||
* @var EntityManager
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* @param ClassMetadataInfo $classMetadata
|
||||
* @param EntityManager $em
|
||||
*/
|
||||
public function __construct(ClassMetadataInfo $classMetadata, EntityManager $em)
|
||||
{
|
||||
$this->_classMetadata = $classMetadata;
|
||||
$this->classMetadata = $classMetadata;
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ClassMetadataInfo
|
||||
*/
|
||||
public function getClassMetadata()
|
||||
{
|
||||
return $this->_classMetadata;
|
||||
return $this->classMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EntityManager
|
||||
*/
|
||||
public function getEntityManager()
|
||||
{
|
||||
return $this->em;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -184,11 +184,18 @@ abstract class AbstractHydrator
|
||||
$cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']);
|
||||
$cache[$key]['isIdentifier'] = $classMetadata->isIdentifier($fieldName);
|
||||
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key];
|
||||
} else if (!isset($this->_rsm->metaMappings[$key])) {
|
||||
// this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2
|
||||
// maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping.
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,15 +206,24 @@ 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;
|
||||
}
|
||||
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) {
|
||||
@@ -245,6 +261,10 @@ abstract class AbstractHydrator
|
||||
$cache[$key]['fieldName'] = $fieldName;
|
||||
$cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']);
|
||||
$cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key];
|
||||
} else if (!isset($this->_rsm->metaMappings[$key])) {
|
||||
// this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2
|
||||
// maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping.
|
||||
continue;
|
||||
} else {
|
||||
// Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns).
|
||||
$cache[$key]['isMetaColumn'] = true;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
namespace Doctrine\ORM\Internal\Hydration;
|
||||
|
||||
use PDO, Doctrine\DBAL\Connection;
|
||||
use PDO, Doctrine\DBAL\Connection, Doctrine\ORM\Mapping\ClassMetadata;
|
||||
|
||||
/**
|
||||
* The ArrayHydrator produces a nested array "graph" that is often (not always)
|
||||
@@ -109,7 +109,7 @@ class ArrayHydrator extends AbstractHydrator
|
||||
$relation = $this->_getClassMetadata($this->_rsm->aliasMap[$parent])->associationMappings[$relationAlias];
|
||||
|
||||
// Check the type of the relation (many or single-valued)
|
||||
if ( ! $relation->isOneToOne()) {
|
||||
if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) {
|
||||
$oneToOne = false;
|
||||
if (isset($nonemptyComponents[$dqlAlias])) {
|
||||
if ( ! isset($baseElement[$relationAlias])) {
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
namespace Doctrine\ORM\Internal\Hydration;
|
||||
|
||||
use PDO,
|
||||
Doctrine\ORM\Mapping\ClassMetadata,
|
||||
Doctrine\ORM\PersistentCollection,
|
||||
Doctrine\ORM\Query,
|
||||
Doctrine\Common\Collections\ArrayCollection,
|
||||
@@ -38,9 +39,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;
|
||||
@@ -49,7 +50,7 @@ class ObjectHydrator extends AbstractHydrator
|
||||
private $_initializedCollections = array();
|
||||
private $_existingCollections = array();
|
||||
//private $_createdEntities;
|
||||
|
||||
|
||||
|
||||
/** @override */
|
||||
protected function _prepare()
|
||||
@@ -58,6 +59,9 @@ 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();
|
||||
@@ -67,31 +71,35 @@ 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->sourceFieldName] = true;
|
||||
$this->_hints['fetched'][$sourceClassName][$assoc['fieldName']] = true;
|
||||
if ($sourceClass->subClasses) {
|
||||
foreach ($sourceClass->subClasses as $sourceSubclassName) {
|
||||
$this->_hints['fetched'][$sourceSubclassName][$assoc->sourceFieldName] = true;
|
||||
$this->_hints['fetched'][$sourceSubclassName][$assoc['fieldName']] = true;
|
||||
}
|
||||
}
|
||||
if ( ! $assoc->isManyToMany()) {
|
||||
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;
|
||||
if ($assoc['mappedBy']) {
|
||||
$this->_hints['fetched'][$className][$assoc['mappedBy']] = true;
|
||||
} else {
|
||||
if ($assoc->inversedBy) {
|
||||
$inverseAssoc = $class->associationMappings[$assoc->inversedBy];
|
||||
if ($inverseAssoc->isOneToOne()) {
|
||||
$this->_hints['fetched'][$className][$inverseAssoc->sourceFieldName] = true;
|
||||
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->sourceFieldName] = true;
|
||||
$this->_hints['fetched'][$targetSubclassName][$inverseAssoc['fieldName']] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,11 +115,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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,36 +158,38 @@ class ObjectHydrator extends AbstractHydrator
|
||||
{
|
||||
$oid = spl_object_hash($entity);
|
||||
$relation = $class->associationMappings[$fieldName];
|
||||
|
||||
|
||||
$value = $class->reflFields[$fieldName]->getValue($entity);
|
||||
if ($value === null) {
|
||||
$value = new ArrayCollection;
|
||||
}
|
||||
|
||||
|
||||
if ( ! $value instanceof PersistentCollection) {
|
||||
$value = new PersistentCollection(
|
||||
$this->_em,
|
||||
$this->_ce[$relation->targetEntityName],
|
||||
$this->_ce[$relation['targetEntity']],
|
||||
$value
|
||||
);
|
||||
$value->setOwner($entity, $relation);
|
||||
$class->reflFields[$fieldName]->setValue($entity, $value);
|
||||
$this->_uow->setOriginalEntityProperty($oid, $fieldName, $value);
|
||||
$this->_initializedCollections[$oid . $fieldName] = $value;
|
||||
} else if (isset($this->_hints[Query::HINT_REFRESH])) {
|
||||
// Is already PersistentCollection, but REFRESH
|
||||
} else if (isset($this->_hints[Query::HINT_REFRESH]) ||
|
||||
isset($this->_hints['fetched'][$class->name][$fieldName]) &&
|
||||
! $value->isInitialized()) {
|
||||
// Is already PersistentCollection, but either REFRESH or FETCH-JOIN and UNINITIALIZED!
|
||||
$value->setDirty(false);
|
||||
$value->setInitialized(true);
|
||||
$value->unwrap()->clear();
|
||||
$this->_initializedCollections[$oid . $fieldName] = $value;
|
||||
} else {
|
||||
// Is already PersistentCollection, and DONT REFRESH
|
||||
// Is already PersistentCollection, and DON'T REFRESH or FETCH-JOIN!
|
||||
$this->_existingCollections[$oid . $fieldName] = $value;
|
||||
}
|
||||
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets an entity instance.
|
||||
*
|
||||
@@ -183,7 +199,7 @@ class ObjectHydrator extends AbstractHydrator
|
||||
*/
|
||||
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]];
|
||||
@@ -191,7 +207,7 @@ class ObjectHydrator extends AbstractHydrator
|
||||
}
|
||||
return $this->_uow->createEntity($className, $data, $this->_hints);
|
||||
}
|
||||
|
||||
|
||||
private function _getEntityFromIdentityMap($className, array $data)
|
||||
{
|
||||
$class = $this->_ce[$className];
|
||||
@@ -205,7 +221,7 @@ class ObjectHydrator extends AbstractHydrator
|
||||
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
|
||||
@@ -263,15 +279,18 @@ 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
|
||||
|
||||
$parentAlias = $this->_rsm->parentAliasMap[$dqlAlias];
|
||||
// we need the $path to save into the identifier map which entities were already
|
||||
// seen for this parent-child relationship
|
||||
$path = $parentAlias . '.' . $dqlAlias;
|
||||
|
||||
// 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);
|
||||
$first = reset($this->_resultPointers);
|
||||
$parentObject = $this->_resultPointers[$parentAlias][key($first)];
|
||||
} else if (isset($this->_resultPointers[$parentAlias])) {
|
||||
$parentObject = $this->_resultPointers[$parentAlias];
|
||||
@@ -285,9 +304,9 @@ class ObjectHydrator extends AbstractHydrator
|
||||
$relationField = $this->_rsm->relationMap[$dqlAlias];
|
||||
$relation = $parentClass->associationMappings[$relationField];
|
||||
$reflField = $parentClass->reflFields[$relationField];
|
||||
|
||||
|
||||
// Check the type of the relation (many or single-valued)
|
||||
if ( ! $relation->isOneToOne()) {
|
||||
if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) {
|
||||
// PATH A: Collection-valued association
|
||||
if (isset($nonemptyComponents[$dqlAlias])) {
|
||||
$collKey = $oid . $relationField;
|
||||
@@ -296,11 +315,11 @@ class ObjectHydrator extends AbstractHydrator
|
||||
} else if ( ! isset($this->_existingCollections[$collKey])) {
|
||||
$reflFieldValue = $this->_initRelatedCollection($parentObject, $parentClass, $relationField);
|
||||
}
|
||||
|
||||
$indexExists = isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]]);
|
||||
$index = $indexExists ? $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] : false;
|
||||
|
||||
$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.
|
||||
@@ -316,11 +335,11 @@ class ObjectHydrator extends AbstractHydrator
|
||||
$field = $this->_rsm->indexByMap[$dqlAlias];
|
||||
$indexValue = $this->_ce[$entityName]->reflFields[$field]->getValue($element);
|
||||
$reflFieldValue->hydrateSet($indexValue, $element);
|
||||
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $indexValue;
|
||||
$this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $indexValue;
|
||||
} else {
|
||||
$reflFieldValue->hydrateAdd($element);
|
||||
$reflFieldValue->last();
|
||||
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $reflFieldValue->key();
|
||||
$this->_identifierMap[$path][$id[$parentAlias]][$id[$dqlAlias]] = $reflFieldValue->key();
|
||||
}
|
||||
// Update result pointer
|
||||
$this->_resultPointers[$dqlAlias] = $element;
|
||||
@@ -331,6 +350,7 @@ class ObjectHydrator extends AbstractHydrator
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
@@ -342,24 +362,24 @@ class ObjectHydrator extends AbstractHydrator
|
||||
$element = $this->_getEntity($data, $dqlAlias);
|
||||
$reflField->setValue($parentObject, $element);
|
||||
$this->_uow->setOriginalEntityProperty($oid, $relationField, $element);
|
||||
$targetClass = $this->_ce[$relation->targetEntityName];
|
||||
if ($relation->isOwningSide) {
|
||||
$targetClass = $this->_ce[$relation['targetEntity']];
|
||||
if ($relation['isOwningSide']) {
|
||||
//TODO: Just check hints['fetched'] here?
|
||||
// If there is an inverse mapping on the target class its bidirectional
|
||||
if ($relation->inversedBy) {
|
||||
$inverseAssoc = $targetClass->associationMappings[$relation->inversedBy];
|
||||
if ($inverseAssoc->isOneToOne()) {
|
||||
$targetClass->reflFields[$inverseAssoc->sourceFieldName]->setValue($element, $parentObject);
|
||||
$this->_uow->setOriginalEntityProperty(spl_object_hash($element), $inverseAssoc->sourceFieldName, $parentObject);
|
||||
if ($relation['inversedBy']) {
|
||||
$inverseAssoc = $targetClass->associationMappings[$relation['inversedBy']];
|
||||
if ($inverseAssoc['type'] & ClassMetadata::TO_ONE) {
|
||||
$targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($element, $parentObject);
|
||||
$this->_uow->setOriginalEntityProperty(spl_object_hash($element), $inverseAssoc['fieldName'], $parentObject);
|
||||
}
|
||||
} else if ($parentClass === $targetClass && $relation->mappedBy) {
|
||||
} else if ($parentClass === $targetClass && $relation['mappedBy']) {
|
||||
// Special case: bi-directional self-referencing one-one on the same class
|
||||
$targetClass->reflFields[$relationField]->setValue($element, $parentObject);
|
||||
}
|
||||
} else {
|
||||
// For sure bidirectional, as there is no inverse side in unidirectional mappings
|
||||
$targetClass->reflFields[$relation->mappedBy]->setValue($element, $parentObject);
|
||||
$this->_uow->setOriginalEntityProperty(spl_object_hash($element), $relation->mappedBy, $parentObject);
|
||||
$targetClass->reflFields[$relation['mappedBy']]->setValue($element, $parentObject);
|
||||
$this->_uow->setOriginalEntityProperty(spl_object_hash($element), $relation['mappedBy'], $parentObject);
|
||||
}
|
||||
// Update result pointer
|
||||
$this->_resultPointers[$dqlAlias] = $element;
|
||||
@@ -388,6 +408,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);
|
||||
@@ -395,6 +419,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
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
<?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;
|
||||
|
||||
class SimpleObjectHydrator extends AbstractHydrator
|
||||
{
|
||||
const REFRESH_ENTITY = 'doctrine_refresh_entity';
|
||||
|
||||
/**
|
||||
* @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[self::REFRESH_ENTITY])) {
|
||||
$this->_hints[Query::HINT_REFRESH] = true;
|
||||
$id = array();
|
||||
if ($this->_class->isIdentifierComposite) {
|
||||
foreach ($this->_class->identifier as $fieldName) {
|
||||
$id[$fieldName] = $data[$fieldName];
|
||||
}
|
||||
} else {
|
||||
$id = array($this->_class->identifier[0] => $data[$this->_class->identifier[0]]);
|
||||
}
|
||||
$this->_em->getUnitOfWork()->registerManaged($this->_hints[self::REFRESH_ENTITY], $id, $data);
|
||||
}
|
||||
|
||||
$result[] = $this->_em->getUnitOfWork()->createEntity($entityName, $data, $this->_hints);
|
||||
}
|
||||
}
|
||||
@@ -1,399 +0,0 @@
|
||||
<?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\Mapping;
|
||||
|
||||
/**
|
||||
* Base class for association mappings.
|
||||
*
|
||||
* <b>IMPORTANT NOTE:</b>
|
||||
*
|
||||
* The fields of this class are only public for 2 reasons:
|
||||
* 1) To allow fast, internal READ access.
|
||||
* 2) To drastically reduce the size of a serialized instance (private/protected members
|
||||
* get the whole class name, namespace inclusive, prepended to every property in
|
||||
* the serialized representation).
|
||||
*
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
* @since 2.0
|
||||
* @todo Potentially remove if assoc mapping objects get replaced by simple arrays.
|
||||
*/
|
||||
abstract class AssociationMapping
|
||||
{
|
||||
/**
|
||||
* Specifies that an association is to be fetched when it is first accessed.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const FETCH_LAZY = 2;
|
||||
/**
|
||||
* Specifies that an association is to be fetched when the owner of the
|
||||
* association is fetched.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const FETCH_EAGER = 3;
|
||||
|
||||
/**
|
||||
* READ-ONLY: Whether the association cascades delete() operations from the source entity
|
||||
* to the target entity/entities.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $isCascadeRemove;
|
||||
|
||||
/**
|
||||
* READ-ONLY: Whether the association cascades persist() operations from the source entity
|
||||
* to the target entity/entities.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $isCascadePersist;
|
||||
|
||||
/**
|
||||
* READ-ONLY: Whether the association cascades refresh() operations from the source entity
|
||||
* to the target entity/entities.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $isCascadeRefresh;
|
||||
|
||||
/**
|
||||
* READ-ONLY: Whether the association cascades merge() operations from the source entity
|
||||
* to the target entity/entities.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $isCascadeMerge;
|
||||
|
||||
/**
|
||||
* READ-ONLY: Whether the association cascades detach() operations from the source entity
|
||||
* to the target entity/entities.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $isCascadeDetach;
|
||||
|
||||
/**
|
||||
* READ-ONLY: The fetch mode used for the association.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $fetchMode;
|
||||
|
||||
/**
|
||||
* READ-ONLY: Flag that indicates whether the class that defines this mapping is
|
||||
* the owning side of the association.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $isOwningSide = true;
|
||||
|
||||
/**
|
||||
* READ-ONLY: The name of the source Entity (the Entity that defines this mapping).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $sourceEntityName;
|
||||
|
||||
/**
|
||||
* READ-ONLY: The name of the target Entity (the Enitity that is the target of the
|
||||
* association).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $targetEntityName;
|
||||
|
||||
/**
|
||||
* READ-ONLY: Identifies the field on the source class (the class this AssociationMapping
|
||||
* belongs to) that represents the association and stores the reference to the
|
||||
* other entity/entities.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $sourceFieldName;
|
||||
|
||||
/**
|
||||
* READ-ONLY: Identifies the field on the owning side of a bidirectional association that
|
||||
* controls the mapping for the association. This is only set on the inverse side
|
||||
* of an association.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $mappedBy;
|
||||
|
||||
/**
|
||||
* READ-ONLY: Identifies the field on the inverse side of a bidirectional association.
|
||||
* This is only set on the owning side of an association.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $inversedBy;
|
||||
|
||||
/**
|
||||
* READ-ONLY: The join table definition, if any.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $joinTable;
|
||||
|
||||
/**
|
||||
* READ-ONLY: The name of the entity class from which the association was
|
||||
* inherited in an inheritance hierarchy.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $inherited;
|
||||
|
||||
/**
|
||||
* READ-ONLY: The name of the entity or mapped superclass that declares
|
||||
* the association field in an inheritance hierarchy.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $declared;
|
||||
|
||||
/**
|
||||
* Initializes a new instance of a class derived from AssociationMapping.
|
||||
*
|
||||
* @param array $mapping The mapping definition.
|
||||
*/
|
||||
public function __construct(array $mapping)
|
||||
{
|
||||
$this->_validateAndCompleteMapping($mapping);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates & completes the mapping. Mapping defaults are applied here.
|
||||
*
|
||||
* @param array $mapping
|
||||
* @throws MappingException If something is wrong with the mapping.
|
||||
*/
|
||||
protected function _validateAndCompleteMapping(array $mapping)
|
||||
{
|
||||
// Mandatory attributes for both sides
|
||||
if ( ! isset($mapping['fieldName'])) {
|
||||
throw MappingException::missingFieldName();
|
||||
}
|
||||
$this->sourceFieldName = $mapping['fieldName'];
|
||||
|
||||
if ( ! isset($mapping['sourceEntity'])) {
|
||||
throw MappingException::missingSourceEntity($mapping['fieldName']);
|
||||
}
|
||||
$this->sourceEntityName = $mapping['sourceEntity'];
|
||||
|
||||
if ( ! isset($mapping['targetEntity'])) {
|
||||
throw MappingException::missingTargetEntity($mapping['fieldName']);
|
||||
}
|
||||
$this->targetEntityName = $mapping['targetEntity'];
|
||||
|
||||
// Mandatory and optional attributes for either side
|
||||
if ( ! isset($mapping['mappedBy'])) {
|
||||
// Optional
|
||||
if (isset($mapping['joinTable']) && $mapping['joinTable']) {
|
||||
if ($mapping['joinTable']['name'][0] == '`') {
|
||||
$mapping['joinTable']['name'] = trim($mapping['joinTable']['name'], '`');
|
||||
$mapping['joinTable']['quoted'] = true;
|
||||
}
|
||||
$this->joinTable = $mapping['joinTable'];
|
||||
}
|
||||
if (isset($mapping['inversedBy'])) {
|
||||
$this->inversedBy = $mapping['inversedBy'];
|
||||
}
|
||||
} else {
|
||||
$this->isOwningSide = false;
|
||||
$this->mappedBy = $mapping['mappedBy'];
|
||||
}
|
||||
|
||||
// Optional attributes for both sides
|
||||
$this->fetchMode = isset($mapping['fetch']) ? $mapping['fetch'] : self::FETCH_LAZY;
|
||||
$cascades = isset($mapping['cascade']) ? $mapping['cascade'] : array();
|
||||
|
||||
if (in_array('all', $cascades)) {
|
||||
$cascades = array(
|
||||
'remove',
|
||||
'persist',
|
||||
'refresh',
|
||||
'merge',
|
||||
'detach'
|
||||
);
|
||||
}
|
||||
|
||||
$this->isCascadeRemove = in_array('remove', $cascades);
|
||||
$this->isCascadePersist = in_array('persist', $cascades);
|
||||
$this->isCascadeRefresh = in_array('refresh', $cascades);
|
||||
$this->isCascadeMerge = in_array('merge', $cascades);
|
||||
$this->isCascadeDetach = in_array('detach', $cascades);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the target entity/entities of the association are eagerly fetched.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isEagerlyFetched()
|
||||
{
|
||||
return $this->fetchMode == self::FETCH_EAGER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the target entity/entities of the association are lazily fetched.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isLazilyFetched()
|
||||
{
|
||||
return $this->fetchMode == self::FETCH_LAZY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the association is a one-to-one association.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isOneToOne()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the association is a one-to-many association.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isOneToMany()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the association is a many-to-many association.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isManyToMany()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the association uses a join table for the mapping.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function usesJoinTable()
|
||||
{
|
||||
return (bool) $this->joinTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the association has any cascades configured.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasCascades()
|
||||
{
|
||||
return $this->isCascadePersist ||
|
||||
$this->isCascadeRemove ||
|
||||
$this->isCascadeRefresh ||
|
||||
$this->isCascadeMerge ||
|
||||
$this->isCascadeDetach;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads data in $target domain object using this association.
|
||||
* The data comes from the association navigated from $sourceEntity
|
||||
* using $em.
|
||||
*
|
||||
* @param object $sourceEntity
|
||||
* @param object $target an entity or a collection
|
||||
* @param EntityManager $em
|
||||
* @param array $joinColumnValues foreign keys (significative for this
|
||||
* association) of $sourceEntity, if needed
|
||||
*/
|
||||
abstract public function load($sourceEntity, $target, $em, array $joinColumnValues = array());
|
||||
|
||||
/**
|
||||
* Gets the (possibly quoted) name of the join table.
|
||||
*
|
||||
* @param AbstractPlatform $platform
|
||||
* @return string
|
||||
*/
|
||||
public function getQuotedJoinTableName($platform)
|
||||
{
|
||||
return isset($this->joinTable['quoted'])
|
||||
? $platform->quoteIdentifier($this->joinTable['name'])
|
||||
: $this->joinTable['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines which fields get serialized.
|
||||
*
|
||||
* It is only serialized what is necessary for best unserialization performance.
|
||||
* That means any metadata properties that are not set or empty or simply have
|
||||
* their default value are NOT serialized.
|
||||
*
|
||||
* @return array The names of all the fields that should be serialized.
|
||||
*/
|
||||
public function __sleep()
|
||||
{
|
||||
$serialized = array(
|
||||
'sourceEntityName',
|
||||
'targetEntityName',
|
||||
'sourceFieldName',
|
||||
'fetchMode'
|
||||
);
|
||||
|
||||
if ($this->isCascadeDetach) {
|
||||
$serialized[] = 'isCascadeDetach';
|
||||
}
|
||||
if ($this->isCascadeMerge) {
|
||||
$serialized[] = 'isCascadeMerge';
|
||||
}
|
||||
if ($this->isCascadePersist) {
|
||||
$serialized[] = 'isCascadePersist';
|
||||
}
|
||||
if ($this->isCascadeRefresh) {
|
||||
$serialized[] = 'isCascadeRefresh';
|
||||
}
|
||||
if ($this->isCascadeRemove) {
|
||||
$serialized[] = 'isCascadeRemove';
|
||||
}
|
||||
if ( ! $this->isOwningSide) {
|
||||
$serialized[] = 'isOwningSide';
|
||||
}
|
||||
if ($this->mappedBy) {
|
||||
$serialized[] = 'mappedBy';
|
||||
}
|
||||
if ($this->inversedBy) {
|
||||
$serialized[] = 'inversedBy';
|
||||
}
|
||||
if ($this->joinTable) {
|
||||
$serialized[] = 'joinTable';
|
||||
}
|
||||
if ($this->inherited) {
|
||||
$serialized[] = 'inherited';
|
||||
}
|
||||
if ($this->declared) {
|
||||
$serialized[] = 'declared';
|
||||
}
|
||||
|
||||
return $serialized;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,7 +143,11 @@ class ClassMetadata extends ClassMetadataInfo
|
||||
}
|
||||
return $id;
|
||||
} else {
|
||||
return array($this->identifier[0] => $this->reflFields[$this->identifier[0]]->getValue($entity));
|
||||
$value = $this->reflFields[$this->identifier[0]]->getValue($entity);
|
||||
if ($value !== null) {
|
||||
return array($this->identifier[0] => $value);
|
||||
}
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,14 +158,10 @@ class ClassMetadata extends ClassMetadataInfo
|
||||
* @param mixed $id
|
||||
* @todo Rename to assignIdentifier()
|
||||
*/
|
||||
public function setIdentifierValues($entity, $id)
|
||||
public function setIdentifierValues($entity, array $id)
|
||||
{
|
||||
if ($this->isIdentifierComposite) {
|
||||
foreach ($id as $idField => $idValue) {
|
||||
$this->reflFields[$idField]->setValue($entity, $idValue);
|
||||
}
|
||||
} else {
|
||||
$this->reflFields[$this->identifier[0]]->setValue($entity, $id);
|
||||
foreach ($id as $idField => $idValue) {
|
||||
$this->reflFields[$idField]->setValue($entity, $idValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,47 +193,18 @@ class ClassMetadata extends ClassMetadataInfo
|
||||
*
|
||||
* @param AssociationMapping $assocMapping
|
||||
*/
|
||||
protected function _storeAssociationMapping(AssociationMapping $assocMapping)
|
||||
protected function _storeAssociationMapping(array $assocMapping)
|
||||
{
|
||||
parent::_storeAssociationMapping($assocMapping);
|
||||
|
||||
// Store ReflectionProperty of mapped field
|
||||
$sourceFieldName = $assocMapping->sourceFieldName;
|
||||
$sourceFieldName = $assocMapping['fieldName'];
|
||||
|
||||
$refProp = $this->reflClass->getProperty($sourceFieldName);
|
||||
$refProp->setAccessible(true);
|
||||
$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'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a string representation of this instance.
|
||||
*
|
||||
@@ -304,6 +275,10 @@ class ClassMetadata extends ClassMetadataInfo
|
||||
$serialized[] = 'isMappedSuperclass';
|
||||
}
|
||||
|
||||
if ($this->containsForeignIdentifier) {
|
||||
$serialized[] = 'containsForeignIdentifier';
|
||||
}
|
||||
|
||||
if ($this->isVersioned) {
|
||||
$serialized[] = 'isVersioned';
|
||||
$serialized[] = 'versionField';
|
||||
@@ -313,6 +288,14 @@ class ClassMetadata extends ClassMetadataInfo
|
||||
$serialized[] = 'lifecycleCallbacks';
|
||||
}
|
||||
|
||||
if ($this->namedQueries) {
|
||||
$serialized[] = 'namedQueries';
|
||||
}
|
||||
|
||||
if ($this->isReadOnly) {
|
||||
$serialized[] = 'isReadOnly';
|
||||
}
|
||||
|
||||
return $serialized;
|
||||
}
|
||||
|
||||
@@ -337,8 +320,8 @@ class ClassMetadata extends ClassMetadataInfo
|
||||
}
|
||||
|
||||
foreach ($this->associationMappings as $field => $mapping) {
|
||||
if ($mapping->declared) {
|
||||
$reflField = new ReflectionProperty($mapping->declared, $field);
|
||||
if (isset($mapping['declared'])) {
|
||||
$reflField = new ReflectionProperty($mapping['declared'], $field);
|
||||
} else {
|
||||
$reflField = $this->reflClass->getProperty($field);
|
||||
}
|
||||
|
||||
@@ -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,28 +37,49 @@ use ReflectionException,
|
||||
* @author Jonathan Wage <jonwage@gmail.com>
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
*/
|
||||
class ClassMetadataFactory
|
||||
class ClassMetadataFactory implements ClassMetadataFactoryInterface
|
||||
{
|
||||
private $_em;
|
||||
/** The targeted database platform. */
|
||||
private $_targetPlatform;
|
||||
/** The used metadata driver. */
|
||||
private $_driver;
|
||||
/** The event manager instance */
|
||||
private $_evm;
|
||||
/** The used cache driver. */
|
||||
private $_cacheDriver;
|
||||
private $_loadedMetadata = array();
|
||||
private $_initialized = false;
|
||||
/**
|
||||
* @var EntityManager
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* Creates a new factory instance that uses the given metadata driver implementation.
|
||||
*
|
||||
* @param $driver The metadata driver to use.
|
||||
* @var AbstractPlatform
|
||||
*/
|
||||
public function __construct(EntityManager $em)
|
||||
private $targetPlatform;
|
||||
|
||||
/**
|
||||
* @var Driver\Driver
|
||||
*/
|
||||
private $driver;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\Common\EventManager
|
||||
*/
|
||||
private $evm;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\Common\Cache\Cache
|
||||
*/
|
||||
private $cacheDriver;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $loadedMetadata = array();
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $initialized = false;
|
||||
|
||||
/**
|
||||
* @param EntityManager $$em
|
||||
*/
|
||||
public function setEntityManager(EntityManager $em)
|
||||
{
|
||||
$this->_em = $em;
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,7 +89,7 @@ class ClassMetadataFactory
|
||||
*/
|
||||
public function setCacheDriver($cacheDriver)
|
||||
{
|
||||
$this->_cacheDriver = $cacheDriver;
|
||||
$this->cacheDriver = $cacheDriver;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,12 +99,12 @@ class ClassMetadataFactory
|
||||
*/
|
||||
public function getCacheDriver()
|
||||
{
|
||||
return $this->_cacheDriver;
|
||||
return $this->cacheDriver;
|
||||
}
|
||||
|
||||
public function getLoadedMetadata()
|
||||
{
|
||||
return $this->_loadedMetadata;
|
||||
return $this->loadedMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,12 +115,12 @@ class ClassMetadataFactory
|
||||
*/
|
||||
public function getAllMetadata()
|
||||
{
|
||||
if ( ! $this->_initialized) {
|
||||
$this->_initialize();
|
||||
if ( ! $this->initialized) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
$metadata = array();
|
||||
foreach ($this->_driver->getAllClassNames() as $className) {
|
||||
foreach ($this->driver->getAllClassNames() as $className) {
|
||||
$metadata[] = $this->getMetadataFor($className);
|
||||
}
|
||||
|
||||
@@ -109,12 +131,12 @@ class ClassMetadataFactory
|
||||
* Lazy initialization of this stuff, especially the metadata driver,
|
||||
* since these are not needed at all when a metadata cache is active.
|
||||
*/
|
||||
private function _initialize()
|
||||
private function initialize()
|
||||
{
|
||||
$this->_driver = $this->_em->getConfiguration()->getMetadataDriverImpl();
|
||||
$this->_targetPlatform = $this->_em->getConnection()->getDatabasePlatform();
|
||||
$this->_evm = $this->_em->getEventManager();
|
||||
$this->_initialized = true;
|
||||
$this->driver = $this->em->getConfiguration()->getMetadataDriverImpl();
|
||||
$this->targetPlatform = $this->em->getConnection()->getDatabasePlatform();
|
||||
$this->evm = $this->em->getEventManager();
|
||||
$this->initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,43 +147,43 @@ class ClassMetadataFactory
|
||||
*/
|
||||
public function getMetadataFor($className)
|
||||
{
|
||||
if ( ! isset($this->_loadedMetadata[$className])) {
|
||||
if ( ! isset($this->loadedMetadata[$className])) {
|
||||
$realClassName = $className;
|
||||
|
||||
// Check for namespace alias
|
||||
if (strpos($className, ':') !== false) {
|
||||
list($namespaceAlias, $simpleClassName) = explode(':', $className);
|
||||
$realClassName = $this->_em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
|
||||
$realClassName = $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
|
||||
|
||||
if (isset($this->_loadedMetadata[$realClassName])) {
|
||||
if (isset($this->loadedMetadata[$realClassName])) {
|
||||
// We do not have the alias name in the map, include it
|
||||
$this->_loadedMetadata[$className] = $this->_loadedMetadata[$realClassName];
|
||||
$this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];
|
||||
|
||||
return $this->_loadedMetadata[$realClassName];
|
||||
return $this->loadedMetadata[$realClassName];
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->_cacheDriver) {
|
||||
if (($cached = $this->_cacheDriver->fetch("$realClassName\$CLASSMETADATA")) !== false) {
|
||||
$this->_loadedMetadata[$realClassName] = $cached;
|
||||
if ($this->cacheDriver) {
|
||||
if (($cached = $this->cacheDriver->fetch("$realClassName\$CLASSMETADATA")) !== false) {
|
||||
$this->loadedMetadata[$realClassName] = $cached;
|
||||
} else {
|
||||
foreach ($this->_loadMetadata($realClassName) as $loadedClassName) {
|
||||
$this->_cacheDriver->save(
|
||||
"$loadedClassName\$CLASSMETADATA", $this->_loadedMetadata[$loadedClassName], null
|
||||
foreach ($this->loadMetadata($realClassName) as $loadedClassName) {
|
||||
$this->cacheDriver->save(
|
||||
"$loadedClassName\$CLASSMETADATA", $this->loadedMetadata[$loadedClassName], null
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->_loadMetadata($realClassName);
|
||||
$this->loadMetadata($realClassName);
|
||||
}
|
||||
|
||||
if ($className != $realClassName) {
|
||||
// We do not have the alias name in the map, include it
|
||||
$this->_loadedMetadata[$className] = $this->_loadedMetadata[$realClassName];
|
||||
$this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->_loadedMetadata[$className];
|
||||
return $this->loadedMetadata[$className];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -172,7 +194,7 @@ class ClassMetadataFactory
|
||||
*/
|
||||
public function hasMetadataFor($className)
|
||||
{
|
||||
return isset($this->_loadedMetadata[$className]);
|
||||
return isset($this->loadedMetadata[$className]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,7 +207,7 @@ class ClassMetadataFactory
|
||||
*/
|
||||
public function setMetadataFor($className, $class)
|
||||
{
|
||||
$this->_loadedMetadata[$className] = $class;
|
||||
$this->loadedMetadata[$className] = $class;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -194,12 +216,12 @@ class ClassMetadataFactory
|
||||
* @param string $name
|
||||
* @return array $parentClasses
|
||||
*/
|
||||
protected function _getParentClasses($name)
|
||||
protected function getParentClasses($name)
|
||||
{
|
||||
// Collect parent classes, ignoring transient (not-mapped) classes.
|
||||
$parentClasses = array();
|
||||
foreach (array_reverse(class_parents($name)) as $parentClass) {
|
||||
if ( ! $this->_driver->isTransient($parentClass)) {
|
||||
if ( ! $this->driver->isTransient($parentClass)) {
|
||||
$parentClasses[] = $parentClass;
|
||||
}
|
||||
}
|
||||
@@ -213,55 +235,52 @@ class ClassMetadataFactory
|
||||
* @param string $name The name of the class for which the metadata should get loaded.
|
||||
* @param array $tables The metadata collection to which the loaded metadata is added.
|
||||
*/
|
||||
protected function _loadMetadata($name)
|
||||
protected function loadMetadata($name)
|
||||
{
|
||||
if ( ! $this->_initialized) {
|
||||
$this->_initialize();
|
||||
if ( ! $this->initialized) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
$loaded = array();
|
||||
|
||||
$parentClasses = $this->_getParentClasses($name);
|
||||
$parentClasses = $this->getParentClasses($name);
|
||||
$parentClasses[] = $name;
|
||||
|
||||
// Move down the hierarchy of parent classes, starting from the topmost class
|
||||
$parent = null;
|
||||
$visited = array();
|
||||
foreach ($parentClasses as $className) {
|
||||
if (isset($this->_loadedMetadata[$className])) {
|
||||
$parent = $this->_loadedMetadata[$className];
|
||||
if (isset($this->loadedMetadata[$className])) {
|
||||
$parent = $this->loadedMetadata[$className];
|
||||
if ( ! $parent->isMappedSuperclass) {
|
||||
array_unshift($visited, $className);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$class = $this->_newClassMetadataInstance($className);
|
||||
$class = $this->newClassMetadataInstance($className);
|
||||
|
||||
if ($parent) {
|
||||
$class->setInheritanceType($parent->inheritanceType);
|
||||
$class->setDiscriminatorColumn($parent->discriminatorColumn);
|
||||
$class->setIdGeneratorType($parent->generatorType);
|
||||
$this->_addInheritedFields($class, $parent);
|
||||
$this->_addInheritedRelations($class, $parent);
|
||||
$this->addInheritedFields($class, $parent);
|
||||
$this->addInheritedRelations($class, $parent);
|
||||
$class->setIdentifier($parent->identifier);
|
||||
$class->setVersioned($parent->isVersioned);
|
||||
$class->setVersionField($parent->versionField);
|
||||
$class->setDiscriminatorMap($parent->discriminatorMap);
|
||||
$class->setLifecycleCallbacks($parent->lifecycleCallbacks);
|
||||
$class->setChangeTrackingPolicy($parent->changeTrackingPolicy);
|
||||
}
|
||||
|
||||
// Invoke driver
|
||||
try {
|
||||
$this->_driver->loadMetadataForClass($className, $class);
|
||||
} catch(ReflectionException $e) {
|
||||
$this->driver->loadMetadataForClass($className, $class);
|
||||
} catch (ReflectionException $e) {
|
||||
throw MappingException::reflectionFailure($className, $e);
|
||||
}
|
||||
|
||||
// Verify & complete identifier mapping
|
||||
if ( ! $class->identifier && ! $class->isMappedSuperclass) {
|
||||
throw MappingException::identifierRequired($className);
|
||||
}
|
||||
if ($parent && ! $parent->isMappedSuperclass) {
|
||||
if ($parent->isIdGeneratorSequence()) {
|
||||
$class->setSequenceGeneratorDefinition($parent->sequenceGeneratorDefinition);
|
||||
@@ -275,7 +294,7 @@ class ClassMetadataFactory
|
||||
$class->setIdGenerator($parent->idGenerator);
|
||||
}
|
||||
} else {
|
||||
$this->_completeIdGeneratorMapping($class);
|
||||
$this->completeIdGeneratorMapping($class);
|
||||
}
|
||||
|
||||
if ($parent && $parent->isInheritanceTypeSingleTable()) {
|
||||
@@ -284,12 +303,35 @@ class ClassMetadataFactory
|
||||
|
||||
$class->setParentClasses($visited);
|
||||
|
||||
if ($this->_evm->hasListeners(Events::loadClassMetadata)) {
|
||||
$eventArgs = new \Doctrine\ORM\Event\LoadClassMetadataEventArgs($class);
|
||||
$this->_evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
|
||||
if ($this->evm->hasListeners(Events::loadClassMetadata)) {
|
||||
$eventArgs = new \Doctrine\ORM\Event\LoadClassMetadataEventArgs($class, $this->em);
|
||||
$this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
|
||||
}
|
||||
|
||||
$this->_loadedMetadata[$className] = $class;
|
||||
// Verify & complete identifier mapping
|
||||
if ( ! $class->identifier && ! $class->isMappedSuperclass) {
|
||||
throw MappingException::identifierRequired($className);
|
||||
}
|
||||
|
||||
// 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 && !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);
|
||||
}
|
||||
|
||||
$this->loadedMetadata[$className] = $class;
|
||||
|
||||
$parent = $class;
|
||||
|
||||
@@ -309,18 +351,18 @@ class ClassMetadataFactory
|
||||
* @param string $className
|
||||
* @return Doctrine\ORM\Mapping\ClassMetadata
|
||||
*/
|
||||
protected function _newClassMetadataInstance($className)
|
||||
protected function newClassMetadataInstance($className)
|
||||
{
|
||||
return new ClassMetadata($className);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds inherited fields to the subclass mapping.
|
||||
*
|
||||
* @param Doctrine\ORM\Mapping\ClassMetadata $subClass
|
||||
* @param Doctrine\ORM\Mapping\ClassMetadata $parentClass
|
||||
*/
|
||||
private function _addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass)
|
||||
private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass)
|
||||
{
|
||||
foreach ($parentClass->fieldMappings as $fieldName => $mapping) {
|
||||
if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
|
||||
@@ -342,17 +384,24 @@ class ClassMetadataFactory
|
||||
* @param Doctrine\ORM\Mapping\ClassMetadata $subClass
|
||||
* @param Doctrine\ORM\Mapping\ClassMetadata $parentClass
|
||||
*/
|
||||
private function _addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass)
|
||||
private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass)
|
||||
{
|
||||
foreach ($parentClass->associationMappings as $field => $mapping) {
|
||||
$subclassMapping = clone $mapping;
|
||||
if ( ! isset($mapping->inherited) && ! $parentClass->isMappedSuperclass) {
|
||||
$subclassMapping->inherited = $parentClass->name;
|
||||
if ($parentClass->isMappedSuperclass) {
|
||||
if ($mapping['type'] & ClassMetadata::TO_MANY && !$mapping['isOwningSide']) {
|
||||
throw MappingException::illegalToManyAssocationOnMappedSuperclass($parentClass->name, $field);
|
||||
}
|
||||
$mapping['sourceEntity'] = $subClass->name;
|
||||
}
|
||||
if ( ! isset($mapping->declared)) {
|
||||
$subclassMapping->declared = $parentClass->name;
|
||||
|
||||
//$subclassMapping = $mapping;
|
||||
if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
|
||||
$mapping['inherited'] = $parentClass->name;
|
||||
}
|
||||
$subClass->addInheritedAssociationMapping($subclassMapping);
|
||||
if ( ! isset($mapping['declared'])) {
|
||||
$mapping['declared'] = $parentClass->name;
|
||||
}
|
||||
$subClass->addInheritedAssociationMapping($mapping);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,13 +411,13 @@ class ClassMetadataFactory
|
||||
*
|
||||
* @param Doctrine\ORM\Mapping\ClassMetadata $class
|
||||
*/
|
||||
private function _completeIdGeneratorMapping(ClassMetadataInfo $class)
|
||||
private function completeIdGeneratorMapping(ClassMetadataInfo $class)
|
||||
{
|
||||
$idGenType = $class->generatorType;
|
||||
if ($idGenType == ClassMetadata::GENERATOR_TYPE_AUTO) {
|
||||
if ($this->_targetPlatform->prefersSequences()) {
|
||||
if ($this->targetPlatform->prefersSequences()) {
|
||||
$class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_SEQUENCE);
|
||||
} else if ($this->_targetPlatform->prefersIdentityColumns()) {
|
||||
} else if ($this->targetPlatform->prefersIdentityColumns()) {
|
||||
$class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_IDENTITY);
|
||||
} else {
|
||||
$class->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_TABLE);
|
||||
@@ -381,7 +430,7 @@ class ClassMetadataFactory
|
||||
// For PostgreSQL IDENTITY (SERIAL) we need a sequence name. It defaults to
|
||||
// <table>_<column>_seq in PostgreSQL for SERIAL columns.
|
||||
// Not pretty but necessary and the simplest solution that currently works.
|
||||
$seqName = $this->_targetPlatform instanceof Platforms\PostgreSQLPlatform ?
|
||||
$seqName = $this->targetPlatform instanceof Platforms\PostgreSQLPlatform ?
|
||||
$class->table['name'] . '_' . $class->columnNames[$class->identifier[0]] . '_seq' :
|
||||
null;
|
||||
$class->setIdGenerator(new \Doctrine\ORM\Id\IdentityGenerator($seqName));
|
||||
@@ -391,8 +440,8 @@ class ClassMetadataFactory
|
||||
$definition = $class->sequenceGeneratorDefinition;
|
||||
if ( ! $definition) {
|
||||
$sequenceName = $class->getTableName() . '_' . $class->getSingleIdentifierColumnName() . '_seq';
|
||||
$definition['sequenceName'] = $this->_targetPlatform->fixSchemaElementName($sequenceName);
|
||||
$definition['allocationSize'] = 10;
|
||||
$definition['sequenceName'] = $this->targetPlatform->fixSchemaElementName($sequenceName);
|
||||
$definition['allocationSize'] = 1;
|
||||
$definition['initialValue'] = 1;
|
||||
$class->setSequenceGeneratorDefinition($definition);
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
/**
|
||||
@@ -112,6 +113,45 @@ class ClassMetadataInfo
|
||||
* the <tt>NotifyPropertyChanged</tt> interface.
|
||||
*/
|
||||
const CHANGETRACKING_NOTIFY = 3;
|
||||
/**
|
||||
* Specifies that an association is to be fetched when it is first accessed.
|
||||
*/
|
||||
const FETCH_LAZY = 2;
|
||||
/**
|
||||
* Specifies that an association is to be fetched when the owner of the
|
||||
* 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.
|
||||
*/
|
||||
const ONE_TO_ONE = 1;
|
||||
/**
|
||||
* Identifies a many-to-one association.
|
||||
*/
|
||||
const MANY_TO_ONE = 2;
|
||||
/**
|
||||
* Combined bitmask for to-one (single-valued) associations.
|
||||
*/
|
||||
const TO_ONE = 3;
|
||||
/**
|
||||
* Identifies a one-to-many association.
|
||||
*/
|
||||
const ONE_TO_MANY = 4;
|
||||
/**
|
||||
* Identifies a many-to-many association.
|
||||
*/
|
||||
const MANY_TO_MANY = 8;
|
||||
/**
|
||||
* Combined bitmask for to-many (collection-valued) associations.
|
||||
*/
|
||||
const TO_MANY = 12;
|
||||
|
||||
/**
|
||||
* READ-ONLY: The name of the entity class.
|
||||
@@ -164,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.
|
||||
@@ -222,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
|
||||
@@ -288,7 +335,6 @@ class ClassMetadataInfo
|
||||
* uniqueConstraints => array
|
||||
*
|
||||
* @var array
|
||||
* @todo Rename to just $table
|
||||
*/
|
||||
public $table;
|
||||
|
||||
@@ -302,6 +348,57 @@ class ClassMetadataInfo
|
||||
/**
|
||||
* READ-ONLY: The association mappings of this class.
|
||||
*
|
||||
* The mapping definition array supports the following keys:
|
||||
*
|
||||
* - <b>fieldName</b> (string)
|
||||
* The name of the field in the entity the association is mapped to.
|
||||
*
|
||||
* - <b>targetEntity</b> (string)
|
||||
* The class name of the target entity. If it is fully-qualified it is used as is.
|
||||
* If it is a simple, unqualified class name the namespace is assumed to be the same
|
||||
* as the namespace of the source entity.
|
||||
*
|
||||
* - <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.
|
||||
*
|
||||
* - <b>cascade</b> (array, optional)
|
||||
* The names of persistence operations to cascade on the association. The set of possible
|
||||
* values are: "persist", "remove", "detach", "merge", "refresh", "all" (implies all others).
|
||||
*
|
||||
* - <b>orderBy</b> (array, one-to-many/many-to-many only)
|
||||
* A map of field names (of the target entity) to sorting directions (ASC/DESC).
|
||||
* Example: array('priority' => 'desc')
|
||||
*
|
||||
* - <b>fetch</b> (integer, optional)
|
||||
* The fetching strategy to use for the association, usually defaults to FETCH_LAZY.
|
||||
* Possible values are: ClassMetadata::FETCH_EAGER, ClassMetadata::FETCH_LAZY.
|
||||
*
|
||||
* - <b>joinTable</b> (array, optional, many-to-many only)
|
||||
* Specification of the join table and its join columns (foreign keys).
|
||||
* 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(
|
||||
* 'name' => <join table name>,
|
||||
* 'joinColumns' => array(<join column mapping from join table to source table>),
|
||||
* 'inverseJoinColumns' => array(<join column mapping from join table to target table>)
|
||||
* )
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $associationMappings = array();
|
||||
@@ -313,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.
|
||||
*
|
||||
@@ -377,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.
|
||||
@@ -519,9 +636,10 @@ class ClassMetadataInfo
|
||||
/**
|
||||
* Gets the mapping of an association.
|
||||
*
|
||||
* @see ClassMetadataInfo::$associationMappings
|
||||
* @param string $fieldName The field name that represents the association in
|
||||
* the object model.
|
||||
* @return Doctrine\ORM\Mapping\AssociationMapping The mapping.
|
||||
* @return array The mapping.
|
||||
*/
|
||||
public function getAssociationMapping($fieldName)
|
||||
{
|
||||
@@ -554,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.
|
||||
*
|
||||
@@ -563,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
|
||||
@@ -590,6 +734,10 @@ class ClassMetadataInfo
|
||||
|
||||
// Complete id mapping
|
||||
if (isset($mapping['id']) && $mapping['id'] === true) {
|
||||
if ($this->versionField == $mapping['fieldName']) {
|
||||
throw MappingException::cannotVersionIdField($this->name, $mapping['fieldName']);
|
||||
}
|
||||
|
||||
if ( ! in_array($mapping['fieldName'], $this->identifier)) {
|
||||
$this->identifier[] = $mapping['fieldName'];
|
||||
}
|
||||
@@ -600,6 +748,276 @@ class ClassMetadataInfo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates & completes the basic mapping information that is common to all
|
||||
* association mappings (one-to-one, many-ot-one, one-to-many, many-to-many).
|
||||
*
|
||||
* @param array $mapping The mapping.
|
||||
* @return array The updated mapping.
|
||||
* @throws MappingException If something is wrong with the mapping.
|
||||
*/
|
||||
protected function _validateAndCompleteAssociationMapping(array $mapping)
|
||||
{
|
||||
if ( ! isset($mapping['mappedBy'])) {
|
||||
$mapping['mappedBy'] = null;
|
||||
}
|
||||
if ( ! isset($mapping['inversedBy'])) {
|
||||
$mapping['inversedBy'] = null;
|
||||
}
|
||||
$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'];
|
||||
}
|
||||
|
||||
// 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']) || 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']) {
|
||||
if (isset($mapping['joinTable']['name']) && $mapping['joinTable']['name'][0] == '`') {
|
||||
$mapping['joinTable']['name'] = trim($mapping['joinTable']['name'], '`');
|
||||
$mapping['joinTable']['quoted'] = true;
|
||||
}
|
||||
}
|
||||
} 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();
|
||||
if (in_array('all', $cascades)) {
|
||||
$cascades = array(
|
||||
'remove',
|
||||
'persist',
|
||||
'refresh',
|
||||
'merge',
|
||||
'detach'
|
||||
);
|
||||
}
|
||||
$mapping['cascade'] = $cascades;
|
||||
$mapping['isCascadeRemove'] = in_array('remove', $cascades);
|
||||
$mapping['isCascadePersist'] = in_array('persist', $cascades);
|
||||
$mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
|
||||
$mapping['isCascadeMerge'] = in_array('merge', $cascades);
|
||||
$mapping['isCascadeDetach'] = in_array('detach', $cascades);
|
||||
|
||||
return $mapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates & completes a one-to-one association mapping.
|
||||
*
|
||||
* @param array $mapping The mapping to validate & complete.
|
||||
* @return array The validated & completed mapping.
|
||||
* @override
|
||||
*/
|
||||
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
|
||||
$mapping['joinColumns'] = array(array(
|
||||
'name' => $mapping['fieldName'] . '_id',
|
||||
'referencedColumnName' => 'id'
|
||||
));
|
||||
}
|
||||
|
||||
$uniqueContraintColumns = array();
|
||||
foreach ($mapping['joinColumns'] as $key => &$joinColumn) {
|
||||
if ($mapping['type'] === self::ONE_TO_ONE) {
|
||||
if (count($mapping['joinColumns']) == 1) {
|
||||
$joinColumn['unique'] = true;
|
||||
} else {
|
||||
$uniqueContraintColumns[] = $joinColumn['name'];
|
||||
}
|
||||
}
|
||||
if (empty($joinColumn['name'])) {
|
||||
$joinColumn['name'] = $mapping['fieldName'] . '_id';
|
||||
}
|
||||
if (empty($joinColumn['referencedColumnName'])) {
|
||||
$joinColumn['referencedColumnName'] = 'id';
|
||||
}
|
||||
$mapping['sourceToTargetKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName'];
|
||||
$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;
|
||||
|
||||
if (isset($mapping['id']) && $mapping['id'] === true && !$mapping['isOwningSide']) {
|
||||
throw MappingException::illegalInverseIdentifierAssocation($this->name, $mapping['fieldName']);
|
||||
}
|
||||
|
||||
return $mapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and completes the mapping.
|
||||
*
|
||||
* @param array $mapping The mapping to validate and complete.
|
||||
* @return array The validated and completed mapping.
|
||||
* @override
|
||||
*/
|
||||
protected function _validateAndCompleteOneToManyMapping(array $mapping)
|
||||
{
|
||||
$mapping = $this->_validateAndCompleteAssociationMapping($mapping);
|
||||
|
||||
// OneToMany-side MUST be inverse (must have mappedBy)
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
protected function _validateAndCompleteManyToManyMapping(array $mapping)
|
||||
{
|
||||
$mapping = $this->_validateAndCompleteAssociationMapping($mapping);
|
||||
if ($mapping['isOwningSide']) {
|
||||
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;
|
||||
}
|
||||
if ( ! isset($mapping['joinTable']['joinColumns'])) {
|
||||
$mapping['joinTable']['joinColumns'] = array(array(
|
||||
'name' => $sourceShortName . '_id',
|
||||
'referencedColumnName' => 'id',
|
||||
'onDelete' => 'CASCADE'));
|
||||
}
|
||||
if ( ! isset($mapping['joinTable']['inverseJoinColumns'])) {
|
||||
$mapping['joinTable']['inverseJoinColumns'] = array(array(
|
||||
'name' => $targetShortName . '_id',
|
||||
'referencedColumnName' => 'id',
|
||||
'onDelete' => 'CASCADE'));
|
||||
}
|
||||
|
||||
foreach ($mapping['joinTable']['joinColumns'] as &$joinColumn) {
|
||||
if (empty($joinColumn['name'])) {
|
||||
$joinColumn['name'] = $sourceShortName . '_id';
|
||||
}
|
||||
if (empty($joinColumn['referencedColumnName'])) {
|
||||
$joinColumn['referencedColumnName'] = 'id';
|
||||
}
|
||||
if (isset($joinColumn['onDelete']) && strtolower($joinColumn['onDelete']) == 'cascade') {
|
||||
$mapping['isOnDeleteCascade'] = true;
|
||||
}
|
||||
$mapping['relationToSourceKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName'];
|
||||
$mapping['joinTableColumns'][] = $joinColumn['name'];
|
||||
}
|
||||
|
||||
foreach ($mapping['joinTable']['inverseJoinColumns'] as &$inverseJoinColumn) {
|
||||
if (empty($inverseJoinColumn['name'])) {
|
||||
$inverseJoinColumn['name'] = $targetShortName . '_id';
|
||||
}
|
||||
if (empty($inverseJoinColumn['referencedColumnName'])) {
|
||||
$inverseJoinColumn['referencedColumnName'] = 'id';
|
||||
}
|
||||
if (isset($inverseJoinColumn['onDelete']) && strtolower($inverseJoinColumn['onDelete']) == 'cascade') {
|
||||
$mapping['isOnDeleteCascade'] = true;
|
||||
}
|
||||
$mapping['relationToTargetKeyColumns'][$inverseJoinColumn['name']] = $inverseJoinColumn['referencedColumnName'];
|
||||
$mapping['joinTableColumns'][] = $inverseJoinColumn['name'];
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the identifier (primary key) field names of the class.
|
||||
*
|
||||
@@ -647,6 +1065,17 @@ class ClassMetadataInfo
|
||||
public function setIdentifier(array $identifier)
|
||||
{
|
||||
$this->identifier = $identifier;
|
||||
$this->isIdentifierComposite = (count($this->identifier) > 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the mapped identifier field of this class.
|
||||
*
|
||||
* @return string $identifier
|
||||
*/
|
||||
public function getIdentifier()
|
||||
{
|
||||
return $this->identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -687,11 +1116,19 @@ class ClassMetadataInfo
|
||||
if ($this->isIdentifierComposite) {
|
||||
$columnNames = array();
|
||||
foreach ($this->identifier as $idField) {
|
||||
$columnNames[] = $this->fieldMappings[$idField]['columnName'];
|
||||
if (isset($this->associationMappings[$idField])) {
|
||||
// no composite pk as fk entity assumption:
|
||||
$columnNames[] = $this->associationMappings[$idField]['joinColumns'][0]['name'];
|
||||
} else {
|
||||
$columnNames[] = $this->fieldMappings[$idField]['columnName'];
|
||||
}
|
||||
}
|
||||
return $columnNames;
|
||||
} else {
|
||||
} else if(isset($this->fieldMappings[$this->identifier[0]])) {
|
||||
return array($this->fieldMappings[$this->identifier[0]]['columnName']);
|
||||
} else {
|
||||
// no composite pk as fk entity assumption:
|
||||
return array($this->associationMappings[$this->identifier[0]]['joinColumns'][0]['name']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -897,7 +1334,7 @@ class ClassMetadataInfo
|
||||
*/
|
||||
public function isInheritedAssociation($fieldName)
|
||||
{
|
||||
return isset($this->associationMappings[$fieldName]->inherited);
|
||||
return isset($this->associationMappings[$fieldName]['inherited']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -919,22 +1356,33 @@ class ClassMetadataInfo
|
||||
* indexes => array of indexes (optional)
|
||||
* uniqueConstraints => array of constraints (optional)
|
||||
*
|
||||
* @param array $table
|
||||
* If a key is omitted, the current value is kept.
|
||||
*
|
||||
* @param array $table The table description.
|
||||
*/
|
||||
public function setPrimaryTable(array $table)
|
||||
{
|
||||
if (isset($table['name']) && $table['name'][0] == '`') {
|
||||
$table['name'] = trim($table['name'], '`');
|
||||
$table['quoted'] = true;
|
||||
if (isset($table['name'])) {
|
||||
if ($table['name'][0] == '`') {
|
||||
$this->table['name'] = trim($table['name'], '`');
|
||||
$this->table['quoted'] = true;
|
||||
} else {
|
||||
$this->table['name'] = $table['name'];
|
||||
}
|
||||
}
|
||||
if (isset($table['indexes'])) {
|
||||
$this->table['indexes'] = $table['indexes'];
|
||||
}
|
||||
if (isset($table['uniqueConstraints'])) {
|
||||
$this->table['uniqueConstraints'] = $table['uniqueConstraints'];
|
||||
}
|
||||
$this->table = $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given type identifies an inheritance type.
|
||||
*
|
||||
* @param string $type
|
||||
* @return boolean
|
||||
* @param integer $type
|
||||
* @return boolean TRUE if the given type identifies an inheritance type, FALSe otherwise.
|
||||
*/
|
||||
private function _isInheritanceType($type)
|
||||
{
|
||||
@@ -944,22 +1392,6 @@ class ClassMetadataInfo
|
||||
$type == self::INHERITANCE_TYPE_TABLE_PER_CLASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes some automatic additions to the association mapping to make the life
|
||||
* easier for the user, and store join columns in the metadata.
|
||||
*
|
||||
* @param array $mapping
|
||||
* @todo Pass param by ref?
|
||||
*/
|
||||
private function _completeAssociationMapping(array $mapping)
|
||||
{
|
||||
$mapping['sourceEntity'] = $this->name;
|
||||
if (isset($mapping['targetEntity']) && strpos($mapping['targetEntity'], '\\') === false && strlen($this->namespace) > 0) {
|
||||
$mapping['targetEntity'] = $this->namespace . '\\' . $mapping['targetEntity'];
|
||||
}
|
||||
return $mapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a mapped field to the class.
|
||||
*
|
||||
@@ -979,15 +1411,14 @@ 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(AssociationMapping $mapping/*, $owningClassName = null*/)
|
||||
public function addInheritedAssociationMapping(array $mapping/*, $owningClassName = null*/)
|
||||
{
|
||||
if (isset($this->associationMappings[$mapping->sourceFieldName])) {
|
||||
throw MappingException::duplicateAssociationMapping($this->name, $mapping->sourceFieldName);
|
||||
if (isset($this->associationMappings[$mapping['fieldName']])) {
|
||||
throw MappingException::duplicateAssociationMapping($this->name, $mapping['fieldName']);
|
||||
}
|
||||
$this->associationMappings[$mapping->sourceFieldName] = $mapping;
|
||||
$this->associationMappings[$mapping['fieldName']] = $mapping;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -996,7 +1427,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)
|
||||
{
|
||||
@@ -1005,6 +1435,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.
|
||||
*
|
||||
@@ -1012,9 +1458,9 @@ class ClassMetadataInfo
|
||||
*/
|
||||
public function mapOneToOne(array $mapping)
|
||||
{
|
||||
$mapping = $this->_completeAssociationMapping($mapping);
|
||||
$oneToOneMapping = new OneToOneMapping($mapping);
|
||||
$this->_storeAssociationMapping($oneToOneMapping);
|
||||
$mapping['type'] = self::ONE_TO_ONE;
|
||||
$mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
|
||||
$this->_storeAssociationMapping($mapping);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1024,9 +1470,9 @@ class ClassMetadataInfo
|
||||
*/
|
||||
public function mapOneToMany(array $mapping)
|
||||
{
|
||||
$mapping = $this->_completeAssociationMapping($mapping);
|
||||
$oneToManyMapping = new OneToManyMapping($mapping);
|
||||
$this->_storeAssociationMapping($oneToManyMapping);
|
||||
$mapping['type'] = self::ONE_TO_MANY;
|
||||
$mapping = $this->_validateAndCompleteOneToManyMapping($mapping);
|
||||
$this->_storeAssociationMapping($mapping);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1036,8 +1482,10 @@ class ClassMetadataInfo
|
||||
*/
|
||||
public function mapManyToOne(array $mapping)
|
||||
{
|
||||
// A many-to-one mapping is simply a one-one backreference
|
||||
$this->mapOneToOne($mapping);
|
||||
$mapping['type'] = self::MANY_TO_ONE;
|
||||
// A many-to-one mapping is essentially a one-one backreference
|
||||
$mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
|
||||
$this->_storeAssociationMapping($mapping);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1047,9 +1495,9 @@ class ClassMetadataInfo
|
||||
*/
|
||||
public function mapManyToMany(array $mapping)
|
||||
{
|
||||
$mapping = $this->_completeAssociationMapping($mapping);
|
||||
$manyToManyMapping = new ManyToManyMapping($mapping);
|
||||
$this->_storeAssociationMapping($manyToManyMapping);
|
||||
$mapping['type'] = self::MANY_TO_MANY;
|
||||
$mapping = $this->_validateAndCompleteManyToManyMapping($mapping);
|
||||
$this->_storeAssociationMapping($mapping);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1057,9 +1505,9 @@ class ClassMetadataInfo
|
||||
*
|
||||
* @param AssociationMapping $assocMapping
|
||||
*/
|
||||
protected function _storeAssociationMapping(AssociationMapping $assocMapping)
|
||||
protected function _storeAssociationMapping(array $assocMapping)
|
||||
{
|
||||
$sourceFieldName = $assocMapping->sourceFieldName;
|
||||
$sourceFieldName = $assocMapping['fieldName'];
|
||||
if (isset($this->fieldMappings[$sourceFieldName]) || isset($this->associationMappings[$sourceFieldName])) {
|
||||
throw MappingException::duplicateFieldMapping($this->name, $sourceFieldName);
|
||||
}
|
||||
@@ -1156,6 +1604,13 @@ class ClassMetadataInfo
|
||||
if ( ! isset($columnDef['fieldName'])) {
|
||||
$columnDef['fieldName'] = $columnDef['name'];
|
||||
}
|
||||
if ( ! isset($columnDef['type'])) {
|
||||
$columnDef['type'] = "string";
|
||||
}
|
||||
if (in_array($columnDef['type'], array("boolean", "array", "object", "datetime", "time", "date"))) {
|
||||
throw MappingException::invalidDiscriminatorColumnType($this->name, $columnDef['type']);
|
||||
}
|
||||
|
||||
$this->discriminatorColumn = $columnDef;
|
||||
}
|
||||
}
|
||||
@@ -1172,6 +1627,7 @@ class ClassMetadataInfo
|
||||
if (strpos($className, '\\') === false && strlen($this->namespace)) {
|
||||
$className = $this->namespace . '\\' . $className;
|
||||
}
|
||||
$className = ltrim($className, '\\');
|
||||
$this->discriminatorMap[$value] = $className;
|
||||
if ($this->name == $className) {
|
||||
$this->discriminatorValue = $value;
|
||||
@@ -1186,6 +1642,17 @@ class ClassMetadataInfo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@@ -1207,7 +1674,7 @@ class ClassMetadataInfo
|
||||
public function isSingleValuedAssociation($fieldName)
|
||||
{
|
||||
return isset($this->associationMappings[$fieldName]) &&
|
||||
$this->associationMappings[$fieldName]->isOneToOne();
|
||||
($this->associationMappings[$fieldName]['type'] & self::TO_ONE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1220,7 +1687,75 @@ class ClassMetadataInfo
|
||||
public function isCollectionValuedAssociation($fieldName)
|
||||
{
|
||||
return isset($this->associationMappings[$fieldName]) &&
|
||||
! $this->associationMappings[$fieldName]->isOneToOne();
|
||||
! ($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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1294,4 +1829,104 @@ 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) 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'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ abstract class AbstractFileDriver implements Driver
|
||||
if ($this->_paths) {
|
||||
foreach ((array) $this->_paths as $path) {
|
||||
if ( ! is_dir($path)) {
|
||||
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath();
|
||||
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
|
||||
}
|
||||
|
||||
$iterator = new \RecursiveIteratorIterator(
|
||||
|
||||
@@ -62,22 +62,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 +128,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,27 +176,44 @@ 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'];
|
||||
$metadata->setInheritanceType(constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceTypeAnnot->value));
|
||||
|
||||
if ($metadata->inheritanceType != \Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_NONE) {
|
||||
// Evaluate DiscriminatorColumn annotation
|
||||
if (isset($classAnnotations['Doctrine\ORM\Mapping\DiscriminatorColumn'])) {
|
||||
$discrColumnAnnot = $classAnnotations['Doctrine\ORM\Mapping\DiscriminatorColumn'];
|
||||
$metadata->setDiscriminatorColumn(array(
|
||||
'name' => $discrColumnAnnot->name,
|
||||
'type' => $discrColumnAnnot->type,
|
||||
'length' => $discrColumnAnnot->length
|
||||
));
|
||||
} else {
|
||||
$metadata->setDiscriminatorColumn(array('name' => 'dtype', 'type' => 'string', 'length' => 255));
|
||||
}
|
||||
|
||||
// Evaluate DiscriminatorMap annotation
|
||||
if (isset($classAnnotations['Doctrine\ORM\Mapping\DiscriminatorMap'])) {
|
||||
$discrMapAnnot = $classAnnotations['Doctrine\ORM\Mapping\DiscriminatorMap'];
|
||||
$metadata->setDiscriminatorMap($discrMapAnnot->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate DiscriminatorColumn annotation
|
||||
if (isset($classAnnotations['Doctrine\ORM\Mapping\DiscriminatorColumn'])) {
|
||||
$discrColumnAnnot = $classAnnotations['Doctrine\ORM\Mapping\DiscriminatorColumn'];
|
||||
$metadata->setDiscriminatorColumn(array(
|
||||
'name' => $discrColumnAnnot->name,
|
||||
'type' => $discrColumnAnnot->type,
|
||||
'length' => $discrColumnAnnot->length
|
||||
));
|
||||
}
|
||||
|
||||
// Evaluate DiscriminatorMap annotation
|
||||
if (isset($classAnnotations['Doctrine\ORM\Mapping\DiscriminatorMap'])) {
|
||||
$discrMapAnnot = $classAnnotations['Doctrine\ORM\Mapping\DiscriminatorMap'];
|
||||
$metadata->setDiscriminatorMap($discrMapAnnot->value);
|
||||
}
|
||||
|
||||
// Evaluate DoctrineChangeTrackingPolicy annotation
|
||||
if (isset($classAnnotations['Doctrine\ORM\Mapping\ChangeTrackingPolicy'])) {
|
||||
@@ -283,20 +311,25 @@ 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;
|
||||
$mapping['inversedBy'] = $oneToOneAnnot->inversedBy;
|
||||
$mapping['cascade'] = $oneToOneAnnot->cascade;
|
||||
$mapping['orphanRemoval'] = $oneToOneAnnot->orphanRemoval;
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\AssociationMapping::FETCH_' . $oneToOneAnnot->fetch);
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $oneToOneAnnot->fetch);
|
||||
$metadata->mapOneToOne($mapping);
|
||||
} else if ($oneToManyAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OneToMany')) {
|
||||
$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\AssociationMapping::FETCH_' . $oneToManyAnnot->fetch);
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $oneToManyAnnot->fetch);
|
||||
|
||||
if ($orderByAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OrderBy')) {
|
||||
$mapping['orderBy'] = $orderByAnnot->value;
|
||||
@@ -304,11 +337,15 @@ 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;
|
||||
$mapping['targetEntity'] = $manyToOneAnnot->targetEntity;
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\AssociationMapping::FETCH_' . $manyToOneAnnot->fetch);
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $manyToOneAnnot->fetch);
|
||||
$metadata->mapManyToOne($mapping);
|
||||
} else if ($manyToManyAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\ManyToMany')) {
|
||||
$joinTable = array();
|
||||
@@ -349,7 +386,8 @@ class AnnotationDriver implements Driver
|
||||
$mapping['mappedBy'] = $manyToManyAnnot->mappedBy;
|
||||
$mapping['inversedBy'] = $manyToManyAnnot->inversedBy;
|
||||
$mapping['cascade'] = $manyToManyAnnot->cascade;
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\AssociationMapping::FETCH_' . $manyToManyAnnot->fetch);
|
||||
$mapping['indexBy'] = $manyToManyAnnot->indexBy;
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $manyToManyAnnot->fetch);
|
||||
|
||||
if ($orderByAnnot = $this->_reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\OrderBy')) {
|
||||
$mapping['orderBy'] = $orderByAnnot->value;
|
||||
@@ -362,9 +400,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);
|
||||
}
|
||||
@@ -410,6 +456,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']);
|
||||
}
|
||||
@@ -432,21 +492,23 @@ class AnnotationDriver implements Driver
|
||||
|
||||
foreach ($this->_paths as $path) {
|
||||
if ( ! is_dir($path)) {
|
||||
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath();
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -468,7 +530,7 @@ class AnnotationDriver implements Driver
|
||||
|
||||
/**
|
||||
* Factory method for the Annotation Driver
|
||||
*
|
||||
*
|
||||
* @param array|string $paths
|
||||
* @param AnnotationReader $reader
|
||||
* @return AnnotationDriver
|
||||
|
||||
@@ -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
|
||||
@@ -24,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;
|
||||
@@ -34,15 +33,46 @@ use Doctrine\Common\Cache\ArrayCache,
|
||||
* @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 Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class DatabaseDriver implements Driver
|
||||
{
|
||||
/** The SchemaManager. */
|
||||
/**
|
||||
* @var AbstractSchemaManager
|
||||
*/
|
||||
private $_sm;
|
||||
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $tables = null;
|
||||
|
||||
private $classToTableNames = array();
|
||||
|
||||
/**
|
||||
* @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.
|
||||
@@ -53,37 +83,113 @@ 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;
|
||||
}
|
||||
|
||||
foreach ($this->_sm->listTableNames() as $tableName) {
|
||||
$tables[$tableName] = $this->_sm->listTableDetails($tableName);
|
||||
}
|
||||
|
||||
$this->tables = $this->manyToManyTables = $this->classToTableNames = array();
|
||||
foreach ($tables AS $tableName => $table) {
|
||||
/* @var $table Table */
|
||||
if ($this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints()) {
|
||||
$foreignKeys = $table->getForeignKeys();
|
||||
} else {
|
||||
$foreignKeys = array();
|
||||
}
|
||||
|
||||
$allForeignKeyColumns = array();
|
||||
foreach ($foreignKeys AS $foreignKey) {
|
||||
$allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns());
|
||||
}
|
||||
|
||||
$pkColumns = $table->getPrimaryKey()->getColumns();
|
||||
sort($pkColumns);
|
||||
sort($allForeignKeyColumns);
|
||||
|
||||
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 = $this->getClassNameForTable($tableName);
|
||||
$this->tables[$tableName] = $table;
|
||||
$this->classToTableNames[$className] = $tableName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadMetadataForClass($className, ClassMetadataInfo $metadata)
|
||||
{
|
||||
$tableName = $className;
|
||||
$className = Inflector::classify(strtolower($tableName));
|
||||
$this->reverseEngineerMappingFromDatabase();
|
||||
|
||||
if (!isset($this->classToTableNames[$className])) {
|
||||
throw new \InvalidArgumentException("Unknown class " . $className);
|
||||
}
|
||||
|
||||
$tableName = $this->classToTableNames[$className];
|
||||
|
||||
$metadata->name = $className;
|
||||
$metadata->table['name'] = $tableName;
|
||||
|
||||
$columns = $this->_sm->listTableColumns($tableName);
|
||||
$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->_sm->listTableForeignKeys($tableName);
|
||||
$foreignKeys = $this->tables[$tableName]->getForeignKeys();
|
||||
} else {
|
||||
$foreignKeys = array();
|
||||
}
|
||||
|
||||
$indexes = $this->_sm->listTableIndexes($tableName);
|
||||
$allForeignKeyColumns = array();
|
||||
foreach ($foreignKeys AS $foreignKey) {
|
||||
$allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns());
|
||||
}
|
||||
|
||||
$ids = array();
|
||||
$fieldMappings = array();
|
||||
foreach ($columns as $column) {
|
||||
$fieldMapping = array();
|
||||
if (isset($indexes['primary']) && in_array($column->getName(), $indexes['primary']->getColumns())) {
|
||||
if ($primaryKeyColumns && in_array($column->getName(), $primaryKeyColumns)) {
|
||||
$fieldMapping['id'] = true;
|
||||
} else if (in_array($column->getName(), $allForeignKeyColumns)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$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());
|
||||
|
||||
@@ -116,15 +222,72 @@ class DatabaseDriver implements Driver
|
||||
$metadata->mapField($fieldMapping);
|
||||
}
|
||||
|
||||
foreach ($foreignKeys as $foreignKey) {
|
||||
$cols = $foreignKey->getColumns();
|
||||
$localColumn = current($cols);
|
||||
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;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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'] = $this->getFieldNameForColumn($manyTable->getName(), current($otherFk->getColumns()), true);
|
||||
$associationMapping['targetEntity'] = $this->getClassNameForTable($otherFk->getForeignTableName());
|
||||
if (current($manyTable->getColumns())->getName() == $localColumn) {
|
||||
$associationMapping['inversedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true);
|
||||
$associationMapping['joinTable'] = array(
|
||||
'name' => strtolower($manyTable->getName()),
|
||||
'joinColumns' => array(),
|
||||
'inverseJoinColumns' => array(),
|
||||
);
|
||||
|
||||
$fkCols = $myFk->getForeignColumns();
|
||||
$cols = $myFk->getColumns();
|
||||
for ($i = 0; $i < count($cols); $i++) {
|
||||
$associationMapping['joinTable']['joinColumns'][] = array(
|
||||
'name' => $cols[$i],
|
||||
'referencedColumnName' => $fkCols[$i],
|
||||
);
|
||||
}
|
||||
|
||||
$fkCols = $otherFk->getForeignColumns();
|
||||
$cols = $otherFk->getColumns();
|
||||
for ($i = 0; $i < count($cols); $i++) {
|
||||
$associationMapping['joinTable']['inverseJoinColumns'][] = array(
|
||||
'name' => $cols[$i],
|
||||
'referencedColumnName' => $fkCols[$i],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$associationMapping['mappedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getColumns()), true);
|
||||
}
|
||||
$metadata->mapManyToMany($associationMapping);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($foreignKeys as $foreignKey) {
|
||||
$foreignTable = $foreignKey->getForeignTableName();
|
||||
$cols = $foreignKey->getColumns();
|
||||
$fkCols = $foreignKey->getForeignColumns();
|
||||
|
||||
$localColumn = current($cols);
|
||||
$associationMapping = array();
|
||||
$associationMapping['fieldName'] = Inflector::camelize(str_ireplace('_id', '', $localColumn));
|
||||
$associationMapping['targetEntity'] = Inflector::classify($foreignKey->getForeignTableName());
|
||||
$associationMapping['fieldName'] = $this->getFieldNameForColumn($tableName, $localColumn, true);
|
||||
$associationMapping['targetEntity'] = $this->getClassNameForTable($foreignTable);
|
||||
|
||||
for ($i = 0; $i < count($cols); $i++) {
|
||||
$associationMapping['joinColumns'][] = array(
|
||||
@@ -132,7 +295,6 @@ class DatabaseDriver implements Driver
|
||||
'referencedColumnName' => $fkCols[$i],
|
||||
);
|
||||
}
|
||||
|
||||
$metadata->mapManyToOne($associationMapping);
|
||||
}
|
||||
}
|
||||
@@ -146,18 +308,90 @@ class DatabaseDriver implements Driver
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* Return all the class names supported by this driver.
|
||||
*
|
||||
* IMPORTANT: This method must return an array of class not tables names.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAllClassNames()
|
||||
{
|
||||
$classes = array();
|
||||
|
||||
foreach ($this->_sm->listTables() as $table) {
|
||||
// This method must return an array of table names because we need
|
||||
// to know the table name after we inflect it to create the entity class name.
|
||||
$classes[] = $table->getName();
|
||||
$this->reverseEngineerMappingFromDatabase();
|
||||
|
||||
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 $classes;
|
||||
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
|
||||
@@ -27,6 +25,7 @@ use Doctrine\Common\Annotations\Annotation;
|
||||
|
||||
final class Entity extends Annotation {
|
||||
public $repositoryClass;
|
||||
public $readOnly = false;
|
||||
}
|
||||
final class MappedSuperclass extends Annotation {}
|
||||
final class InheritanceType extends Annotation {}
|
||||
@@ -80,6 +79,7 @@ final class OneToMany extends Annotation {
|
||||
public $cascade;
|
||||
public $fetch = 'LAZY';
|
||||
public $orphanRemoval = false;
|
||||
public $indexBy;
|
||||
}
|
||||
final class ManyToOne extends Annotation {
|
||||
public $targetEntity;
|
||||
@@ -93,6 +93,7 @@ final class ManyToMany extends Annotation {
|
||||
public $inversedBy;
|
||||
public $cascade;
|
||||
public $fetch = 'LAZY';
|
||||
public $indexBy;
|
||||
}
|
||||
final class ElementCollection extends Annotation {
|
||||
public $tableName;
|
||||
@@ -114,17 +115,23 @@ final class Index extends Annotation {
|
||||
final class JoinTable extends Annotation {
|
||||
public $name;
|
||||
public $schema;
|
||||
public $joinColumns;
|
||||
public $inverseJoinColumns;
|
||||
public $joinColumns = array();
|
||||
public $inverseJoinColumns = array();
|
||||
}
|
||||
final class SequenceGenerator extends Annotation {
|
||||
public $sequenceName;
|
||||
public $allocationSize = 10;
|
||||
public $allocationSize = 1;
|
||||
public $initialValue = 1;
|
||||
}
|
||||
final class ChangeTrackingPolicy extends Annotation {}
|
||||
final class OrderBy extends Annotation {}
|
||||
|
||||
final class NamedQueries extends Annotation {}
|
||||
final class NamedQuery extends Annotation {
|
||||
public $name;
|
||||
public $query;
|
||||
}
|
||||
|
||||
/* Annotations for lifecycle callbacks */
|
||||
final class HasLifecycleCallbacks extends Annotation {}
|
||||
final class PrePersist extends Annotation {}
|
||||
|
||||
@@ -88,16 +88,21 @@ class DriverChain implements Driver
|
||||
public function getAllClassNames()
|
||||
{
|
||||
$classNames = array();
|
||||
foreach ($this->_drivers AS $driver) {
|
||||
$classNames = array_merge($classNames, $driver->getAllClassNames());
|
||||
foreach ($this->_drivers AS $namespace => $driver) {
|
||||
$driverClasses = $driver->getAllClassNames();
|
||||
foreach ($driverClasses AS $className) {
|
||||
if (strpos($className, $namespace) === 0) {
|
||||
$classNames[] = $className;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $classNames;
|
||||
return array_unique($classNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the class with the specified name should have its metadata loaded.
|
||||
* This is only the case if it is either mapped as an Entity or a
|
||||
* MappedSuperclass.
|
||||
*
|
||||
* This is only the case for non-transient classes either mapped as an Entity or MappedSuperclass.
|
||||
*
|
||||
* @param string $className
|
||||
* @return boolean
|
||||
@@ -110,6 +115,7 @@ class DriverChain implements Driver
|
||||
}
|
||||
}
|
||||
|
||||
throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className);
|
||||
// class isTransient, i.e. not an entity or mapped superclass
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -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)) {
|
||||
throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath();
|
||||
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,6 +72,16 @@ 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'];
|
||||
@@ -77,50 +90,59 @@ class XmlDriver extends AbstractFileDriver
|
||||
if (isset($xmlRoot['inheritance-type'])) {
|
||||
$inheritanceType = (string)$xmlRoot['inheritance-type'];
|
||||
$metadata->setInheritanceType(constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceType));
|
||||
}
|
||||
|
||||
// Evaluate <discriminator-column...>
|
||||
if (isset($xmlRoot->{'discriminator-column'})) {
|
||||
$discrColumn = $xmlRoot->{'discriminator-column'};
|
||||
$metadata->setDiscriminatorColumn(array(
|
||||
'name' => (string)$discrColumn['name'],
|
||||
'type' => (string)$discrColumn['type'],
|
||||
'length' => (string)$discrColumn['length']
|
||||
));
|
||||
}
|
||||
if ($metadata->inheritanceType != \Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_NONE) {
|
||||
// Evaluate <discriminator-column...>
|
||||
if (isset($xmlRoot->{'discriminator-column'})) {
|
||||
$discrColumn = $xmlRoot->{'discriminator-column'};
|
||||
$metadata->setDiscriminatorColumn(array(
|
||||
'name' => (string)$discrColumn['name'],
|
||||
'type' => (string)$discrColumn['type'],
|
||||
'length' => (string)$discrColumn['length']
|
||||
));
|
||||
} else {
|
||||
$metadata->setDiscriminatorColumn(array('name' => 'dtype', 'type' => 'string', 'length' => 255));
|
||||
}
|
||||
|
||||
// Evaluate <discriminator-map...>
|
||||
if (isset($xmlRoot->{'discriminator-map'})) {
|
||||
$map = array();
|
||||
foreach ($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} AS $discrMapElement) {
|
||||
$map[(string)$discrMapElement['value']] = (string)$discrMapElement['class'];
|
||||
// Evaluate <discriminator-map...>
|
||||
if (isset($xmlRoot->{'discriminator-map'})) {
|
||||
$map = array();
|
||||
foreach ($xmlRoot->{'discriminator-map'}->{'discriminator-mapping'} AS $discrMapElement) {
|
||||
$map[(string)$discrMapElement['value']] = (string)$discrMapElement['class'];
|
||||
}
|
||||
$metadata->setDiscriminatorMap($map);
|
||||
}
|
||||
}
|
||||
$metadata->setDiscriminatorMap($map);
|
||||
}
|
||||
|
||||
|
||||
// Evaluate <change-tracking-policy...>
|
||||
if (isset($xmlRoot->{'change-tracking-policy'})) {
|
||||
if (isset($xmlRoot['change-tracking-policy'])) {
|
||||
$metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_'
|
||||
. strtoupper((string)$xmlRoot->{'change-tracking-policy'})));
|
||||
. strtoupper((string)$xmlRoot['change-tracking-policy'])));
|
||||
}
|
||||
|
||||
// Evaluate <indexes...>
|
||||
if (isset($xmlRoot->indexes)) {
|
||||
$metadata->table['indexes'] = array();
|
||||
foreach ($xmlRoot->indexes->index as $index) {
|
||||
if (is_string($index['columns'])) {
|
||||
$columns = explode(',', $index['columns']);
|
||||
} else {
|
||||
$columns = $index['columns'];
|
||||
}
|
||||
$columns = explode(',', (string)$index['columns']);
|
||||
|
||||
$metadata->table['indexes'][$index['name']] = array(
|
||||
'columns' => $columns
|
||||
);
|
||||
if (isset($index['name'])) {
|
||||
$metadata->table['indexes'][(string)$index['name']] = array(
|
||||
'columns' => $columns
|
||||
);
|
||||
} else {
|
||||
$metadata->table['indexes'][] = array(
|
||||
'columns' => $columns
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate <unique-constraints..>
|
||||
if (isset($xmlRoot->{'unique-constraints'})) {
|
||||
$metadata->table['uniqueConstraints'] = array();
|
||||
foreach ($xmlRoot->{'unique-constraints'}->{'unique-constraint'} as $unique) {
|
||||
$columns = explode(',', (string)$unique['columns']);
|
||||
|
||||
@@ -185,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['fieldName']] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
$mapping = array(
|
||||
'id' => true,
|
||||
'fieldName' => (string)$idElement['name'],
|
||||
@@ -226,8 +254,12 @@ 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\AssociationMapping::FETCH_' . (string)$oneToOneElement['fetch']);
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string)$oneToOneElement['fetch']);
|
||||
}
|
||||
|
||||
if (isset($oneToOneElement['mapped-by'])) {
|
||||
@@ -271,7 +303,7 @@ class XmlDriver extends AbstractFileDriver
|
||||
);
|
||||
|
||||
if (isset($oneToManyElement['fetch'])) {
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\AssociationMapping::FETCH_' . (string)$oneToManyElement['fetch']);
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string)$oneToManyElement['fetch']);
|
||||
}
|
||||
|
||||
if (isset($oneToManyElement->cascade)) {
|
||||
@@ -290,6 +322,10 @@ class XmlDriver extends AbstractFileDriver
|
||||
$mapping['orderBy'] = $orderBy;
|
||||
}
|
||||
|
||||
if (isset($oneToManyElement->{'index-by'})) {
|
||||
$mapping['indexBy'] = (string)$oneToManyElement->{'index-by'};
|
||||
}
|
||||
|
||||
$metadata->mapOneToMany($mapping);
|
||||
}
|
||||
}
|
||||
@@ -302,8 +338,12 @@ 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\AssociationMapping::FETCH_' . (string)$manyToOneElement['fetch']);
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string)$manyToOneElement['fetch']);
|
||||
}
|
||||
|
||||
if (isset($manyToOneElement['inversed-by'])) {
|
||||
@@ -316,9 +356,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);
|
||||
}
|
||||
}
|
||||
@@ -346,7 +383,7 @@ class XmlDriver extends AbstractFileDriver
|
||||
);
|
||||
|
||||
if (isset($manyToManyElement['fetch'])) {
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\AssociationMapping::FETCH_' . (string)$manyToManyElement['fetch']);
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string)$manyToManyElement['fetch']);
|
||||
}
|
||||
|
||||
if (isset($manyToManyElement['mapped-by'])) {
|
||||
@@ -392,6 +429,10 @@ class XmlDriver extends AbstractFileDriver
|
||||
$mapping['orderBy'] = $orderBy;
|
||||
}
|
||||
|
||||
if (isset($manyToManyElement->{'index-by'})) {
|
||||
$mapping['indexBy'] = (string)$manyToManyElement->{'index-by'};
|
||||
}
|
||||
|
||||
$metadata->mapManyToMany($mapping);
|
||||
}
|
||||
}
|
||||
@@ -483,4 +524,4 @@ class XmlDriver extends AbstractFileDriver
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'];
|
||||
@@ -69,22 +87,27 @@ class YamlDriver extends AbstractFileDriver
|
||||
|
||||
if (isset($element['inheritanceType'])) {
|
||||
$metadata->setInheritanceType(constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . strtoupper($element['inheritanceType'])));
|
||||
|
||||
if ($metadata->inheritanceType != \Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_NONE) {
|
||||
// Evaluate discriminatorColumn
|
||||
if (isset($element['discriminatorColumn'])) {
|
||||
$discrColumn = $element['discriminatorColumn'];
|
||||
$metadata->setDiscriminatorColumn(array(
|
||||
'name' => $discrColumn['name'],
|
||||
'type' => $discrColumn['type'],
|
||||
'length' => $discrColumn['length']
|
||||
));
|
||||
} else {
|
||||
$metadata->setDiscriminatorColumn(array('name' => 'dtype', 'type' => 'string', 'length' => 255));
|
||||
}
|
||||
|
||||
// Evaluate discriminatorMap
|
||||
if (isset($element['discriminatorMap'])) {
|
||||
$metadata->setDiscriminatorMap($element['discriminatorMap']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate discriminatorColumn
|
||||
if (isset($element['discriminatorColumn'])) {
|
||||
$discrColumn = $element['discriminatorColumn'];
|
||||
$metadata->setDiscriminatorColumn(array(
|
||||
'name' => $discrColumn['name'],
|
||||
'type' => $discrColumn['type'],
|
||||
'length' => $discrColumn['length']
|
||||
));
|
||||
}
|
||||
|
||||
// Evaluate discriminatorMap
|
||||
if (isset($element['discriminatorMap'])) {
|
||||
$metadata->setDiscriminatorMap($element['discriminatorMap']);
|
||||
}
|
||||
|
||||
// Evaluate changeTrackingPolicy
|
||||
if (isset($element['changeTrackingPolicy'])) {
|
||||
@@ -130,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);
|
||||
}
|
||||
@@ -147,6 +176,10 @@ class YamlDriver extends AbstractFileDriver
|
||||
$mapping['columnName'] = $idElement['column'];
|
||||
}
|
||||
|
||||
if (isset($idElement['length'])) {
|
||||
$mapping['length'] = $idElement['length'];
|
||||
}
|
||||
|
||||
$metadata->mapField($mapping);
|
||||
|
||||
if (isset($idElement['generator'])) {
|
||||
@@ -225,8 +258,12 @@ 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\AssociationMapping::FETCH_' . $oneToOneElement['fetch']);
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $oneToOneElement['fetch']);
|
||||
}
|
||||
|
||||
if (isset($oneToOneElement['mappedBy'])) {
|
||||
@@ -257,6 +294,10 @@ class YamlDriver extends AbstractFileDriver
|
||||
$mapping['cascade'] = $oneToOneElement['cascade'];
|
||||
}
|
||||
|
||||
if (isset($oneToOneElement['orphanRemoval'])) {
|
||||
$mapping['orphanRemoval'] = (bool)$oneToOneElement['orphanRemoval'];
|
||||
}
|
||||
|
||||
$metadata->mapOneToOne($mapping);
|
||||
}
|
||||
}
|
||||
@@ -271,17 +312,25 @@ class YamlDriver extends AbstractFileDriver
|
||||
);
|
||||
|
||||
if (isset($oneToManyElement['fetch'])) {
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\AssociationMapping::FETCH_' . $oneToManyElement['fetch']);
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $oneToManyElement['fetch']);
|
||||
}
|
||||
|
||||
if (isset($oneToManyElement['cascade'])) {
|
||||
$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);
|
||||
}
|
||||
}
|
||||
@@ -294,8 +343,12 @@ 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\AssociationMapping::FETCH_' . $manyToOneElement['fetch']);
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $manyToOneElement['fetch']);
|
||||
}
|
||||
|
||||
if (isset($manyToOneElement['inversedBy'])) {
|
||||
@@ -322,6 +375,10 @@ class YamlDriver extends AbstractFileDriver
|
||||
$mapping['cascade'] = $manyToOneElement['cascade'];
|
||||
}
|
||||
|
||||
if (isset($manyToOneElement['orphanRemoval'])) {
|
||||
$mapping['orphanRemoval'] = (bool)$manyToOneElement['orphanRemoval'];
|
||||
}
|
||||
|
||||
$metadata->mapManyToOne($mapping);
|
||||
}
|
||||
}
|
||||
@@ -335,7 +392,7 @@ class YamlDriver extends AbstractFileDriver
|
||||
);
|
||||
|
||||
if (isset($manyToManyElement['fetch'])) {
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\AssociationMapping::FETCH_' . $manyToManyElement['fetch']);
|
||||
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $manyToManyElement['fetch']);
|
||||
}
|
||||
|
||||
if (isset($manyToManyElement['mappedBy'])) {
|
||||
@@ -377,10 +434,18 @@ class YamlDriver extends AbstractFileDriver
|
||||
$mapping['cascade'] = $manyToManyElement['cascade'];
|
||||
}
|
||||
|
||||
if (isset($manyToManyElement['orphanRemoval'])) {
|
||||
$mapping['orphanRemoval'] = (bool)$manyToManyElement['orphan-removal'];
|
||||
}
|
||||
|
||||
if (isset($manyToManyElement['orderBy'])) {
|
||||
$mapping['orderBy'] = $manyToManyElement['orderBy'];
|
||||
}
|
||||
|
||||
if (isset($manyToManyElement['indexBy'])) {
|
||||
$mapping['indexBy'] = $manyToManyElement['indexBy'];
|
||||
}
|
||||
|
||||
$metadata->mapManyToMany($mapping);
|
||||
}
|
||||
}
|
||||
@@ -441,6 +506,6 @@ class YamlDriver extends AbstractFileDriver
|
||||
*/
|
||||
protected function _loadMappingFile($file)
|
||||
{
|
||||
return \Symfony\Components\Yaml\Yaml::load($file);
|
||||
return \Symfony\Component\Yaml\Yaml::parse($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
<?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\Mapping;
|
||||
|
||||
/**
|
||||
* A many-to-many mapping describes the mapping between two collections of
|
||||
* entities.
|
||||
*
|
||||
* <b>IMPORTANT NOTE:</b>
|
||||
*
|
||||
* The fields of this class are only public for 2 reasons:
|
||||
* 1) To allow fast READ access.
|
||||
* 2) To drastically reduce the size of a serialized instance (private/protected members
|
||||
* get the whole class name, namespace inclusive, prepended to every property in
|
||||
* the serialized representation).
|
||||
*
|
||||
* Instances of this class are stored serialized in the metadata cache together with the
|
||||
* owning <tt>ClassMetadata</tt> instance.
|
||||
*
|
||||
* @since 2.0
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
* @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
|
||||
* @todo Potentially remove if assoc mapping objects get replaced by simple arrays.
|
||||
*/
|
||||
class ManyToManyMapping extends AssociationMapping
|
||||
{
|
||||
/**
|
||||
* READ-ONLY: Maps the columns in the relational table to the columns in the source table.
|
||||
*/
|
||||
public $relationToSourceKeyColumns = array();
|
||||
|
||||
/**
|
||||
* READ-ONLY: Maps the columns in the relation table to the columns in the target table.
|
||||
*/
|
||||
public $relationToTargetKeyColumns = array();
|
||||
|
||||
/**
|
||||
* READ-ONLY: List of aggregated column names on the join table.
|
||||
*/
|
||||
public $joinTableColumns = array();
|
||||
|
||||
/** FUTURE: The key column mapping, if any. The key column holds the keys of the Collection. */
|
||||
//public $keyColumn;
|
||||
|
||||
/**
|
||||
* READ-ONLY: Order this collection by the given DQL snippet.
|
||||
*
|
||||
* Only simple unqualified field names and ASC|DESC are allowed
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $orderBy;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function _validateAndCompleteMapping(array $mapping)
|
||||
{
|
||||
parent::_validateAndCompleteMapping($mapping);
|
||||
if ($this->isOwningSide) {
|
||||
// owning side MUST have a join table
|
||||
if ( ! isset($mapping['joinTable']) || ! $mapping['joinTable']) {
|
||||
// Apply default join table
|
||||
$sourceShortName = substr($this->sourceEntityName, strrpos($this->sourceEntityName, '\\') + 1);
|
||||
$targetShortName = substr($this->targetEntityName, strrpos($this->targetEntityName, '\\') + 1);
|
||||
$mapping['joinTable'] = array(
|
||||
'name' => $sourceShortName .'_' . $targetShortName,
|
||||
'joinColumns' => array(
|
||||
array(
|
||||
'name' => $sourceShortName . '_id',
|
||||
'referencedColumnName' => 'id',
|
||||
'onDelete' => 'CASCADE'
|
||||
)
|
||||
),
|
||||
'inverseJoinColumns' => array(
|
||||
array(
|
||||
'name' => $targetShortName . '_id',
|
||||
'referencedColumnName' => 'id',
|
||||
'onDelete' => 'CASCADE'
|
||||
)
|
||||
)
|
||||
);
|
||||
$this->joinTable = $mapping['joinTable'];
|
||||
}
|
||||
// owning side MUST specify joinColumns
|
||||
else if ( ! isset($mapping['joinTable']['joinColumns'])) {
|
||||
throw MappingException::missingRequiredOption(
|
||||
$this->sourceFieldName, 'joinColumns',
|
||||
'Did you think of case sensitivity / plural s?'
|
||||
);
|
||||
}
|
||||
// owning side MUST specify inverseJoinColumns
|
||||
else if ( ! isset($mapping['joinTable']['inverseJoinColumns'])) {
|
||||
throw MappingException::missingRequiredOption(
|
||||
$this->sourceFieldName, 'inverseJoinColumns',
|
||||
'Did you think of case sensitivity / plural s?'
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) {
|
||||
$this->relationToSourceKeyColumns[$joinColumn['name']] = $joinColumn['referencedColumnName'];
|
||||
$this->joinTableColumns[] = $joinColumn['name'];
|
||||
}
|
||||
|
||||
foreach ($mapping['joinTable']['inverseJoinColumns'] as $inverseJoinColumn) {
|
||||
$this->relationToTargetKeyColumns[$inverseJoinColumn['name']] = $inverseJoinColumn['referencedColumnName'];
|
||||
$this->joinTableColumns[] = $inverseJoinColumn['name'];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($mapping['orderBy'])) {
|
||||
if ( ! is_array($mapping['orderBy'])) {
|
||||
throw new \InvalidArgumentException("'orderBy' is expected to be an array, not ".gettype($mapping['orderBy']));
|
||||
}
|
||||
$this->orderBy = $mapping['orderBy'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads entities in $targetCollection using $em.
|
||||
* The data of $sourceEntity are used to restrict the collection
|
||||
* via the join table.
|
||||
*
|
||||
* @param object The owner of the collection.
|
||||
* @param object The collection to populate.
|
||||
* @param array
|
||||
* @todo Remove
|
||||
*/
|
||||
public function load($sourceEntity, $targetCollection, $em, array $joinColumnValues = array())
|
||||
{
|
||||
$em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->loadManyToManyCollection($this, $sourceEntity, $targetCollection);
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function isManyToMany()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines which fields get serialized.
|
||||
*
|
||||
* It is only serialized what is necessary for best unserialization performance.
|
||||
* That means any metadata properties that are not set or empty or simply have
|
||||
* their default value are NOT serialized.
|
||||
*
|
||||
* @return array The names of all the fields that should be serialized.
|
||||
*/
|
||||
public function __sleep()
|
||||
{
|
||||
$serialized = parent::__sleep();
|
||||
$serialized[] = 'joinTableColumns';
|
||||
$serialized[] = 'relationToSourceKeyColumns';
|
||||
$serialized[] = 'relationToTargetKeyColumns';
|
||||
if ($this->orderBy) {
|
||||
$serialized[] = 'orderBy';
|
||||
}
|
||||
return $serialized;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -170,9 +179,16 @@ class MappingException extends \Doctrine\ORM\ORMException
|
||||
);
|
||||
}
|
||||
|
||||
public static function fileMappingDriversRequireConfiguredDirectoryPath()
|
||||
public static function fileMappingDriversRequireConfiguredDirectoryPath($path = null)
|
||||
{
|
||||
return new self('File mapping drivers must have a directory path');
|
||||
if ( ! empty($path)) {
|
||||
$path = '[' . $path . ']';
|
||||
}
|
||||
|
||||
return new self(
|
||||
'File mapping drivers must have a valid directory path, ' .
|
||||
'however the given path ' . $path . ' seems to be incorrect!'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -190,6 +206,26 @@ class MappingException extends \Doctrine\ORM\ORMException
|
||||
);
|
||||
}
|
||||
|
||||
public static function missingDiscriminatorMap($className)
|
||||
{
|
||||
return new self("Entity class '$className' is using inheritance but no discriminator map was defined.");
|
||||
}
|
||||
|
||||
public static function missingDiscriminatorColumn($className)
|
||||
{
|
||||
return new self("Entity class '$className' is using inheritance but no discriminator column was defined.");
|
||||
}
|
||||
|
||||
public static function invalidDiscriminatorColumnType($className, $type)
|
||||
{
|
||||
return new self("Discriminator column type on entity class '$className' is not allowed to be '$type'. 'string' or 'integer' type variables are suggested!");
|
||||
}
|
||||
|
||||
public static function cannotVersionIdField($className, $fieldName)
|
||||
{
|
||||
return new self("Setting Id field '$fieldName' as versionale in entity class '$className' is not supported.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
* @param string $columnName
|
||||
@@ -199,4 +235,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. If you want to avoid instantiation of this type mark it abstract."
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
<?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\Mapping;
|
||||
|
||||
/**
|
||||
* Represents a one-to-many mapping.
|
||||
*
|
||||
* NOTE: One-to-many mappings can currently not be uni-directional (one -> many).
|
||||
* They must either be bidirectional (one <-> many) or unidirectional (many -> one).
|
||||
* In other words, the many-side MUST be the owning side and the one-side MUST be
|
||||
* the inverse side.
|
||||
*
|
||||
* <b>IMPORTANT NOTE:</b>
|
||||
*
|
||||
* The fields of this class are only public for 2 reasons:
|
||||
* 1) To allow fast READ access.
|
||||
* 2) To drastically reduce the size of a serialized instance (private/protected members
|
||||
* get the whole class name, namespace inclusive, prepended to every property in
|
||||
* the serialized representation).
|
||||
*
|
||||
* Instances of this class are stored serialized in the metadata cache together with the
|
||||
* owning <tt>ClassMetadata</tt> instance.
|
||||
*
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
* @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
|
||||
* @since 2.0
|
||||
* @todo Potentially remove if assoc mapping objects get replaced by simple arrays.
|
||||
*/
|
||||
class OneToManyMapping extends AssociationMapping
|
||||
{
|
||||
/**
|
||||
* READ-ONLY: Whether to delete orphaned elements (removed from the collection)
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $orphanRemoval = false;
|
||||
|
||||
/** FUTURE: The key column mapping, if any. The key column holds the keys of the Collection. */
|
||||
//public $keyColumn;
|
||||
|
||||
/**
|
||||
* READ-ONLY: Order this collection by the given SQL snippet.
|
||||
*/
|
||||
public $orderBy;
|
||||
|
||||
/**
|
||||
* Validates and completes the mapping.
|
||||
*
|
||||
* @param array $mapping The mapping to validate and complete.
|
||||
* @return array The validated and completed mapping.
|
||||
* @override
|
||||
*/
|
||||
protected function _validateAndCompleteMapping(array $mapping)
|
||||
{
|
||||
parent::_validateAndCompleteMapping($mapping);
|
||||
|
||||
// OneToMany-side MUST be inverse (must have mappedBy)
|
||||
if ( ! isset($mapping['mappedBy'])) {
|
||||
throw MappingException::oneToManyRequiresMappedBy($mapping['fieldName']);
|
||||
}
|
||||
|
||||
//TODO: if orphanRemoval, cascade=remove is implicit!
|
||||
$this->orphanRemoval = isset($mapping['orphanRemoval']) ?
|
||||
(bool) $mapping['orphanRemoval'] : false;
|
||||
|
||||
if (isset($mapping['orderBy'])) {
|
||||
if (!is_array($mapping['orderBy'])) {
|
||||
throw new \InvalidArgumentException("'orderBy' is expected to be an array, not ".gettype($mapping['orderBy']));
|
||||
}
|
||||
$this->orderBy = $mapping['orderBy'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether orphaned elements (removed from the collection) should be deleted.
|
||||
*
|
||||
* @return boolean TRUE if orphaned elements should be deleted, FALSE otherwise.
|
||||
*/
|
||||
public function shouldDeleteOrphans()
|
||||
{
|
||||
return $this->deleteOrphans;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isOneToMany()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a one-to-many collection.
|
||||
*
|
||||
* @param $sourceEntity The entity that owns the collection.
|
||||
* @param $targetCollection The collection to load/fill.
|
||||
* @param $em The EntityManager to use.
|
||||
* @param $joinColumnValues
|
||||
* @return void
|
||||
* @todo Remove
|
||||
*/
|
||||
public function load($sourceEntity, $targetCollection, $em, array $joinColumnValues = array())
|
||||
{
|
||||
$em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->loadOneToManyCollection($this, $sourceEntity, $targetCollection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines which fields get serialized.
|
||||
*
|
||||
* It is only serialized what is necessary for best unserialization performance.
|
||||
* That means any metadata properties that are not set or empty or simply have
|
||||
* their default value are NOT serialized.
|
||||
*
|
||||
* @return array The names of all the fields that should be serialized.
|
||||
*/
|
||||
public function __sleep()
|
||||
{
|
||||
$serialized = parent::__sleep();
|
||||
if ($this->orderBy) {
|
||||
$serialized[] = 'orderBy';
|
||||
}
|
||||
if ($this->orphanRemoval) {
|
||||
$serialized[] = 'orphanRemoval';
|
||||
}
|
||||
return $serialized;
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
<?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\Mapping;
|
||||
|
||||
/**
|
||||
* A one-to-one mapping describes a uni-directional mapping from one entity
|
||||
* to another entity.
|
||||
*
|
||||
* <b>IMPORTANT NOTE:</b>
|
||||
*
|
||||
* The fields of this class are only public for 2 reasons:
|
||||
* 1) To allow fast READ access.
|
||||
* 2) To drastically reduce the size of a serialized instance (private/protected members
|
||||
* get the whole class name, namespace inclusive, prepended to every property in
|
||||
* the serialized representation).
|
||||
*
|
||||
* Instances of this class are stored serialized in the metadata cache together with the
|
||||
* owning <tt>ClassMetadata</tt> instance.
|
||||
*
|
||||
* @since 2.0
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
* @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
|
||||
* @todo Potentially remove if assoc mapping objects get replaced by simple arrays.
|
||||
*/
|
||||
class OneToOneMapping extends AssociationMapping
|
||||
{
|
||||
/**
|
||||
* READ-ONLY: Maps the source foreign/primary key columns to the target primary/foreign key columns.
|
||||
* i.e. source.id (pk) => target.user_id (fk).
|
||||
* Reverse mapping of _targetToSourceKeyColumns.
|
||||
*/
|
||||
public $sourceToTargetKeyColumns = array();
|
||||
|
||||
/**
|
||||
* READ-ONLY: Maps the target primary/foreign key columns to the source foreign/primary key columns.
|
||||
* i.e. target.user_id (fk) => source.id (pk).
|
||||
* Reverse mapping of _sourceToTargetKeyColumns.
|
||||
*/
|
||||
public $targetToSourceKeyColumns = array();
|
||||
|
||||
/**
|
||||
* READ-ONLY: Whether to delete orphaned elements (when nulled out, i.e. $foo->other = null)
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $orphanRemoval = false;
|
||||
|
||||
/**
|
||||
* READ-ONLY: The join column definitions. Only present on the owning side.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $joinColumns = array();
|
||||
|
||||
/**
|
||||
* READ-ONLY: A map of join column names to field names that are used in cases
|
||||
* when the join columns are fetched as part of the query result.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $joinColumnFieldNames = array();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param array $mapping The mapping to validate & complete.
|
||||
* @return array The validated & completed mapping.
|
||||
* @override
|
||||
*/
|
||||
protected function _validateAndCompleteMapping(array $mapping)
|
||||
{
|
||||
parent::_validateAndCompleteMapping($mapping);
|
||||
|
||||
if (isset($mapping['joinColumns']) && $mapping['joinColumns']) {
|
||||
$this->isOwningSide = true;
|
||||
}
|
||||
|
||||
if ($this->isOwningSide) {
|
||||
if ( ! isset($mapping['joinColumns']) || ! $mapping['joinColumns']) {
|
||||
// Apply default join column
|
||||
$mapping['joinColumns'] = array(array(
|
||||
'name' => $this->sourceFieldName . '_id',
|
||||
'referencedColumnName' => 'id'
|
||||
));
|
||||
}
|
||||
foreach ($mapping['joinColumns'] as $joinColumn) {
|
||||
$this->sourceToTargetKeyColumns[$joinColumn['name']] = $joinColumn['referencedColumnName'];
|
||||
$this->joinColumnFieldNames[$joinColumn['name']] = isset($joinColumn['fieldName'])
|
||||
? $joinColumn['fieldName'] : $joinColumn['name'];
|
||||
}
|
||||
$this->joinColumns = $mapping['joinColumns'];
|
||||
$this->targetToSourceKeyColumns = array_flip($this->sourceToTargetKeyColumns);
|
||||
}
|
||||
|
||||
//TODO: if orphanRemoval, cascade=remove is implicit!
|
||||
$this->orphanRemoval = isset($mapping['orphanRemoval']) ?
|
||||
(bool) $mapping['orphanRemoval'] : false;
|
||||
|
||||
return $mapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return boolean
|
||||
* @override
|
||||
*/
|
||||
public function isOneToOne()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param object $sourceEntity the entity source of this association
|
||||
* @param object $targetEntity the entity to load data in
|
||||
* @param EntityManager $em
|
||||
* @param array $joinColumnValues Values of the join columns of $sourceEntity.
|
||||
* @todo Remove
|
||||
*/
|
||||
public function load($sourceEntity, $targetEntity, $em, array $joinColumnValues = array())
|
||||
{
|
||||
return $em->getUnitOfWork()->getEntityPersister($this->targetEntityName)->loadOneToOneEntity($this, $sourceEntity, $targetEntity, $joinColumnValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines which fields get serialized.
|
||||
*
|
||||
* It is only serialized what is necessary for best unserialization performance.
|
||||
* That means any metadata properties that are not set or empty or simply have
|
||||
* their default value are NOT serialized.
|
||||
*
|
||||
* @return array The names of all the fields that should be serialized.
|
||||
*/
|
||||
public function __sleep()
|
||||
{
|
||||
$serialized = parent::__sleep();
|
||||
$serialized[] = 'joinColumns';
|
||||
$serialized[] = 'joinColumnFieldNames';
|
||||
$serialized[] = 'sourceToTargetKeyColumns';
|
||||
$serialized[] = 'targetToSourceKeyColumns';
|
||||
if ($this->orphanRemoval) {
|
||||
$serialized[] = 'orphanRemoval';
|
||||
}
|
||||
return $serialized;
|
||||
}
|
||||
}
|
||||
@@ -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,23 +60,6 @@ class ORMException extends Exception
|
||||
return new self("Unrecognized field: $field");
|
||||
}
|
||||
|
||||
public static function removedEntityInCollectionDetected($entity, $assoc)
|
||||
{
|
||||
return new self("Removed entity of type " . get_class($entity)
|
||||
. " detected in collection '" . $assoc->sourceFieldName . "' during flush."
|
||||
. " Remove deleted entities from collections.");
|
||||
}
|
||||
|
||||
public static function invalidEntityState($state)
|
||||
{
|
||||
return new self("Invalid entity state: $state.");
|
||||
}
|
||||
|
||||
public static function detachedEntityCannotBeRemoved()
|
||||
{
|
||||
return new self("A detached entity can not be removed.");
|
||||
}
|
||||
|
||||
public static function invalidFlushMode($mode)
|
||||
{
|
||||
return new self("'$mode' is an invalid flush mode.");
|
||||
@@ -95,6 +93,14 @@ class ORMException extends Exception
|
||||
);
|
||||
}
|
||||
|
||||
public static function invalidFindByInverseAssociation($entityName, $associationFieldName)
|
||||
{
|
||||
return new self(
|
||||
"You cannot search for the association field '".$entityName."#".$associationFieldName."', ".
|
||||
"because it is the inverse side of an association. Find methods only work on owning side associations."
|
||||
);
|
||||
}
|
||||
|
||||
public static function invalidResultCacheDriver() {
|
||||
return new self("Invalid result cache driver; it must implement \Doctrine\Common\Cache\Cache.");
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use Doctrine\ORM\Mapping\AssociationMapping,
|
||||
use Doctrine\ORM\Mapping\ClassMetadata,
|
||||
Doctrine\Common\Collections\Collection,
|
||||
Closure;
|
||||
|
||||
@@ -32,12 +32,11 @@ use Doctrine\ORM\Mapping\AssociationMapping,
|
||||
* Similarly, if you remove entities from a collection that is part of a one-many
|
||||
* mapping this will only result in the nulling out of the foreign keys on flush.
|
||||
*
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @since 2.0
|
||||
* @version $Revision: 4930 $
|
||||
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
* @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
|
||||
* @todo Design for inheritance to allow custom implementations?
|
||||
*/
|
||||
final class PersistentCollection implements Collection
|
||||
{
|
||||
@@ -47,29 +46,29 @@ final class PersistentCollection implements Collection
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_snapshot = array();
|
||||
private $snapshot = array();
|
||||
|
||||
/**
|
||||
* The entity that owns this collection.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
private $_owner;
|
||||
private $owner;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
private $association;
|
||||
|
||||
/**
|
||||
* The EntityManager that manages the persistence of the collection.
|
||||
*
|
||||
* @var Doctrine\ORM\EntityManager
|
||||
*/
|
||||
private $_em;
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* The name of the field on the target entities that points to the owner
|
||||
@@ -77,12 +76,12 @@ final class PersistentCollection implements Collection
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_backRefFieldName;
|
||||
private $backRefFieldName;
|
||||
|
||||
/**
|
||||
* The class descriptor of the collection's entity type.
|
||||
*/
|
||||
private $_typeClass;
|
||||
private $typeClass;
|
||||
|
||||
/**
|
||||
* Whether the collection is dirty and needs to be synchronized with the database
|
||||
@@ -90,21 +89,21 @@ final class PersistentCollection implements Collection
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
private $_isDirty = false;
|
||||
private $isDirty = false;
|
||||
|
||||
/**
|
||||
* Whether the collection has already been initialized.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
private $_initialized = true;
|
||||
private $initialized = true;
|
||||
|
||||
/**
|
||||
* The wrapped Collection instance.
|
||||
*
|
||||
* @var Collection
|
||||
*/
|
||||
private $_coll;
|
||||
private $coll;
|
||||
|
||||
/**
|
||||
* Creates a new persistent collection.
|
||||
@@ -115,9 +114,9 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function __construct(EntityManager $em, $class, $coll)
|
||||
{
|
||||
$this->_coll = $coll;
|
||||
$this->_em = $em;
|
||||
$this->_typeClass = $class;
|
||||
$this->coll = $coll;
|
||||
$this->em = $em;
|
||||
$this->typeClass = $class;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,11 +127,11 @@ final class PersistentCollection implements Collection
|
||||
* @param object $entity
|
||||
* @param AssociationMapping $assoc
|
||||
*/
|
||||
public function setOwner($entity, AssociationMapping $assoc)
|
||||
public function setOwner($entity, array $assoc)
|
||||
{
|
||||
$this->_owner = $entity;
|
||||
$this->_association = $assoc;
|
||||
$this->_backRefFieldName = $assoc->inversedBy ?: $assoc->mappedBy;
|
||||
$this->owner = $entity;
|
||||
$this->association = $assoc;
|
||||
$this->backRefFieldName = $assoc['inversedBy'] ?: $assoc['mappedBy'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,12 +142,12 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function getOwner()
|
||||
{
|
||||
return $this->_owner;
|
||||
return $this->owner;
|
||||
}
|
||||
|
||||
public function getTypeClass()
|
||||
{
|
||||
return $this->_typeClass;
|
||||
return $this->typeClass;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,17 +159,17 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function hydrateAdd($element)
|
||||
{
|
||||
$this->_coll->add($element);
|
||||
$this->coll->add($element);
|
||||
// If _backRefFieldName is set and its a one-to-many association,
|
||||
// we need to set the back reference.
|
||||
if ($this->_backRefFieldName && $this->_association->isOneToMany()) {
|
||||
if ($this->backRefFieldName && $this->association['type'] == ClassMetadata::ONE_TO_MANY) {
|
||||
// Set back reference to owner
|
||||
$this->_typeClass->reflFields[$this->_backRefFieldName]
|
||||
->setValue($element, $this->_owner);
|
||||
$this->_em->getUnitOfWork()->setOriginalEntityProperty(
|
||||
$this->typeClass->reflFields[$this->backRefFieldName]
|
||||
->setValue($element, $this->owner);
|
||||
$this->em->getUnitOfWork()->setOriginalEntityProperty(
|
||||
spl_object_hash($element),
|
||||
$this->_backRefFieldName,
|
||||
$this->_owner);
|
||||
$this->backRefFieldName,
|
||||
$this->owner);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,13 +182,13 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function hydrateSet($key, $element)
|
||||
{
|
||||
$this->_coll->set($key, $element);
|
||||
$this->coll->set($key, $element);
|
||||
// If _backRefFieldName is set, then the association is bidirectional
|
||||
// and we need to set the back reference.
|
||||
if ($this->_backRefFieldName && $this->_association->isOneToMany()) {
|
||||
if ($this->backRefFieldName && $this->association['type'] == ClassMetadata::ONE_TO_MANY) {
|
||||
// Set back reference to owner
|
||||
$this->_typeClass->reflFields[$this->_backRefFieldName]
|
||||
->setValue($element, $this->_owner);
|
||||
$this->typeClass->reflFields[$this->backRefFieldName]
|
||||
->setValue($element, $this->owner);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,24 +196,24 @@ final class PersistentCollection implements Collection
|
||||
* Initializes the collection by loading its contents from the database
|
||||
* if the collection is not yet initialized.
|
||||
*/
|
||||
private function _initialize()
|
||||
public function initialize()
|
||||
{
|
||||
if ( ! $this->_initialized) {
|
||||
if ($this->_isDirty) {
|
||||
if ( ! $this->initialized && $this->association) {
|
||||
if ($this->isDirty) {
|
||||
// Has NEW objects added through add(). Remember them.
|
||||
$newObjects = $this->_coll->toArray();
|
||||
$newObjects = $this->coll->toArray();
|
||||
}
|
||||
$this->_coll->clear();
|
||||
$this->_association->load($this->_owner, $this, $this->_em);
|
||||
$this->coll->clear();
|
||||
$this->em->getUnitOfWork()->loadCollection($this);
|
||||
$this->takeSnapshot();
|
||||
// Reattach NEW objects added through add(), if any.
|
||||
if (isset($newObjects)) {
|
||||
foreach ($newObjects as $obj) {
|
||||
$this->_coll->add($obj);
|
||||
$this->coll->add($obj);
|
||||
}
|
||||
$this->_isDirty = true;
|
||||
$this->isDirty = true;
|
||||
}
|
||||
$this->_initialized = true;
|
||||
$this->initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,8 +223,8 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function takeSnapshot()
|
||||
{
|
||||
$this->_snapshot = $this->_coll->toArray();
|
||||
$this->_isDirty = false;
|
||||
$this->snapshot = $this->coll->toArray();
|
||||
$this->isDirty = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -236,7 +235,7 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function getSnapshot()
|
||||
{
|
||||
return $this->_snapshot;
|
||||
return $this->snapshot;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -247,7 +246,7 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function getDeleteDiff()
|
||||
{
|
||||
return array_udiff_assoc($this->_snapshot, $this->_coll->toArray(),
|
||||
return array_udiff_assoc($this->snapshot, $this->coll->toArray(),
|
||||
function($a, $b) {return $a === $b ? 0 : 1;});
|
||||
}
|
||||
|
||||
@@ -259,7 +258,7 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function getInsertDiff()
|
||||
{
|
||||
return array_udiff_assoc($this->_coll->toArray(), $this->_snapshot,
|
||||
return array_udiff_assoc($this->coll->toArray(), $this->snapshot,
|
||||
function($a, $b) {return $a === $b ? 0 : 1;});
|
||||
}
|
||||
|
||||
@@ -270,31 +269,32 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function getMapping()
|
||||
{
|
||||
return $this->_association;
|
||||
return $this->association;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks this collection as changed/dirty.
|
||||
*/
|
||||
private function _changed()
|
||||
private function changed()
|
||||
{
|
||||
if ( ! $this->_isDirty) {
|
||||
$this->_isDirty = true;
|
||||
//if ($this->_isNotifyRequired) {
|
||||
//$this->_em->getUnitOfWork()->scheduleCollectionUpdate($this);
|
||||
//}
|
||||
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->em->getUnitOfWork()->scheduleForDirtyCheck($this->owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a boolean flag indicating whether this colleciton is dirty which means
|
||||
* Gets a boolean flag indicating whether this collection is dirty which means
|
||||
* its state needs to be synchronized with the database.
|
||||
*
|
||||
* @return boolean TRUE if the collection is dirty, FALSE otherwise.
|
||||
*/
|
||||
public function isDirty()
|
||||
{
|
||||
return $this->_isDirty;
|
||||
return $this->isDirty;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -304,7 +304,7 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function setDirty($dirty)
|
||||
{
|
||||
$this->_isDirty = $dirty;
|
||||
$this->isDirty = $dirty;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -314,7 +314,7 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function setInitialized($bool)
|
||||
{
|
||||
$this->_initialized = $bool;
|
||||
$this->initialized = $bool;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -324,21 +324,21 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function isInitialized()
|
||||
{
|
||||
return $this->_initialized;
|
||||
return $this->initialized;
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function first()
|
||||
{
|
||||
$this->_initialize();
|
||||
return $this->_coll->first();
|
||||
$this->initialize();
|
||||
return $this->coll->first();
|
||||
}
|
||||
|
||||
/** {@inheritdoc} */
|
||||
public function last()
|
||||
{
|
||||
$this->_initialize();
|
||||
return $this->_coll->last();
|
||||
$this->initialize();
|
||||
return $this->coll->last();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -350,13 +350,13 @@ final class PersistentCollection implements Collection
|
||||
// and the collection is not initialized and orphanRemoval is
|
||||
// not used we can issue a straight SQL delete/update on the
|
||||
// association (table). Without initializing the collection.
|
||||
$this->_initialize();
|
||||
$removed = $this->_coll->remove($key);
|
||||
$this->initialize();
|
||||
$removed = $this->coll->remove($key);
|
||||
if ($removed) {
|
||||
$this->_changed();
|
||||
if ($this->_association !== null && $this->_association->isOneToMany() &&
|
||||
$this->_association->orphanRemoval) {
|
||||
$this->_em->getUnitOfWork()->scheduleOrphanRemoval($removed);
|
||||
$this->changed();
|
||||
if ($this->association !== null && $this->association['type'] == ClassMetadata::ONE_TO_MANY &&
|
||||
$this->association['orphanRemoval']) {
|
||||
$this->em->getUnitOfWork()->scheduleOrphanRemoval($removed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,15 +373,21 @@ final class PersistentCollection implements Collection
|
||||
// if the collection is not initialized, we could issue a straight
|
||||
// SQL DELETE/UPDATE on the association (table) without initializing
|
||||
// the collection.
|
||||
/*if ( ! $this->_initialized) {
|
||||
$this->_em->getUnitOfWork()->getCollectionPersister($this->_association)
|
||||
/*if ( ! $this->initialized) {
|
||||
$this->em->getUnitOfWork()->getCollectionPersister($this->association)
|
||||
->deleteRows($this, $element);
|
||||
}*/
|
||||
|
||||
$this->_initialize();
|
||||
$result = $this->_coll->removeElement($element);
|
||||
$this->_changed();
|
||||
return $result;
|
||||
$this->initialize();
|
||||
$removed = $this->coll->removeElement($element);
|
||||
if ($removed) {
|
||||
$this->changed();
|
||||
if ($this->association !== null && $this->association['type'] == ClassMetadata::ONE_TO_MANY &&
|
||||
$this->association['orphanRemoval']) {
|
||||
$this->em->getUnitOfWork()->scheduleOrphanRemoval($element);
|
||||
}
|
||||
}
|
||||
return $removed;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -389,8 +395,8 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function containsKey($key)
|
||||
{
|
||||
$this->_initialize();
|
||||
return $this->_coll->containsKey($key);
|
||||
$this->initialize();
|
||||
return $this->coll->containsKey($key);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -398,25 +404,15 @@ 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);
|
||||
$this->initialize();
|
||||
return $this->coll->contains($element);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -424,8 +420,8 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function exists(Closure $p)
|
||||
{
|
||||
$this->_initialize();
|
||||
return $this->_coll->exists($p);
|
||||
$this->initialize();
|
||||
return $this->coll->exists($p);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -433,8 +429,8 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function indexOf($element)
|
||||
{
|
||||
$this->_initialize();
|
||||
return $this->_coll->indexOf($element);
|
||||
$this->initialize();
|
||||
return $this->coll->indexOf($element);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -442,8 +438,8 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function get($key)
|
||||
{
|
||||
$this->_initialize();
|
||||
return $this->_coll->get($key);
|
||||
$this->initialize();
|
||||
return $this->coll->get($key);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -451,8 +447,8 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function getKeys()
|
||||
{
|
||||
$this->_initialize();
|
||||
return $this->_coll->getKeys();
|
||||
$this->initialize();
|
||||
return $this->coll->getKeys();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -460,8 +456,8 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function getValues()
|
||||
{
|
||||
$this->_initialize();
|
||||
return $this->_coll->getValues();
|
||||
$this->initialize();
|
||||
return $this->coll->getValues();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -469,8 +465,14 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
$this->_initialize();
|
||||
return $this->_coll->count();
|
||||
if (!$this->initialized && $this->association['fetch'] == Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) {
|
||||
return $this->em->getUnitOfWork()
|
||||
->getCollectionPersister($this->association)
|
||||
->count($this) + $this->coll->count();
|
||||
}
|
||||
|
||||
$this->initialize();
|
||||
return $this->coll->count();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -478,9 +480,9 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function set($key, $value)
|
||||
{
|
||||
$this->_initialize();
|
||||
$this->_coll->set($key, $value);
|
||||
$this->_changed();
|
||||
$this->initialize();
|
||||
$this->coll->set($key, $value);
|
||||
$this->changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -488,8 +490,8 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function add($value)
|
||||
{
|
||||
$this->_coll->add($value);
|
||||
$this->_changed();
|
||||
$this->coll->add($value);
|
||||
$this->changed();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -498,8 +500,8 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function isEmpty()
|
||||
{
|
||||
$this->_initialize();
|
||||
return $this->_coll->isEmpty();
|
||||
$this->initialize();
|
||||
return $this->coll->isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -507,8 +509,8 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
$this->_initialize();
|
||||
return $this->_coll->getIterator();
|
||||
$this->initialize();
|
||||
return $this->coll->getIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -516,8 +518,8 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function map(Closure $func)
|
||||
{
|
||||
$this->_initialize();
|
||||
return $this->_coll->map($func);
|
||||
$this->initialize();
|
||||
return $this->coll->map($func);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -525,8 +527,8 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function filter(Closure $p)
|
||||
{
|
||||
$this->_initialize();
|
||||
return $this->_coll->filter($p);
|
||||
$this->initialize();
|
||||
return $this->coll->filter($p);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -534,8 +536,8 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function forAll(Closure $p)
|
||||
{
|
||||
$this->_initialize();
|
||||
return $this->_coll->forAll($p);
|
||||
$this->initialize();
|
||||
return $this->coll->forAll($p);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -543,8 +545,8 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function partition(Closure $p)
|
||||
{
|
||||
$this->_initialize();
|
||||
return $this->_coll->partition($p);
|
||||
$this->initialize();
|
||||
return $this->coll->partition($p);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -552,8 +554,8 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
$this->_initialize();
|
||||
return $this->_coll->toArray();
|
||||
$this->initialize();
|
||||
return $this->coll->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -561,14 +563,20 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$this->_initialize();
|
||||
$result = $this->_coll->clear();
|
||||
if ($this->_association->isOwningSide) {
|
||||
$this->_changed();
|
||||
$this->_em->getUnitOfWork()->scheduleCollectionDeletion($this);
|
||||
if ($this->initialized && $this->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if ($this->association['type'] == ClassMetadata::ONE_TO_MANY && $this->association['orphanRemoval']) {
|
||||
foreach ($this->coll as $element) {
|
||||
$this->em->getUnitOfWork()->scheduleOrphanRemoval($element);
|
||||
}
|
||||
}
|
||||
$this->coll->clear();
|
||||
if ($this->association['isOwningSide']) {
|
||||
$this->changed();
|
||||
$this->em->getUnitOfWork()->scheduleCollectionDeletion($this);
|
||||
$this->takeSnapshot();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -580,7 +588,7 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function __sleep()
|
||||
{
|
||||
return array('_coll');
|
||||
return array('coll', 'initialized');
|
||||
}
|
||||
|
||||
/* ArrayAccess implementation */
|
||||
@@ -623,7 +631,7 @@ final class PersistentCollection implements Collection
|
||||
|
||||
public function key()
|
||||
{
|
||||
return $this->_coll->key();
|
||||
return $this->coll->key();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -631,7 +639,7 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
return $this->_coll->current();
|
||||
return $this->coll->current();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -639,7 +647,7 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function next()
|
||||
{
|
||||
return $this->_coll->next();
|
||||
return $this->coll->next();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -647,6 +655,29 @@ final class PersistentCollection implements Collection
|
||||
*/
|
||||
public function unwrap()
|
||||
{
|
||||
return $this->_coll;
|
||||
return $this->coll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a slice of $length elements starting at position $offset from the Collection.
|
||||
*
|
||||
* If $length is null it returns all elements from $offset to the end of the Collection.
|
||||
* Keys have to be preserved by this method. Calling this method will only return the
|
||||
* selected slice and NOT change the elements contained in the collection slice is called on.
|
||||
*
|
||||
* @param int $offset
|
||||
* @param int $length
|
||||
* @return array
|
||||
*/
|
||||
public function slice($offset, $length = null)
|
||||
{
|
||||
if (!$this->initialized && $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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,8 @@ abstract class AbstractCollectionPersister
|
||||
*/
|
||||
public function delete(PersistentCollection $coll)
|
||||
{
|
||||
if ( ! $coll->getMapping()->isOwningSide) {
|
||||
$mapping = $coll->getMapping();
|
||||
if ( ! $mapping['isOwningSide']) {
|
||||
return; // ignore inverse side
|
||||
}
|
||||
$sql = $this->_getDeleteSQL($coll);
|
||||
@@ -94,7 +95,8 @@ abstract class AbstractCollectionPersister
|
||||
*/
|
||||
public function update(PersistentCollection $coll)
|
||||
{
|
||||
if ( ! $coll->getMapping()->isOwningSide) {
|
||||
$mapping = $coll->getMapping();
|
||||
if ( ! $mapping['isOwningSide']) {
|
||||
return; // ignore inverse side
|
||||
}
|
||||
$this->deleteRows($coll);
|
||||
@@ -123,6 +125,31 @@ abstract class AbstractCollectionPersister
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -28,17 +28,11 @@ 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}
|
||||
*/
|
||||
@@ -62,42 +56,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];
|
||||
$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";
|
||||
}
|
||||
|
||||
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,16 @@
|
||||
namespace Doctrine\ORM\Persisters;
|
||||
|
||||
use Doctrine\ORM\ORMException,
|
||||
Doctrine\ORM\Mapping\ManyToManyMapping;
|
||||
Doctrine\ORM\Mapping\ClassMetadata,
|
||||
Doctrine\DBAL\LockMode,
|
||||
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
|
||||
*/
|
||||
@@ -35,9 +38,18 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
/**
|
||||
* Map that maps column names to the table names that own them.
|
||||
* This is mainly a temporary cache, used during a single request.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_owningTableMap = array();
|
||||
|
||||
/**
|
||||
* Map of table to quoted table names.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_quotedTableMap = array();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -74,18 +86,16 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
*/
|
||||
public function getOwningTable($fieldName)
|
||||
{
|
||||
if ( ! isset($this->_owningTableMap[$fieldName])) {
|
||||
if (isset($this->_class->associationMappings[$fieldName]->inherited)) {
|
||||
$this->_owningTableMap[$fieldName] = $this->_em->getClassMetadata(
|
||||
$this->_class->associationMappings[$fieldName]->inherited
|
||||
)->table['name'];
|
||||
if (!isset($this->_owningTableMap[$fieldName])) {
|
||||
if (isset($this->_class->associationMappings[$fieldName]['inherited'])) {
|
||||
$cm = $this->_em->getClassMetadata($this->_class->associationMappings[$fieldName]['inherited']);
|
||||
} else if (isset($this->_class->fieldMappings[$fieldName]['inherited'])) {
|
||||
$this->_owningTableMap[$fieldName] = $this->_em->getClassMetadata(
|
||||
$this->_class->fieldMappings[$fieldName]['inherited']
|
||||
)->table['name'];
|
||||
$cm = $this->_em->getClassMetadata($this->_class->fieldMappings[$fieldName]['inherited']);
|
||||
} else {
|
||||
$this->_owningTableMap[$fieldName] = $this->_class->table['name'];
|
||||
$cm = $this->_class;
|
||||
}
|
||||
$this->_owningTableMap[$fieldName] = $cm->table['name'];
|
||||
$this->_quotedTableMap[$cm->table['name']] = $cm->getQuotedTableName($this->_platform);
|
||||
}
|
||||
|
||||
return $this->_owningTableMap[$fieldName];
|
||||
@@ -100,10 +110,6 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->_class->isVersioned) {
|
||||
$versionedClass = $this->_getVersionedClassMetadata();
|
||||
}
|
||||
|
||||
$postInsertIds = array();
|
||||
$idGen = $this->_class->idGenerator;
|
||||
$isPostInsertId = $idGen->isPostInsertGenerator();
|
||||
@@ -169,8 +175,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();
|
||||
@@ -186,17 +192,21 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
$updateData = $this->_prepareUpdateData($entity);
|
||||
|
||||
if ($isVersioned = $this->_class->isVersioned) {
|
||||
$versionedTable = $this->_getVersionedClassMetadata()->table['name'];
|
||||
$versionedClass = $this->_getVersionedClassMetadata();
|
||||
$versionedTable = $versionedClass->table['name'];
|
||||
}
|
||||
|
||||
if ($updateData) {
|
||||
foreach ($updateData as $tableName => $data) {
|
||||
$this->_updateTable($entity, $tableName, $data, $isVersioned && $versionedTable == $tableName);
|
||||
$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, $versionedTable, array(), true);
|
||||
$this->_updateTable($entity, $versionedClass->getQuotedTableName($this->_platform), array(), true);
|
||||
|
||||
$id = $this->_em->getUnitOfWork()->getEntityIdentifier($entity);
|
||||
$this->assignDefaultVersionValue($entity, $id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,10 +216,10 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
*/
|
||||
public function delete($entity)
|
||||
{
|
||||
$id = array_combine(
|
||||
$this->_class->getIdentifierColumnNames(),
|
||||
$this->_em->getUnitOfWork()->getEntityIdentifier($entity)
|
||||
);
|
||||
$identifier = $this->_em->getUnitOfWork()->getEntityIdentifier($entity);
|
||||
$this->deleteJoinTableRecords($identifier);
|
||||
|
||||
$id = array_combine($this->_class->getIdentifierColumnNames(), $identifier);
|
||||
|
||||
// If the database platform supports FKs, just
|
||||
// delete the row from the root table. Cascades do the rest.
|
||||
@@ -228,13 +238,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) {
|
||||
@@ -247,17 +261,15 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
|
||||
// Add foreign key columns
|
||||
foreach ($this->_class->associationMappings as $assoc2) {
|
||||
if ($assoc2->isOwningSide && $assoc2->isOneToOne()) {
|
||||
$tableAlias = $assoc2->inherited ?
|
||||
$this->_getSQLTableAlias($assoc2->inherited)
|
||||
if ($assoc2['isOwningSide'] && $assoc2['type'] & ClassMetadata::TO_ONE) {
|
||||
$tableAlias = isset($assoc2['inherited']) ?
|
||||
$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;
|
||||
}
|
||||
foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) {
|
||||
if ($columnList != '') $columnList .= ', ';
|
||||
$columnList .= $this->getSelectJoinColumnSQL($tableAlias, $srcColumn,
|
||||
isset($assoc2['inherited']) ? $assoc2['inherited'] : $this->_class->name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -272,7 +284,8 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
}
|
||||
|
||||
$resultColumnName = $this->_platform->getSQLResultCasing($discrColumn);
|
||||
$this->_resultColumnNames[$resultColumnName] = $discrColumn;
|
||||
$this->_rsm->setDiscriminatorColumn('r', $discrColumn);
|
||||
$this->_rsm->addMetaResult('r', $resultColumnName, $discrColumn);
|
||||
}
|
||||
|
||||
// INNER JOIN parent tables
|
||||
@@ -304,14 +317,13 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
|
||||
// Add join columns (foreign keys)
|
||||
foreach ($subClass->associationMappings as $assoc2) {
|
||||
if ($assoc2->isOwningSide && $assoc2->isOneToOne() && ! $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 ($assoc2['isOwningSide'] && $assoc2['type'] & ClassMetadata::TO_ONE
|
||||
&& ! isset($assoc2['inherited'])) {
|
||||
foreach ($assoc2['targetToSourceKeyColumns'] as $srcColumn) {
|
||||
if ($columnList != '') $columnList .= ', ';
|
||||
$columnList .= $this->getSelectJoinColumnSQL($tableAlias, $srcColumn,
|
||||
isset($assoc2['inherited']) ? $assoc2['inherited'] : $subClass->name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -326,36 +338,56 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
}
|
||||
}
|
||||
|
||||
$joinSql .= $assoc != null && $assoc->isManyToMany() ?
|
||||
$joinSql .= $assoc != null && $assoc['type'] == ClassMetadata::MANY_TO_MANY ?
|
||||
$this->_getSelectManyToManyJoinSQL($assoc) : '';
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock all rows of this entity matching the given criteria with the specified pessimistic lock mode
|
||||
* Get the FROM and optionally JOIN conditions to lock the entity managed by this persister.
|
||||
*
|
||||
* @param array $criteria
|
||||
* @param int $lockMode
|
||||
* @return void
|
||||
* @return string
|
||||
*/
|
||||
public function lock(array $criteria, $lockMode)
|
||||
public function getLockTablesSql()
|
||||
{
|
||||
throw new \BadMethodCallException("lock() is not yet supported for JoinedSubclassPersister");
|
||||
$idColumns = $this->_class->getIdentifierColumnNames();
|
||||
$baseTableAlias = $this->_getSQLTableAlias($this->_class->name);
|
||||
|
||||
// INNER JOIN parent tables
|
||||
$joinSql = '';
|
||||
foreach ($this->_class->parentClasses as $parentClassName) {
|
||||
$parentClass = $this->_em->getClassMetadata($parentClassName);
|
||||
$tableAlias = $this->_getSQLTableAlias($parentClassName);
|
||||
$joinSql .= ' INNER JOIN ' . $parentClass->getQuotedTableName($this->_platform) . ' ' . $tableAlias . ' ON ';
|
||||
$first = true;
|
||||
foreach ($idColumns as $idColumn) {
|
||||
if ($first) $first = false; else $joinSql .= ' AND ';
|
||||
$joinSql .= $baseTableAlias . '.' . $idColumn . ' = ' . $tableAlias . '.' . $idColumn;
|
||||
}
|
||||
}
|
||||
|
||||
return 'FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' ' . $baseTableAlias . $joinSql;
|
||||
}
|
||||
|
||||
/* Ensure this method is never called. This persister overrides _getSelectEntitiesSQL directly. */
|
||||
@@ -372,15 +404,15 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
|
||||
foreach ($this->_class->reflFields as $name => $field) {
|
||||
if (isset($this->_class->fieldMappings[$name]['inherited']) && ! isset($this->_class->fieldMappings[$name]['id'])
|
||||
|| isset($this->_class->associationMappings[$name]->inherited)
|
||||
|| isset($this->_class->associationMappings[$name]['inherited'])
|
||||
|| ($this->_class->isVersioned && $this->_class->versionField == $name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($this->_class->associationMappings[$name])) {
|
||||
$assoc = $this->_class->associationMappings[$name];
|
||||
if ($assoc->isOneToOne() && $assoc->isOwningSide) {
|
||||
foreach ($assoc->targetToSourceKeyColumns as $sourceCol) {
|
||||
if ($assoc['type'] & ClassMetadata::TO_ONE && $assoc['isOwningSide']) {
|
||||
foreach ($assoc['targetToSourceKeyColumns'] as $sourceCol) {
|
||||
$columns[] = $sourceCol;
|
||||
}
|
||||
}
|
||||
@@ -397,4 +429,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.
|
||||
@@ -39,8 +40,8 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
protected function _getDeleteRowSQL(PersistentCollection $coll)
|
||||
{
|
||||
$mapping = $coll->getMapping();
|
||||
$joinTable = $mapping->joinTable;
|
||||
$columns = $mapping->joinTableColumns;
|
||||
$joinTable = $mapping['joinTable'];
|
||||
$columns = $mapping['joinTableColumns'];
|
||||
return 'DELETE FROM ' . $joinTable['name'] . ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?';
|
||||
}
|
||||
|
||||
@@ -74,8 +75,8 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
protected function _getInsertRowSQL(PersistentCollection $coll)
|
||||
{
|
||||
$mapping = $coll->getMapping();
|
||||
$joinTable = $mapping->joinTable;
|
||||
$columns = $mapping->joinTableColumns;
|
||||
$joinTable = $mapping['joinTable'];
|
||||
$columns = $mapping['joinTableColumns'];
|
||||
return 'INSERT INTO ' . $joinTable['name'] . ' (' . implode(', ', $columns) . ')'
|
||||
. ' VALUES (' . implode(', ', array_fill(0, count($columns), '?')) . ')';
|
||||
}
|
||||
@@ -104,7 +105,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
{
|
||||
$params = array();
|
||||
$mapping = $coll->getMapping();
|
||||
$isComposite = count($mapping->joinTableColumns) > 2;
|
||||
$isComposite = count($mapping['joinTableColumns']) > 2;
|
||||
|
||||
$identifier1 = $this->_uow->getEntityIdentifier($coll->getOwner());
|
||||
$identifier2 = $this->_uow->getEntityIdentifier($element);
|
||||
@@ -114,16 +115,24 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
$class2 = $coll->getTypeClass();
|
||||
}
|
||||
|
||||
foreach ($mapping->joinTableColumns as $joinTableColumn) {
|
||||
if (isset($mapping->relationToSourceKeyColumns[$joinTableColumn])) {
|
||||
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);
|
||||
}
|
||||
@@ -141,9 +150,9 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
protected function _getDeleteSQL(PersistentCollection $coll)
|
||||
{
|
||||
$mapping = $coll->getMapping();
|
||||
$joinTable = $mapping->joinTable;
|
||||
$joinTable = $mapping['joinTable'];
|
||||
$whereClause = '';
|
||||
foreach ($mapping->relationToSourceKeyColumns as $relationColumn => $srcColumn) {
|
||||
foreach ($mapping['relationToSourceKeyColumns'] as $relationColumn => $srcColumn) {
|
||||
if ($whereClause !== '') $whereClause .= ' AND ';
|
||||
$whereClause .= "$relationColumn = ?";
|
||||
}
|
||||
@@ -162,9 +171,9 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
$params = array();
|
||||
$mapping = $coll->getMapping();
|
||||
$identifier = $this->_uow->getEntityIdentifier($coll->getOwner());
|
||||
if (count($mapping->relationToSourceKeyColumns) > 1) {
|
||||
if (count($mapping['relationToSourceKeyColumns']) > 1) {
|
||||
$sourceClass = $this->_em->getClassMetadata(get_class($mapping->getOwner()));
|
||||
foreach ($mapping->relationToSourceKeyColumns as $relColumn => $srcColumn) {
|
||||
foreach ($mapping['relationToSourceKeyColumns'] as $relColumn => $srcColumn) {
|
||||
$params[] = $identifier[$sourceClass->fieldNames[$srcColumn]];
|
||||
}
|
||||
} else {
|
||||
@@ -173,4 +182,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.
|
||||
@@ -50,7 +51,7 @@ class OneToManyPersister extends AbstractCollectionPersister
|
||||
$targetClass = $this->_em->getClassMetadata($mapping->getTargetEntityName());
|
||||
$table = $targetClass->getTableName();
|
||||
|
||||
$ownerMapping = $targetClass->getAssociationMapping($mapping->mappedBy);
|
||||
$ownerMapping = $targetClass->getAssociationMapping($mapping['mappedBy']);
|
||||
|
||||
$setClause = '';
|
||||
foreach ($ownerMapping->sourceToTargetKeyColumns as $sourceCol => $targetCol) {
|
||||
@@ -116,4 +117,67 @@ class OneToManyPersister extends AbstractCollectionPersister
|
||||
*/
|
||||
protected function _getDeleteRowSQLParameters(PersistentCollection $coll, $element)
|
||||
{}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function count(PersistentCollection $coll)
|
||||
{
|
||||
$mapping = $coll->getMapping();
|
||||
$class = $this->_em->getClassMetadata($mapping['targetEntity']);
|
||||
$params = array();
|
||||
$id = $this->_em->getUnitOfWork()->getEntityIdentifier($coll->getOwner());
|
||||
|
||||
$where = '';
|
||||
foreach ($class->associationMappings[$mapping['mappedBy']]['joinColumns'] AS $joinColumn) {
|
||||
if ($where != '') {
|
||||
$where .= ' AND ';
|
||||
}
|
||||
$where .= $joinColumn['name'] . " = ?";
|
||||
if ($class->containsForeignIdentifier) {
|
||||
$params[] = $id[$class->getFieldForColumn($joinColumn['referencedColumnName'])];
|
||||
} else {
|
||||
$params[] = $id[$class->fieldNames[$joinColumn['referencedColumnName']]];
|
||||
}
|
||||
}
|
||||
|
||||
$sql = "SELECT count(*) FROM " . $class->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
|
||||
*/
|
||||
@@ -48,7 +49,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', $discrColumn);
|
||||
$this->_rsm->addMetaResult('r', $resultColumnName, $discrColumn);
|
||||
|
||||
// Append subclass columns
|
||||
foreach ($this->_class->subClasses as $subClassName) {
|
||||
@@ -61,14 +63,12 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
|
||||
}
|
||||
// Foreign key columns
|
||||
foreach ($subClass->associationMappings as $assoc) {
|
||||
if ($assoc->isOwningSide && $assoc->isOneToOne() && ! $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 ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE && ! isset($assoc['inherited'])) {
|
||||
foreach ($assoc['targetToSourceKeyColumns'] as $srcColumn) {
|
||||
if ($columnList != '') $columnList .= ', ';
|
||||
$columnList .= $this->getSelectJoinColumnSQL($tableAlias, $srcColumn,
|
||||
isset($assoc['inherited']) ? $assoc['inherited'] : $this->_class->name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,9 +88,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} */
|
||||
@@ -100,7 +100,11 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
|
||||
|
||||
// Append discriminator condition
|
||||
if ($conditionSql) $conditionSql .= ' AND ';
|
||||
$values = array($this->_conn->quote($this->_class->discriminatorValue));
|
||||
$values = array();
|
||||
if ($this->_class->discriminatorValue !== null) { // discriminators can be 0
|
||||
$values[] = $this->_conn->quote($this->_class->discriminatorValue);
|
||||
}
|
||||
|
||||
$discrValues = array_flip($this->_class->discriminatorMap);
|
||||
foreach ($this->_class->subClasses as $subclassName) {
|
||||
$values[] = $this->_conn->quote($discrValues[$subclassName]);
|
||||
|
||||
@@ -77,9 +77,11 @@ class ProxyFactory
|
||||
$proxyClassName = str_replace('\\', '', $className) . 'Proxy';
|
||||
$fqn = $this->_proxyNamespace . '\\' . $proxyClassName;
|
||||
|
||||
if ($this->_autoGenerate && ! class_exists($fqn, false)) {
|
||||
if (! class_exists($fqn, false)) {
|
||||
$fileName = $this->_proxyDir . DIRECTORY_SEPARATOR . $proxyClassName . '.php';
|
||||
$this->_generateProxyClass($this->_em->getClassMetadata($className), $proxyClassName, $fileName, self::$_proxyClassTemplate);
|
||||
if ($this->_autoGenerate) {
|
||||
$this->_generateProxyClass($this->_em->getClassMetadata($className), $proxyClassName, $fileName, self::$_proxyClassTemplate);
|
||||
}
|
||||
require $fileName;
|
||||
}
|
||||
|
||||
@@ -105,6 +107,11 @@ class ProxyFactory
|
||||
$proxyDir = $toDir ?: $this->_proxyDir;
|
||||
$proxyDir = rtrim($proxyDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
|
||||
foreach ($classes as $class) {
|
||||
/* @var $class ClassMetadata */
|
||||
if ($class->isMappedSuperclass) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$proxyClassName = str_replace('\\', '', $class->name) . 'Proxy';
|
||||
$proxyFileName = $proxyDir . $proxyClassName . '.php';
|
||||
$this->_generateProxyClass($class, $proxyClassName, $proxyFileName, self::$_proxyClassTemplate);
|
||||
@@ -123,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) == "\\") {
|
||||
@@ -139,12 +147,12 @@ class ProxyFactory
|
||||
$replacements = array(
|
||||
$this->_proxyNamespace,
|
||||
$proxyClassName, $className,
|
||||
$methods, $sleepImpl
|
||||
$methods, $sleepImpl, $cloneImpl
|
||||
);
|
||||
|
||||
$file = str_replace($placeholders, $replacements, $file);
|
||||
|
||||
file_put_contents($fileName, $file);
|
||||
file_put_contents($fileName, $file, LOCK_EX);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,7 +167,7 @@ class ProxyFactory
|
||||
|
||||
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"))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -201,7 +209,7 @@ class ProxyFactory
|
||||
|
||||
$methods .= $parameterString . ')';
|
||||
$methods .= PHP_EOL . ' {' . PHP_EOL;
|
||||
$methods .= ' $this->_load();' . PHP_EOL;
|
||||
$methods .= ' $this->__load();' . PHP_EOL;
|
||||
$methods .= ' return parent::' . $method->getName() . '(' . $argumentString . ');';
|
||||
$methods .= PHP_EOL . ' }' . PHP_EOL;
|
||||
}
|
||||
@@ -221,9 +229,9 @@ class ProxyFactory
|
||||
$sleepImpl = '';
|
||||
|
||||
if ($class->reflClass->hasMethod('__sleep')) {
|
||||
$sleepImpl .= 'return parent::__sleep();';
|
||||
$sleepImpl .= "return array_merge(array('__isInitialized__'), parent::__sleep());";
|
||||
} else {
|
||||
$sleepImpl .= 'return array(';
|
||||
$sleepImpl .= "return array('__isInitialized__', ";
|
||||
$first = true;
|
||||
|
||||
foreach ($class->getReflectionProperties() as $name => $prop) {
|
||||
@@ -261,26 +269,40 @@ 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 ($this->_entityPersister->load($this->_identifier, $this) === null) {
|
||||
throw new \Doctrine\ORM\EntityNotFoundException();
|
||||
}
|
||||
unset($this->_entityPersister);
|
||||
unset($this->_identifier);
|
||||
unset($this->_entityPersister, $this->_identifier);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
<methods>
|
||||
|
||||
public function __sleep()
|
||||
{
|
||||
if (!$this->__isInitialized__) {
|
||||
throw new \RuntimeException("Not fully loaded proxy can not be serialized.");
|
||||
}
|
||||
<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>
|
||||
}
|
||||
}';
|
||||
}
|
||||
|
||||
@@ -242,9 +242,19 @@ final class Query extends AbstractQuery
|
||||
}
|
||||
|
||||
if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value))) {
|
||||
$values = $this->_em->getUnitOfWork()->getEntityIdentifier($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, $values);
|
||||
$cSqlPos = count($sqlPositions);
|
||||
$cIdValues = count($idValues);
|
||||
$idValues = array_values($idValues);
|
||||
for ($i = 0; $i < $cSqlPos; $i++) {
|
||||
$sqlParams[$sqlPositions[$i]] = $idValues[ ($i % $cIdValues) ];
|
||||
}
|
||||
} else {
|
||||
foreach ($paramMappings[$key] as $position) {
|
||||
$sqlParams[$position] = $value;
|
||||
@@ -543,4 +553,15 @@ final class Query extends AbstractQuery
|
||||
'&hydrationMode='.$this->_hydrationMode.'DOCTRINE_QUERY_CACHE_SALT'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup Query resource when clone is called.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
parent::__clone();
|
||||
$this->_state = self::STATE_DIRTY;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -29,10 +27,10 @@ use Doctrine\ORM\Query\Lexer;
|
||||
* @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>
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class AbsFunction extends FunctionNode
|
||||
{
|
||||
@@ -53,8 +51,6 @@ class AbsFunction extends FunctionNode
|
||||
*/
|
||||
public function parse(\Doctrine\ORM\Query\Parser $parser)
|
||||
{
|
||||
$lexer = $parser->getLexer();
|
||||
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -29,10 +27,10 @@ use Doctrine\ORM\Query\Lexer;
|
||||
* @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>
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class ConcatFunction extends FunctionNode
|
||||
{
|
||||
@@ -56,8 +54,6 @@ class ConcatFunction extends FunctionNode
|
||||
*/
|
||||
public function parse(\Doctrine\ORM\Query\Parser $parser)
|
||||
{
|
||||
$lexer = $parser->getLexer();
|
||||
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -29,10 +27,10 @@ use Doctrine\ORM\Query\Lexer;
|
||||
* @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>
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class CurrentDateFunction extends FunctionNode
|
||||
{
|
||||
@@ -49,8 +47,6 @@ class CurrentDateFunction extends FunctionNode
|
||||
*/
|
||||
public function parse(\Doctrine\ORM\Query\Parser $parser)
|
||||
{
|
||||
$lexer = $parser->getLexer();
|
||||
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
|
||||
@@ -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
|
||||
@@ -29,10 +27,10 @@ use Doctrine\ORM\Query\Lexer;
|
||||
* @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>
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class CurrentTimeFunction extends FunctionNode
|
||||
{
|
||||
@@ -49,8 +47,6 @@ class CurrentTimeFunction extends FunctionNode
|
||||
*/
|
||||
public function parse(\Doctrine\ORM\Query\Parser $parser)
|
||||
{
|
||||
$lexer = $parser->getLexer();
|
||||
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
<?php
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
/*
|
||||
* 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;
|
||||
@@ -11,7 +24,13 @@ use Doctrine\ORM\Query\Lexer;
|
||||
/**
|
||||
* "CURRENT_TIMESTAMP"
|
||||
*
|
||||
* @author robo
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
* @since 2.0
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @author Jonathan Wage <jonwage@gmail.com>
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class CurrentTimestampFunction extends FunctionNode
|
||||
{
|
||||
@@ -28,8 +47,6 @@ class CurrentTimestampFunction extends FunctionNode
|
||||
*/
|
||||
public function parse(\Doctrine\ORM\Query\Parser $parser)
|
||||
{
|
||||
$lexer = $parser->getLexer();
|
||||
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -29,10 +27,10 @@ use Doctrine\ORM\Query\AST\Node;
|
||||
* @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>
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
abstract class FunctionNode extends Node
|
||||
{
|
||||
|
||||
@@ -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
|
||||
@@ -29,10 +27,10 @@ use Doctrine\ORM\Query\Lexer;
|
||||
* @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>
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class LengthFunction extends FunctionNode
|
||||
{
|
||||
@@ -43,8 +41,9 @@ class LengthFunction extends FunctionNode
|
||||
*/
|
||||
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
|
||||
{
|
||||
//TODO: Use platform to get SQL
|
||||
return 'LENGTH(' . $sqlWalker->walkStringPrimary($this->stringPrimary) . ')';
|
||||
return $sqlWalker->getConnection()->getDatabasePlatform()->getLengthExpression(
|
||||
$sqlWalker->walkSimpleArithmeticExpression($this->stringPrimary)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,8 +51,6 @@ class LengthFunction extends FunctionNode
|
||||
*/
|
||||
public function parse(\Doctrine\ORM\Query\Parser $parser)
|
||||
{
|
||||
$lexer = $parser->getLexer();
|
||||
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -29,10 +27,10 @@ use Doctrine\ORM\Query\Lexer;
|
||||
* @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>
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class LocateFunction extends FunctionNode
|
||||
{
|
||||
@@ -61,8 +59,6 @@ class LocateFunction extends FunctionNode
|
||||
*/
|
||||
public function parse(\Doctrine\ORM\Query\Parser $parser)
|
||||
{
|
||||
$lexer = $parser->getLexer();
|
||||
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
@@ -72,6 +68,7 @@ class LocateFunction extends FunctionNode
|
||||
|
||||
$this->secondStringPrimary = $parser->StringPrimary();
|
||||
|
||||
$lexer = $parser->getLexer();
|
||||
if ($lexer->isNextToken(Lexer::T_COMMA)) {
|
||||
$parser->match(Lexer::T_COMMA);
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -29,10 +27,10 @@ use Doctrine\ORM\Query\Lexer;
|
||||
* @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>
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class LowerFunction extends FunctionNode
|
||||
{
|
||||
@@ -43,8 +41,9 @@ class LowerFunction extends FunctionNode
|
||||
*/
|
||||
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
|
||||
{
|
||||
//TODO: Use platform to get SQL
|
||||
return 'LOWER(' . $sqlWalker->walkStringPrimary($this->stringPrimary) . ')';
|
||||
return $sqlWalker->getConnection()->getDatabasePlatform()->getLowerExpression(
|
||||
$sqlWalker->walkSimpleArithmeticExpression($this->stringPrimary)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,8 +51,6 @@ class LowerFunction extends FunctionNode
|
||||
*/
|
||||
public function parse(\Doctrine\ORM\Query\Parser $parser)
|
||||
{
|
||||
$lexer = $parser->getLexer();
|
||||
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -29,10 +27,10 @@ use Doctrine\ORM\Query\Lexer;
|
||||
* @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>
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class ModFunction extends FunctionNode
|
||||
{
|
||||
@@ -44,12 +42,10 @@ class ModFunction extends FunctionNode
|
||||
*/
|
||||
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
|
||||
{
|
||||
//TODO: Use platform to get SQL
|
||||
return 'MOD('
|
||||
. $sqlWalker->walkSimpleArithmeticExpression($this->firstSimpleArithmeticExpression)
|
||||
. ', '
|
||||
. $sqlWalker->walkSimpleArithmeticExpression($this->secondSimpleArithmeticExpression)
|
||||
. ')';
|
||||
return $sqlWalker->getConnection()->getDatabasePlatform()->getModExpression(
|
||||
$sqlWalker->walkSimpleArithmeticExpression($this->firstSimpleArithmeticExpression),
|
||||
$sqlWalker->walkSimpleArithmeticExpression($this->secondSimpleArithmeticExpression)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,8 +53,6 @@ class ModFunction extends FunctionNode
|
||||
*/
|
||||
public function parse(\Doctrine\ORM\Query\Parser $parser)
|
||||
{
|
||||
$lexer = $parser->getLexer();
|
||||
|
||||
$parser->match(Lexer::T_MOD);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -29,7 +27,6 @@ use Doctrine\ORM\Query\Lexer;
|
||||
* @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>
|
||||
@@ -45,46 +42,66 @@ class SizeFunction extends FunctionNode
|
||||
*/
|
||||
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
|
||||
{
|
||||
$platform = $sqlWalker->getConnection()->getDatabasePlatform();
|
||||
$dqlAlias = $this->collectionPathExpression->identificationVariable;
|
||||
$parts = $this->collectionPathExpression->parts;
|
||||
$assocField = array_pop($parts);
|
||||
|
||||
$qComp = $sqlWalker->getQueryComponent(implode('.', array_merge((array) $dqlAlias, $parts)));
|
||||
$assoc = $qComp['metadata']->associationMappings[$assocField];
|
||||
$sql = '';
|
||||
$assocField = $this->collectionPathExpression->field;
|
||||
|
||||
if ($assoc->isOneToMany()) {
|
||||
$targetClass = $sqlWalker->getEntityManager()->getClassMetadata($assoc->targetEntityName);
|
||||
$targetAssoc = $targetClass->associationMappings[$assoc->mappedBy];
|
||||
|
||||
$targetTableAlias = $sqlWalker->getSqlTableAlias($targetClass->table['name']);
|
||||
$sourceTableAlias = $sqlWalker->getSqlTableAlias($qComp['metadata']->table['name'], $dqlAlias);
|
||||
|
||||
$whereSql = '';
|
||||
$qComp = $sqlWalker->getQueryComponent($dqlAlias);
|
||||
$class = $qComp['metadata'];
|
||||
$assoc = $class->associationMappings[$assocField];
|
||||
$sql = 'SELECT COUNT(*) FROM ';
|
||||
|
||||
foreach ($targetAssoc->targetToSourceKeyColumns as $targetKeyColumn => $sourceKeyColumn) {
|
||||
$whereSql .= (($whereSql == '') ? ' WHERE ' : ' AND ')
|
||||
. $targetTableAlias . '.' . $sourceKeyColumn . ' = '
|
||||
. $sourceTableAlias . '.' . $targetKeyColumn;
|
||||
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);
|
||||
|
||||
$sql .= $targetClass->getQuotedTableName($platform) . ' ' . $targetTableAlias . ' WHERE ';
|
||||
|
||||
$owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']];
|
||||
|
||||
$first = true;
|
||||
|
||||
foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) {
|
||||
if ($first) $first = false; else $sql .= ' AND ';
|
||||
|
||||
$sql .= $targetTableAlias . '.' . $sourceColumn
|
||||
. ' = '
|
||||
. $sourceTableAlias . '.' . $class->getQuotedColumnName($class->fieldNames[$targetColumn], $platform);
|
||||
}
|
||||
} else { // many-to-many
|
||||
$targetClass = $sqlWalker->getEntityManager()->getClassMetadata($assoc['targetEntity']);
|
||||
|
||||
$tableName = $targetClass->table['name'];
|
||||
} else if ($assoc->isManyToMany()) {
|
||||
$targetTableAlias = $sqlWalker->getSqlTableAlias($assoc->joinTable['name']);
|
||||
$sourceTableAlias = $sqlWalker->getSqlTableAlias($qComp['metadata']->table['name'], $dqlAlias);
|
||||
|
||||
$whereSql = '';
|
||||
$owningAssoc = $assoc['isOwningSide'] ? $assoc : $targetClass->associationMappings[$assoc['mappedBy']];
|
||||
$joinTable = $owningAssoc['joinTable'];
|
||||
|
||||
foreach ($assoc->relationToSourceKeyColumns as $targetKeyColumn => $sourceKeyColumn) {
|
||||
$whereSql .= (($whereSql == '') ? ' WHERE ' : ' AND ')
|
||||
. $targetTableAlias . '.' . $targetKeyColumn . ' = '
|
||||
. $sourceTableAlias . '.' . $sourceKeyColumn;
|
||||
// SQL table aliases
|
||||
$joinTableAlias = $sqlWalker->getSQLTableAlias($joinTable['name']);
|
||||
$sourceTableAlias = $sqlWalker->getSQLTableAlias($class->table['name'], $dqlAlias);
|
||||
|
||||
// join to target table
|
||||
$sql .= $targetClass->getQuotedJoinTableName($owningAssoc, $platform) . ' ' . $joinTableAlias . ' WHERE ';
|
||||
|
||||
$joinColumns = $assoc['isOwningSide']
|
||||
? $joinTable['joinColumns']
|
||||
: $joinTable['inverseJoinColumns'];
|
||||
|
||||
$first = true;
|
||||
|
||||
foreach ($joinColumns as $joinColumn) {
|
||||
if ($first) $first = false; else $sql .= ' AND ';
|
||||
|
||||
$sourceColumnName = $class->getQuotedColumnName(
|
||||
$class->fieldNames[$joinColumn['referencedColumnName']], $platform
|
||||
);
|
||||
|
||||
$sql .= $joinTableAlias . '.' . $joinColumn['name']
|
||||
. ' = '
|
||||
. $sourceTableAlias . '.' . $sourceColumnName;
|
||||
}
|
||||
|
||||
$tableName = $assoc->joinTable['name'];
|
||||
}
|
||||
|
||||
return '(SELECT COUNT(*) FROM ' . $tableName . ' ' . $targetTableAlias . $whereSql . ')';
|
||||
return '(' . $sql . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
@@ -29,10 +27,10 @@ use Doctrine\ORM\Query\Lexer;
|
||||
* @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>
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class SqrtFunction extends FunctionNode
|
||||
{
|
||||
@@ -52,8 +50,6 @@ class SqrtFunction extends FunctionNode
|
||||
*/
|
||||
public function parse(\Doctrine\ORM\Query\Parser $parser)
|
||||
{
|
||||
$lexer = $parser->getLexer();
|
||||
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -29,10 +27,10 @@ use Doctrine\ORM\Query\Lexer;
|
||||
* @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>
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class SubstringFunction extends FunctionNode
|
||||
{
|
||||
@@ -62,8 +60,6 @@ class SubstringFunction extends FunctionNode
|
||||
*/
|
||||
public function parse(\Doctrine\ORM\Query\Parser $parser)
|
||||
{
|
||||
$lexer = $parser->getLexer();
|
||||
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
@@ -73,6 +69,7 @@ class SubstringFunction extends FunctionNode
|
||||
|
||||
$this->firstSimpleArithmeticExpression = $parser->SimpleArithmeticExpression();
|
||||
|
||||
$lexer = $parser->getLexer();
|
||||
if ($lexer->isNextToken(Lexer::T_COMMA)) {
|
||||
$parser->match(Lexer::T_COMMA);
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -30,10 +28,10 @@ use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
* @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>
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class TrimFunction extends FunctionNode
|
||||
{
|
||||
|
||||
@@ -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
|
||||
@@ -29,10 +27,10 @@ use Doctrine\ORM\Query\Lexer;
|
||||
* @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>
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
*/
|
||||
class UpperFunction extends FunctionNode
|
||||
{
|
||||
@@ -43,8 +41,9 @@ class UpperFunction extends FunctionNode
|
||||
*/
|
||||
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
|
||||
{
|
||||
//TODO: Use platform to get SQL
|
||||
return 'UPPER(' . $sqlWalker->walkStringPrimary($this->stringPrimary) . ')';
|
||||
return $sqlWalker->getConnection()->getDatabasePlatform()->getUpperExpression(
|
||||
$sqlWalker->walkSimpleArithmeticExpression($this->stringPrimary)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,8 +51,6 @@ class UpperFunction extends FunctionNode
|
||||
*/
|
||||
public function parse(\Doctrine\ORM\Query\Parser $parser)
|
||||
{
|
||||
$lexer = $parser->getLexer();
|
||||
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (AbstractSchemaName | InputParameter)
|
||||
*
|
||||
* @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>
|
||||
*/
|
||||
class InstanceOfExpression extends Node
|
||||
{
|
||||
public $not;
|
||||
public $identificationVariable;
|
||||
public $value;
|
||||
|
||||
public function __construct($identVariable)
|
||||
{
|
||||
$this->identificationVariable = $identVariable;
|
||||
}
|
||||
|
||||
public function dispatch($sqlWalker)
|
||||
{
|
||||
return $sqlWalker->walkInstanceOfExpression($this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -23,11 +23,10 @@ namespace Doctrine\ORM\Query\AST;
|
||||
* AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
|
||||
* SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
|
||||
* StateFieldPathExpression ::= SimpleStateFieldPathExpression | SimpleStateFieldAssociationPathExpression
|
||||
* SingleValuedAssociationPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* SingleValuedAssociationField
|
||||
* CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField
|
||||
* SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
|
||||
* CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField
|
||||
* StateField ::= {EmbeddedClassStateField "."}* SimpleStateField
|
||||
* SimpleStateFieldPathExpression ::= IdentificationVariable "." StateField
|
||||
* SimpleStateFieldAssociationPathExpression ::= SingleValuedAssociationPathExpression "." StateField
|
||||
*
|
||||
* @since 2.0
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
@@ -43,13 +42,13 @@ class PathExpression extends Node
|
||||
public $type;
|
||||
public $expectedType;
|
||||
public $identificationVariable;
|
||||
public $parts;
|
||||
public $field;
|
||||
|
||||
public function __construct($expectedType, $identificationVariable, array $parts)
|
||||
public function __construct($expectedType, $identificationVariable, $field = null)
|
||||
{
|
||||
$this->expectedType = $expectedType;
|
||||
$this->identificationVariable = $identificationVariable;
|
||||
$this->parts = $parts;
|
||||
$this->field = $field;
|
||||
}
|
||||
|
||||
public function dispatch($walker)
|
||||
|
||||
@@ -36,13 +36,12 @@ namespace Doctrine\ORM\Query\AST;
|
||||
*/
|
||||
class UpdateItem extends Node
|
||||
{
|
||||
public $identificationVariable;
|
||||
public $field;
|
||||
public $pathExpression;
|
||||
public $newValue;
|
||||
|
||||
public function __construct($field, $newValue)
|
||||
public function __construct($pathExpression, $newValue)
|
||||
{
|
||||
$this->field = $field;
|
||||
$this->pathExpression = $pathExpression;
|
||||
$this->newValue = $newValue;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
@@ -84,8 +87,10 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor
|
||||
$updateSql = 'UPDATE ' . $class->getQuotedTableName($platform) . ' SET ';
|
||||
|
||||
foreach ($updateItems as $updateItem) {
|
||||
$field = $updateItem->field;
|
||||
if (isset($class->fieldMappings[$field]) && ! isset($class->fieldMappings[$field]['inherited'])) {
|
||||
$field = $updateItem->pathExpression->field;
|
||||
|
||||
if (isset($class->fieldMappings[$field]) && ! isset($class->fieldMappings[$field]['inherited']) ||
|
||||
isset($class->associationMappings[$field]) && ! isset($class->associationMappings[$field]['inherited'])) {
|
||||
$newValue = $updateItem->newValue;
|
||||
|
||||
if ( ! $affected) {
|
||||
@@ -102,6 +107,7 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor
|
||||
if ($newValue instanceof AST\InputParameter) {
|
||||
$paramKey = $newValue->name;
|
||||
$this->_sqlParameters[$i][] = $sqlWalker->getQuery()->getParameter($paramKey);
|
||||
|
||||
++$this->_numParametersInUpdateClause;
|
||||
}
|
||||
}
|
||||
@@ -119,14 +125,17 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,10 +25,10 @@ namespace Doctrine\ORM\Query;
|
||||
* @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>
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
* @todo Rename: ExpressionBuilder
|
||||
*/
|
||||
class Expr
|
||||
@@ -435,9 +433,38 @@ class Expr
|
||||
*/
|
||||
public function notIn($x, $y)
|
||||
{
|
||||
if (is_array($y)) {
|
||||
foreach ($y as &$literal) {
|
||||
if ( ! ($literal instanceof Expr\Literal)) {
|
||||
$literal = $this->_quoteLiteral($literal);
|
||||
}
|
||||
}
|
||||
}
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<?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[] = (is_object($part) && $part instanceof self && $part->count() > 1)
|
||||
? $this->_preSeparator . ((string) $part) . $this->_postSeparator
|
||||
: ((string) $part);
|
||||
}
|
||||
|
||||
return implode($this->_separator, $components);
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -36,4 +36,7 @@ class Select extends Base
|
||||
{
|
||||
protected $_preSeparator = '';
|
||||
protected $_postSeparator = '';
|
||||
protected $_allowedClasses = array(
|
||||
'Doctrine\ORM\Query\Expr\Func'
|
||||
);
|
||||
}
|
||||
@@ -61,47 +61,52 @@ class Lexer extends \Doctrine\Common\Lexer
|
||||
const T_BETWEEN = 107;
|
||||
const T_BOTH = 108;
|
||||
const T_BY = 109;
|
||||
const T_COUNT = 110;
|
||||
const T_DELETE = 111;
|
||||
const T_DESC = 112;
|
||||
const T_DISTINCT = 113;
|
||||
const T_EMPTY = 114;
|
||||
const T_ESCAPE = 115;
|
||||
const T_EXISTS = 116;
|
||||
const T_FALSE = 117;
|
||||
const T_FROM = 118;
|
||||
const T_GROUP = 119;
|
||||
const T_HAVING = 120;
|
||||
const T_IN = 121;
|
||||
const T_INDEX = 122;
|
||||
const T_INNER = 123;
|
||||
const T_IS = 124;
|
||||
const T_JOIN = 125;
|
||||
const T_LEADING = 126;
|
||||
const T_LEFT = 127;
|
||||
const T_LIKE = 128;
|
||||
const T_MAX = 129;
|
||||
const T_MEMBER = 130;
|
||||
const T_MIN = 131;
|
||||
const T_NOT = 132;
|
||||
const T_NULL = 133;
|
||||
const T_OF = 134;
|
||||
const T_OR = 135;
|
||||
const T_ORDER = 136;
|
||||
const T_OUTER = 137;
|
||||
const T_SELECT = 138;
|
||||
const T_SET = 139;
|
||||
const T_SIZE = 140;
|
||||
const T_SOME = 141;
|
||||
const T_SUM = 142;
|
||||
const T_TRAILING = 143;
|
||||
const T_TRUE = 144;
|
||||
const T_UPDATE = 145;
|
||||
const T_WHERE = 146;
|
||||
const T_WITH = 147;
|
||||
const T_PARTIAL = 148;
|
||||
const T_MOD = 149;
|
||||
|
||||
const T_CASE = 110;
|
||||
const T_COALESCE = 111;
|
||||
const T_COUNT = 112;
|
||||
const T_DELETE = 113;
|
||||
const T_DESC = 114;
|
||||
const T_DISTINCT = 115;
|
||||
const T_EMPTY = 116;
|
||||
const T_ESCAPE = 117;
|
||||
const T_EXISTS = 118;
|
||||
const T_FALSE = 119;
|
||||
const T_FROM = 120;
|
||||
const T_GROUP = 121;
|
||||
const T_HAVING = 122;
|
||||
const T_IN = 123;
|
||||
const T_INDEX = 124;
|
||||
const T_INNER = 125;
|
||||
const T_INSTANCE = 126;
|
||||
const T_IS = 127;
|
||||
const T_JOIN = 128;
|
||||
const T_LEADING = 129;
|
||||
const T_LEFT = 130;
|
||||
const T_LIKE = 131;
|
||||
const T_MAX = 132;
|
||||
const T_MEMBER = 133;
|
||||
const T_MIN = 134;
|
||||
const T_NOT = 135;
|
||||
const T_NULL = 136;
|
||||
const T_NULLIF = 137;
|
||||
const T_OF = 138;
|
||||
const T_OR = 139;
|
||||
const T_ORDER = 140;
|
||||
const T_OUTER = 141;
|
||||
const T_SELECT = 142;
|
||||
const T_SET = 143;
|
||||
const T_SIZE = 144;
|
||||
const T_SOME = 145;
|
||||
const T_SUM = 146;
|
||||
const T_TRAILING = 147;
|
||||
const T_TRUE = 148;
|
||||
const T_UPDATE = 149;
|
||||
const T_WHEN = 150;
|
||||
const T_WHERE = 151;
|
||||
const T_WITH = 153;
|
||||
const T_PARTIAL = 154;
|
||||
const T_MOD = 155;
|
||||
|
||||
/**
|
||||
* Creates a new query scanner object.
|
||||
*
|
||||
@@ -118,10 +123,10 @@ class Lexer extends \Doctrine\Common\Lexer
|
||||
protected function getCatchablePatterns()
|
||||
{
|
||||
return array(
|
||||
'[a-z_][a-z0-9_\:\\\]*[a-z0-9_]{1}',
|
||||
'[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,}'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -136,7 +141,7 @@ class Lexer extends \Doctrine\Common\Lexer
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected function _getType(&$value)
|
||||
protected function getType(&$value)
|
||||
{
|
||||
$type = self::T_NONE;
|
||||
|
||||
@@ -152,12 +157,15 @@ class Lexer extends \Doctrine\Common\Lexer
|
||||
return self::T_STRING;
|
||||
} else if (ctype_alpha($value[0]) || $value[0] === '_') {
|
||||
$name = 'Doctrine\ORM\Query\Lexer::T_' . strtoupper($value);
|
||||
|
||||
if (defined($name)) {
|
||||
$type = constant($name);
|
||||
|
||||
if ($type > 100) {
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
|
||||
return self::T_IDENTIFIER;
|
||||
} else if ($value[0] === '?' || $value[0] === ':') {
|
||||
return self::T_INPUT_PARAMETER;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
+331
-200
@@ -20,6 +20,7 @@
|
||||
namespace Doctrine\ORM\Query;
|
||||
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
|
||||
/**
|
||||
* An LL(*) recursive-descent parser for the context-free grammar of the Doctrine Query Language.
|
||||
@@ -44,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',
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -124,6 +128,11 @@ class Parser
|
||||
*/
|
||||
private $_customOutputWalker;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $_identVariableExpressions = array();
|
||||
|
||||
/**
|
||||
* Creates a new query parser object.
|
||||
*
|
||||
@@ -225,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.
|
||||
*/
|
||||
@@ -271,6 +280,9 @@ class Parser
|
||||
{
|
||||
$AST = $this->getAST();
|
||||
|
||||
$this->fixIdentificationVariableOrder($AST);
|
||||
$this->assertSelectEntityRootAliasRequirement();
|
||||
|
||||
if (($customWalkers = $this->_query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) !== false) {
|
||||
$this->_customTreeWalkers = $customWalkers;
|
||||
}
|
||||
@@ -311,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.
|
||||
@@ -396,6 +448,41 @@ class Parser
|
||||
return $peek;
|
||||
}
|
||||
|
||||
/**
|
||||
* Peek beyond the matched closing parenthesis and return the first token after that one.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function _peekBeyondClosingParenthesis()
|
||||
{
|
||||
$token = $this->_lexer->peek();
|
||||
$numUnmatched = 1;
|
||||
|
||||
while ($numUnmatched > 0 && $token !== null) {
|
||||
if ($token['value'] == ')') {
|
||||
--$numUnmatched;
|
||||
} else if ($token['value'] == '(') {
|
||||
++$numUnmatched;
|
||||
}
|
||||
|
||||
$token = $this->_lexer->peek();
|
||||
}
|
||||
|
||||
$this->_lexer->resetPeek();
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given token indicates a mathematical operator.
|
||||
*
|
||||
* @return boolean TRUE if the token is a mathematical operator, FALSE otherwise.
|
||||
*/
|
||||
private function _isMathOperator($token)
|
||||
{
|
||||
return in_array($token['value'], array("+", "-", "/", "*"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the next-next (after lookahead) token starts a function.
|
||||
*
|
||||
@@ -450,7 +537,7 @@ class Parser
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the given <tt>IdentificationVariable</tt> is a semantically correct.
|
||||
* Validates that the given <tt>IdentificationVariable</tt> is semantically correct.
|
||||
* It must exist in query components list.
|
||||
*
|
||||
* @return void
|
||||
@@ -485,11 +572,18 @@ class Parser
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the given <tt>PartialObjectExpression</tt> is semantically correct.
|
||||
* It must exist in query components list.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function _processDeferredPartialObjectExpressions()
|
||||
{
|
||||
foreach ($this->_deferredPartialObjectExpressions as $deferredItem) {
|
||||
$expr = $deferredItem['expression'];
|
||||
$class = $this->_queryComponents[$expr->identificationVariable]['metadata'];
|
||||
|
||||
foreach ($expr->partialFieldSet as $field) {
|
||||
if ( ! isset($class->fieldMappings[$field])) {
|
||||
$this->semanticalError(
|
||||
@@ -498,6 +592,7 @@ class Parser
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (array_intersect($class->identifier, $expr->partialFieldSet) != $class->identifier) {
|
||||
$this->semanticalError(
|
||||
"The partial field selection of class " . $class->name . " must contain the identifier.",
|
||||
@@ -508,7 +603,7 @@ class Parser
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the given <tt>ResultVariable</tt> is a semantically correct.
|
||||
* Validates that the given <tt>ResultVariable</tt> is semantically correct.
|
||||
* It must exist in query components list.
|
||||
*
|
||||
* @return void
|
||||
@@ -544,13 +639,13 @@ class Parser
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the given <tt>PathExpression</tt> is a semantically correct for grammar rules:
|
||||
* Validates that the given <tt>PathExpression</tt> is semantically correct for grammar rules:
|
||||
*
|
||||
* AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
|
||||
* SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
|
||||
* StateFieldPathExpression ::= IdentificationVariable "." StateField | SingleValuedAssociationPathExpression "." StateField
|
||||
* SingleValuedAssociationPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* SingleValuedAssociationField
|
||||
* CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField
|
||||
* StateFieldPathExpression ::= IdentificationVariable "." StateField
|
||||
* SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
|
||||
* CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField
|
||||
*
|
||||
* @param array $deferredItem
|
||||
* @param mixed $AST
|
||||
@@ -561,93 +656,31 @@ class Parser
|
||||
$pathExpression = $deferredItem['expression'];
|
||||
|
||||
$qComp = $this->_queryComponents[$pathExpression->identificationVariable];
|
||||
$numParts = count($pathExpression->parts);
|
||||
$class = $qComp['metadata'];
|
||||
|
||||
if ($numParts == 0) {
|
||||
$pathExpression->parts = array($qComp['metadata']->identifier[0]);
|
||||
$numParts++;
|
||||
if (($field = $pathExpression->field) === null) {
|
||||
$field = $pathExpression->field = $class->identifier[0];
|
||||
}
|
||||
|
||||
$parts = $pathExpression->parts;
|
||||
$aliasIdentificationVariable = $pathExpression->identificationVariable;
|
||||
$parentField = $pathExpression->identificationVariable;
|
||||
$class = $qComp['metadata'];
|
||||
$fieldType = null;
|
||||
$curIndex = 0;
|
||||
// Check if field or association exists
|
||||
if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) {
|
||||
$this->semanticalError(
|
||||
'Class ' . $class->name . ' has no field or association named ' . $field,
|
||||
$deferredItem['token']
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($parts as $field) {
|
||||
// Check if it is not in a state field
|
||||
if ($fieldType & AST\PathExpression::TYPE_STATE_FIELD) {
|
||||
$this->semanticalError(
|
||||
'Cannot navigate through state field named ' . $field . ' on ' . $parentField,
|
||||
$deferredItem['token']
|
||||
);
|
||||
}
|
||||
if (isset($class->fieldMappings[$field])) {
|
||||
$fieldType = AST\PathExpression::TYPE_STATE_FIELD;
|
||||
} else {
|
||||
$assoc = $class->associationMappings[$field];
|
||||
$class = $this->_em->getClassMetadata($assoc['targetEntity']);
|
||||
|
||||
// Check if it is not a collection field
|
||||
if ($fieldType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) {
|
||||
$this->semanticalError(
|
||||
'Cannot navigate through collection field named ' . $field . ' on ' . $parentField,
|
||||
$deferredItem['token']
|
||||
);
|
||||
}
|
||||
|
||||
// Check if field or association exists
|
||||
if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) {
|
||||
$this->semanticalError(
|
||||
'Class ' . $class->name . ' has no field or association named ' . $field,
|
||||
$deferredItem['token']
|
||||
);
|
||||
}
|
||||
|
||||
$parentField = $field;
|
||||
|
||||
if (isset($class->fieldMappings[$field])) {
|
||||
$fieldType = AST\PathExpression::TYPE_STATE_FIELD;
|
||||
if ($assoc['type'] & ClassMetadata::TO_ONE) {
|
||||
$fieldType = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
|
||||
} else {
|
||||
$assoc = $class->associationMappings[$field];
|
||||
$class = $this->_em->getClassMetadata($assoc->targetEntityName);
|
||||
|
||||
if (
|
||||
($curIndex != $numParts - 1) &&
|
||||
! isset($this->_queryComponents[$aliasIdentificationVariable . '.' . $field])
|
||||
) {
|
||||
// Building queryComponent
|
||||
$joinQueryComponent = array(
|
||||
'metadata' => $class,
|
||||
'parent' => $aliasIdentificationVariable,
|
||||
'relation' => $assoc,
|
||||
'map' => null,
|
||||
'nestingLevel' => $this->_nestingLevel,
|
||||
'token' => $deferredItem['token'],
|
||||
);
|
||||
|
||||
// Create AST node
|
||||
$joinVariableDeclaration = new AST\JoinVariableDeclaration(
|
||||
new AST\Join(
|
||||
AST\Join::JOIN_TYPE_INNER,
|
||||
new AST\JoinAssociationPathExpression($aliasIdentificationVariable, $field),
|
||||
$aliasIdentificationVariable . '.' . $field,
|
||||
false
|
||||
),
|
||||
null
|
||||
);
|
||||
|
||||
$AST->fromClause->identificationVariableDeclarations[0]->joinVariableDeclarations[] = $joinVariableDeclaration;
|
||||
|
||||
$this->_queryComponents[$aliasIdentificationVariable . '.' . $field] = $joinQueryComponent;
|
||||
}
|
||||
|
||||
$aliasIdentificationVariable .= '.' . $field;
|
||||
|
||||
if ($assoc->isOneToOne()) {
|
||||
$fieldType = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
|
||||
} else {
|
||||
$fieldType = AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION;
|
||||
}
|
||||
$fieldType = AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION;
|
||||
}
|
||||
|
||||
++$curIndex;
|
||||
}
|
||||
|
||||
// Validate if PathExpression is one of the expected types
|
||||
@@ -683,7 +716,7 @@ class Parser
|
||||
|
||||
$this->semanticalError($semanticalError, $deferredItem['token']);
|
||||
}
|
||||
|
||||
|
||||
// We need to force the type in PathExpression
|
||||
$pathExpression->type = $fieldType;
|
||||
}
|
||||
@@ -825,7 +858,7 @@ class Parser
|
||||
{
|
||||
$this->match(Lexer::T_IDENTIFIER);
|
||||
|
||||
$schemaName = $this->_lexer->token['value'];
|
||||
$schemaName = ltrim($this->_lexer->token['value'], '\\');
|
||||
|
||||
if (strrpos($schemaName, ':') !== false) {
|
||||
list($namespaceAlias, $simpleClassName) = explode(':', $schemaName);
|
||||
@@ -891,11 +924,15 @@ class Parser
|
||||
public function JoinAssociationPathExpression()
|
||||
{
|
||||
$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);
|
||||
//$this->match($this->_lexer->lookahead['type']);
|
||||
|
||||
$field = $this->_lexer->token['value'];
|
||||
|
||||
// Validate association field
|
||||
@@ -913,7 +950,7 @@ class Parser
|
||||
* Parses an arbitrary path expression and defers semantical validation
|
||||
* based on expected types.
|
||||
*
|
||||
* PathExpression ::= IdentificationVariable {"." identifier}* "." identifier
|
||||
* PathExpression ::= IdentificationVariable "." identifier
|
||||
*
|
||||
* @param integer $expectedTypes
|
||||
* @return \Doctrine\ORM\Query\AST\PathExpression
|
||||
@@ -922,17 +959,17 @@ class Parser
|
||||
{
|
||||
$token = $this->_lexer->lookahead;
|
||||
$identVariable = $this->IdentificationVariable();
|
||||
$parts = array();
|
||||
$field = null;
|
||||
|
||||
while ($this->_lexer->isNextToken(Lexer::T_DOT)) {
|
||||
if ($this->_lexer->isNextToken(Lexer::T_DOT)) {
|
||||
$this->match(Lexer::T_DOT);
|
||||
$this->match(Lexer::T_IDENTIFIER);
|
||||
|
||||
$parts[] = $this->_lexer->token['value'];
|
||||
$field = $this->_lexer->token['value'];
|
||||
}
|
||||
|
||||
|
||||
// Creating AST node
|
||||
$pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $parts);
|
||||
$pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field);
|
||||
|
||||
// Defer PathExpression validation if requested to be defered
|
||||
$this->_deferredPathExpressions[] = array(
|
||||
@@ -971,7 +1008,7 @@ class Parser
|
||||
}
|
||||
|
||||
/**
|
||||
* StateFieldPathExpression ::= SimpleStateFieldPathExpression | SimpleStateFieldAssociationPathExpression
|
||||
* StateFieldPathExpression ::= IdentificationVariable "." StateField
|
||||
*
|
||||
* @return \Doctrine\ORM\Query\AST\PathExpression
|
||||
*/
|
||||
@@ -981,7 +1018,7 @@ class Parser
|
||||
}
|
||||
|
||||
/**
|
||||
* SingleValuedAssociationPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* SingleValuedAssociationField
|
||||
* SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
|
||||
*
|
||||
* @return \Doctrine\ORM\Query\AST\PathExpression
|
||||
*/
|
||||
@@ -991,7 +1028,7 @@ class Parser
|
||||
}
|
||||
|
||||
/**
|
||||
* CollectionValuedPathExpression ::= IdentificationVariable "." {SingleValuedAssociationField "."}* CollectionValuedAssociationField
|
||||
* CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField
|
||||
*
|
||||
* @return \Doctrine\ORM\Query\AST\PathExpression
|
||||
*/
|
||||
@@ -1000,26 +1037,6 @@ class Parser
|
||||
return $this->PathExpression(AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* SimpleStateFieldPathExpression ::= IdentificationVariable "." StateField
|
||||
*
|
||||
* @return \Doctrine\ORM\Query\AST\PathExpression
|
||||
*/
|
||||
public function SimpleStateFieldPathExpression()
|
||||
{
|
||||
$pathExpression = $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD);
|
||||
$parts = $pathExpression->parts;
|
||||
|
||||
if (count($parts) > 1) {
|
||||
$this->semanticalError(
|
||||
"Invalid SimpleStateFieldPathExpression. " .
|
||||
"Expected state field, got association '{$parts[0]}'."
|
||||
);
|
||||
}
|
||||
|
||||
return $pathExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
* SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}
|
||||
*
|
||||
@@ -1285,34 +1302,17 @@ class Parser
|
||||
}
|
||||
|
||||
/**
|
||||
* UpdateItem ::= IdentificationVariable "." {StateField | SingleValuedAssociationField} "=" NewValue
|
||||
* UpdateItem ::= SingleValuedPathExpression "=" NewValue
|
||||
*
|
||||
* @return \Doctrine\ORM\Query\AST\UpdateItem
|
||||
*/
|
||||
public function UpdateItem()
|
||||
{
|
||||
$token = $this->_lexer->lookahead;
|
||||
|
||||
$identVariable = $this->IdentificationVariable();
|
||||
$this->match(Lexer::T_DOT);
|
||||
$this->match(Lexer::T_IDENTIFIER);
|
||||
$field = $this->_lexer->token['value'];
|
||||
|
||||
// Check if field exists
|
||||
$class = $this->_queryComponents[$identVariable]['metadata'];
|
||||
|
||||
if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) {
|
||||
$this->semanticalError(
|
||||
'Class ' . $class->name . ' has no field named ' . $field, $token
|
||||
);
|
||||
}
|
||||
$pathExpr = $this->SingleValuedPathExpression();
|
||||
|
||||
$this->match(Lexer::T_EQUALS);
|
||||
|
||||
$newValue = $this->NewValue();
|
||||
|
||||
$updateItem = new AST\UpdateItem($field, $newValue);
|
||||
$updateItem->identificationVariable = $identVariable;
|
||||
$updateItem = new AST\UpdateItem($pathExpr, $this->NewValue());
|
||||
|
||||
return $updateItem;
|
||||
}
|
||||
@@ -1327,10 +1327,14 @@ class Parser
|
||||
// We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
|
||||
$glimpse = $this->_lexer->glimpse();
|
||||
|
||||
if ($glimpse['value'] != '.') {
|
||||
if ($glimpse['type'] != Lexer::T_DOT) {
|
||||
$token = $this->_lexer->lookahead;
|
||||
$identVariable = $this->IdentificationVariable();
|
||||
|
||||
if (!isset($this->_queryComponents[$identVariable])) {
|
||||
$this->semanticalError('Cannot group by undefined identification variable.');
|
||||
}
|
||||
|
||||
return $identVariable;
|
||||
}
|
||||
|
||||
@@ -1352,7 +1356,7 @@ class Parser
|
||||
// We need to check if we are in a ResultVariable or StateFieldPathExpression
|
||||
$glimpse = $this->_lexer->glimpse();
|
||||
|
||||
if ($glimpse['value'] != '.') {
|
||||
if ($glimpse['type'] != Lexer::T_DOT) {
|
||||
$token = $this->_lexer->lookahead;
|
||||
$expr = $this->ResultVariable();
|
||||
} else {
|
||||
@@ -1428,9 +1432,11 @@ class Parser
|
||||
*/
|
||||
public function SubselectIdentificationVariableDeclaration()
|
||||
{
|
||||
$peek = $this->_lexer->glimpse();
|
||||
$glimpse = $this->_lexer->glimpse();
|
||||
|
||||
if ($peek['value'] == '.') {
|
||||
/* NOT YET IMPLEMENTED!
|
||||
|
||||
if ($glimpse['type'] == Lexer::T_DOT) {
|
||||
$subselectIdVarDecl = new AST\SubselectIdentificationVariableDeclaration();
|
||||
$subselectIdVarDecl->associationPathExpression = $this->AssociationPathExpression();
|
||||
$this->match(Lexer::T_AS);
|
||||
@@ -1438,6 +1444,7 @@ class Parser
|
||||
|
||||
return $subselectIdVarDecl;
|
||||
}
|
||||
*/
|
||||
|
||||
return $this->IdentificationVariableDeclaration();
|
||||
}
|
||||
@@ -1505,11 +1512,13 @@ class Parser
|
||||
$this->match(Lexer::T_OPEN_CURLY_BRACE);
|
||||
$this->match(Lexer::T_IDENTIFIER);
|
||||
$partialFieldSet[] = $this->_lexer->token['value'];
|
||||
|
||||
while ($this->_lexer->isNextToken(Lexer::T_COMMA)) {
|
||||
$this->match(Lexer::T_COMMA);
|
||||
$this->match(Lexer::T_IDENTIFIER);
|
||||
$partialFieldSet[] = $this->_lexer->token['value'];
|
||||
}
|
||||
|
||||
$this->match(Lexer::T_CLOSE_CURLY_BRACE);
|
||||
|
||||
$partialObjectExpression = new AST\PartialObjectExpression($identificationVariable, $partialFieldSet);
|
||||
@@ -1570,7 +1579,7 @@ class Parser
|
||||
);
|
||||
}
|
||||
|
||||
$targetClassName = $parentClass->getAssociationMapping($assocField)->targetEntityName;
|
||||
$targetClassName = $parentClass->associationMappings[$assocField]['targetEntity'];
|
||||
|
||||
// Building queryComponent
|
||||
$joinQueryComponent = array(
|
||||
@@ -1596,7 +1605,7 @@ class Parser
|
||||
}
|
||||
|
||||
/**
|
||||
* IndexBy ::= "INDEX" "BY" SimpleStateFieldPathExpression
|
||||
* IndexBy ::= "INDEX" "BY" StateFieldPathExpression
|
||||
*
|
||||
* @return Doctrine\ORM\Query\AST\IndexBy
|
||||
*/
|
||||
@@ -1604,13 +1613,12 @@ class Parser
|
||||
{
|
||||
$this->match(Lexer::T_INDEX);
|
||||
$this->match(Lexer::T_BY);
|
||||
$pathExp = $this->SimpleStateFieldPathExpression();
|
||||
$pathExpr = $this->StateFieldPathExpression();
|
||||
|
||||
// Add the INDEX BY info to the query component
|
||||
$parts = $pathExp->parts;
|
||||
$this->_queryComponents[$pathExp->identificationVariable]['map'] = $parts[0];
|
||||
$this->_queryComponents[$pathExpr->identificationVariable]['map'] = $pathExpr->field;
|
||||
|
||||
return $pathExp;
|
||||
return new AST\IndexBy($pathExpr);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1629,15 +1637,31 @@ class Parser
|
||||
$peek = $this->_lexer->peek(); // lookahead => token after the token after the '.'
|
||||
$this->_lexer->resetPeek();
|
||||
|
||||
if ($peek['value'] == '+' || $peek['value'] == '-' || $peek['value'] == '/' || $peek['value'] == '*') {
|
||||
if ($this->_isMathOperator($peek)) {
|
||||
return $this->SimpleArithmeticExpression();
|
||||
}
|
||||
|
||||
return $this->StateFieldPathExpression();
|
||||
} else if ($lookahead == Lexer::T_INTEGER || $lookahead == Lexer::T_FLOAT) {
|
||||
return $this->SimpleArithmeticExpression();
|
||||
} else if ($this->_isFunction()) {
|
||||
return $this->FunctionDeclaration();
|
||||
} 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();
|
||||
|
||||
if ($this->_isMathOperator($peek)) {
|
||||
return $this->SimpleArithmeticExpression();
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -1645,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();
|
||||
}
|
||||
@@ -1654,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1671,17 +1748,19 @@ class Parser
|
||||
public function SelectExpression()
|
||||
{
|
||||
$expression = null;
|
||||
$identVariable = null;
|
||||
$fieldAliasIdentificationVariable = null;
|
||||
$peek = $this->_lexer->glimpse();
|
||||
|
||||
$supportsAlias = true;
|
||||
|
||||
if ($peek['value'] != '(' && $this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER) {
|
||||
if ($peek['value'] == '.') {
|
||||
// ScalarExpression
|
||||
$expression = $this->ScalarExpression();
|
||||
} else {
|
||||
$supportsAlias = false;
|
||||
$expression = $this->IdentificationVariable();
|
||||
$expression = $identVariable = $this->IdentificationVariable();
|
||||
}
|
||||
} else if ($this->_lexer->lookahead['value'] == '(') {
|
||||
if ($peek['type'] == Lexer::T_SELECT) {
|
||||
@@ -1694,8 +1773,17 @@ class Parser
|
||||
$expression = $this->SimpleArithmeticExpression();
|
||||
}
|
||||
} else if ($this->_isFunction()) {
|
||||
if ($this->_isAggregateFunction($this->_lexer->lookahead['type'])) {
|
||||
$this->_lexer->peek(); // "("
|
||||
|
||||
$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();
|
||||
@@ -1703,8 +1791,10 @@ class Parser
|
||||
} else if ($this->_lexer->lookahead['type'] == Lexer::T_PARTIAL) {
|
||||
$supportsAlias = false;
|
||||
$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 {
|
||||
@@ -1731,30 +1821,52 @@ class Parser
|
||||
}
|
||||
}
|
||||
|
||||
return new AST\SelectExpression($expression, $fieldAliasIdentificationVariable);
|
||||
$expr = new AST\SelectExpression($expression, $fieldAliasIdentificationVariable);
|
||||
if (!$supportsAlias) {
|
||||
$this->_identVariableExpressions[$identVariable] = $expr;
|
||||
}
|
||||
return $expr;
|
||||
}
|
||||
|
||||
/**
|
||||
* SimpleSelectExpression ::= StateFieldPathExpression | IdentificationVariable | (AggregateExpression [["AS"] AliasResultVariable])
|
||||
* SimpleSelectExpression ::=
|
||||
* StateFieldPathExpression | IdentificationVariable |
|
||||
* ((AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable])
|
||||
*
|
||||
* @return \Doctrine\ORM\Query\AST\SimpleSelectExpression
|
||||
*/
|
||||
public function SimpleSelectExpression()
|
||||
{
|
||||
if ($this->_lexer->isNextToken(Lexer::T_IDENTIFIER)) {
|
||||
// SingleValuedPathExpression | IdentificationVariable
|
||||
$peek = $this->_lexer->glimpse();
|
||||
$peek = $this->_lexer->glimpse();
|
||||
|
||||
if ($peek['value'] != '(' && $this->_lexer->lookahead['type'] === Lexer::T_IDENTIFIER) {
|
||||
// SingleValuedPathExpression | IdentificationVariable
|
||||
if ($peek['value'] == '.') {
|
||||
return new AST\SimpleSelectExpression($this->StateFieldPathExpression());
|
||||
$expression = $this->StateFieldPathExpression();
|
||||
} else {
|
||||
$expression = $this->IdentificationVariable();
|
||||
}
|
||||
|
||||
$this->match(Lexer::T_IDENTIFIER);
|
||||
return new AST\SimpleSelectExpression($expression);
|
||||
} else if ($this->_lexer->lookahead['value'] == '(') {
|
||||
if ($peek['type'] == Lexer::T_SELECT) {
|
||||
// Subselect
|
||||
$this->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
$expression = $this->Subselect();
|
||||
$this->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
} else {
|
||||
// Shortcut: ScalarExpression => SimpleArithmeticExpression
|
||||
$expression = $this->SimpleArithmeticExpression();
|
||||
}
|
||||
|
||||
return new AST\SimpleSelectExpression($this->_lexer->token['value']);
|
||||
return new AST\SimpleSelectExpression($expression);
|
||||
}
|
||||
|
||||
$expr = new AST\SimpleSelectExpression($this->AggregateExpression());
|
||||
$this->_lexer->peek();
|
||||
|
||||
$expression = $this->ScalarExpression();
|
||||
|
||||
$expr = new AST\SimpleSelectExpression($expression);
|
||||
|
||||
if ($this->_lexer->isNextToken(Lexer::T_AS)) {
|
||||
$this->match(Lexer::T_AS);
|
||||
@@ -1889,7 +2001,8 @@ class Parser
|
||||
* SimpleConditionalExpression ::=
|
||||
* ComparisonExpression | BetweenExpression | LikeExpression |
|
||||
* InExpression | NullComparisonExpression | ExistsExpression |
|
||||
* EmptyCollectionComparisonExpression | CollectionMemberExpression
|
||||
* EmptyCollectionComparisonExpression | CollectionMemberExpression |
|
||||
* InstanceOfExpression
|
||||
*/
|
||||
public function SimpleConditionalExpression()
|
||||
{
|
||||
@@ -1945,6 +2058,8 @@ class Parser
|
||||
return $this->LikeExpression();
|
||||
case Lexer::T_IN:
|
||||
return $this->InExpression();
|
||||
case Lexer::T_INSTANCE:
|
||||
return $this->InstanceOfExpression();
|
||||
case Lexer::T_IS:
|
||||
if ($lookahead['type'] == Lexer::T_NULL) {
|
||||
return $this->NullComparisonExpression();
|
||||
@@ -1957,23 +2072,6 @@ class Parser
|
||||
}
|
||||
}
|
||||
|
||||
private function _peekBeyondClosingParenthesis()
|
||||
{
|
||||
$numUnmatched = 1;
|
||||
$token = $this->_lexer->peek();
|
||||
while ($numUnmatched > 0 && $token !== null) {
|
||||
if ($token['value'] == ')') {
|
||||
--$numUnmatched;
|
||||
} else if ($token['value'] == '(') {
|
||||
++$numUnmatched;
|
||||
}
|
||||
$token = $this->_lexer->peek();
|
||||
}
|
||||
$this->_lexer->resetPeek();
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY"
|
||||
*
|
||||
@@ -2217,7 +2315,7 @@ class Parser
|
||||
return $this->SingleValuedPathExpression();
|
||||
}
|
||||
|
||||
return $this->SimpleStateFieldPathExpression();
|
||||
return $this->StateFieldPathExpression();
|
||||
|
||||
case Lexer::T_INPUT_PARAMETER:
|
||||
return $this->InputParameter();
|
||||
@@ -2271,7 +2369,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 +2457,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);
|
||||
}
|
||||
|
||||
@@ -2443,13 +2542,13 @@ class Parser
|
||||
}
|
||||
|
||||
/**
|
||||
* InExpression ::= StateFieldPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")"
|
||||
* InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")"
|
||||
*
|
||||
* @return \Doctrine\ORM\Query\AST\InExpression
|
||||
*/
|
||||
public function InExpression()
|
||||
{
|
||||
$inExpression = new AST\InExpression($this->StateFieldPathExpression());
|
||||
$inExpression = new AST\InExpression($this->SingleValuedPathExpression());
|
||||
|
||||
if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
|
||||
$this->match(Lexer::T_NOT);
|
||||
@@ -2478,6 +2577,38 @@ class Parser
|
||||
return $inExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
* InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (AbstractSchemaName | InputParameter)
|
||||
*
|
||||
* @return \Doctrine\ORM\Query\AST\InstanceOfExpression
|
||||
*/
|
||||
public function InstanceOfExpression()
|
||||
{
|
||||
$instanceOfExpression = new AST\InstanceOfExpression($this->IdentificationVariable());
|
||||
|
||||
if ($this->_lexer->isNextToken(Lexer::T_NOT)) {
|
||||
$this->match(Lexer::T_NOT);
|
||||
$instanceOfExpression->not = true;
|
||||
}
|
||||
|
||||
$this->match(Lexer::T_INSTANCE);
|
||||
|
||||
if ($this->_lexer->isNextToken(Lexer::T_OF)) {
|
||||
$this->match(Lexer::T_OF);
|
||||
}
|
||||
|
||||
if ($this->_lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
|
||||
$this->match(Lexer::T_INPUT_PARAMETER);
|
||||
$exprValue = new AST\InputParameter($this->_lexer->token['value']);
|
||||
} else {
|
||||
$exprValue = $this->AliasIdentificationVariable();
|
||||
}
|
||||
|
||||
$instanceOfExpression->value = $exprValue;
|
||||
|
||||
return $instanceOfExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
* LikeExpression ::= StringExpression ["NOT"] "LIKE" (string | input_parameter) ["ESCAPE" char]
|
||||
*
|
||||
|
||||
@@ -47,6 +47,11 @@ class QueryException extends \Doctrine\ORM\ORMException
|
||||
return new self('[Semantical Error] ' . $message);
|
||||
}
|
||||
|
||||
public static function invalidParameterType($expected, $received)
|
||||
{
|
||||
return new self('Invalid parameter type, ' . $received . ' given, but ' . $expected . ' expected.');
|
||||
}
|
||||
|
||||
public static function invalidParameterPosition($pos)
|
||||
{
|
||||
return new self('Invalid parameter position: ' . $pos);
|
||||
@@ -70,8 +75,7 @@ class QueryException extends \Doctrine\ORM\ORMException
|
||||
public static function invalidPathExpression($pathExpr)
|
||||
{
|
||||
return new self(
|
||||
"Invalid PathExpression '" . $pathExpr->identificationVariable .
|
||||
"." . implode('.', $pathExpr->parts) . "'."
|
||||
"Invalid PathExpression '" . $pathExpr->identificationVariable . "." . $pathExpr->field . "'."
|
||||
);
|
||||
}
|
||||
|
||||
@@ -86,7 +90,7 @@ class QueryException extends \Doctrine\ORM\ORMException
|
||||
{
|
||||
return new self(
|
||||
"Invalid query operation: Not allowed to iterate over fetch join collections ".
|
||||
"in class ".$assoc->sourceEntityName." assocation ".$assoc->sourceFieldName
|
||||
"in class ".$assoc['sourceEntity']." assocation ".$assoc['fieldName']
|
||||
);
|
||||
}
|
||||
|
||||
@@ -103,7 +107,7 @@ class QueryException extends \Doctrine\ORM\ORMException
|
||||
{
|
||||
return new self(
|
||||
"Unsupported query operation: It is not yet possible to overwrite the join ".
|
||||
"conditions in class ".$assoc->sourceEntityName." assocation ".$assoc->sourceFieldName.". ".
|
||||
"conditions in class ".$assoc['sourceEntityName']." assocation ".$assoc['fieldName'].". ".
|
||||
"Use WITH to append additional join conditions to the association."
|
||||
);
|
||||
}
|
||||
@@ -118,8 +122,8 @@ class QueryException extends \Doctrine\ORM\ORMException
|
||||
|
||||
public static function iterateWithFetchJoinNotAllowed($assoc) {
|
||||
return new self(
|
||||
"Iterate with fetch join in class " . $assoc->sourceEntityName .
|
||||
" using association " . $assoc->sourceFieldName . " not allowed."
|
||||
"Iterate with fetch join in class " . $assoc['sourceEntity'] .
|
||||
" using association " . $assoc['fieldName'] . " not allowed."
|
||||
);
|
||||
}
|
||||
|
||||
@@ -131,4 +135,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -290,6 +290,14 @@ interface TreeWalker
|
||||
*/
|
||||
function walkInExpression($inExpr);
|
||||
|
||||
/**
|
||||
* Walks down an InstanceOfExpression AST node, thereby generating the appropriate SQL.
|
||||
*
|
||||
* @param InstanceOfExpression
|
||||
* @return string The SQL.
|
||||
*/
|
||||
function walkInstanceOfExpression($instanceOfExpr);
|
||||
|
||||
/**
|
||||
* Walks down a literal that represents an AST node, thereby generating the appropriate SQL.
|
||||
*
|
||||
|
||||
@@ -324,6 +324,14 @@ abstract class TreeWalkerAdapter implements TreeWalker
|
||||
*/
|
||||
public function walkInExpression($inExpr) {}
|
||||
|
||||
/**
|
||||
* Walks down an InstanceOfExpression AST node, thereby generating the appropriate SQL.
|
||||
*
|
||||
* @param InstanceOfExpression
|
||||
* @return string The SQL.
|
||||
*/
|
||||
function walkInstanceOfExpression($instanceOfExpr) {}
|
||||
|
||||
/**
|
||||
* Walks down a literal that represents an AST node, thereby generating the appropriate SQL.
|
||||
*
|
||||
|
||||
@@ -472,6 +472,19 @@ class TreeWalkerChain implements TreeWalker
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks down an InstanceOfExpression AST node, thereby generating the appropriate SQL.
|
||||
*
|
||||
* @param InstanceOfExpression
|
||||
* @return string The SQL.
|
||||
*/
|
||||
function walkInstanceOfExpression($instanceOfExpr)
|
||||
{
|
||||
foreach ($this->_walkers as $walker) {
|
||||
$walker->walkInstanceOfExpression($instanceOfExpr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks down a literal that represents an AST node, thereby generating the appropriate SQL.
|
||||
*
|
||||
|
||||
@@ -79,6 +79,11 @@ class QueryBuilder
|
||||
* @var array The query parameters.
|
||||
*/
|
||||
private $_params = array();
|
||||
|
||||
/**
|
||||
* @var array The parameter type map of this query.
|
||||
*/
|
||||
private $_paramTypes = array();
|
||||
|
||||
/**
|
||||
* @var integer The index of the first result to retrieve.
|
||||
@@ -208,13 +213,34 @@ class QueryBuilder
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->_em->createQuery($this->getDQL())
|
||||
->setParameters($this->_params)
|
||||
->setParameters($this->_params, $this->_paramTypes)
|
||||
->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>
|
||||
@@ -222,15 +248,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -246,11 +318,18 @@ class QueryBuilder
|
||||
*
|
||||
* @param string|integer $key The parameter position or name.
|
||||
* @param mixed $value The parameter value.
|
||||
* @param string|null $type PDO::PARAM_* or \Doctrine\DBAL\Types\Type::* constant
|
||||
* @return QueryBuilder This QueryBuilder instance.
|
||||
*/
|
||||
public function setParameter($key, $value)
|
||||
public function setParameter($key, $value, $type = null)
|
||||
{
|
||||
if ($type === null) {
|
||||
$type = Query\ParameterTypeInferer::inferType($value);
|
||||
}
|
||||
|
||||
$this->_paramTypes[$key] = $type;
|
||||
$this->_params[$key] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -271,9 +350,15 @@ class QueryBuilder
|
||||
* @param array $params The query parameters to set.
|
||||
* @return QueryBuilder This QueryBuilder instance.
|
||||
*/
|
||||
public function setParameters(array $params)
|
||||
public function setParameters(array $params, array $types = array())
|
||||
{
|
||||
$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;
|
||||
}
|
||||
|
||||
@@ -358,9 +443,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;
|
||||
}
|
||||
@@ -513,11 +618,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -537,12 +643,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);
|
||||
}
|
||||
|
||||
@@ -564,12 +676,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);
|
||||
}
|
||||
|
||||
@@ -619,7 +737,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());
|
||||
}
|
||||
|
||||
@@ -855,14 +973,36 @@ 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->_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 (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())
|
||||
@@ -878,6 +1018,40 @@ class QueryBuilder
|
||||
. (isset($options['post']) ? $options['post'] : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset DQL parts
|
||||
*
|
||||
* @param array $parts
|
||||
* @return QueryBuilder
|
||||
*/
|
||||
public function resetDQLParts($parts = null)
|
||||
{
|
||||
if (is_null($parts)) {
|
||||
$parts = array_keys($this->_dqlParts);
|
||||
}
|
||||
foreach ($parts as $part) {
|
||||
$this->resetDQLPart($part);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset single DQL part
|
||||
*
|
||||
* @param string $part
|
||||
* @return QueryBuilder;
|
||||
*/
|
||||
public function resetDQLPart($part)
|
||||
{
|
||||
if (is_array($this->_dqlParts[$part])) {
|
||||
$this->_dqlParts[$part] = array();
|
||||
} else {
|
||||
$this->_dqlParts[$part] = null;
|
||||
}
|
||||
$this->_state = self::STATE_DIRTY;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a string representation of this QueryBuilder which corresponds to
|
||||
* the final DQL query being constructed.
|
||||
@@ -888,4 +1062,24 @@ class QueryBuilder
|
||||
{
|
||||
return $this->getDQL();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep clone of all expression objects in the DQL parts.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
foreach ($this->_dqlParts AS $part => $elements) {
|
||||
if (is_array($this->_dqlParts[$part])) {
|
||||
foreach ($this->_dqlParts[$part] AS $idx => $element) {
|
||||
if (is_object($element)) {
|
||||
$this->_dqlParts[$part][$idx] = clone $element;
|
||||
}
|
||||
}
|
||||
} else if (\is_object($elements)) {
|
||||
$this->_dqlParts[$part] = clone $elements;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,9 +21,9 @@
|
||||
|
||||
namespace Doctrine\ORM\Tools\Console\Command\ClearCache;
|
||||
|
||||
use Symfony\Components\Console\Input\InputArgument,
|
||||
Symfony\Components\Console\Input\InputOption,
|
||||
Symfony\Components\Console;
|
||||
use Symfony\Component\Console\Input\InputArgument,
|
||||
Symfony\Component\Console\Input\InputOption,
|
||||
Symfony\Component\Console;
|
||||
|
||||
/**
|
||||
* Command to clear the metadata cache of the various cache drivers.
|
||||
@@ -66,6 +66,10 @@ EOT
|
||||
throw new \InvalidArgumentException('No Metadata cache driver is configured on given EntityManager.');
|
||||
}
|
||||
|
||||
if ($cacheDriver instanceof \Doctrine\Common\Cache\ApcCache) {
|
||||
throw new \LogicException("Cannot clear APC Cache from Console, its shared in the Webserver memory and not accessible from the CLI.");
|
||||
}
|
||||
|
||||
$output->write('Clearing ALL Metadata cache entries' . PHP_EOL);
|
||||
|
||||
$cacheIds = $cacheDriver->deleteAll();
|
||||
@@ -78,4 +82,4 @@ EOT
|
||||
$output->write('No entries to be deleted.' . PHP_EOL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
|
||||
namespace Doctrine\ORM\Tools\Console\Command\ClearCache;
|
||||
|
||||
use Symfony\Components\Console\Input\InputArgument,
|
||||
Symfony\Components\Console\Input\InputOption,
|
||||
Symfony\Components\Console;
|
||||
use Symfony\Component\Console\Input\InputArgument,
|
||||
Symfony\Component\Console\Input\InputOption,
|
||||
Symfony\Component\Console;
|
||||
|
||||
/**
|
||||
* Command to clear the query cache of the various cache drivers.
|
||||
@@ -66,6 +66,10 @@ EOT
|
||||
throw new \InvalidArgumentException('No Query cache driver is configured on given EntityManager.');
|
||||
}
|
||||
|
||||
if ($cacheDriver instanceof \Doctrine\Common\Cache\ApcCache) {
|
||||
throw new \LogicException("Cannot clear APC Cache from Console, its shared in the Webserver memory and not accessible from the CLI.");
|
||||
}
|
||||
|
||||
$output->write('Clearing ALL Query cache entries' . PHP_EOL);
|
||||
|
||||
$cacheIds = $cacheDriver->deleteAll();
|
||||
@@ -78,4 +82,4 @@ EOT
|
||||
$output->write('No entries to be deleted.' . PHP_EOL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
|
||||
namespace Doctrine\ORM\Tools\Console\Command\ClearCache;
|
||||
|
||||
use Symfony\Components\Console\Input\InputArgument,
|
||||
Symfony\Components\Console\Input\InputOption,
|
||||
Symfony\Components\Console;
|
||||
use Symfony\Component\Console\Input\InputArgument,
|
||||
Symfony\Component\Console\Input\InputOption,
|
||||
Symfony\Component\Console;
|
||||
|
||||
/**
|
||||
* Command to clear the result cache of the various cache drivers.
|
||||
@@ -49,19 +49,19 @@ class ResultCommand extends Console\Command\Command
|
||||
->setDescription('Clear result cache of the various cache drivers.')
|
||||
->setDefinition(array(
|
||||
new InputOption(
|
||||
'id', null, InputOption::PARAMETER_REQUIRED | InputOption::PARAMETER_IS_ARRAY,
|
||||
'id', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
|
||||
'ID(s) of the cache entry to delete (accepts * wildcards).', array()
|
||||
),
|
||||
new InputOption(
|
||||
'regex', null, InputOption::PARAMETER_REQUIRED | InputOption::PARAMETER_IS_ARRAY,
|
||||
'regex', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
|
||||
'Delete cache entries that match the given regular expression(s).', array()
|
||||
),
|
||||
new InputOption(
|
||||
'prefix', null, InputOption::PARAMETER_REQUIRED | InputOption::PARAMETER_IS_ARRAY,
|
||||
'prefix', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
|
||||
'Delete cache entries that have the given prefix(es).', array()
|
||||
),
|
||||
new InputOption(
|
||||
'suffix', null, InputOption::PARAMETER_REQUIRED | InputOption::PARAMETER_IS_ARRAY,
|
||||
'suffix', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
|
||||
'Delete cache entries that have the given suffix(es).', array()
|
||||
),
|
||||
))
|
||||
@@ -84,6 +84,10 @@ EOT
|
||||
throw new \InvalidArgumentException('No Result cache driver is configured on given EntityManager.');
|
||||
}
|
||||
|
||||
if ($cacheDriver instanceof \Doctrine\Common\Cache\ApcCache) {
|
||||
throw new \LogicException("Cannot clear APC Cache from Console, its shared in the Webserver memory and not accessible from the CLI.");
|
||||
}
|
||||
|
||||
$outputed = false;
|
||||
|
||||
// Removing based on --id
|
||||
@@ -161,4 +165,4 @@ EOT
|
||||
$output->write('No entries to be deleted.' . PHP_EOL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,12 @@
|
||||
|
||||
namespace Doctrine\ORM\Tools\Console\Command;
|
||||
|
||||
use Symfony\Components\Console\Input\InputArgument,
|
||||
Symfony\Components\Console\Input\InputOption,
|
||||
Symfony\Components\Console,
|
||||
use Symfony\Component\Console\Input\InputArgument,
|
||||
Symfony\Component\Console\Input\InputOption,
|
||||
Symfony\Component\Console,
|
||||
Doctrine\ORM\Tools\Export\ClassMetadataExporter,
|
||||
Doctrine\ORM\Tools\ConvertDoctrine1Schema;
|
||||
Doctrine\ORM\Tools\ConvertDoctrine1Schema,
|
||||
Doctrine\ORM\Tools\EntityGenerator;
|
||||
|
||||
/**
|
||||
* Command to convert a Doctrine 1 schema to a Doctrine 2 mapping file.
|
||||
@@ -41,6 +42,56 @@ use Symfony\Components\Console\Input\InputArgument,
|
||||
*/
|
||||
class ConvertDoctrine1SchemaCommand extends Console\Command\Command
|
||||
{
|
||||
/**
|
||||
* @var EntityGenerator
|
||||
*/
|
||||
private $entityGenerator = null;
|
||||
|
||||
/**
|
||||
* @var ClassMetadataExporter
|
||||
*/
|
||||
private $metadataExporter = null;
|
||||
|
||||
/**
|
||||
* @return EntityGenerator
|
||||
*/
|
||||
public function getEntityGenerator()
|
||||
{
|
||||
if ($this->entityGenerator == null) {
|
||||
$this->entityGenerator = new EntityGenerator();
|
||||
}
|
||||
|
||||
return $this->entityGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param EntityGenerator $entityGenerator
|
||||
*/
|
||||
public function setEntityGenerator(EntityGenerator $entityGenerator)
|
||||
{
|
||||
$this->entityGenerator = $entityGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ClassMetadataExporter
|
||||
*/
|
||||
public function getMetadataExporter()
|
||||
{
|
||||
if ($this->metadataExporter == null) {
|
||||
$this->metadataExporter = new ClassMetadataExporter();
|
||||
}
|
||||
|
||||
return $this->metadataExporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassMetadataExporter $metadataExporter
|
||||
*/
|
||||
public function setMetadataExporter(ClassMetadataExporter $metadataExporter)
|
||||
{
|
||||
$this->metadataExporter = $metadataExporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Console\Command\Command
|
||||
*/
|
||||
@@ -61,16 +112,16 @@ class ConvertDoctrine1SchemaCommand extends Console\Command\Command
|
||||
'The path to generate your Doctrine 2.X mapping information.'
|
||||
),
|
||||
new InputOption(
|
||||
'from', null, InputOption::PARAMETER_REQUIRED | InputOption::PARAMETER_IS_ARRAY,
|
||||
'from', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
|
||||
'Optional paths of Doctrine 1.X schema information.',
|
||||
array()
|
||||
),
|
||||
new InputOption(
|
||||
'extend', null, InputOption::PARAMETER_OPTIONAL,
|
||||
'extend', null, InputOption::VALUE_OPTIONAL,
|
||||
'Defines a base class to be extended by generated entity classes.'
|
||||
),
|
||||
new InputOption(
|
||||
'num-spaces', null, InputOption::PARAMETER_OPTIONAL,
|
||||
'num-spaces', null, InputOption::VALUE_OPTIONAL,
|
||||
'Defines the number of indentation spaces', 4
|
||||
)
|
||||
))
|
||||
@@ -90,6 +141,27 @@ EOT
|
||||
// Process source directories
|
||||
$fromPaths = array_merge(array($input->getArgument('from-path')), $input->getOption('from'));
|
||||
|
||||
// Process destination directory
|
||||
$destPath = realpath($input->getArgument('dest-path'));
|
||||
|
||||
$toType = $input->getArgument('to-type');
|
||||
$extend = $input->getOption('extend');
|
||||
$numSpaces = $input->getOption('num-spaces');
|
||||
|
||||
$this->convertDoctrine1Schema($em, $fromPaths, $destPath, $toType, $numSpaces, $extend, $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\EntityManager $em
|
||||
* @param array $fromPaths
|
||||
* @param string $destPath
|
||||
* @param string $toType
|
||||
* @param int $numSpaces
|
||||
* @param string|null $extend
|
||||
* @param Console\Output\OutputInterface $output
|
||||
*/
|
||||
public function convertDoctrine1Schema($em, $fromPaths, $destPath, $toType, $numSpaces, $extend, $output)
|
||||
{
|
||||
foreach ($fromPaths as &$dirName) {
|
||||
$dirName = realpath($dirName);
|
||||
|
||||
@@ -104,9 +176,6 @@ EOT
|
||||
}
|
||||
}
|
||||
|
||||
// Process destination directory
|
||||
$destPath = realpath($input->getArgument('dest-path'));
|
||||
|
||||
if ( ! file_exists($destPath)) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf("Doctrine 2.X mapping destination directory '<info>%s</info>' does not exist.", $destPath)
|
||||
@@ -117,18 +186,16 @@ EOT
|
||||
);
|
||||
}
|
||||
|
||||
$toType = $input->getArgument('to-type');
|
||||
|
||||
$cme = new ClassMetadataExporter();
|
||||
$cme = $this->getMetadataExporter();
|
||||
$exporter = $cme->getExporter($toType, $destPath);
|
||||
|
||||
if (strtolower($toType) === 'annotation') {
|
||||
$entityGenerator = new EntityGenerator();
|
||||
$entityGenerator = $this->getEntityGenerator();
|
||||
$exporter->setEntityGenerator($entityGenerator);
|
||||
|
||||
$entityGenerator->setNumSpaces($input->getOption('num-spaces'));
|
||||
$entityGenerator->setNumSpaces($numSpaces);
|
||||
|
||||
if (($extend = $input->getOption('extend')) !== null) {
|
||||
if ($extend !== null) {
|
||||
$entityGenerator->setClassToExtend($extend);
|
||||
}
|
||||
}
|
||||
@@ -153,4 +220,4 @@ EOT
|
||||
$output->write('No Metadata Classes to process.' . PHP_EOL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
|
||||
namespace Doctrine\ORM\Tools\Console\Command;
|
||||
|
||||
use Symfony\Components\Console\Input\InputArgument,
|
||||
Symfony\Components\Console\Input\InputOption,
|
||||
Symfony\Components\Console,
|
||||
use Symfony\Component\Console\Input\InputArgument,
|
||||
Symfony\Component\Console\Input\InputOption,
|
||||
Symfony\Component\Console,
|
||||
Doctrine\ORM\Tools\Console\MetadataFilter,
|
||||
Doctrine\ORM\Tools\Export\ClassMetadataExporter,
|
||||
Doctrine\ORM\Tools\EntityGenerator,
|
||||
@@ -53,7 +53,7 @@ class ConvertMappingCommand extends Console\Command\Command
|
||||
->setDescription('Convert mapping information between supported formats.')
|
||||
->setDefinition(array(
|
||||
new InputOption(
|
||||
'filter', null, InputOption::PARAMETER_REQUIRED | InputOption::PARAMETER_IS_ARRAY,
|
||||
'filter', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
|
||||
'A string pattern used to match entities that should be processed.'
|
||||
),
|
||||
new InputArgument(
|
||||
@@ -63,20 +63,42 @@ 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.'
|
||||
),
|
||||
new InputOption(
|
||||
'extend', null, InputOption::PARAMETER_OPTIONAL,
|
||||
'extend', null, InputOption::VALUE_OPTIONAL,
|
||||
'Defines a base class to be extended by generated entity classes.'
|
||||
),
|
||||
new InputOption(
|
||||
'num-spaces', null, InputOption::PARAMETER_OPTIONAL,
|
||||
'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,14 +111,21 @@ 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($em);
|
||||
$cmf = new DisconnectedClassMetadataFactory();
|
||||
$cmf->setEntityManager($em);
|
||||
$metadata = $cmf->getAllMetadata();
|
||||
$metadata = MetadataFilter::filter($metadata, $input->getOption('filter'));
|
||||
|
||||
@@ -118,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();
|
||||
@@ -147,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
|
||||
namespace Doctrine\ORM\Tools\Console\Command;
|
||||
|
||||
use Symfony\Components\Console\Input\InputArgument,
|
||||
Symfony\Components\Console\Input\InputOption,
|
||||
Symfony\Components\Console;
|
||||
use Symfony\Component\Console\Input\InputArgument,
|
||||
Symfony\Component\Console\Input\InputOption,
|
||||
Symfony\Component\Console;
|
||||
|
||||
/**
|
||||
* Command to ensure that Doctrine is properly configured for a production environment.
|
||||
@@ -49,7 +49,7 @@ class EnsureProductionSettingsCommand extends Console\Command\Command
|
||||
->setDescription('Verify that Doctrine is properly configured for a production environment.')
|
||||
->setDefinition(array(
|
||||
new InputOption(
|
||||
'complete', null, InputOption::PARAMETER_NONE,
|
||||
'complete', null, InputOption::VALUE_NONE,
|
||||
'Flag to also inspect database connection existance.'
|
||||
)
|
||||
))
|
||||
@@ -82,4 +82,4 @@ EOT
|
||||
$output->write('<info>Environment is correctly configured for production.</info>' . PHP_EOL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
|
||||
namespace Doctrine\ORM\Tools\Console\Command;
|
||||
|
||||
use Symfony\Components\Console\Input\InputArgument,
|
||||
Symfony\Components\Console\Input\InputOption,
|
||||
Symfony\Components\Console,
|
||||
use Symfony\Component\Console\Input\InputArgument,
|
||||
Symfony\Component\Console\Input\InputOption,
|
||||
Symfony\Component\Console,
|
||||
Doctrine\ORM\Tools\Console\MetadataFilter,
|
||||
Doctrine\ORM\Tools\EntityGenerator,
|
||||
Doctrine\ORM\Tools\DisconnectedClassMetadataFactory;
|
||||
@@ -52,39 +52,56 @@ class GenerateEntitiesCommand extends Console\Command\Command
|
||||
->setDescription('Generate entity classes and method stubs from your mapping information.')
|
||||
->setDefinition(array(
|
||||
new InputOption(
|
||||
'filter', null, InputOption::PARAMETER_REQUIRED | InputOption::PARAMETER_IS_ARRAY,
|
||||
'filter', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
|
||||
'A string pattern used to match entities that should be processed.'
|
||||
),
|
||||
new InputArgument(
|
||||
'dest-path', InputArgument::REQUIRED, 'The path to generate your entity classes.'
|
||||
),
|
||||
new InputOption(
|
||||
'generate-annotations', null, InputOption::PARAMETER_OPTIONAL,
|
||||
'generate-annotations', null, InputOption::VALUE_OPTIONAL,
|
||||
'Flag to define if generator should generate annotation metadata on entities.', false
|
||||
),
|
||||
new InputOption(
|
||||
'generate-methods', null, InputOption::PARAMETER_OPTIONAL,
|
||||
'generate-methods', null, InputOption::VALUE_OPTIONAL,
|
||||
'Flag to define if generator should generate stub methods on entities.', true
|
||||
),
|
||||
new InputOption(
|
||||
'regenerate-entities', null, InputOption::PARAMETER_OPTIONAL,
|
||||
'regenerate-entities', null, InputOption::VALUE_OPTIONAL,
|
||||
'Flag to define if generator should regenerate entity if it exists.', false
|
||||
),
|
||||
new InputOption(
|
||||
'update-entities', null, InputOption::PARAMETER_OPTIONAL,
|
||||
'update-entities', null, InputOption::VALUE_OPTIONAL,
|
||||
'Flag to define if generator should only update entity if it exists.', true
|
||||
),
|
||||
new InputOption(
|
||||
'extend', null, InputOption::PARAMETER_OPTIONAL,
|
||||
'extend', null, InputOption::VALUE_OPTIONAL,
|
||||
'Defines a base class to be extended by generated entity classes.'
|
||||
),
|
||||
new InputOption(
|
||||
'num-spaces', null, InputOption::PARAMETER_OPTIONAL,
|
||||
'num-spaces', null, InputOption::VALUE_OPTIONAL,
|
||||
'Defines the number of indentation spaces', 4
|
||||
)
|
||||
))
|
||||
->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
|
||||
);
|
||||
}
|
||||
@@ -96,7 +113,8 @@ EOT
|
||||
{
|
||||
$em = $this->getHelper('em')->getEntityManager();
|
||||
|
||||
$cmf = new DisconnectedClassMetadataFactory($em);
|
||||
$cmf = new DisconnectedClassMetadataFactory();
|
||||
$cmf->setEntityManager($em);
|
||||
$metadatas = $cmf->getAllMetadata();
|
||||
$metadatas = MetadataFilter::filter($metadatas, $input->getOption('filter'));
|
||||
|
||||
@@ -142,4 +160,4 @@ EOT
|
||||
$output->write('No Metadata Classes to process.' . PHP_EOL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
|
||||
namespace Doctrine\ORM\Tools\Console\Command;
|
||||
|
||||
use Symfony\Components\Console\Input\InputArgument,
|
||||
Symfony\Components\Console\Input\InputOption,
|
||||
Symfony\Components\Console,
|
||||
use Symfony\Component\Console\Input\InputArgument,
|
||||
Symfony\Component\Console\Input\InputOption,
|
||||
Symfony\Component\Console,
|
||||
Doctrine\ORM\Tools\Console\MetadataFilter;
|
||||
|
||||
/**
|
||||
@@ -50,7 +50,7 @@ class GenerateProxiesCommand extends Console\Command\Command
|
||||
->setDescription('Generates proxy classes for entity classes.')
|
||||
->setDefinition(array(
|
||||
new InputOption(
|
||||
'filter', null, InputOption::PARAMETER_REQUIRED | InputOption::PARAMETER_IS_ARRAY,
|
||||
'filter', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
|
||||
'A string pattern used to match entities that should be processed.'
|
||||
),
|
||||
new InputArgument(
|
||||
@@ -112,4 +112,4 @@ EOT
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
|
||||
namespace Doctrine\ORM\Tools\Console\Command;
|
||||
|
||||
use Symfony\Components\Console\Input\InputArgument,
|
||||
Symfony\Components\Console\Input\InputOption,
|
||||
Symfony\Components\Console,
|
||||
use Symfony\Component\Console\Input\InputArgument,
|
||||
Symfony\Component\Console\Input\InputOption,
|
||||
Symfony\Component\Console,
|
||||
Doctrine\ORM\Tools\Console\MetadataFilter,
|
||||
Doctrine\ORM\Tools\EntityRepositoryGenerator;
|
||||
|
||||
@@ -51,7 +51,7 @@ class GenerateRepositoriesCommand extends Console\Command\Command
|
||||
->setDescription('Generate repository classes from your mapping information.')
|
||||
->setDefinition(array(
|
||||
new InputOption(
|
||||
'filter', null, InputOption::PARAMETER_REQUIRED | InputOption::PARAMETER_IS_ARRAY,
|
||||
'filter', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
|
||||
'A string pattern used to match entities that should be processed.'
|
||||
),
|
||||
new InputArgument(
|
||||
@@ -113,4 +113,4 @@ EOT
|
||||
$output->write('No Metadata Classes to process.' . PHP_EOL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user