mirror of
https://github.com/doctrine/orm.git
synced 2026-03-24 15:02:22 +01:00
Compare commits
384 Commits
old-protot
...
v2.7.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dafe298ce5 | ||
|
|
58b8130ea1 | ||
|
|
a705f526fb | ||
|
|
a9b6b72017 | ||
|
|
cd905fff77 | ||
|
|
eb700405be | ||
|
|
9273057649 | ||
|
|
e04a79526e | ||
|
|
d157a6cbeb | ||
|
|
ca57222010 | ||
|
|
9bb2bf0cce | ||
|
|
445796af0e | ||
|
|
ab93285284 | ||
|
|
a692670469 | ||
|
|
58677c29b4 | ||
|
|
9a0fcb5a86 | ||
|
|
8104c65d6c | ||
|
|
a64d254d07 | ||
|
|
fdad48278b | ||
|
|
fc94127d7f | ||
|
|
dea3e5df44 | ||
|
|
5d7d3e99a0 | ||
|
|
3bc1096fd0 | ||
|
|
a2f01f7ccc | ||
|
|
401db453a2 | ||
|
|
6e59ec8f16 | ||
|
|
87e491465a | ||
|
|
8b588eceb2 | ||
|
|
edce36598f | ||
|
|
20c46035d1 | ||
|
|
324aacfb54 | ||
|
|
1edfcabead | ||
|
|
2785cde792 | ||
|
|
d67e3e8b1b | ||
|
|
d629c4e487 | ||
|
|
4a4226213f | ||
|
|
0ce1440884 | ||
|
|
9aa28b4e33 | ||
|
|
5c2b6870bf | ||
|
|
4389b2c188 | ||
|
|
e481d9880b | ||
|
|
85528f28e2 | ||
|
|
5873242fb5 | ||
|
|
c9e41d0aa7 | ||
|
|
f37c12834d | ||
|
|
041404e8b3 | ||
|
|
bfc68b3aba | ||
|
|
1e628370c4 | ||
|
|
ae2b9b1921 | ||
|
|
419df77a09 | ||
|
|
d6f6b2e97c | ||
|
|
75d5adf599 | ||
|
|
cfd6fadf9c | ||
|
|
2bf7916c52 | ||
|
|
253fd10cc0 | ||
|
|
2c956d55f2 | ||
|
|
3db992e953 | ||
|
|
6fc9b3ab16 | ||
|
|
2d833a5e86 | ||
|
|
a416a9a8b2 | ||
|
|
4d763ca4c9 | ||
|
|
398d74deaa | ||
|
|
3314322929 | ||
|
|
ce93817bf7 | ||
|
|
50992eafa2 | ||
|
|
9ccb8837e7 | ||
|
|
d959744c0a | ||
|
|
0264ba1759 | ||
|
|
8332fa1855 | ||
|
|
4fae126459 | ||
|
|
2d9b935183 | ||
|
|
4804f602f8 | ||
|
|
3d17290eb5 | ||
|
|
8420d24f90 | ||
|
|
52f2b37921 | ||
|
|
16751d210f | ||
|
|
686f508576 | ||
|
|
00ef1eba90 | ||
|
|
3843eee5cb | ||
|
|
f576e6c41f | ||
|
|
c79d2e0dc2 | ||
|
|
33b8d020a7 | ||
|
|
1b2daac25d | ||
|
|
977985f756 | ||
|
|
0c36f87935 | ||
|
|
e8f265d480 | ||
|
|
7bcbad076d | ||
|
|
57496e32fd | ||
|
|
797bfc53c4 | ||
|
|
8c47dcb6fc | ||
|
|
6347190886 | ||
|
|
9162f3519d | ||
|
|
fc9314d9f5 | ||
|
|
26806d08eb | ||
|
|
6a827d5b61 | ||
|
|
7d77984306 | ||
|
|
ec93014713 | ||
|
|
c83094bde0 | ||
|
|
982d1519db | ||
|
|
f7c04ae537 | ||
|
|
f9a4258ded | ||
|
|
eb9f11bf96 | ||
|
|
2b8cb9de79 | ||
|
|
570abb5bad | ||
|
|
855244fd10 | ||
|
|
c62977412c | ||
|
|
98e557b68e | ||
|
|
3a32c00dcf | ||
|
|
1dde2c9e8e | ||
|
|
adfd010a78 | ||
|
|
1bc4e1f594 | ||
|
|
21680df9bd | ||
|
|
19aa3c125c | ||
|
|
e9e012a037 | ||
|
|
d1db0655ac | ||
|
|
2d643e6b7b | ||
|
|
4d6b1f3e63 | ||
|
|
d9c30e34c4 | ||
|
|
90c1ee0bd0 | ||
|
|
cfcca3a63c | ||
|
|
af0949adab | ||
|
|
cdb652ad87 | ||
|
|
4fb1ebfc10 | ||
|
|
46c1b57560 | ||
|
|
fdbbf7edd1 | ||
|
|
2fed8204c1 | ||
|
|
76f03b5db0 | ||
|
|
9fef4e86e4 | ||
|
|
4781dc03e9 | ||
|
|
cc5f84ac22 | ||
|
|
023e94661a | ||
|
|
b59fc23f86 | ||
|
|
d71dd5d94f | ||
|
|
63513e9a05 | ||
|
|
c802bc46a5 | ||
|
|
506bf0ee12 | ||
|
|
a36809db72 | ||
|
|
5b00d7ba5e | ||
|
|
b22604352d | ||
|
|
00c6b1bc60 | ||
|
|
4b0d86ee92 | ||
|
|
3707c39124 | ||
|
|
fe72b00df2 | ||
|
|
79a7ecc92f | ||
|
|
16df8bfe0d | ||
|
|
b37ceaa9f7 | ||
|
|
c41fdbce8a | ||
|
|
7526adc80a | ||
|
|
766eb693fb | ||
|
|
f9e2ae3488 | ||
|
|
6bf2ff5d10 | ||
|
|
27fcc01d81 | ||
|
|
3ac1f8e680 | ||
|
|
b63db53552 | ||
|
|
bed8186573 | ||
|
|
f08ff83d0a | ||
|
|
7c8c0906be | ||
|
|
167cb44ea1 | ||
|
|
5d74bdb240 | ||
|
|
ca38249f6c | ||
|
|
6a74f373b9 | ||
|
|
b52ef5a100 | ||
|
|
ef783f7049 | ||
|
|
435d624d33 | ||
|
|
53775fe086 | ||
|
|
59f1679fed | ||
|
|
390d081fca | ||
|
|
37d1d57900 | ||
|
|
d7a537c941 | ||
|
|
cfe73cd74f | ||
|
|
d0e1da8c51 | ||
|
|
7fbe663ea0 | ||
|
|
409f2f5d82 | ||
|
|
3d8b672771 | ||
|
|
17650a6100 | ||
|
|
1588ca7e1f | ||
|
|
0de17319d3 | ||
|
|
681ff32e76 | ||
|
|
caee6c8685 | ||
|
|
c67a515cc2 | ||
|
|
24892779f7 | ||
|
|
39d2113549 | ||
|
|
65522d9775 | ||
|
|
50eecf698c | ||
|
|
20ab78e3c1 | ||
|
|
613ffe9bbd | ||
|
|
61ff45f98e | ||
|
|
a8aa475d09 | ||
|
|
a4215cfa59 | ||
|
|
a4ac9a721f | ||
|
|
447183e235 | ||
|
|
642e543b4b | ||
|
|
80503c4837 | ||
|
|
3577064f8c | ||
|
|
b6663733c0 | ||
|
|
b9d6834213 | ||
|
|
eafc4c5a0c | ||
|
|
ecf80b47a0 | ||
|
|
5499555862 | ||
|
|
70df74f65f | ||
|
|
74415becce | ||
|
|
3a56cf8ad9 | ||
|
|
6b7f53f0f3 | ||
|
|
e51666e8be | ||
|
|
6e56bcd75f | ||
|
|
48bfef1f7a | ||
|
|
e8f91434a7 | ||
|
|
7e26d82790 | ||
|
|
869b70e4db | ||
|
|
33904cb9c1 | ||
|
|
a42191eecf | ||
|
|
3fbf163d34 | ||
|
|
1c45e1b744 | ||
|
|
c777aa62b6 | ||
|
|
6296bd4e1d | ||
|
|
5a236c19f5 | ||
|
|
4f8a1f92a3 | ||
|
|
5612790307 | ||
|
|
0b5be00374 | ||
|
|
145cc782ff | ||
|
|
9712506be8 | ||
|
|
bd9ead11c5 | ||
|
|
a98ebf7344 | ||
|
|
c721ab63ee | ||
|
|
2820438afc | ||
|
|
180cfcc3e3 | ||
|
|
52d806a34a | ||
|
|
49a8f2ec96 | ||
|
|
7f5f4629e5 | ||
|
|
d91e0b3867 | ||
|
|
b537758b32 | ||
|
|
2ba6e473de | ||
|
|
de97061d65 | ||
|
|
624ee78081 | ||
|
|
e003bb2bb4 | ||
|
|
5c5f310646 | ||
|
|
c10433e512 | ||
|
|
580c530041 | ||
|
|
4d461afbd6 | ||
|
|
536e31f343 | ||
|
|
c6eb4df25e | ||
|
|
aae00e3987 | ||
|
|
b56800b15c | ||
|
|
be461be36b | ||
|
|
85171a9490 | ||
|
|
f5b9f2052a | ||
|
|
3d652997d1 | ||
|
|
10393dca68 | ||
|
|
597bfaea03 | ||
|
|
98b8ced814 | ||
|
|
efaee8ce85 | ||
|
|
6e93f5bb72 | ||
|
|
a41f5673bc | ||
|
|
ca436f0bae | ||
|
|
d8212e8dd6 | ||
|
|
12eb9f42dc | ||
|
|
23af164d7a | ||
|
|
960a437d46 | ||
|
|
237bebe2ed | ||
|
|
fc3dca772e | ||
|
|
ee64d31f48 | ||
|
|
493ff74a0d | ||
|
|
78c7000962 | ||
|
|
6a05e01298 | ||
|
|
7de3434733 | ||
|
|
74e6189f3e | ||
|
|
2e7a3affba | ||
|
|
505ec21f97 | ||
|
|
434820973c | ||
|
|
41ff526921 | ||
|
|
0be52b0087 | ||
|
|
ee8dc496d9 | ||
|
|
f80656cddf | ||
|
|
72121c01ec | ||
|
|
ac505390dd | ||
|
|
728e6e15c5 | ||
|
|
d21305378c | ||
|
|
0552749059 | ||
|
|
fbd3fe95e4 | ||
|
|
c6d02daee0 | ||
|
|
5208035003 | ||
|
|
d93956eff0 | ||
|
|
b3b06d3e7d | ||
|
|
427f815975 | ||
|
|
bf601ce268 | ||
|
|
8bfb363fcc | ||
|
|
ebf2630a66 | ||
|
|
9018955e1f | ||
|
|
88d58ae0a3 | ||
|
|
2fc99afd44 | ||
|
|
fa0885e25d | ||
|
|
0e4a0108d2 | ||
|
|
17bc627bf2 | ||
|
|
58370256c0 | ||
|
|
d5364231c2 | ||
|
|
4df3a4d436 | ||
|
|
812989490c | ||
|
|
892ef9edb7 | ||
|
|
982782f8c9 | ||
|
|
7319f524a3 | ||
|
|
1d71fbf77b | ||
|
|
7eacfec2c3 | ||
|
|
46f2a41cf7 | ||
|
|
fd2baf6f65 | ||
|
|
c8bf06d549 | ||
|
|
3acfa50214 | ||
|
|
3dbe205498 | ||
|
|
899cce8094 | ||
|
|
7400d51444 | ||
|
|
96c344d22b | ||
|
|
f48d71ecd0 | ||
|
|
d3acbbf79b | ||
|
|
cb9ec8234b | ||
|
|
47c72e583e | ||
|
|
568c2d308c | ||
|
|
11a7f359d1 | ||
|
|
145f1f5198 | ||
|
|
ff1df41485 | ||
|
|
d36aec8fb7 | ||
|
|
2779b5ee91 | ||
|
|
32efbd3edd | ||
|
|
68718eac1b | ||
|
|
7b64b4a207 | ||
|
|
f1143f591f | ||
|
|
07fc401d25 | ||
|
|
96f166a7e9 | ||
|
|
f4b775323d | ||
|
|
43d308116d | ||
|
|
d2b4dd71d2 | ||
|
|
36e6a73d5b | ||
|
|
e26158a45e | ||
|
|
3cfcd6a856 | ||
|
|
ff68806bfa | ||
|
|
4192c3abf4 | ||
|
|
ac1e1c7d23 | ||
|
|
9ab999618c | ||
|
|
f2666a472f | ||
|
|
ceda5d3bc7 | ||
|
|
6d81d519b6 | ||
|
|
88d1d79516 | ||
|
|
cfc6cfd1a3 | ||
|
|
6b7d67b427 | ||
|
|
b6d08b15c0 | ||
|
|
01f89a8cdc | ||
|
|
efd7a5dca6 | ||
|
|
7ba0290643 | ||
|
|
8ceb47178b | ||
|
|
2560d4f419 | ||
|
|
87ee409783 | ||
|
|
d47c1f3e9b | ||
|
|
b952dac339 | ||
|
|
ffb7d4c79c | ||
|
|
e68717b725 | ||
|
|
30a063ef9d | ||
|
|
35c3669ebc | ||
|
|
23f4f03575 | ||
|
|
a912fc09be | ||
|
|
a736a3713b | ||
|
|
f2da5bc93e | ||
|
|
2905b435db | ||
|
|
48ca6dbcec | ||
|
|
15a4302902 | ||
|
|
1f82a20312 | ||
|
|
fc943b70f6 | ||
|
|
f36470941c | ||
|
|
ae6d80daab | ||
|
|
44e82e2720 | ||
|
|
e94467d6da | ||
|
|
794c7708e8 | ||
|
|
8e73926359 | ||
|
|
8fc1d74820 | ||
|
|
496c6a9f03 | ||
|
|
7873f700b0 | ||
|
|
46c0861f45 | ||
|
|
5149c0ff25 | ||
|
|
cf99d62472 | ||
|
|
5878797eae | ||
|
|
8c2d090dc8 | ||
|
|
3f772eac32 | ||
|
|
62c952d258 | ||
|
|
c2f698e56e | ||
|
|
40f2a3efba | ||
|
|
333b9c0b99 | ||
|
|
90d19b4131 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,6 +12,5 @@ lib/Doctrine/DBAL
|
||||
.idea
|
||||
*.iml
|
||||
vendor/
|
||||
composer.lock
|
||||
/tests/Doctrine/Performance/history.db
|
||||
/.phpcs-cache
|
||||
|
||||
42
.travis.yml
42
.travis.yml
@@ -5,7 +5,8 @@ language: php
|
||||
php:
|
||||
- 7.1
|
||||
- 7.2
|
||||
- nightly
|
||||
- 7.3
|
||||
- 7.4
|
||||
|
||||
env:
|
||||
- DB=sqlite
|
||||
@@ -16,7 +17,7 @@ before_install:
|
||||
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{,.disabled} || echo "xdebug not available"
|
||||
- composer self-update
|
||||
|
||||
install: travis_retry composer update --prefer-dist
|
||||
install: travis_retry composer validate --strict && composer update --prefer-dist
|
||||
|
||||
script:
|
||||
- if [[ "$DB" == "mysql" || "$DB" == "mariadb" ]]; then mysql -e "CREATE SCHEMA doctrine_tests; GRANT ALL PRIVILEGES ON doctrine_tests.* to travis@'%'"; fi
|
||||
@@ -31,22 +32,31 @@ jobs:
|
||||
mariadb: 10.1
|
||||
|
||||
- stage: Test
|
||||
dist: xenial
|
||||
env: DB=mysql MYSQL_VERSION=5.7
|
||||
php: 7.1
|
||||
services:
|
||||
- mysql
|
||||
before_script:
|
||||
- ./tests/travis/install-mysql-$MYSQL_VERSION.sh
|
||||
sudo: required
|
||||
|
||||
- stage: Test
|
||||
dist: xenial
|
||||
env: DB=mysql MYSQL_VERSION=5.7
|
||||
php: 7.2
|
||||
services:
|
||||
- mysql
|
||||
before_script:
|
||||
- ./tests/travis/install-mysql-$MYSQL_VERSION.sh
|
||||
sudo: required
|
||||
|
||||
- stage: Test
|
||||
dist: xenial
|
||||
env: DB=mysql MYSQL_VERSION=5.7
|
||||
php: nightly
|
||||
php: 7.4
|
||||
services:
|
||||
- mysql
|
||||
before_script:
|
||||
- ./tests/travis/install-mysql-$MYSQL_VERSION.sh
|
||||
sudo: required
|
||||
@@ -57,6 +67,7 @@ jobs:
|
||||
|
||||
- stage: Test
|
||||
if: type = cron
|
||||
php: 7.3
|
||||
env: DB=sqlite DEV_DEPENDENCIES
|
||||
install:
|
||||
- composer config minimum-stability dev
|
||||
@@ -75,7 +86,8 @@ jobs:
|
||||
|
||||
- stage: Code Quality
|
||||
env: DB=none STATIC_ANALYSIS
|
||||
install: travis_retry composer update --prefer-dist --prefer-stable
|
||||
php: 7.4
|
||||
install: travis_retry composer install --prefer-dist
|
||||
before_script:
|
||||
- echo "extension=memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
|
||||
- echo "extension=redis.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
|
||||
@@ -88,13 +100,31 @@ jobs:
|
||||
script: php phpbench.phar run -l dots --report=default
|
||||
|
||||
- stage: Code Quality
|
||||
if: NOT type = pull_request
|
||||
env: DB=none CODING_STANDARDS
|
||||
php: nightly
|
||||
php: 7.4
|
||||
install: travis_retry composer install --prefer-dist
|
||||
script:
|
||||
- ./vendor/bin/phpcs
|
||||
|
||||
- stage: Code Quality
|
||||
if: type = pull_request
|
||||
env: DB=none PULL_REQUEST_CODING_STANDARDS
|
||||
php: 7.1
|
||||
install: travis_retry composer install --prefer-dist
|
||||
script:
|
||||
- |
|
||||
if [ $TRAVIS_BRANCH != "master" ]; then
|
||||
git remote set-branches --add origin $TRAVIS_BRANCH;
|
||||
git fetch origin $TRAVIS_BRANCH;
|
||||
fi
|
||||
- git merge-base origin/$TRAVIS_BRANCH $TRAVIS_PULL_REQUEST_SHA || git fetch origin +refs/pull/$TRAVIS_PULL_REQUEST/merge --unshallow
|
||||
- wget https://github.com/diff-sniffer/git/releases/download/0.2.0/git-phpcs.phar
|
||||
- php git-phpcs.phar origin/$TRAVIS_BRANCH...$TRAVIS_PULL_REQUEST_SHA
|
||||
|
||||
allow_failures:
|
||||
- php: nightly
|
||||
- stage: Code Quality
|
||||
env: DB=none CODING_STANDARDS
|
||||
|
||||
cache:
|
||||
directories:
|
||||
|
||||
@@ -44,12 +44,12 @@ Please try to add a test for your pull-request.
|
||||
You can run the unit-tests by calling ``vendor/bin/phpunit`` from the root of the project.
|
||||
It will run all the tests with an in memory SQLite database.
|
||||
|
||||
In order to do that, you will need a fresh copy of doctrine2, and you
|
||||
In order to do that, you will need a fresh copy of the ORM, and you
|
||||
will have to run a composer installation in the project:
|
||||
|
||||
```sh
|
||||
git clone git@github.com:doctrine/doctrine2.git
|
||||
cd doctrine2
|
||||
git clone git@github.com:doctrine/orm.git
|
||||
cd orm
|
||||
curl -sS https://getcomposer.org/installer | php --
|
||||
./composer.phar install
|
||||
```
|
||||
@@ -66,7 +66,7 @@ sqlite database.
|
||||
Tips for creating unit tests:
|
||||
|
||||
1. If you put a test into the `Ticket` namespace as described above, put the testcase and all entities into the same class.
|
||||
See `https://github.com/doctrine/doctrine2/tree/master/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2306Test.php` for an
|
||||
See `https://github.com/doctrine/orm/tree/master/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2306Test.php` for an
|
||||
example.
|
||||
|
||||
## Travis
|
||||
|
||||
16
README.md
16
README.md
@@ -16,11 +16,11 @@ without requiring unnecessary code duplication.
|
||||
* [Documentation](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/index.html)
|
||||
|
||||
|
||||
[Master image]: https://img.shields.io/travis/doctrine/doctrine2/master.svg?style=flat-square
|
||||
[Master]: https://travis-ci.org/doctrine/doctrine2
|
||||
[Master coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/doctrine2/master.svg?style=flat-square
|
||||
[Master coverage]: https://scrutinizer-ci.com/g/doctrine/doctrine2/?branch=master
|
||||
[2.5 image]: https://img.shields.io/travis/doctrine/doctrine2/2.5.svg?style=flat-square
|
||||
[2.5]: https://github.com/doctrine/doctrine2/tree/2.5
|
||||
[2.5 coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/doctrine2/2.5.svg?style=flat-square
|
||||
[2.5 coverage]: https://scrutinizer-ci.com/g/doctrine/doctrine2/?branch=2.5
|
||||
[Master image]: https://img.shields.io/travis/doctrine/orm/master.svg?style=flat-square
|
||||
[Master]: https://travis-ci.org/doctrine/orm
|
||||
[Master coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/orm/master.svg?style=flat-square
|
||||
[Master coverage]: https://scrutinizer-ci.com/g/doctrine/orm/?branch=master
|
||||
[2.5 image]: https://img.shields.io/travis/doctrine/orm/2.5.svg?style=flat-square
|
||||
[2.5]: https://github.com/doctrine/orm/tree/2.5
|
||||
[2.5 coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/orm/2.5.svg?style=flat-square
|
||||
[2.5 coverage]: https://scrutinizer-ci.com/g/doctrine/orm/?branch=2.5
|
||||
|
||||
@@ -11,7 +11,7 @@ Please read the documentation chapter on Security in Doctrine DBAL and ORM to
|
||||
understand the assumptions we make.
|
||||
|
||||
- [DBAL Security Page](https://github.com/doctrine/dbal/blob/master/docs/en/reference/security.rst)
|
||||
- [ORM Security Page](https://github.com/doctrine/doctrine2/blob/master/docs/en/reference/security.rst)
|
||||
- [ORM Security Page](https://github.com/doctrine/orm/blob/master/docs/en/reference/security.rst)
|
||||
|
||||
If you find a Security bug in Doctrine, please report it on Jira and change the
|
||||
Security Level to "Security Issues". It will be visible to Doctrine Core
|
||||
|
||||
155
UPGRADE.md
155
UPGRADE.md
@@ -1,5 +1,154 @@
|
||||
# Upgrade to 2.7
|
||||
|
||||
## Added `Doctrine\ORM\AbstractQuery#enableResultCache()` and `Doctrine\ORM\AbstractQuery#disableResultCache()` methods
|
||||
|
||||
Method `Doctrine\ORM\AbstractQuery#useResultCache()` which could be used for both enabling and disabling the cache
|
||||
(depending on passed flag) was split into two.
|
||||
|
||||
## Minor BC BREAK: paginator output walkers aren't be called anymore on sub-queries for queries without max results
|
||||
|
||||
To optimize DB interaction, `Doctrine\ORM\Tools\Pagination\Paginator` no longer fetches identifiers to be able to
|
||||
perform the pagination with join collections when max results isn't set in the query.
|
||||
|
||||
## Minor BC BREAK: tables filtered with `schema_filter` are no longer created
|
||||
|
||||
When generating schema diffs, if a source table is filtered out by a `schema_filter` expression, then a `CREATE TABLE` was
|
||||
always generated, even if the table already existed. This has been changed in this release and the table will no longer
|
||||
be created.
|
||||
|
||||
## Deprecated number unaware `Doctrine\ORM\Mapping\UnderscoreNamingStrategy`
|
||||
|
||||
In the last patch of the `v2.6.x` series, we fixed a bug that was not converting names properly when they had numbers
|
||||
(e.g.: `base64Encoded` was wrongly converted to `base64encoded` instead of `base64_encoded`).
|
||||
|
||||
In order to not break BC we've introduced a way to enable the fixed behavior using a boolean constructor argument. This
|
||||
argument will be removed in 3.0 and the default behavior will be the fixed one.
|
||||
|
||||
## Deprecated: `Doctrine\ORM\AbstractQuery#useResultCache()`
|
||||
|
||||
Method `Doctrine\ORM\AbstractQuery#useResultCache()` is deprecated because it is split into `enableResultCache()`
|
||||
and `disableResultCache()`. It will be removed in 3.0.
|
||||
|
||||
## Deprecated code generators and related console commands
|
||||
|
||||
These console commands have been deprecated:
|
||||
|
||||
* `orm:convert-mapping`
|
||||
* `orm:generate:entities`
|
||||
* `orm:generate-repositories`
|
||||
|
||||
These classes have been deprecated:
|
||||
|
||||
* `Doctrine\ORM\Tools\EntityGenerator`
|
||||
* `Doctrine\ORM\Tools\EntityRepositoryGenerator`
|
||||
|
||||
Whole Doctrine\ORM\Tools\Export namespace with all its members have been deprecated as well.
|
||||
|
||||
## Deprecated `Doctrine\ORM\Proxy\Proxy` marker interface
|
||||
|
||||
Proxy objects in Doctrine ORM 3.0 will no longer implement `Doctrine\ORM\Proxy\Proxy` nor
|
||||
`Doctrine\Common\Persistence\Proxy`: instead, they implement
|
||||
`ProxyManager\Proxy\GhostObjectInterface`.
|
||||
|
||||
These related classes have been deprecated:
|
||||
|
||||
* `Doctrine\ORM\Proxy\ProxyFactory`
|
||||
* `Doctrine\ORM\Proxy\Autoloader` - we suggest using the composer autoloader instead
|
||||
|
||||
These methods have been deprecated:
|
||||
|
||||
* `Doctrine\ORM\Configuration#getAutoGenerateProxyClasses()`
|
||||
* `Doctrine\ORM\Configuration#getProxyDir()`
|
||||
* `Doctrine\ORM\Configuration#getProxyNamespace()`
|
||||
|
||||
## Deprecated `Doctrine\ORM\Version`
|
||||
|
||||
The `Doctrine\ORM\Version` class is now deprecated and will be removed in Doctrine ORM 3.0:
|
||||
please refrain from checking the ORM version at runtime or use
|
||||
[ocramius/package-versions](https://github.com/Ocramius/PackageVersions/).
|
||||
|
||||
## Deprecated `EntityManager#merge()` and `EntityManager#detach()` methods
|
||||
|
||||
Merge and detach semantics were a poor fit for the PHP "share-nothing" architecture.
|
||||
In addition to that, merging/detaching caused multiple issues with data integrity
|
||||
in the managed entity graph, which was constantly spawning more edge-case bugs/scenarios.
|
||||
|
||||
The following API methods were therefore deprecated:
|
||||
|
||||
* `EntityManager#merge()`
|
||||
* `EntityManager#detach()`
|
||||
* `UnitOfWork#merge()`
|
||||
* `UnitOfWork#detach()`
|
||||
|
||||
Users are encouraged to migrate `EntityManager#detach()` calls to `EntityManager#clear()`.
|
||||
|
||||
In order to maintain performance on batch processing jobs, it is endorsed to enable
|
||||
the second level cache (http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/second-level-cache.html)
|
||||
on entities that are frequently reused across multiple `EntityManager#clear()` calls.
|
||||
|
||||
An alternative to `EntityManager#merge()` will not be provided by ORM 3.0, since the merging
|
||||
semantics should be part of the business domain rather than the persistence domain of an
|
||||
application. If your application relies heavily on CRUD-alike interactions and/or `PATCH`
|
||||
restful operations, you should look at alternatives such as [JMSSerializer](https://github.com/schmittjoh/serializer).
|
||||
|
||||
## Extending `EntityManager` is deprecated
|
||||
|
||||
Final keyword will be added to the `EntityManager::class` in Doctrine ORM 3.0 in order to ensure that EntityManager
|
||||
is not used as valid extension point. Valid extension point should be EntityManagerInterface.
|
||||
|
||||
## Deprecated `EntityManager#clear($entityName)`
|
||||
|
||||
If your code relies on clearing a single entity type via `EntityManager#clear($entityName)`,
|
||||
the signature has been changed to `EntityManager#clear()`.
|
||||
|
||||
The main reason is that partial clears caused multiple issues with data integrity
|
||||
in the managed entity graph, which was constantly spawning more edge-case bugs/scenarios.
|
||||
|
||||
## Deprecated `EntityManager#flush($entity)` and `EntityManager#flush($entities)`
|
||||
|
||||
If your code relies on single entity flushing optimisations via
|
||||
`EntityManager#flush($entity)`, the signature has been changed to
|
||||
`EntityManager#flush()`.
|
||||
|
||||
Said API was affected by multiple data integrity bugs due to the fact
|
||||
that change tracking was being restricted upon a subset of the managed
|
||||
entities. The ORM cannot support committing subsets of the managed
|
||||
entities while also guaranteeing data integrity, therefore this
|
||||
utility was removed.
|
||||
|
||||
The `flush()` semantics will remain the same, but the change tracking will be performed
|
||||
on all entities managed by the unit of work, and not just on the provided
|
||||
`$entity` or `$entities`, as the parameter is now completely ignored.
|
||||
|
||||
The same applies to `UnitOfWork#commit($entity)`, which will simply be
|
||||
`UnitOfWork#commit()`.
|
||||
|
||||
If you would still like to perform batching operations over small `UnitOfWork`
|
||||
instances, it is suggested to follow these paths instead:
|
||||
|
||||
* eagerly use `EntityManager#clear()` in conjunction with a specific second level
|
||||
cache configuration (see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/second-level-cache.html)
|
||||
* use an explicit change tracking policy (see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/change-tracking-policies.html)
|
||||
|
||||
## Deprecated `YAML` mapping drivers.
|
||||
|
||||
If your code relies on `YamlDriver` or `SimpleYamlDriver`, you **MUST** change to
|
||||
annotation or XML drivers instead.
|
||||
|
||||
## Deprecated: `Doctrine\ORM\EntityManagerInterface#copy()`
|
||||
|
||||
Method `Doctrine\ORM\EntityManagerInterface#copy()` never got its implementation and is deprecated.
|
||||
It will be removed in 3.0.
|
||||
|
||||
# Upgrade to 2.6
|
||||
|
||||
## Added `Doctrine\ORM\EntityRepository::count()` method
|
||||
|
||||
`Doctrine\ORM\EntityRepository::count()` has been added. This new method has different
|
||||
signature than `Countable::count()` (required parameter) and therefore are not compatible.
|
||||
If your repository implemented the `Countable` interface, you will have to use
|
||||
`$repository->count([])` instead and not implement `Countable` interface anymore.
|
||||
|
||||
## Minor BC BREAK: `Doctrine\ORM\Tools\Console\ConsoleRunner` is now final
|
||||
|
||||
Since it's just an utilitarian class and should not be inherited.
|
||||
@@ -13,13 +162,13 @@ now has a required parameter `$pathExpr`.
|
||||
|
||||
Method `Doctrine\ORM\Query\Parser#isInternalFunction()` was removed because
|
||||
the distinction between internal function and user defined DQL was removed.
|
||||
[#6500](https://github.com/doctrine/doctrine2/pull/6500)
|
||||
[#6500](https://github.com/doctrine/orm/pull/6500)
|
||||
|
||||
## Minor BC BREAK: removed `Doctrine\ORM\ORMException#overwriteInternalDQLFunctionNotAllowed()`
|
||||
|
||||
Method `Doctrine\ORM\Query\Parser#overwriteInternalDQLFunctionNotAllowed()` was
|
||||
removed because of the choice to allow users to overwrite internal functions, ie
|
||||
`AVG`, `SUM`, `COUNT`, `MIN` and `MAX`. [#6500](https://github.com/doctrine/doctrine2/pull/6500)
|
||||
`AVG`, `SUM`, `COUNT`, `MIN` and `MAX`. [#6500](https://github.com/doctrine/orm/pull/6500)
|
||||
|
||||
## PHP 7.1 is now required
|
||||
|
||||
@@ -35,7 +184,7 @@ As a consequence, automatic cache setup in Doctrine\ORM\Tools\Setup::create*Conf
|
||||
## Minor BC BREAK: removed `Doctrine\ORM\Query\SqlWalker#walkCaseExpression()`
|
||||
|
||||
Method `Doctrine\ORM\Query\SqlWalker#walkCaseExpression()` was unused and part
|
||||
of the internal API of the ORM, so it was removed. [#5600](https://github.com/doctrine/doctrine2/pull/5600).
|
||||
of the internal API of the ORM, so it was removed. [#5600](https://github.com/doctrine/orm/pull/5600).
|
||||
|
||||
## Minor BC BREAK: removed $className parameter on `AbstractEntityInheritancePersister#getSelectJoinColumnSQL()`
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# Version class and file
|
||||
project.version_class = Doctrine\\ORM\\Version
|
||||
project.version_file = lib/Doctrine/ORM/Version.php
|
||||
@@ -1,16 +0,0 @@
|
||||
version=2.0.0BETA2
|
||||
dependencies.common=2.0.0BETA4
|
||||
dependencies.dbal=2.0.0BETA4
|
||||
stability=beta
|
||||
build.dir=build
|
||||
dist.dir=dist
|
||||
report.dir=reports
|
||||
log.archive.dir=logs
|
||||
project.pirum_dir=
|
||||
project.download_dir=
|
||||
project.xsd_dir=
|
||||
test.phpunit_configuration_file=
|
||||
test.phpunit_generate_coverage=0
|
||||
test.pmd_reports=0
|
||||
test.pdepend_exec=
|
||||
test.phpmd_exec=
|
||||
101
build.xml
101
build.xml
@@ -1,101 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<project name="DoctrineORM" default="build" basedir=".">
|
||||
<property file="build.properties" />
|
||||
|
||||
<target name="php">
|
||||
<exec executable="which" outputproperty="php_executable">
|
||||
<arg value="php" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="prepare">
|
||||
<mkdir dir="build" />
|
||||
</target>
|
||||
|
||||
<target name="build" depends="check-git-checkout-clean,prepare,php,composer">
|
||||
<exec executable="${php_executable}">
|
||||
<arg value="build/composer.phar" />
|
||||
<arg value="archive" />
|
||||
<arg value="--dir=build" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="composer" depends="php,composer-check,composer-download">
|
||||
<exec executable="${php_executable}">
|
||||
<arg value="build/composer.phar" />
|
||||
<arg value="install" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="composer-check" depends="prepare">
|
||||
<available file="build/composer.phar" property="composer.present"/>
|
||||
</target>
|
||||
|
||||
<target name="composer-download" unless="composer.present">
|
||||
<exec executable="wget">
|
||||
<arg value="-Obuild/composer.phar" />
|
||||
<arg value="http://getcomposer.org/composer.phar" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="make-release" depends="check-git-checkout-clean,prepare,php">
|
||||
<replace file="${project.version_file}" token="-DEV" value="" failOnNoReplacements="true" />
|
||||
<exec executable="${php_executable}" outputproperty="doctrine.current_version" failonerror="true">
|
||||
<arg value="-r" />
|
||||
<arg value="require_once '${project.version_file}';echo ${project.version_class}::VERSION;" />
|
||||
</exec>
|
||||
<exec executable="${php_executable}" outputproperty="doctrine.next_version" failonerror="true">
|
||||
<arg value="-r" />
|
||||
<arg value="$parts = explode('.', str_ireplace(array('-DEV', '-ALPHA', '-BETA'), '', '${doctrine.current_version}'));
|
||||
if (count($parts) != 3) {
|
||||
throw new \InvalidArgumentException('Version is assumed in format x.y.z, ${doctrine.current_version} given');
|
||||
}
|
||||
$parts[2]++;
|
||||
echo implode('.', $parts);
|
||||
" />
|
||||
</exec>
|
||||
|
||||
<git-commit file="${project.version_file}" message="Release ${doctrine.current_version}" />
|
||||
<git-tag version="${doctrine.current_version}" />
|
||||
<replace file="${project.version_file}" token="${doctrine.current_version}" value="${doctrine.next_version}-DEV" />
|
||||
<git-commit file="${project.version_file}" message="Bump version to ${doctrine.next_version}" />
|
||||
</target>
|
||||
|
||||
<target name="check-git-checkout-clean">
|
||||
<exec executable="git" failonerror="true">
|
||||
<arg value="diff-index" />
|
||||
<arg value="--quiet" />
|
||||
<arg value="HEAD" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<macrodef name="git-commit">
|
||||
<attribute name="file" default="NOT SET"/>
|
||||
<attribute name="message" default="NOT SET"/>
|
||||
|
||||
<sequential>
|
||||
<exec executable="git">
|
||||
<arg value="add" />
|
||||
<arg value="@{file}" />
|
||||
</exec>
|
||||
<exec executable="git">
|
||||
<arg value="commit" />
|
||||
<arg value="-m" />
|
||||
<arg value="@{message}" />
|
||||
</exec>
|
||||
</sequential>
|
||||
</macrodef>
|
||||
|
||||
<macrodef name="git-tag">
|
||||
<attribute name="version" default="NOT SET" />
|
||||
|
||||
<sequential>
|
||||
<exec executable="git">
|
||||
<arg value="tag" />
|
||||
<arg value="-m" />
|
||||
<arg value="v@{version}" />
|
||||
<arg value="v@{version}" />
|
||||
</exec>
|
||||
</sequential>
|
||||
</macrodef>
|
||||
</project>
|
||||
@@ -3,7 +3,7 @@
|
||||
"type": "library",
|
||||
"description": "Object-Relational-Mapper for PHP",
|
||||
"keywords": ["orm", "database"],
|
||||
"homepage": "http://www.doctrine-project.org",
|
||||
"homepage": "https://www.doctrine-project.org/projects/orm.html",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
|
||||
@@ -18,19 +18,21 @@
|
||||
"require": {
|
||||
"php": "^7.1",
|
||||
"ext-pdo": "*",
|
||||
"doctrine/annotations": "~1.5",
|
||||
"doctrine/cache": "~1.6",
|
||||
"doctrine/collections": "^1.4",
|
||||
"doctrine/common": "^2.7.1",
|
||||
"doctrine/dbal": "^2.6",
|
||||
"doctrine/instantiator": "~1.1",
|
||||
"symfony/console": "~3.0|~4.0"
|
||||
"doctrine/annotations": "^1.8",
|
||||
"doctrine/cache": "^1.9.1",
|
||||
"doctrine/collections": "^1.5",
|
||||
"doctrine/common": "^2.11",
|
||||
"doctrine/dbal": "^2.9.3",
|
||||
"doctrine/event-manager": "^1.1",
|
||||
"doctrine/instantiator": "^1.3",
|
||||
"doctrine/persistence": "^1.2",
|
||||
"ocramius/package-versions": "^1.2",
|
||||
"symfony/console": "^3.0|^4.0|^5.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^1.0",
|
||||
"phpunit/phpunit": "^6.5",
|
||||
"squizlabs/php_codesniffer": "^3.2",
|
||||
"symfony/yaml": "~3.4|~4.0"
|
||||
"doctrine/coding-standard": "^5.0",
|
||||
"phpunit/phpunit": "^7.5",
|
||||
"symfony/yaml": "^3.4|^4.0|^5.0"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/yaml": "If you want to use YAML Metadata Mapping Driver"
|
||||
@@ -47,7 +49,7 @@
|
||||
"bin": ["bin/doctrine"],
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.6.x-dev"
|
||||
"dev-master": "2.7.x-dev"
|
||||
}
|
||||
},
|
||||
"archive": {
|
||||
|
||||
2793
composer.lock
generated
Normal file
2793
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,711 +0,0 @@
|
||||
What is new in Doctrine ORM 2.5?
|
||||
================================
|
||||
|
||||
This document describes changes between Doctrine ORM 2.4 and 2.5.
|
||||
It contains a description of all the new features and sections about
|
||||
behavioral changes and potential backwards compatibility breaks.
|
||||
Please review this document carefully when updating to Doctrine 2.5.
|
||||
|
||||
First note, that with the ORM 2.5 release we are dropping support
|
||||
for PHP 5.3. We are enforcing this with Composer, servers without
|
||||
at least PHP 5.4 will not allow installing Doctrine 2.5.
|
||||
|
||||
New Features and Improvements
|
||||
-----------------------------
|
||||
|
||||
Events: PostLoad now triggered after associations are loaded
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Before Doctrine 2.5 if you had an entity with a ``@PostLoad`` event
|
||||
defined then Doctrine would trigger listeners after the fields were
|
||||
loaded, but before assocations are available.
|
||||
|
||||
- `DDC-54 <http://doctrine-project.org/jira/browse/DDC-54>`_
|
||||
- `Commit #a90629 <https://github.com/doctrine/doctrine2/commit/a906295c65f1516737458fbee2f6fa96254f27a5>`_
|
||||
|
||||
Events: Add API to programatically add event listeners to Entity
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When developing third party libraries or decoupled applications
|
||||
it can be interesting to develop an entity listener without knowing
|
||||
the entities that require this listener.
|
||||
|
||||
You can now attach entity listeners to entities using the
|
||||
``AttachEntityListenersListener`` class, which is listening to the
|
||||
``loadMetadata`` event that is fired once for every entity during
|
||||
metadata generation:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Tools\AttachEntityListenersListener;
|
||||
use Doctrine\ORM\Events;
|
||||
|
||||
$listener = new AttachEntityListenersListener();
|
||||
$listener->addEntityListener(
|
||||
'MyProject\Entity\User', 'MyProject\Listener\TimestampableListener',
|
||||
Events::prePersist, 'onPrePersist'
|
||||
);
|
||||
|
||||
$evm->addEventListener(Events::loadClassMetadata, $listener);
|
||||
|
||||
class TimestampableListener
|
||||
{
|
||||
public function onPrePersist($event)
|
||||
{
|
||||
$entity = $event->getEntity();
|
||||
$entity->setCreated(new \DateTime('now'));
|
||||
}
|
||||
}
|
||||
|
||||
Embeddable Objects
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Doctrine now supports creating multiple PHP objects from one database table
|
||||
implementing a feature called "Embeddable Objects". Next to an ``@Entity``
|
||||
class you can now define a class that is embeddable into a database table of an
|
||||
entity using the ``@Embeddable`` annotation. Embeddable objects can never be
|
||||
saved, updated or deleted on their own, only as part of an entity (called
|
||||
"root-entity" or "aggregate"). Consequently embeddables don't have a primary
|
||||
key, they are identified only by their values.
|
||||
|
||||
Example of defining and using embeddables classes:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
/** @Entity */
|
||||
class Product
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private $id;
|
||||
|
||||
/** @Embedded(class = "Money") */
|
||||
private $price;
|
||||
}
|
||||
|
||||
/** @Embeddable */
|
||||
class Money
|
||||
{
|
||||
/** @Column(type = "decimal") */
|
||||
private $value;
|
||||
|
||||
/** @Column(type = "string") */
|
||||
private $currency = 'EUR';
|
||||
}
|
||||
|
||||
You can read more on the features of Embeddables objects `in the documentation
|
||||
<http://docs.doctrine-project.org/en/latest/tutorials/embeddables.html>`_.
|
||||
|
||||
This feature was developed by external contributor `Johannes Schmitt
|
||||
<https://twitter.com/schmittjoh>`_
|
||||
|
||||
- `DDC-93 <http://doctrine-project.org/jira/browse/DDC-93>`_
|
||||
- `Pull Request #835 <https://github.com/doctrine/doctrine2/pull/835>`_
|
||||
|
||||
Second-Level-Cache
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Since version 2.0 of Doctrine, fetching the same object twice by primary key
|
||||
would result in just one query. This was achieved by the identity map pattern
|
||||
(first-level-cache) that kept entities in memory.
|
||||
|
||||
The newly introduced second-level-cache works a bit differently. Instead
|
||||
of saving objects in memory, it saves them in a fast in-memory cache such
|
||||
as Memcache, Redis, Riak or MongoDB. Additionally it allows saving the result
|
||||
of more complex queries than by primary key. Summarized this feature works
|
||||
like the existing Query result cache, but it is much more powerful.
|
||||
|
||||
As an example lets cache an entity Country that is a relation to the User
|
||||
entity. We always want to display the country, but avoid the additional
|
||||
query to this table.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
* @Cache(usage="READ_ONLY", region="country_region")
|
||||
*/
|
||||
class Country
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @GeneratedValue
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @Column(unique=true)
|
||||
*/
|
||||
protected $name;
|
||||
}
|
||||
|
||||
In this example we have specified a caching region name called
|
||||
``country_region``, which we have to configure now on the EntityManager:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$config->setSecondLevelCacheEnabled();
|
||||
|
||||
$cacheConfig = $config->getSecondLevelCacheConfiguration();
|
||||
$regionConfig = $cacheConfig->getRegionsConfiguration();
|
||||
$regionConfig->setLifetime('country_region', 3600);
|
||||
|
||||
Now Doctrine will first check for the data of any country in the cache
|
||||
instead of the database.
|
||||
|
||||
- `Documentation
|
||||
<http://docs.doctrine-project.org/en/latest/reference/second-level-cache.html>`_
|
||||
- `Pull Request #808 <https://github.com/doctrine/doctrine2/pull/808>`_
|
||||
|
||||
Criteria API: Support for ManyToMany assocations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
We introduced support for querying collections using the `Criteria API
|
||||
<http://docs.doctrine-project.org/en/latest/reference/working-with-associations.html#filtering-collections>`_
|
||||
in 2.4. This only worked efficently for One-To-Many assocations, not for
|
||||
Many-To-Many. With the start of 2.5 also Many-To-Many associations get queried
|
||||
instead of loading them into memory.
|
||||
|
||||
Criteria API: Add new contains() expression
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
It is now possible to use the Criteria API to check for string contains needle
|
||||
using ``contains()``. This translates to using a ``column LIKE '%needle%'`` SQL
|
||||
condition.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use \Doctrine\Common\Collections\Criteria;
|
||||
|
||||
$criteria = Criteria::create()
|
||||
->where(Criteria::expr()->contains('name', 'Benjamin'));
|
||||
|
||||
$users = $repository->matching($criteria);
|
||||
|
||||
Criteria API: Support for EXTRA_LAZY
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A collection that is marked as ``fetch="EXTRA_LAZY"`` will now return another
|
||||
lazy collection when using ``Collection::matching($criteria)``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
class Post
|
||||
{
|
||||
/** @OneToMany(targetEntity="Comment", fetch="EXTRA_LAZY") */
|
||||
private $comments;
|
||||
}
|
||||
|
||||
$criteria = Criteria::create()
|
||||
->where(Criteria->expr()->eq("published", 1));
|
||||
|
||||
$publishedComments = $post->getComments()->matching($criteria);
|
||||
|
||||
echo count($publishedComments);
|
||||
|
||||
The lazy criteria currently supports the ``count()`` and ``contains()``
|
||||
functionality lazily. All other operations of the ``Collection`` interface
|
||||
trigger a full load of the collection.
|
||||
|
||||
This feature was contributed by `Michaël Gallego <https://github.com/bakura10>`_.
|
||||
|
||||
- `Pull Request #882 <https://github.com/doctrine/doctrine2/pull/882>`_
|
||||
- `Pull Request #1032 <https://github.com/doctrine/doctrine2/pull/1032>`_
|
||||
|
||||
Mapping: Allow configuring Index flags
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
It is now possible to control the index flags in the DBAL
|
||||
schema abstraction from the ORM using metadata. This was possible
|
||||
only with a schema event listener before.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @Table(name="product", indexes={@Index(columns={"description"},flags={"fulltext"})})
|
||||
*/
|
||||
class Product
|
||||
{
|
||||
private $description;
|
||||
}
|
||||
|
||||
This feature was contributed by `Adrian Olek <https://github.com/adrianolek>`_.
|
||||
|
||||
- `Pull Request #973 <https://github.com/doctrine/doctrine2/pull/973>`_
|
||||
|
||||
SQLFilter API: Check if a parameter is set
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can now check in your SQLFilter if a parameter was set. This allows
|
||||
to more easily control which features of a filter to enable or disable.
|
||||
|
||||
Extending on the locale example of the documentation:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class MyLocaleFilter extends SQLFilter
|
||||
{
|
||||
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
|
||||
{
|
||||
if (!$targetEntity->reflClass->implementsInterface('LocaleAware')) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!$this->hasParameter('locale')) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return $targetTableAlias.'.locale = ' . $this->getParameter('locale');
|
||||
}
|
||||
}
|
||||
|
||||
This feature was contributed by `Miroslav Demovic <https://github.com/mdemo>`_
|
||||
|
||||
- `Pull Request #963 <https://github.com/doctrine/doctrine2/pull/963>`_
|
||||
|
||||
|
||||
EXTRA_LAZY Improvements
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
1. Efficient query when using EXTRA_LAZY and containsKey
|
||||
|
||||
When calling ``Collection::containsKey($key)`` on one-to-many and many-to-many
|
||||
collections using ``indexBy`` and ``EXTRA_LAZY`` a query is now executed to check
|
||||
for the existance for the item. Prevoiusly this operation was performed in memory
|
||||
by loading all entities of the collection.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
class User
|
||||
{
|
||||
/** @OneToMany(targetEntity="Group", indexBy="id") */
|
||||
private $groups;
|
||||
}
|
||||
|
||||
if ($user->getGroups()->containsKey($groupId)) {
|
||||
echo "User is in group $groupId\n";
|
||||
}
|
||||
|
||||
This feature was contributed by `Asmir Mustafic <https://github.com/goetas>`_
|
||||
|
||||
- `Pull Request #937 <https://github.com/doctrine/doctrine2/pull/937>`_
|
||||
|
||||
2. Add EXTRA_LAZY Support for get() for owning and inverse many-to-many
|
||||
|
||||
This was contributed by `Sander Marechal <https://github.com/sandermarechal>`_.
|
||||
|
||||
Improve efficiency of One-To-Many EAGER
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When marking a one-to-many association with ``fetch="EAGER"`` it will now
|
||||
execute one query less than before and work correctly in combination with
|
||||
``indexBy``.
|
||||
|
||||
Better support for EntityManagerInterface
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Many of the locations where previously only the ``Doctrine\ORM\EntityManager``
|
||||
was allowed are now changed to accept the ``EntityManagerInterface`` that was
|
||||
introduced in 2.4. This allows you to more easily use the decorator pattern
|
||||
to extend the EntityManager if you need. It's still not replaced everywhere,
|
||||
so you still have to be careful.
|
||||
|
||||
DQL Improvements
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
1. It is now possible to add functions to the ``ORDER BY`` clause in DQL statements:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$dql = "SELECT u FROM User u ORDER BY CONCAT(u.username, u.name)";
|
||||
|
||||
2. Support for functions in ``IS NULL`` expressions:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$dql = "SELECT u.name FROM User u WHERE MAX(u.name) IS NULL";
|
||||
|
||||
3. A ``LIKE`` expression is now suported in ``HAVING`` clause.
|
||||
|
||||
4. Subselects are now supported inside a ``NEW()`` expression:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$dql = "SELECT new UserDTO(u.name, SELECT count(g.id) FROM Group g WHERE g.id = u.id) FROM User u";
|
||||
|
||||
5. ``MEMBER OF`` expression now allows to filter for more than one result:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$dql = "SELECT u FROM User u WHERE :groups MEMBER OF u.groups";
|
||||
$query = $entityManager->createQuery($dql);
|
||||
$query->setParameter('groups', array(1, 2, 3));
|
||||
|
||||
$users = $query->getResult();
|
||||
|
||||
6. Expressions inside ``COUNT()`` now allowed
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$dql = "SELECT COUNT(DISTINCT CONCAT(u.name, u.lastname)) FROM User u";
|
||||
|
||||
7. Add support for ``HOUR`` in ``DATE_ADD()``/``DATE_SUB()`` functions
|
||||
|
||||
Custom DQL Functions: Add support for factories
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Previously custom DQL functions could only be provided with their
|
||||
full-qualified class-name, preventing runtime configuration through
|
||||
dependency injection.
|
||||
|
||||
A simplistic approach has been contributed by `Matthieu Napoli
|
||||
<https://github.com/mnapoli>`_ to pass a callback instead that resolves
|
||||
the function:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
|
||||
$config->addCustomNumericFunction(
|
||||
'IS_PUBLISHED', function($funcName) use ($currentSiteId) {
|
||||
return new IsPublishedFunction($currentSiteId);
|
||||
}
|
||||
);
|
||||
|
||||
Query API: WHERE IN Query using a Collection as parameter
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When performing a ``WHERE IN`` query for a collection of entities you can
|
||||
now pass the array collection of entities as a parameter value to the query
|
||||
object:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
$categories = $rootCategory->getChildren();
|
||||
|
||||
$queryBuilder
|
||||
->select('p')
|
||||
->from('Product', 'p')
|
||||
->where('p.category IN (:categories)')
|
||||
->setParameter('categories', $categories)
|
||||
;
|
||||
|
||||
This feature was contributed by `Michael Perrin
|
||||
<https://github.com/michaelperrin>`_.
|
||||
|
||||
- `Pull Request #590 <https://github.com/doctrine/doctrine2/pull/590>`_
|
||||
- `DDC-2319 <http://doctrine-project.org/jira/browse/DDC-2319>`_
|
||||
|
||||
Query API: Add support for default Query Hints
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To configure multiple different features such as custom AST Walker, fetch modes,
|
||||
locking and other features affecting DQL generation we have had a feature
|
||||
called "query hints" since version 2.0.
|
||||
|
||||
It is now possible to add query hints that are always enabled for every Query:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$config->setDefaultQueryHints(
|
||||
'doctrine.customOutputWalker' => 'MyProject\CustomOutputWalker'
|
||||
);
|
||||
|
||||
This feature was contributed by `Artur Eshenbrener
|
||||
<https://github.com/Strate>`_.
|
||||
|
||||
- `Pull Request #863 <https://github.com/doctrine/doctrine2/pull/863>`_
|
||||
|
||||
ResultSetMappingBuilder: Add support for Single-Table Inheritance
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Before 2.5 the ResultSetMappingBuilder did not work with entities
|
||||
that are using Single-Table-Inheritance. This restriction was lifted
|
||||
by adding the missing support.
|
||||
|
||||
YAML Mapping: Many-To-Many doesnt require join column definition
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In Annotations and XML it was not necessary using conventions for naming
|
||||
the many-to-many join column names, in YAML it was not possible however.
|
||||
|
||||
A many-to-many definition in YAML is now possible using this minimal
|
||||
definition:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
manyToMany:
|
||||
groups:
|
||||
targetEntity: Group
|
||||
joinTable:
|
||||
name: users_groups
|
||||
|
||||
Schema Validator Command: Allow to skip sub-checks
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The Schema Validator command executes two independent checks
|
||||
for validity of the mappings and if the schema is synchronized
|
||||
correctly. It is now possible to skip any of the two steps
|
||||
when executing the command:
|
||||
|
||||
::
|
||||
|
||||
$ php vendor/bin/doctrine orm:validate-schema --skip-mapping
|
||||
$ php vendor/bin/doctrine orm:validate-schema --skip-sync
|
||||
|
||||
This allows you to write more specialized continuous integration and automation
|
||||
checks. When no changes are found the command returns the exit code 0
|
||||
and 1, 2 or 3 when failing because of mapping, sync or both.
|
||||
|
||||
EntityGenerator Command: Avoid backups
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When calling the EntityGenerator for an existing entity, Doctrine would
|
||||
create a backup file every time to avoid losing changes to the code. You
|
||||
can now skip generating the backup file by passing the ``--no-backup``
|
||||
flag:
|
||||
|
||||
::
|
||||
|
||||
$ php vendor/bin/doctrine orm:generate-entities src/ --no-backup
|
||||
|
||||
Support for Objects as Identifiers
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
It is now possible to use Objects as identifiers for Entities
|
||||
as long as they implement the magic method ``__toString()``.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
class UserId
|
||||
{
|
||||
private $value;
|
||||
|
||||
public function __construct($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return (string)$this->value;
|
||||
}
|
||||
}
|
||||
|
||||
class User
|
||||
{
|
||||
/** @Id @Column(type="userid") */
|
||||
private $id;
|
||||
|
||||
public function __construct(UserId $id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
}
|
||||
|
||||
class UserIdType extends \Doctrine\DBAL\Types\Type
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
Doctrine\DBAL\Types\Type::addType('userid', 'MyProject\UserIdType');
|
||||
|
||||
Behavioral Changes (BC Breaks)
|
||||
------------------------------
|
||||
|
||||
NamingStrategy interface changed
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``Doctrine\ORM\Mapping\NamingStrategyInterface`` changed slightly
|
||||
to pass the Class Name of the entity into the join column name generation:
|
||||
|
||||
::
|
||||
|
||||
- function joinColumnName($propertyName);
|
||||
+ function joinColumnName($propertyName, $className = null);
|
||||
|
||||
It also received a new method for supporting embeddables:
|
||||
|
||||
::
|
||||
|
||||
public function embeddedFieldToColumnName($propertyName, $embeddedColumnName);
|
||||
|
||||
Minor BC BREAK: EntityManagerInterface instead of EntityManager in type-hints
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As of 2.5, classes requiring the ``EntityManager`` in any method signature will now require
|
||||
an ``EntityManagerInterface`` instead.
|
||||
If you are extending any of the following classes, then you need to check following
|
||||
signatures:
|
||||
|
||||
- ``Doctrine\ORM\Tools\DebugUnitOfWorkListener#dumpIdentityMap(EntityManagerInterface $em)``
|
||||
- ``Doctrine\ORM\Mapping\ClassMetadataFactory#setEntityManager(EntityManagerInterface $em)``
|
||||
|
||||
Minor BC BREAK: Custom Hydrators API change
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As of 2.5, ``AbstractHydrator`` does not enforce the usage of cache as part of
|
||||
API, and now provides you a clean API for column information through the method
|
||||
``hydrateColumnInfo($column)``.
|
||||
Cache variable being passed around by reference is no longer needed since
|
||||
Hydrators are per query instantiated since Doctrine 2.4.
|
||||
|
||||
- `DDC-3060 <http://doctrine-project.org/jira/browse/DDC-3060>`_
|
||||
|
||||
Minor BC BREAK: All non-transient classes in an inheritance must be part of the inheritance map
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As of 2.5, classes, if you define an inheritance map for an inheritance tree, you are required
|
||||
to map all non-transient classes in that inheritance, including the root of the inheritance.
|
||||
|
||||
So far, the root of the inheritance was allowed to be skipped in the inheritance map: this is
|
||||
not possible anymore, and if you don't plan to persist instances of that class, then you should
|
||||
either:
|
||||
|
||||
- make that class as ``abstract``
|
||||
- add that class to your inheritance map
|
||||
|
||||
If you fail to do so, then a ``Doctrine\ORM\Mapping\MappingException`` will be thrown.
|
||||
|
||||
|
||||
- `DDC-3300 <http://doctrine-project.org/jira/browse/DDC-3300>`_
|
||||
- `DDC-3503 <http://doctrine-project.org/jira/browse/DDC-3503>`_
|
||||
|
||||
Minor BC BREAK: Entity based EntityManager#clear() calls follow cascade detach
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Whenever ``EntityManager#clear()`` method gets called with a given entity class
|
||||
name, until 2.4, it was only detaching the specific requested entity.
|
||||
As of 2.5, ``EntityManager`` will follow configured cascades, providing a better
|
||||
memory management since associations will be garbage collected, optimizing
|
||||
resources consumption on long running jobs.
|
||||
|
||||
Updates on entities scheduled for deletion are no longer processed
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In Doctrine 2.4, if you modified properties of an entity scheduled for deletion, UnitOfWork would
|
||||
produce an ``UPDATE`` statement to be executed right before the ``DELETE`` statement. The entity in question
|
||||
was therefore present in ``UnitOfWork#entityUpdates``, which means that ``preUpdate`` and ``postUpdate``
|
||||
listeners were (quite pointlessly) called. In ``preFlush`` listeners, it used to be possible to undo
|
||||
the scheduled deletion for updated entities (by calling ``persist()`` if the entity was found in both
|
||||
``entityUpdates`` and ``entityDeletions``). This does not work any longer, because the entire changeset
|
||||
calculation logic is optimized away.
|
||||
|
||||
Minor BC BREAK: Default lock mode changed from LockMode::NONE to null in method signatures
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A misconception concerning default lock mode values in method signatures lead to unexpected behaviour
|
||||
in SQL statements on SQL Server. With a default lock mode of ``LockMode::NONE`` throughout the
|
||||
method signatures in ORM, the table lock hint ``WITH (NOLOCK)`` was appended to all locking related
|
||||
queries by default. This could result in unpredictable results because an explicit ``WITH (NOLOCK)``
|
||||
table hint tells SQL Server to run a specific query in transaction isolation level READ UNCOMMITTED
|
||||
instead of the default READ COMMITTED transaction isolation level.
|
||||
Therefore there now is a distinction between ``LockMode::NONE`` and ``null`` to be able to tell
|
||||
Doctrine whether to add table lock hints to queries by intention or not. To achieve this, the following
|
||||
method signatures have been changed to declare ``$lockMode = null`` instead of ``$lockMode = LockMode::NONE``:
|
||||
|
||||
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#getSelectSQL()``
|
||||
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#load()``
|
||||
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#refresh()``
|
||||
- ``Doctrine\ORM\Decorator\EntityManagerDecorator#find()``
|
||||
- ``Doctrine\ORM\EntityManager#find()``
|
||||
- ``Doctrine\ORM\EntityRepository#find()``
|
||||
- ``Doctrine\ORM\Persisters\BasicEntityPersister#getSelectSQL()``
|
||||
- ``Doctrine\ORM\Persisters\BasicEntityPersister#load()``
|
||||
- ``Doctrine\ORM\Persisters\BasicEntityPersister#refresh()``
|
||||
- ``Doctrine\ORM\Persisters\EntityPersister#getSelectSQL()``
|
||||
- ``Doctrine\ORM\Persisters\EntityPersister#load()``
|
||||
- ``Doctrine\ORM\Persisters\EntityPersister#refresh()``
|
||||
- ``Doctrine\ORM\Persisters\JoinedSubclassPersister#getSelectSQL()``
|
||||
|
||||
You should update signatures for these methods if you have subclassed one of the above classes.
|
||||
Please also check the calling code of these methods in your application and update if necessary.
|
||||
|
||||
.. note::
|
||||
|
||||
This in fact is really a minor BC BREAK and should not have any affect on database vendors
|
||||
other than SQL Server because it is the only one that supports and therefore cares about
|
||||
``LockMode::NONE``. It's really just a FIX for SQL Server environments using ORM.
|
||||
|
||||
Minor BC BREAK: __clone method not called anymore when entities are instantiated via metadata API
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As of PHP 5.6, instantiation of new entities is deferred to the
|
||||
`doctrine/instantiator <https://github.com/doctrine/instantiator>`_ library, which will avoid calling ``__clone``
|
||||
or any public API on instantiated objects.
|
||||
|
||||
BC BREAK: DefaultRepositoryFactory is now final
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Please implement the ``Doctrine\ORM\Repository\RepositoryFactory`` interface instead of extending
|
||||
the ``Doctrine\ORM\Repository\DefaultRepositoryFactory``.
|
||||
|
||||
BC BREAK: New object expression DQL queries now respects user provided aliasing and not return consumed fields
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When executing DQL queries with new object expressions, instead of returning
|
||||
DTOs numerically indexes, it will now respect user provided aliases. Consider
|
||||
the following query:
|
||||
|
||||
::
|
||||
|
||||
SELECT new UserDTO(u.id,u.name) as user,new AddressDTO(a.street,a.postalCode) as address, a.id as addressId
|
||||
FROM User u INNER JOIN u.addresses a WITH a.isPrimary = true
|
||||
|
||||
Previously, your result would be similar to this:
|
||||
|
||||
::
|
||||
|
||||
array(
|
||||
0=>array(
|
||||
0=>{UserDTO object},
|
||||
1=>{AddressDTO object},
|
||||
2=>{u.id scalar},
|
||||
3=>{u.name scalar},
|
||||
4=>{a.street scalar},
|
||||
5=>{a.postalCode scalar},
|
||||
'addressId'=>{a.id scalar},
|
||||
),
|
||||
...
|
||||
)
|
||||
|
||||
From now on, the resultset will look like this:
|
||||
|
||||
::
|
||||
|
||||
array(
|
||||
0=>array(
|
||||
'user'=>{UserDTO object},
|
||||
'address'=>{AddressDTO object},
|
||||
'addressId'=>{a.id scalar}
|
||||
),
|
||||
...
|
||||
)
|
||||
|
||||
@@ -11,9 +11,9 @@ There are several ways to achieve this: converting the value inside the Type
|
||||
class, converting the value on the database-level or a combination of both.
|
||||
|
||||
This article describes the third way by implementing the MySQL specific column
|
||||
type `Point <http://dev.mysql.com/doc/refman/5.5/en/gis-class-point.html>`_.
|
||||
type `Point <https://dev.mysql.com/doc/refman/8.0/en/gis-class-point.html>`_.
|
||||
|
||||
The ``Point`` type is part of the `Spatial extension <http://dev.mysql.com/doc/refman/5.5/en/spatial-extensions.html>`_
|
||||
The ``Point`` type is part of the `Spatial extension <https://dev.mysql.com/doc/refman/8.0/en/spatial-extensions.html>`_
|
||||
of MySQL and enables you to store a single location in a coordinate space by
|
||||
using x and y coordinates. You can use the Point type to store a
|
||||
longitude/latitude pair to represent a geographic location.
|
||||
@@ -192,9 +192,9 @@ object into a string representation before saving to the database (in the
|
||||
``convertToDatabaseValue`` method) and back into an object after fetching the
|
||||
value from the database (in the ``convertToPHPValue`` method).
|
||||
|
||||
The format of the string representation format is called `Well-known text (WKT)
|
||||
<http://en.wikipedia.org/wiki/Well-known_text>`_. The advantage of this format
|
||||
is, that it is both human readable and parsable by MySQL.
|
||||
The format of the string representation format is called
|
||||
`Well-known text (WKT) <http://en.wikipedia.org/wiki/Well-known_text>`_.
|
||||
The advantage of this format is, that it is both human readable and parsable by MySQL.
|
||||
|
||||
Internally, MySQL stores geometry values in a binary format that is not
|
||||
identical to the WKT format. So, we need to let MySQL transform the WKT
|
||||
@@ -204,8 +204,8 @@ This is where the ``convertToPHPValueSQL`` and ``convertToDatabaseValueSQL``
|
||||
methods come into play.
|
||||
|
||||
This methods wrap a sql expression (the WKT representation of the Point) into
|
||||
MySQL functions `PointFromText <http://dev.mysql.com/doc/refman/5.5/en/creating-spatial-values.html#function_pointfromtext>`_
|
||||
and `AsText <http://dev.mysql.com/doc/refman/5.5/en/functions-to-convert-geometries-between-formats.html#function_astext>`_
|
||||
MySQL functions `ST_PointFromText <https://dev.mysql.com/doc/refman/8.0/en/gis-wkt-functions.html#function_st-pointfromtext>`_
|
||||
and `ST_AsText <https://dev.mysql.com/doc/refman/8.0/en/gis-format-conversion-functions.html#function_st-astext>`_
|
||||
which convert WKT strings to and from the internal format of MySQL.
|
||||
|
||||
.. note::
|
||||
@@ -249,7 +249,7 @@ Example usage
|
||||
$em->clear();
|
||||
|
||||
// Fetch the Location object
|
||||
$query = $em->createQuery("SELECT l FROM Geo\Entity\Location WHERE l.address = '1600 Amphitheatre Parkway, Mountain View, CA'");
|
||||
$query = $em->createQuery("SELECT l FROM Geo\Entity\Location l WHERE l.address = '1600 Amphitheatre Parkway, Mountain View, CA'");
|
||||
$location = $query->getSingleResult();
|
||||
|
||||
/* @var Geo\ValueObject\Point */
|
||||
|
||||
@@ -322,7 +322,7 @@ The aggregate field ``Account::$balance`` is now -200, however the
|
||||
SUM over all entries amounts yields -400. A violation of our max
|
||||
credit rule.
|
||||
|
||||
You can use both optimistic or pessimistic locking to save-guard
|
||||
You can use both optimistic or pessimistic locking to safe-guard
|
||||
your aggregate fields against this kind of race-conditions. Reading
|
||||
Eric Evans DDD carefully he mentions that the "Aggregate Root"
|
||||
(Account in our example) needs a locking mechanism.
|
||||
|
||||
@@ -21,7 +21,7 @@ the :doc:`Native Query <../reference/native-sql>` chapter.
|
||||
The DQL Parser has hooks to register functions that can then be
|
||||
used in your DQL queries and transformed into SQL, allowing to
|
||||
extend Doctrines Query capabilities to the vendors strength. This
|
||||
post explains the Used-Defined Functions API (UDF) of the Dql
|
||||
post explains the User-Defined Functions API (UDF) of the Dql
|
||||
Parser and shows some examples to give you some hints how you would
|
||||
extend DQL.
|
||||
|
||||
@@ -70,7 +70,7 @@ methods, which are quite handy in my opinion:
|
||||
Date Diff
|
||||
---------
|
||||
|
||||
`Mysql's DateDiff function <http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_datediff>`_
|
||||
`Mysql's DateDiff function <https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_datediff>`_
|
||||
takes two dates as argument and calculates the difference in days
|
||||
with ``date1-date2``.
|
||||
|
||||
@@ -164,7 +164,7 @@ Date Add
|
||||
|
||||
Often useful it the ability to do some simple date calculations in
|
||||
your DQL query using
|
||||
`MySql's DATE\_ADD function <http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date-add>`_.
|
||||
`MySql's DATE_ADD function <https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_date-add>`_.
|
||||
|
||||
I'll skip the blah and show the code for this function:
|
||||
|
||||
@@ -246,6 +246,6 @@ vendor sql functions and extend the DQL languages scope.
|
||||
|
||||
Code for this Extension to DQL and other Doctrine Extensions can be
|
||||
found
|
||||
`in my Github DoctrineExtensions repository <http://github.com/beberlei/DoctrineExtensions>`_.
|
||||
`in the GitHub DoctrineExtensions repository <http://github.com/beberlei/DoctrineExtensions>`_.
|
||||
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ Serializing entity into the session
|
||||
-----------------------------------
|
||||
|
||||
Entities that are serialized into the session normally contain references to
|
||||
other entities as well. Think of the user entity has a reference to his
|
||||
other entities as well. Think of the user entity has a reference to their
|
||||
articles, groups, photos or many other different entities. If you serialize
|
||||
this object into the session then you don't want to serialize the related
|
||||
entities as well. This is why you should call ``EntityManager#detach()`` on this
|
||||
|
||||
@@ -11,8 +11,8 @@ However, it is quite easy to make use of these methods in a safe
|
||||
way by guarding the custom wakeup or clone code with an entity
|
||||
identity check, as demonstrated in the following sections.
|
||||
|
||||
Safely implementing \_\_wakeup
|
||||
------------------------------
|
||||
Safely implementing __wakeup
|
||||
----------------------------
|
||||
|
||||
To safely implement ``__wakeup``, simply enclose your
|
||||
implementation code in an identity check as follows:
|
||||
@@ -37,8 +37,8 @@ implementation code in an identity check as follows:
|
||||
//...
|
||||
}
|
||||
|
||||
Safely implementing \_\_clone
|
||||
-----------------------------
|
||||
Safely implementing __clone
|
||||
---------------------------
|
||||
|
||||
Safely implementing ``__clone`` is pretty much the same:
|
||||
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
Integrating with CodeIgniter
|
||||
============================
|
||||
|
||||
This is recipe for using Doctrine 2 in your
|
||||
`CodeIgniter <http://www.codeigniter.com>`_ framework.
|
||||
|
||||
.. note::
|
||||
|
||||
This might not work for all CodeIgniter versions and may require
|
||||
slight adjustments.
|
||||
|
||||
|
||||
Here is how to set it up:
|
||||
|
||||
Make a CodeIgniter library that is both a wrapper and a bootstrap
|
||||
for Doctrine 2.
|
||||
|
||||
Setting up the file structure
|
||||
-----------------------------
|
||||
|
||||
Here are the steps:
|
||||
|
||||
|
||||
- Add a php file to your system/application/libraries folder
|
||||
called Doctrine.php. This is going to be your wrapper/bootstrap for
|
||||
the D2 entity manager.
|
||||
- Put the Doctrine folder (the one that contains Common, DBAL, and
|
||||
ORM) inside that same libraries folder.
|
||||
- Your system/application/libraries folder now looks like this:
|
||||
|
||||
system/applications/libraries -Doctrine -Doctrine.php -index.html
|
||||
|
||||
- If you want, open your config/autoload.php file and autoload
|
||||
your Doctrine library.
|
||||
|
||||
<?php $autoload['libraries'] = array('doctrine');
|
||||
|
||||
|
||||
Creating your Doctrine CodeIgniter library
|
||||
------------------------------------------
|
||||
|
||||
Now, here is what your Doctrine.php file should look like.
|
||||
Customize it to your needs.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\Common\ClassLoader,
|
||||
Doctrine\ORM\Configuration,
|
||||
Doctrine\ORM\EntityManager,
|
||||
Doctrine\Common\Cache\ArrayCache,
|
||||
Doctrine\DBAL\Logging\EchoSQLLogger;
|
||||
|
||||
class Doctrine {
|
||||
|
||||
public $em = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// load database configuration from CodeIgniter
|
||||
require_once APPPATH.'config/database.php';
|
||||
|
||||
// Set up class loading. You could use different autoloaders, provided by your favorite framework,
|
||||
// if you want to.
|
||||
require_once APPPATH.'libraries/Doctrine/Common/ClassLoader.php';
|
||||
|
||||
$doctrineClassLoader = new ClassLoader('Doctrine', APPPATH.'libraries');
|
||||
$doctrineClassLoader->register();
|
||||
$entitiesClassLoader = new ClassLoader('models', rtrim(APPPATH, "/" ));
|
||||
$entitiesClassLoader->register();
|
||||
$proxiesClassLoader = new ClassLoader('Proxies', APPPATH.'models/proxies');
|
||||
$proxiesClassLoader->register();
|
||||
|
||||
// Set up caches
|
||||
$config = new Configuration;
|
||||
$cache = new ArrayCache;
|
||||
$config->setMetadataCacheImpl($cache);
|
||||
$driverImpl = $config->newDefaultAnnotationDriver(array(APPPATH.'models/Entities'));
|
||||
$config->setMetadataDriverImpl($driverImpl);
|
||||
$config->setQueryCacheImpl($cache);
|
||||
|
||||
$config->setQueryCacheImpl($cache);
|
||||
|
||||
// Proxy configuration
|
||||
$config->setProxyDir(APPPATH.'/models/proxies');
|
||||
$config->setProxyNamespace('Proxies');
|
||||
|
||||
// Set up logger
|
||||
$logger = new EchoSQLLogger;
|
||||
$config->setSQLLogger($logger);
|
||||
|
||||
$config->setAutoGenerateProxyClasses( TRUE );
|
||||
|
||||
// Database connection information
|
||||
$connectionOptions = array(
|
||||
'driver' => 'pdo_mysql',
|
||||
'user' => $db['default']['username'],
|
||||
'password' => $db['default']['password'],
|
||||
'host' => $db['default']['hostname'],
|
||||
'dbname' => $db['default']['database']
|
||||
);
|
||||
|
||||
// Create EntityManager
|
||||
$this->em = EntityManager::create($connectionOptions, $config);
|
||||
}
|
||||
}
|
||||
|
||||
Please note that this is a development configuration; for a
|
||||
production system you'll want to use a real caching system like
|
||||
APC, get rid of EchoSqlLogger, and turn off
|
||||
autoGenerateProxyClasses.
|
||||
|
||||
For more details, consult the
|
||||
`Doctrine 2 Configuration documentation <http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/configuration.html>`_.
|
||||
|
||||
Now to use it
|
||||
-------------
|
||||
|
||||
Whenever you need a reference to the entity manager inside one of
|
||||
your controllers, views, or models you can do this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$em = $this->doctrine->em;
|
||||
|
||||
That's all there is to it. Once you get the reference to your
|
||||
EntityManager do your Doctrine 2.0 voodoo as normal.
|
||||
|
||||
Note: If you do not choose to autoload the Doctrine library, you
|
||||
will need to put this line before you get a reference to it:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$this->load->library('doctrine');
|
||||
|
||||
Good luck!
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ Strategy-Pattern
|
||||
|
||||
This recipe will give you a short introduction on how to design
|
||||
similar entities without using expensive (i.e. slow) inheritance
|
||||
but with not more than \* the well-known strategy pattern \* event
|
||||
but with not more than *the well-known strategy pattern* event
|
||||
listeners
|
||||
|
||||
Scenario / Problem
|
||||
|
||||
@@ -25,8 +25,8 @@ the additional benefit of being able to re-use your validation in
|
||||
any other part of your domain.
|
||||
|
||||
Say we have an ``Order`` with several ``OrderLine`` instances. We
|
||||
never want to allow any customer to order for a larger sum than he
|
||||
is allowed to:
|
||||
never want to allow any customer to order for a larger sum than they
|
||||
are allowed to:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -134,4 +134,4 @@ instances. This was already discussed in the previous blog post on
|
||||
the Versionable extension, which requires another type of event
|
||||
called "onFlush".
|
||||
|
||||
Further readings: :doc:`Lifecycle Events <../reference/events>`
|
||||
Further readings: :ref:`reference-events-lifecycle-events`
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Working with DateTime Instances
|
||||
===============================
|
||||
|
||||
There are many nitty gritty details when working with PHPs DateTime instances. You have know their inner
|
||||
There are many nitty gritty details when working with PHPs DateTime instances. You have to know their inner
|
||||
workings pretty well not to make mistakes with date handling. This cookbook entry holds several
|
||||
interesting pieces of information on how to work with PHP DateTime instances in Doctrine 2.
|
||||
|
||||
@@ -90,7 +90,10 @@ the UTC time at the time of the booking and the timezone the event happened in.
|
||||
|
||||
class UTCDateTimeType extends DateTimeType
|
||||
{
|
||||
static private $utc;
|
||||
/**
|
||||
* @var \DateTimeZone
|
||||
*/
|
||||
private static $utc;
|
||||
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
@@ -110,7 +113,7 @@ the UTC time at the time of the booking and the timezone the event happened in.
|
||||
$converted = \DateTime::createFromFormat(
|
||||
$platform->getDateTimeFormatString(),
|
||||
$value,
|
||||
self::$utc ? self::$utc : self::$utc = new \DateTimeZone('UTC')
|
||||
self::getUtc()
|
||||
);
|
||||
|
||||
if (! $converted) {
|
||||
@@ -123,6 +126,11 @@ the UTC time at the time of the booking and the timezone the event happened in.
|
||||
|
||||
return $converted;
|
||||
}
|
||||
|
||||
private static function getUtc(): \DateTimeZone
|
||||
{
|
||||
return self::$utc ?: self::$utc = new \DateTimeZone('UTC');
|
||||
}
|
||||
}
|
||||
|
||||
This database type makes sure that every DateTime instance is always saved in UTC, relative
|
||||
|
||||
@@ -13,11 +13,11 @@ If this documentation is not helping to answer questions you have about
|
||||
Doctrine ORM don't panic. You can get help from different sources:
|
||||
|
||||
- There is a :doc:`FAQ <reference/faq>` with answers to frequent questions.
|
||||
- The `Doctrine Mailing List <http://groups.google.com/group/doctrine-user>`_
|
||||
- Internet Relay Chat (IRC) in #doctrine on Freenode
|
||||
- Report a bug on `JIRA <http://www.doctrine-project.org/jira>`_.
|
||||
- The `Doctrine Mailing List <https://groups.google.com/group/doctrine-user>`_
|
||||
- Slack chat room `#orm <https://www.doctrine-project.org/slack>`_
|
||||
- Report a bug on `GitHub <https://github.com/doctrine/orm/issues>`_.
|
||||
- On `Twitter <https://twitter.com/search/%23doctrine2>`_ with ``#doctrine2``
|
||||
- On `StackOverflow <http://stackoverflow.com/questions/tagged/doctrine2>`_
|
||||
- On `StackOverflow <https://stackoverflow.com/questions/tagged/doctrine-orm>`_
|
||||
|
||||
If you need more structure over the different topics you can browse the :doc:`table
|
||||
of contents <toc>`.
|
||||
@@ -72,9 +72,9 @@ Advanced Topics
|
||||
* :doc:`Transactions and Concurrency <reference/transactions-and-concurrency>`
|
||||
* :doc:`Filters <reference/filters>`
|
||||
* :doc:`NamingStrategy <reference/namingstrategy>`
|
||||
* :doc:`Improving Performance <reference/improving-performance>`
|
||||
* :doc:`Caching <reference/caching>`
|
||||
* :doc:`Partial Objects <reference/partial-objects>`
|
||||
* :doc:`Improving Performance <reference/improving-performance>`
|
||||
* :doc:`Caching <reference/caching>`
|
||||
* :doc:`Partial Objects <reference/partial-objects>`
|
||||
* :doc:`Change Tracking Policies <reference/change-tracking-policies>`
|
||||
* :doc:`Best Practices <reference/best-practices>`
|
||||
* :doc:`Metadata Drivers <reference/metadata-drivers>`
|
||||
@@ -95,7 +95,7 @@ Tutorials
|
||||
Changelogs
|
||||
----------
|
||||
|
||||
* :doc:`Migration to 2.5 <changelog/migration_2_5>`
|
||||
* `Upgrade <https://github.com/doctrine/doctrine2/blob/master/UPGRADE.md>`_
|
||||
|
||||
Cookbook
|
||||
--------
|
||||
@@ -103,7 +103,7 @@ Cookbook
|
||||
* **Patterns**:
|
||||
:doc:`Aggregate Fields <cookbook/aggregate-fields>` |
|
||||
:doc:`Decorator Pattern <cookbook/decorator-pattern>` |
|
||||
:doc:`Strategy Pattern <cookbook/strategy-cookbook-introduction>`
|
||||
:doc:`Strategy Pattern <cookbook/strategy-cookbook-introduction>`
|
||||
|
||||
* **DQL Extension Points**:
|
||||
:doc:`DQL Custom Walkers <cookbook/dql-custom-walkers>` |
|
||||
@@ -118,9 +118,6 @@ Cookbook
|
||||
:doc:`Entities in the Session <cookbook/entities-in-session>` |
|
||||
:doc:`Keeping your Modules independent <cookbook/resolve-target-entity-listener>`
|
||||
|
||||
* **Integration into Frameworks/Libraries**
|
||||
:doc:`CodeIgniter <cookbook/integrating-with-codeigniter>`
|
||||
|
||||
* **Hidden Gems**
|
||||
:doc:`Prefixing Table Name <cookbook/sql-table-prefixes>`
|
||||
|
||||
|
||||
@@ -292,7 +292,7 @@ instance of ``Doctrine\DBAL\Connection``. If an array is passed it
|
||||
is directly passed along to the DBAL Factory
|
||||
``Doctrine\DBAL\DriverManager::getConnection()``. The DBAL
|
||||
configuration is explained in the
|
||||
`DBAL section <./../../../../../projects/doctrine-dbal/en/latest/reference/configuration.html>`_.
|
||||
`DBAL section <https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/configuration.html>`_.
|
||||
|
||||
Proxy Objects
|
||||
-------------
|
||||
|
||||
@@ -99,7 +99,7 @@ Optional attributes:
|
||||
|
||||
- **length**: Used by the "string" type to determine its maximum
|
||||
length in the database. Doctrine does not validate the length of a
|
||||
string values for you.
|
||||
string value for you.
|
||||
|
||||
- **precision**: The precision for a decimal (exact numeric) column
|
||||
(applies only for decimal column), which is the maximum number of
|
||||
@@ -181,7 +181,7 @@ Examples:
|
||||
protected $initials;
|
||||
|
||||
/**
|
||||
* @Column(type="integer", name="login_count" nullable=false, options={"unsigned":true, "default":0})
|
||||
* @Column(type="integer", name="login_count", nullable=false, options={"unsigned":true, "default":0})
|
||||
*/
|
||||
protected $loginCount;
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ well.
|
||||
Requirements
|
||||
------------
|
||||
|
||||
Doctrine 2 requires a minimum of PHP 5.4. For greatly improved
|
||||
Doctrine 2 requires a minimum of PHP 7.1. For greatly improved
|
||||
performance it is also recommended that you use APC with PHP.
|
||||
|
||||
Doctrine 2 Packages
|
||||
|
||||
@@ -286,7 +286,7 @@ below.
|
||||
// ...
|
||||
|
||||
/**
|
||||
* One Student has One Student.
|
||||
* One Student has One Mentor.
|
||||
* @OneToOne(targetEntity="Student")
|
||||
* @JoinColumn(name="mentor_id", referencedColumnName="id")
|
||||
*/
|
||||
@@ -313,8 +313,8 @@ One-To-Many, Bidirectional
|
||||
--------------------------
|
||||
|
||||
A one-to-many association has to be bidirectional, unless you are using a
|
||||
join table. This is because the many side in a one-to-many association holds
|
||||
the foreign key, making it the owning side. Doctrine needs the many side
|
||||
join table. This is because the "many" side in a one-to-many association holds
|
||||
the foreign key, making it the owning side. Doctrine needs the "many" side
|
||||
defined in order to understand the association.
|
||||
|
||||
This bidirectional mapping requires the ``mappedBy`` attribute on the
|
||||
@@ -335,7 +335,7 @@ bidirectional many-to-one.
|
||||
{
|
||||
// ...
|
||||
/**
|
||||
* One Product has Many Features.
|
||||
* One product has many features. This is the inverse side.
|
||||
* @OneToMany(targetEntity="Feature", mappedBy="product")
|
||||
*/
|
||||
private $features;
|
||||
@@ -351,7 +351,7 @@ bidirectional many-to-one.
|
||||
{
|
||||
// ...
|
||||
/**
|
||||
* Many Features have One Product.
|
||||
* Many features have one product. This is the owning side.
|
||||
* @ManyToOne(targetEntity="Product", inversedBy="features")
|
||||
* @JoinColumn(name="product_id", referencedColumnName="id")
|
||||
*/
|
||||
|
||||
@@ -270,7 +270,7 @@ A cookbook article shows how to define :doc:`your own custom mapping types
|
||||
.. warning::
|
||||
|
||||
All Date types assume that you are exclusively using the default timezone
|
||||
set by `date_default_timezone_set() <http://docs.php.net/manual/en/function.date-default-timezone-set.php>`_
|
||||
set by `date_default_timezone_set() <http://php.net/manual/en/function.date-default-timezone-set.php>`_
|
||||
or by the php.ini configuration ``date.timezone``. Working with
|
||||
different timezones will cause troubles and unexpected behavior.
|
||||
|
||||
@@ -328,8 +328,8 @@ annotation.
|
||||
|
||||
In most cases using the automatic generator strategy (``@GeneratedValue``) is
|
||||
what you want. It defaults to the identifier generation mechanism your current
|
||||
database vendor prefers: AUTO_INCREMENT with MySQL, SERIAL with PostgreSQL,
|
||||
Sequences with Oracle and so on.
|
||||
database vendor prefers: AUTO_INCREMENT with MySQL, sequences with PostgreSQL
|
||||
and Oracle and so on.
|
||||
|
||||
Identifier Generation Strategies
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -16,6 +16,15 @@ especially what the strategies presented here provide help with.
|
||||
operations.
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
Having an SQL logger enabled when processing batches can have a serious impact on performance and resource usage.
|
||||
To avoid that you should disable it in the DBAL configuration:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$em->getConnection()->getConfiguration()->setSQLLogger(null);
|
||||
|
||||
Bulk Inserts
|
||||
------------
|
||||
|
||||
@@ -75,7 +84,7 @@ with the batching strategy that was already used for bulk inserts:
|
||||
|
||||
<?php
|
||||
$batchSize = 20;
|
||||
$i = 0;
|
||||
$i = 1;
|
||||
$q = $em->createQuery('select u from MyProject\Model\User u');
|
||||
$iterableResult = $q->iterate();
|
||||
foreach ($iterableResult as $row) {
|
||||
@@ -136,7 +145,7 @@ The following example shows how to do this:
|
||||
|
||||
<?php
|
||||
$batchSize = 20;
|
||||
$i = 0;
|
||||
$i = 1;
|
||||
$q = $em->createQuery('select u from MyProject\Model\User u');
|
||||
$iterableResult = $q->iterate();
|
||||
while (($row = $iterableResult->next()) !== false) {
|
||||
|
||||
@@ -29,10 +29,10 @@ abstract protected methods that each of the drivers must
|
||||
implement:
|
||||
|
||||
|
||||
- \_doFetch($id)
|
||||
- \_doContains($id)
|
||||
- \_doSave($id, $data, $lifeTime = false)
|
||||
- \_doDelete($id)
|
||||
- doFetch($id)
|
||||
- doContains($id)
|
||||
- doSave($id, $data, $lifeTime = false)
|
||||
- doDelete($id)
|
||||
|
||||
The public methods ``fetch()``, ``contains()`` etc. use the
|
||||
above protected methods which are implemented by the drivers. The
|
||||
@@ -43,43 +43,31 @@ these methods.
|
||||
|
||||
This documentation does not cover every single cache driver included
|
||||
with Doctrine. For an up-to-date-list, see the
|
||||
`cache directory on GitHub <https://github.com/doctrine/cache/tree/master/lib/Doctrine/Common/Cache>`.
|
||||
`cache directory on GitHub <https://github.com/doctrine/cache/tree/master/lib/Doctrine/Common/Cache>`_.
|
||||
|
||||
APC
|
||||
~~~
|
||||
PhpFileCache
|
||||
~~~~~~~~~~~~
|
||||
|
||||
In order to use the APC cache driver you must have it compiled and
|
||||
enabled in your php.ini. You can read about APC
|
||||
`in the PHP Documentation <http://us2.php.net/apc>`_. It will give
|
||||
you a little background information about what it is and how you
|
||||
can use it as well as how to install it.
|
||||
The preferred cache driver for metadata and query caches is ``PhpFileCache``.
|
||||
This driver serializes cache items and writes them to a file. This allows for
|
||||
opcode caching to be used and provides high performance in most scenarios.
|
||||
|
||||
Below is a simple example of how you could use the APC cache driver
|
||||
by itself.
|
||||
In order to use the ``PhpFileCache`` driver it must be able to write to
|
||||
a directory.
|
||||
|
||||
Below is an example of how to use the ``PhpFileCache`` driver by itself.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cacheDriver = new \Doctrine\Common\Cache\ApcCache();
|
||||
$cacheDriver = new \Doctrine\Common\Cache\PhpFileCache(
|
||||
'/path/to/writable/directory'
|
||||
);
|
||||
$cacheDriver->save('cache_id', 'my_data');
|
||||
|
||||
APCu
|
||||
~~~~
|
||||
|
||||
In order to use the APCu cache driver you must have it compiled and
|
||||
enabled in your php.ini. You can read about APCu
|
||||
`in the PHP Documentation <http://us2.php.net/apcu>`_. It will give
|
||||
you a little background information about what it is and how you
|
||||
can use it as well as how to install it.
|
||||
|
||||
Below is a simple example of how you could use the APCu cache driver
|
||||
by itself.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cacheDriver = new \Doctrine\Common\Cache\ApcuCache();
|
||||
$cacheDriver->save('cache_id', 'my_data');
|
||||
The PhpFileCache is not distributed across multiple machines if you are running
|
||||
your application in a distributed setup. This is ok for the metadata and query
|
||||
cache but is not a good approach for the result cache.
|
||||
|
||||
Memcache
|
||||
~~~~~~~~
|
||||
@@ -128,24 +116,6 @@ driver by itself.
|
||||
$cacheDriver->setMemcached($memcached);
|
||||
$cacheDriver->save('cache_id', 'my_data');
|
||||
|
||||
Xcache
|
||||
~~~~~~
|
||||
|
||||
In order to use the Xcache cache driver you must have it compiled
|
||||
and enabled in your php.ini. You can read about Xcache
|
||||
`here <http://xcache.lighttpd.net/>`_. It will give you a little
|
||||
background information about what it is and how you can use it as
|
||||
well as how to install it.
|
||||
|
||||
Below is a simple example of how you could use the Xcache cache
|
||||
driver by itself.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cacheDriver = new \Doctrine\Common\Cache\XcacheCache();
|
||||
$cacheDriver->save('cache_id', 'my_data');
|
||||
|
||||
Redis
|
||||
~~~~~
|
||||
|
||||
@@ -282,6 +252,8 @@ You can set the namespace a cache driver should use by using the
|
||||
<?php
|
||||
$cacheDriver->setNamespace('my_namespace_');
|
||||
|
||||
.. _integrating-with-the-orm:
|
||||
|
||||
Integrating with the ORM
|
||||
------------------------
|
||||
|
||||
@@ -304,8 +276,11 @@ use on your ORM configuration.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cacheDriver = new \Doctrine\Common\Cache\PhpFileCache(
|
||||
'/path/to/writable/directory'
|
||||
);
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$config->setQueryCacheImpl(new \Doctrine\Common\Cache\ApcuCache());
|
||||
$config->setQueryCacheImpl($cacheDriver);
|
||||
|
||||
Result Cache
|
||||
~~~~~~~~~~~~
|
||||
@@ -318,7 +293,11 @@ cache implementation.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config->setResultCacheImpl(new \Doctrine\Common\Cache\ApcuCache());
|
||||
$cacheDriver = new \Doctrine\Common\Cache\PhpFileCache(
|
||||
'/path/to/writable/directory'
|
||||
);
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$config->setResultCacheImpl($cacheDriver);
|
||||
|
||||
Now when you're executing DQL queries you can configure them to use
|
||||
the result cache.
|
||||
@@ -335,7 +314,11 @@ result cache driver.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query->setResultCacheDriver(new \Doctrine\Common\Cache\ApcuCache());
|
||||
$cacheDriver = new \Doctrine\Common\Cache\PhpFileCache(
|
||||
'/path/to/writable/directory'
|
||||
);
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$query->setResultCacheDriver($cacheDriver);
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -387,7 +370,11 @@ first.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcuCache());
|
||||
$cacheDriver = new \Doctrine\Common\Cache\PhpFileCache(
|
||||
'/path/to/writable/directory'
|
||||
);
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$config->setMetadataCacheImpl($cacheDriver);
|
||||
|
||||
Now the metadata information will only be parsed once and stored in
|
||||
the cache driver.
|
||||
@@ -423,6 +410,12 @@ To clear the result cache use the ``orm:clear-cache:result`` task.
|
||||
All these tasks accept a ``--flush`` option to flush the entire
|
||||
contents of the cache instead of invalidating the entries.
|
||||
|
||||
.. note::
|
||||
|
||||
None of these tasks will work with APC, APCu, or XCache drivers
|
||||
because the memory that the cache is stored in is only accessible
|
||||
to the webserver.
|
||||
|
||||
Cache Chaining
|
||||
--------------
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
Installation and Configuration
|
||||
==============================
|
||||
|
||||
Doctrine can be installed with `Composer <http://www.getcomposer.org>`_. For
|
||||
older versions we still have `PEAR packages
|
||||
<http://pear.doctrine-project.org>`_.
|
||||
Doctrine can be installed with `Composer <https://getcomposer.org>`_.
|
||||
|
||||
Define the following requirement in your ``composer.json`` file:
|
||||
|
||||
@@ -16,8 +14,7 @@ Define the following requirement in your ``composer.json`` file:
|
||||
}
|
||||
|
||||
Then call ``composer install`` from your command line. If you don't know
|
||||
how Composer works, check out their `Getting Started
|
||||
<http://getcomposer.org/doc/00-intro.md>`_ to set up.
|
||||
how Composer works, check out their `Getting Started <https://getcomposer.org/doc/00-intro.md>`_ to set up.
|
||||
|
||||
Class loading
|
||||
-------------
|
||||
@@ -93,8 +90,7 @@ Inside the ``Setup`` methods several assumptions are made:
|
||||
- If `$isDevMode` is false, set then proxy classes have to be explicitly created through the command line.
|
||||
- If third argument `$proxyDir` is not set, use the systems temporary directory.
|
||||
|
||||
If you want to configure Doctrine in more detail, take a look at the :doc:`Advanced
|
||||
Configuration <reference/advanced-configuration>` section.
|
||||
If you want to configure Doctrine in more detail, take a look at the :doc:`Advanced Configuration <reference/advanced-configuration>` section.
|
||||
|
||||
.. note::
|
||||
|
||||
|
||||
@@ -103,15 +103,15 @@ their inclusion in the SELECT clause.
|
||||
|
||||
In this case, the result will be an array of arrays. In the example
|
||||
above, each element of the result array would be an array of the
|
||||
scalar name and address values.
|
||||
scalar name and address values.
|
||||
|
||||
You can select scalars from any entity in the query.
|
||||
You can select scalars from any entity in the query.
|
||||
|
||||
**Mixed**
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
``SELECT u, p.quantity FROM Users u...``
|
||||
SELECT u, p.quantity FROM Users u...
|
||||
|
||||
Here, the result will again be an array of arrays, with each element
|
||||
being an array made up of a User object and the scalar value
|
||||
@@ -250,7 +250,7 @@ Retrieve the Username and Name of a CmsUser:
|
||||
$users = $query->getResult(); // array of CmsUser username and name values
|
||||
echo $users[0]['username'];
|
||||
|
||||
Retrieve a ForumUser and his single associated entity:
|
||||
Retrieve a ForumUser and its single associated entity:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -259,7 +259,7 @@ Retrieve a ForumUser and his single associated entity:
|
||||
$users = $query->getResult(); // array of ForumUser objects with the avatar association loaded
|
||||
echo get_class($users[0]->getAvatar());
|
||||
|
||||
Retrieve a CmsUser and fetch join all the phonenumbers he has:
|
||||
Retrieve a CmsUser and fetch join all the phonenumbers it has:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -691,8 +691,8 @@ clauses:
|
||||
- TRIM([LEADING \| TRAILING \| BOTH] ['trchar' FROM] str) - Trim
|
||||
the string by the given trim char, defaults to whitespaces.
|
||||
- UPPER(str) - Return the upper-case of the given string.
|
||||
- DATE_ADD(date, days, unit) - Add the number of days to a given date. (Supported units are DAY, MONTH)
|
||||
- DATE_SUB(date, days, unit) - Substract the number of days from a given date. (Supported units are DAY, MONTH)
|
||||
- DATE_ADD(date, value, unit) - Add the given time to a given date. (Supported units are SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR)
|
||||
- DATE_SUB(date, value, unit) - Subtract the given time from a given date. (Supported units are SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR)
|
||||
- DATE_DIFF(date1, date2) - Calculate the difference in days between date1-date2.
|
||||
|
||||
Arithmetic operators
|
||||
@@ -996,8 +996,9 @@ the Query class. Here they are:
|
||||
result contains more than one object, an ``NonUniqueResultException``
|
||||
is thrown. If the result contains no objects, an ``NoResultException``
|
||||
is thrown. The pure/mixed distinction does not apply.
|
||||
- ``Query#getOneOrNullResult()``: Retrieve a single object. If no
|
||||
object is found null will be returned.
|
||||
- ``Query#getOneOrNullResult()``: Retrieve a single object. If the
|
||||
result contains more than one object, a ``NonUniqueResultException``
|
||||
is thrown. If no object is found null will be returned.
|
||||
- ``Query#getArrayResult()``: Retrieves an array graph (a nested
|
||||
array) that is largely interchangeable with the object graph
|
||||
generated by ``Query#getResult()`` for read-only purposes.
|
||||
@@ -1172,7 +1173,7 @@ why we are listing as many of the assumptions here for reference:
|
||||
- If an object is already in memory from a previous query of any kind, then
|
||||
then the previous object is used, even if the database may contain more
|
||||
recent data. Data from the database is discarded. This even happens if the
|
||||
previous object is still an unloaded proxy.
|
||||
previous object is still an unloaded proxy.
|
||||
|
||||
This list might be incomplete.
|
||||
|
||||
@@ -1452,10 +1453,10 @@ Given that there are 10 users and corresponding addresses in the database the ex
|
||||
SELECT * FROM address WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
|
||||
|
||||
.. note::
|
||||
Changing the fetch mode during a query mostly makes sense for one-to-one and many-to-one relations. In that case,
|
||||
Changing the fetch mode during a query mostly makes sense for one-to-one and many-to-one relations. In that case,
|
||||
all the necessary IDs are available after the root entity (``user`` in the above example) has been loaded. So, one
|
||||
query per association can be executed to fetch all the referred-to entities (``address``).
|
||||
|
||||
|
||||
For one-to-many relations, changing the fetch mode to eager will cause to execute one query **for every root entity
|
||||
loaded**. This gives no improvement over the ``lazy`` fetch mode which will also initialize the associations on
|
||||
a one-by-one basis once they are accessed.
|
||||
|
||||
@@ -243,6 +243,11 @@ a relevant lifecycle event. More than one callback can be defined for each
|
||||
lifecycle event. Lifecycle Callbacks are best used for simple operations
|
||||
specific to a particular entity class's lifecycle.
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
Note that Licecycle Callbacks are not supported for Embeddables.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
@@ -323,7 +328,7 @@ XML would look something like this:
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
/Users/robo/dev/php/Doctrine/doctrine-mapping.xsd">
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="User">
|
||||
|
||||
@@ -548,8 +553,9 @@ preFlush
|
||||
~~~~~~~~
|
||||
|
||||
``preFlush`` is called at ``EntityManager#flush()`` before
|
||||
anything else. ``EntityManager#flush()`` can be called safely
|
||||
inside its listeners.
|
||||
anything else. ``EntityManager#flush()`` should not be called inside
|
||||
its listeners, since `preFlush` event is dispatched in it, which would
|
||||
result in infinite loop.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
|
||||
@@ -21,12 +21,6 @@ created database tables and columns.
|
||||
Entity Classes
|
||||
--------------
|
||||
|
||||
I access a variable and its null, what is wrong?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If this variable is a public variable then you are violating one of the criteria for entities.
|
||||
All properties have to be protected or private for the proxy object pattern to work.
|
||||
|
||||
How can I add default values to a column?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -204,6 +198,21 @@ No, it is not supported to sort by function in DQL. If you need this functionali
|
||||
use a native-query or come up with another solution. As a side note: Sorting with ORDER BY RAND() is painfully slow
|
||||
starting with 1000 rows.
|
||||
|
||||
Is it better to write DQL or to generate it with the query builder?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The purpose of the ``QueryBuilder`` is to generate DQL dynamically,
|
||||
which is useful when you have optional filters, conditional joins, etc.
|
||||
|
||||
But the ``QueryBuilder`` is not an alternative to DQL, it actually generates DQL
|
||||
queries at runtime, which are then interpreted by Doctrine. This means that
|
||||
using the ``QueryBuilder`` to build and run a query is actually always slower
|
||||
than only running the corresponding DQL query.
|
||||
|
||||
So if you only need to generate a query and bind parameters to it,
|
||||
you should use plain DQL, as this is a simpler and much more readable solution.
|
||||
You should only use the ``QueryBuilder`` when you can't achieve what you want to do with a DQL query.
|
||||
|
||||
A Query fails, how can I debug it?
|
||||
----------------------------------
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ proper quoting of parameters.
|
||||
|
||||
<?php
|
||||
namespace Example;
|
||||
use Doctrine\ORM\Mapping\ClassMetaData,
|
||||
use Doctrine\ORM\Mapping\ClassMetadata,
|
||||
Doctrine\ORM\Query\Filter\SQLFilter;
|
||||
|
||||
class MyLocaleFilter extends SQLFilter
|
||||
|
||||
@@ -4,7 +4,7 @@ Improving Performance
|
||||
Bytecode Cache
|
||||
--------------
|
||||
|
||||
It is highly recommended to make use of a bytecode cache like APC.
|
||||
It is highly recommended to make use of a bytecode cache like OPcache.
|
||||
A bytecode cache removes the need for parsing PHP code on every
|
||||
request and can greatly improve performance.
|
||||
|
||||
@@ -26,6 +26,8 @@ Doctrine will need to load your mapping information on every single
|
||||
request and has to parse each DQL query on every single request.
|
||||
This is a waste of resources.
|
||||
|
||||
See :ref:`integrating-with-the-orm`
|
||||
|
||||
Alternative Query Result Formats
|
||||
--------------------------------
|
||||
|
||||
@@ -42,6 +44,8 @@ for updates, which means when you call flush on the EntityManager these entities
|
||||
even if properties changed. Read-Only allows to persist new entities of a kind and remove existing
|
||||
ones, they are just not considered for updates.
|
||||
|
||||
See :ref:`annref_entity`
|
||||
|
||||
Extra-Lazy Collections
|
||||
----------------------
|
||||
|
||||
@@ -52,7 +56,7 @@ for more information on how this fetch mode works.
|
||||
Temporarily change fetch mode in DQL
|
||||
------------------------------------
|
||||
|
||||
See :ref:`Doctrine Query Language chapter <dql-temporarily-change-fetch-mode>`
|
||||
See :ref:`dql-temporarily-change-fetch-mode`
|
||||
|
||||
|
||||
Apply Best Practices
|
||||
@@ -61,6 +65,7 @@ Apply Best Practices
|
||||
A lot of the points mentioned in the Best Practices chapter will
|
||||
also positively affect the performance of Doctrine.
|
||||
|
||||
See :doc:`Best Practices <reference/best-practices>`
|
||||
|
||||
Change Tracking policies
|
||||
------------------------
|
||||
|
||||
@@ -174,7 +174,7 @@ SQL Schema considerations
|
||||
For Single-Table-Inheritance to work in scenarios where you are
|
||||
using either a legacy database schema or a self-written database
|
||||
schema you have to make sure that all columns that are not in the
|
||||
root entity but in any of the different sub-entities has to allows
|
||||
root entity but in any of the different sub-entities has to allow
|
||||
null values. Columns that have NOT NULL constraints have to be on
|
||||
the root entity of the single-table inheritance hierarchy.
|
||||
|
||||
@@ -493,7 +493,7 @@ Could be used by an entity that extends a mapped superclass to override a field
|
||||
* column=@Column(
|
||||
* name = "guest_id",
|
||||
* type = "integer",
|
||||
length = 140
|
||||
* length = 140
|
||||
* )
|
||||
* ),
|
||||
* @AttributeOverride(name="name",
|
||||
@@ -501,7 +501,7 @@ Could be used by an entity that extends a mapped superclass to override a field
|
||||
* name = "guest_name",
|
||||
* nullable = false,
|
||||
* unique = true,
|
||||
length = 240
|
||||
* length = 240
|
||||
* )
|
||||
* )
|
||||
* })
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
Installation
|
||||
============
|
||||
|
||||
The installation chapter has moved to `Installation and Configuration
|
||||
<reference/configuration>`_.
|
||||
The installation chapter has moved to :doc:`Installation and Configuration <reference/configuration>`_.
|
||||
|
||||
@@ -63,7 +63,7 @@ Where the ``attribute_name`` column contains the key and
|
||||
``$attributes``.
|
||||
|
||||
The feature request for persistence of primitive value arrays
|
||||
`is described in the DDC-298 ticket <http://www.doctrine-project.org/jira/browse/DDC-298>`_.
|
||||
`is described in the DDC-298 ticket <https://github.com/doctrine/orm/issues/3743>`_.
|
||||
|
||||
Cascade Merge with Bi-directional Associations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -71,8 +71,8 @@ Cascade Merge with Bi-directional Associations
|
||||
There are two bugs now that concern the use of cascade merge in combination with bi-directional associations.
|
||||
Make sure to study the behavior of cascade merge if you are using it:
|
||||
|
||||
- `DDC-875 <http://www.doctrine-project.org/jira/browse/DDC-875>`_ Merge can sometimes add the same entity twice into a collection
|
||||
- `DDC-763 <http://www.doctrine-project.org/jira/browse/DDC-763>`_ Cascade merge on associated entities can insert too many rows through "Persistence by Reachability"
|
||||
- `DDC-875 <https://github.com/doctrine/orm/issues/5398>`_ Merge can sometimes add the same entity twice into a collection
|
||||
- `DDC-763 <https://github.com/doctrine/orm/issues/5277>`_ Cascade merge on associated entities can insert too many rows through "Persistence by Reachability"
|
||||
|
||||
Custom Persisters
|
||||
~~~~~~~~~~~~~~~~~
|
||||
@@ -83,8 +83,8 @@ Currently there is no way to overwrite the persister implementation
|
||||
for a given entity, however there are several use-cases that can
|
||||
benefit from custom persister implementations:
|
||||
|
||||
- `Add Upsert Support <http://www.doctrine-project.org/jira/browse/DDC-668>`_
|
||||
- `Evaluate possible ways in which stored-procedures can be used <http://www.doctrine-project.org/jira/browse/DDC-445>`_
|
||||
- `Add Upsert Support <https://github.com/doctrine/orm/issues/5178>`_
|
||||
- `Evaluate possible ways in which stored-procedures can be used <https://github.com/doctrine/orm/issues/4946>`_
|
||||
|
||||
Persist Keys of Collections
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -94,7 +94,7 @@ PHP Arrays are ordered hash-maps and so should be the
|
||||
evaluate a feature that optionally persists and hydrates the keys
|
||||
of a Collection instance.
|
||||
|
||||
`Ticket DDC-213 <http://www.doctrine-project.org/jira/browse/DDC-213>`_
|
||||
`Ticket DDC-213 <https://github.com/doctrine/orm/issues/2817>`_
|
||||
|
||||
Mapping many tables to one entity
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -116,7 +116,6 @@ blog posts we have written on this topics:
|
||||
- `A re-usable Versionable behavior for Doctrine2 <http://www.doctrine-project.org/2010/02/24/doctrine2-versionable.html>`_
|
||||
- `Write your own ORM on top of Doctrine2 <http://www.doctrine-project.org/2010/07/19/your-own-orm-doctrine2.html>`_
|
||||
- `Doctrine 2 Behavioral Extensions <http://www.doctrine-project.org/2010/11/18/doctrine2-behavioral-extensions.html>`_
|
||||
- `Doctrator <https://github.com/pablodip/doctrator`>_
|
||||
|
||||
Doctrine 2 has enough hooks and extension points so that **you** can
|
||||
add whatever you want on top of it. None of this will ever become
|
||||
@@ -144,9 +143,7 @@ backwards compatibility issues or where no simple fix exists (yet).
|
||||
We don't plan to add every bug in the tracker there, just those
|
||||
issues that can potentially cause nightmares or pain of any sort.
|
||||
|
||||
See the Open Bugs on Jira for more details on `bugs, improvement and feature
|
||||
requests
|
||||
<http://www.doctrine-project.org/jira/secure/IssueNavigator.jspa?reset=true&mode=hide&pid=10032&resolution=-1&sorter/field=updated&sorter/order=DESC>`_.
|
||||
See bugs, improvement and feature requests on `Github issues <https://github.com/doctrine/orm/issues>`_.
|
||||
|
||||
Identifier Quoting and Legacy Databases
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -4,9 +4,13 @@ Implementing a NamingStrategy
|
||||
.. versionadded:: 2.3
|
||||
|
||||
Using a naming strategy you can provide rules for generating database identifiers,
|
||||
column or table names when the column or table name is not given. This feature helps
|
||||
column or table names. This feature helps
|
||||
reduce the verbosity of the mapping document, eliminating repetitive noise (eg: ``TABLE_``).
|
||||
|
||||
.. warning
|
||||
|
||||
The naming strategy is always overridden by entity mapping such as the `Table` annotation.
|
||||
|
||||
Configuring a naming strategy
|
||||
-----------------------------
|
||||
The default strategy used by Doctrine is quite minimal.
|
||||
|
||||
@@ -92,7 +92,7 @@ a mapping from DQL alias (key) to SQL alias (value)
|
||||
|
||||
<?php
|
||||
|
||||
$selectClause = $builder->generateSelectClause(array(
|
||||
$selectClause = $rsm->generateSelectClause(array(
|
||||
'u' => 't1',
|
||||
'g' => 't2'
|
||||
));
|
||||
|
||||
@@ -9,6 +9,12 @@ programmatically build queries, and also provides a fluent API.
|
||||
This means that you can change between one methodology to the other
|
||||
as you want, or just pick a preferred one.
|
||||
|
||||
.. note::
|
||||
|
||||
The ``QueryBuilder`` is not an abstraction of DQL, but merely a tool to dynamically build it.
|
||||
You should still use plain DQL when you can, as it is simpler and more readable.
|
||||
More about this in the :doc:`FAQ <faq>`_.
|
||||
|
||||
Constructing a new QueryBuilder object
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -80,7 +86,7 @@ Working with QueryBuilder
|
||||
High level API methods
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To simplify even more the way you build a query in Doctrine, you can take
|
||||
The most straightforward way to build a dynamic query with the ``QueryBuilder`` is by taking
|
||||
advantage of Helper methods. For all base code, there is a set of
|
||||
useful methods to simplify a programmer's life. To illustrate how
|
||||
to work with them, here is the same example 6 re-written using
|
||||
@@ -97,10 +103,9 @@ to work with them, here is the same example 6 re-written using
|
||||
->orderBy('u.name', 'ASC');
|
||||
|
||||
``QueryBuilder`` helper methods are considered the standard way to
|
||||
build DQL queries. Although it is supported, using string-based
|
||||
queries should be avoided. You are greatly encouraged to use
|
||||
``$qb->expr()->*`` methods. Here is a converted example 8 to
|
||||
suggested standard way to build queries:
|
||||
use the ``QueryBuilder``. The ``$qb->expr()->*`` methods can help you
|
||||
build conditional expressions dynamically. Here is a converted example 8 to
|
||||
suggested way to build queries with dynamic conditions:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -502,7 +507,7 @@ complete list of supported helper methods available:
|
||||
Adding a Criteria to a Query
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can also add a :ref:`Criteria <filtering-collections>` to a QueryBuilder by
|
||||
You can also add a :ref:`filtering-collections` to a QueryBuilder by
|
||||
using ``addCriteria``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -97,7 +97,7 @@ Defines a contract for accessing a particular region.
|
||||
|
||||
Defines a contract for accessing a particular cache region.
|
||||
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.Region.html>`_.
|
||||
`See API Doc <https://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/Region.html>`_.
|
||||
|
||||
Concurrent cache region
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -111,7 +111,7 @@ If you want to use an ``READ_WRITE`` cache, you should consider providing your o
|
||||
|
||||
Defines contract for concurrently managed data region.
|
||||
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.ConcurrentRegion.html>`_.
|
||||
`See API Doc <https://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/ConcurrentRegion.html>`_.
|
||||
|
||||
Timestamp region
|
||||
~~~~~~~~~~~~~~~~
|
||||
@@ -120,7 +120,7 @@ Timestamp region
|
||||
|
||||
Tracks the timestamps of the most recent updates to particular entity.
|
||||
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.TimestampRegion.html>`_.
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/TimestampRegion.html>`_.
|
||||
|
||||
.. _reference-second-level-cache-mode:
|
||||
|
||||
@@ -209,7 +209,7 @@ It allows you to provide a specific implementation of the following components :
|
||||
* ``EntityHydrator`` Transform an entity into a cache entry and cache entry into entities
|
||||
* ``CollectionHydrator`` Transform a collection into a cache entry and cache entry into collection
|
||||
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.DefaultCacheFactory.html>`_.
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/DefaultCacheFactory.html>`_.
|
||||
|
||||
Region Lifetime
|
||||
~~~~~~~~~~~~~~~
|
||||
@@ -270,7 +270,7 @@ By providing a cache logger you should be able to get information about all cach
|
||||
If you want to get more information you should implement ``\Doctrine\ORM\Cache\Logging\CacheLogger``.
|
||||
and collect all information you want.
|
||||
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.CacheLogger.html>`_.
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/Logging/CacheLogger.html>`_.
|
||||
|
||||
|
||||
Entity cache definition
|
||||
@@ -310,7 +310,7 @@ Entity cache definition
|
||||
.. code-block:: xml
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
<entity name="Country">
|
||||
<cache usage="READ_ONLY" region="my_entity_region" />
|
||||
<id name="id" type="integer" column="id">
|
||||
@@ -386,7 +386,7 @@ It caches the primary keys of association and cache each element will be cached
|
||||
.. code-block:: xml
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
<entity name="State">
|
||||
|
||||
<cache usage="NONSTRICT_READ_WRITE" />
|
||||
|
||||
@@ -10,7 +10,7 @@ we cannot protect you from SQL injection.
|
||||
Please also read the documentation chapter on Security in Doctrine DBAL. This
|
||||
page only handles Security issues in the ORM.
|
||||
|
||||
- [DBAL Security Page](https://github.com/doctrine/dbal/blob/master/docs/en/reference/security.rst)
|
||||
- `DBAL Security Page <http://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/security.html>`
|
||||
|
||||
If you find a Security bug in Doctrine, please report it on Jira and change the
|
||||
Security Level to "Security Issues". It will be visible to Doctrine Core
|
||||
@@ -119,7 +119,7 @@ entity might look like this:
|
||||
}
|
||||
|
||||
Now the possiblity of mass-asignment exists on this entity and can
|
||||
be exploitet by attackers to set the "isAdmin" flag to true on any
|
||||
be exploited by attackers to set the "isAdmin" flag to true on any
|
||||
object when you pass the whole request data to this method like:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -252,15 +252,6 @@ will output the SQL for the ran operation.
|
||||
Before using the orm:schema-tool commands, remember to configure
|
||||
your cli-config.php properly.
|
||||
|
||||
.. note::
|
||||
|
||||
When using the Annotation Mapping Driver you have to either setup
|
||||
your autoloader in the cli-config.php correctly to find all the
|
||||
entities, or you can use the second argument of the
|
||||
``EntityManagerHelper`` to specify all the paths of your entities
|
||||
(or mapping files), i.e.
|
||||
``new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em, $mappingPaths);``
|
||||
|
||||
Entity Generation
|
||||
-----------------
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ side of the association and these 2 references both represent the
|
||||
same association but can change independently of one another. Of
|
||||
course, in a correct application the semantics of the bidirectional
|
||||
association are properly maintained by the application developer
|
||||
(that's his responsibility). Doctrine needs to know which of these
|
||||
(that's their responsibility). Doctrine needs to know which of these
|
||||
two in-memory references is the one that should be persisted and
|
||||
which not. This is what the owning/inverse concept is mainly used
|
||||
for.
|
||||
|
||||
@@ -148,15 +148,15 @@ Hydration
|
||||
~~~~~~~~~
|
||||
|
||||
Responsible for creating a final result from a raw database statement and a
|
||||
result-set mapping object. The developer can choose which kind of result he
|
||||
wishes to be hydrated. Default result-types include:
|
||||
result-set mapping object. The developer can choose which kind of result they
|
||||
wish to be hydrated. Default result-types include:
|
||||
|
||||
- SQL to Entities
|
||||
- SQL to structured Arrays
|
||||
- SQL to simple scalar result arrays
|
||||
- SQL to a single result variable
|
||||
|
||||
Hydration to entities and arrays is one of most complex parts of Doctrine
|
||||
Hydration to entities and arrays is one of the most complex parts of Doctrine
|
||||
algorithm-wise. It can build results with for example:
|
||||
|
||||
- Single table selects
|
||||
|
||||
@@ -15,7 +15,7 @@ with associations in Doctrine:
|
||||
removed, not the entity itself. A collection of entities always
|
||||
only represents the association to the containing entities, not the
|
||||
entity itself.
|
||||
- When a bidirectional assocation is updated, Doctrine only checks
|
||||
- When a bidirectional association is updated, Doctrine only checks
|
||||
on one of both sides for these changes. This is called the :doc:`owning side <unitofwork-associations>`
|
||||
of the association.
|
||||
- A property with a reference to many entities has to be instances of the
|
||||
@@ -716,6 +716,7 @@ methods:
|
||||
* ``in($field, array $values)``
|
||||
* ``notIn($field, array $values)``
|
||||
* ``contains($field, $value)``
|
||||
* ``memberOf($value, $field)``
|
||||
* ``startsWith($field, $value)``
|
||||
* ``endsWith($field, $value)``
|
||||
|
||||
|
||||
@@ -25,6 +25,13 @@ Work that have not yet been persisted are lost.
|
||||
Not calling ``EntityManager#flush()`` will lead to all changes
|
||||
during that request being lost.
|
||||
|
||||
.. note::
|
||||
|
||||
Doctrine does NEVER touch the public API of methods in your entity
|
||||
classes (like getters and setters) nor the constructor method.
|
||||
Instead, it uses reflection to get/set data from/to your entity objects.
|
||||
When Doctrine fetches data from DB and saves it back,
|
||||
any code put in your get/set methods won't be implicitly taken into account.
|
||||
|
||||
Entities and the Identity Map
|
||||
-----------------------------
|
||||
@@ -238,7 +245,7 @@ as follows:
|
||||
persist operation. However, the persist operation is cascaded to
|
||||
entities referenced by X, if the relationships from X to these
|
||||
other entities are mapped with cascade=PERSIST or cascade=ALL (see
|
||||
":ref:`Transitive Persistence <transitive-persistence>`").
|
||||
":ref:`transitive-persistence`").
|
||||
- If X is a removed entity, it becomes managed.
|
||||
- If X is a detached entity, an exception will be thrown on
|
||||
flush.
|
||||
@@ -279,12 +286,12 @@ as follows:
|
||||
- If X is a new entity, it is ignored by the remove operation.
|
||||
However, the remove operation is cascaded to entities referenced by
|
||||
X, if the relationship from X to these other entities is mapped
|
||||
with cascade=REMOVE or cascade=ALL (see ":ref:`Transitive Persistence <transitive-persistence>`").
|
||||
with cascade=REMOVE or cascade=ALL (see ":ref:`transitive-persistence`").
|
||||
- If X is a managed entity, the remove operation causes it to
|
||||
become removed. The remove operation is cascaded to entities
|
||||
referenced by X, if the relationships from X to these other
|
||||
entities is mapped with cascade=REMOVE or cascade=ALL (see
|
||||
":ref:`Transitive Persistence <transitive-persistence>`").
|
||||
":ref:`transitive-persistence`").
|
||||
- If X is a detached entity, an InvalidArgumentException will be
|
||||
thrown.
|
||||
- If X is a removed entity, it is ignored by the remove operation.
|
||||
@@ -350,14 +357,14 @@ as follows:
|
||||
become detached. The detach operation is cascaded to entities
|
||||
referenced by X, if the relationships from X to these other
|
||||
entities is mapped with cascade=DETACH or cascade=ALL (see
|
||||
":ref:`Transitive Persistence <transitive-persistence>`"). Entities which previously referenced X
|
||||
":ref:`transitive-persistence`"). Entities which previously referenced X
|
||||
will continue to reference X.
|
||||
- If X is a new or detached entity, it is ignored by the detach
|
||||
operation.
|
||||
- If X is a removed entity, the detach operation is cascaded to
|
||||
entities referenced by X, if the relationships from X to these
|
||||
other entities is mapped with cascade=DETACH or cascade=ALL (see
|
||||
":ref:`Transitive Persistence <transitive-persistence>`"). Entities which previously referenced X
|
||||
":ref:`transitive-persistence`"). Entities which previously referenced X
|
||||
will continue to reference X.
|
||||
|
||||
There are several situations in which an entity is detached
|
||||
@@ -416,7 +423,7 @@ as follows:
|
||||
- If X is a managed entity, it is ignored by the merge operation,
|
||||
however, the merge operation is cascaded to entities referenced by
|
||||
relationships from X if these relationships have been mapped with
|
||||
the cascade element value MERGE or ALL (see ":ref:`Transitive Persistence <transitive-persistence>`").
|
||||
the cascade element value MERGE or ALL (see ":ref:`transitive-persistence`").
|
||||
- For all entities Y referenced by relationships from X having the
|
||||
cascade element value MERGE or ALL, Y is merged recursively as Y'.
|
||||
For all such Y referenced by X, X' is set to reference Y'. (Note
|
||||
@@ -793,7 +800,9 @@ DQL and its syntax as well as the Doctrine class can be found in
|
||||
:doc:`the dedicated chapter <dql-doctrine-query-language>`.
|
||||
For programmatically building up queries based on conditions that
|
||||
are only known at runtime, Doctrine provides the special
|
||||
``Doctrine\ORM\QueryBuilder`` class. More information on
|
||||
``Doctrine\ORM\QueryBuilder`` class. While this a powerful tool,
|
||||
it also brings more complexity to your code compared to plain DQL,
|
||||
so you should only use it when you need it. More information on
|
||||
constructing queries with a QueryBuilder can be found
|
||||
:doc:`in Query Builder chapter <query-builder>`.
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ form of XML documents.
|
||||
The XML driver is backed by an XML Schema document that describes
|
||||
the structure of a mapping document. The most recent version of the
|
||||
XML Schema document is available online at
|
||||
`http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd <http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd>`_.
|
||||
`https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd <https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd>`_.
|
||||
In order to point to the latest version of the document of a
|
||||
particular stable release branch, just append the release number,
|
||||
i.e.: doctrine-mapping-2.0.xsd The most convenient way to work with
|
||||
@@ -21,7 +21,7 @@ setup for the latest code in trunk.
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
...
|
||||
|
||||
@@ -107,7 +107,7 @@ of several common elements:
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Doctrine\Tests\ORM\Mapping\User" table="cms_users">
|
||||
|
||||
@@ -768,7 +768,7 @@ entity relationship. You can define this in XML with the "association-key" attri
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Application\Model\ArticleAttribute">
|
||||
<id name="article" association-key="true" />
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
YAML Mapping
|
||||
============
|
||||
|
||||
.. note::
|
||||
The YAML driver is deprecated and will be removed in version 3.0.
|
||||
It is strongly recommended to switch to one of the other mappings.
|
||||
|
||||
The YAML mapping driver enables you to provide the ORM metadata in
|
||||
form of YAML documents.
|
||||
|
||||
|
||||
83
docs/en/sidebar.rst
Normal file
83
docs/en/sidebar.rst
Normal file
@@ -0,0 +1,83 @@
|
||||
.. toc::
|
||||
|
||||
.. tocheader:: Tutorials
|
||||
|
||||
.. toctree::
|
||||
:depth: 3
|
||||
|
||||
tutorials/getting-started
|
||||
tutorials/getting-started-database
|
||||
tutorials/getting-started-models
|
||||
tutorials/working-with-indexed-associations
|
||||
tutorials/extra-lazy-associations
|
||||
tutorials/composite-primary-keys
|
||||
tutorials/ordered-associations
|
||||
tutorials/override-field-association-mappings-in-subclasses
|
||||
tutorials/pagination
|
||||
tutorials/embeddables
|
||||
|
||||
.. toc::
|
||||
|
||||
.. tocheader:: Reference
|
||||
|
||||
.. toctree::
|
||||
:depth: 3
|
||||
|
||||
reference/architecture
|
||||
reference/configuration
|
||||
reference/faq
|
||||
reference/basic-mapping
|
||||
reference/association-mapping
|
||||
reference/inheritance-mapping
|
||||
reference/working-with-objects
|
||||
reference/working-with-associations
|
||||
reference/events
|
||||
reference/unitofwork
|
||||
reference/unitofwork-associations
|
||||
reference/transactions-and-concurrency
|
||||
reference/batch-processing
|
||||
reference/dql-doctrine-query-language
|
||||
reference/query-builder
|
||||
reference/native-sql
|
||||
reference/change-tracking-policies
|
||||
reference/partial-objects
|
||||
reference/xml-mapping
|
||||
reference/yaml-mapping
|
||||
reference/annotations-reference
|
||||
reference/php-mapping
|
||||
reference/caching
|
||||
reference/improving-performance
|
||||
reference/tools
|
||||
reference/metadata-drivers
|
||||
reference/best-practices
|
||||
reference/limitations-and-known-issues
|
||||
tutorials/pagination
|
||||
reference/filters
|
||||
reference/namingstrategy
|
||||
reference/advanced-configuration
|
||||
reference/second-level-cache
|
||||
reference/security
|
||||
|
||||
.. toc::
|
||||
|
||||
.. tocheader:: Cookbook
|
||||
|
||||
.. toctree::
|
||||
:depth: 3
|
||||
|
||||
cookbook/aggregate-fields
|
||||
cookbook/custom-mapping-types
|
||||
cookbook/decorator-pattern
|
||||
cookbook/dql-custom-walkers
|
||||
cookbook/dql-user-defined-functions
|
||||
cookbook/implementing-arrayaccess-for-domain-objects
|
||||
cookbook/implementing-the-notify-changetracking-policy
|
||||
cookbook/implementing-wakeup-or-clone
|
||||
cookbook/resolve-target-entity-listener
|
||||
cookbook/sql-table-prefixes
|
||||
cookbook/strategy-cookbook-introduction
|
||||
cookbook/validation-of-entities
|
||||
cookbook/working-with-datetime
|
||||
cookbook/mysql-enums
|
||||
cookbook/advanced-field-value-conversion-using-custom-mapping-types
|
||||
cookbook/entities-in-session
|
||||
@@ -75,7 +75,6 @@ Cookbook
|
||||
cookbook/implementing-arrayaccess-for-domain-objects
|
||||
cookbook/implementing-the-notify-changetracking-policy
|
||||
cookbook/implementing-wakeup-or-clone
|
||||
cookbook/integrating-with-codeigniter
|
||||
cookbook/resolve-target-entity-listener
|
||||
cookbook/sql-table-prefixes
|
||||
cookbook/strategy-cookbook-introduction
|
||||
|
||||
@@ -63,7 +63,7 @@ and year of production as primary keys:
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="VehicleCatalogue\Model\Car">
|
||||
<id field="name" type="string" />
|
||||
@@ -203,7 +203,7 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Application\Model\ArticleAttribute">
|
||||
<id name="article" association-key="true" />
|
||||
|
||||
@@ -8,7 +8,9 @@ or address are the primary use case for this feature.
|
||||
|
||||
.. note::
|
||||
|
||||
Embeddables can only contain properties with basic ``@Column`` mapping.
|
||||
Embeddables can not contain references to entities. They can however compose
|
||||
other embeddables in addition to holding properties with basic ``@Column``
|
||||
mapping.
|
||||
|
||||
For the purposes of this tutorial, we will assume that you have a ``User``
|
||||
class in your application and you would like to store an address in
|
||||
|
||||
@@ -35,6 +35,15 @@ With extra lazy collections you can now not only add entities to large collectio
|
||||
easily using a combination of ``count`` and ``slice``.
|
||||
|
||||
|
||||
.. warning::
|
||||
|
||||
``removeElement`` directly issued DELETE queries to the database from
|
||||
version 2.4.0 to 2.7.0. This circumvents the flush operation and might run
|
||||
outside a transactional boundary if you don't create one yourself. We
|
||||
consider this a critical bug in the assumptio of how the ORM works and
|
||||
reverted ``removeElement`` EXTRA_LAZY behavior in 2.7.1.
|
||||
|
||||
|
||||
Enabling Extra-Lazy Associations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -65,7 +74,7 @@ switch to extra lazy as shown in these examples:
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Doctrine\Tests\Models\CMS\CmsGroup">
|
||||
<!-- ... -->
|
||||
|
||||
@@ -7,7 +7,7 @@ Getting Started: Database First
|
||||
start with developing Objects and then map them onto your database. When
|
||||
you :doc:`Model First <getting-started-models>`, you are modelling your application using tools (for
|
||||
example UML) and generate database schema and PHP code from this model.
|
||||
When you have a :doc:`Database First <getting-started-database>`, you already have a database schema
|
||||
When you have a Database First, you already have a database schema
|
||||
and generate the corresponding PHP code from it.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -5,7 +5,7 @@ Getting Started: Model First
|
||||
|
||||
When you :doc:`Code First <getting-started>`, you
|
||||
start with developing Objects and then map them onto your database. When
|
||||
you :doc:`Model First <getting-started-models>`, you are modelling your application using tools (for
|
||||
you Model First, you are modelling your application using tools (for
|
||||
example UML) and generate database schema and PHP code from this model.
|
||||
When you have a :doc:`Database First <getting-started-database>`, then you already have a database schema
|
||||
and generate the corresponding PHP code from it.
|
||||
|
||||
@@ -19,21 +19,20 @@ installed:
|
||||
|
||||
- PHP (latest stable version)
|
||||
- Composer Package Manager (`Install Composer
|
||||
<http://getcomposer.org/doc/00-intro.md>`_)
|
||||
<https://getcomposer.org/doc/00-intro.md>`_)
|
||||
|
||||
The code of this tutorial is `available on Github <https://github.com/doctrine/doctrine2-orm-tutorial>`_.
|
||||
|
||||
.. note::
|
||||
|
||||
This tutorial assumes you work with **Doctrine 2.4** and above.
|
||||
This tutorial assumes you work with **Doctrine 2.6** and above.
|
||||
Some of the code will not work with lower versions.
|
||||
|
||||
What is Doctrine?
|
||||
-----------------
|
||||
|
||||
Doctrine 2 is an `object-relational mapper (ORM)
|
||||
<http://en.wikipedia.org/wiki/Object-relational_mapping>`_ for PHP 5.4+ that
|
||||
provides transparent persistence for PHP objects. It uses the Data Mapper
|
||||
Doctrine 2 is an `object-relational mapper (ORM) <https://en.wikipedia.org/wiki/Object-relational_mapping>`_
|
||||
for PHP 7.1+ that provides transparent persistence for PHP objects. It uses the Data Mapper
|
||||
pattern at the heart, aiming for a complete separation of your domain/business
|
||||
logic from the persistence in a relational database management system.
|
||||
|
||||
@@ -62,7 +61,7 @@ An Example Model: Bug Tracker
|
||||
|
||||
For this Getting Started Guide for Doctrine we will implement the
|
||||
Bug Tracker domain model from the
|
||||
`Zend\_Db\_Table <http://framework.zend.com/manual/1.12/en/zend.db.adapter.html>`_
|
||||
`Zend_Db_Table <https://framework.zend.com/manual/1.12/en/zend.db.adapter.html>`_
|
||||
documentation. Reading their documentation we can extract the
|
||||
requirements:
|
||||
|
||||
@@ -73,21 +72,21 @@ requirements:
|
||||
- Bug reporters and engineers are both Users of the system.
|
||||
- A User can create new Bugs.
|
||||
- The assigned engineer can close a Bug.
|
||||
- A User can see all his reported or assigned Bugs.
|
||||
- A User can see all their reported or assigned Bugs.
|
||||
- Bugs can be paginated through a list-view.
|
||||
|
||||
Project Setup
|
||||
-------------
|
||||
|
||||
Create a new empty folder for this tutorial project, for example
|
||||
``doctrine2-tutorial`` and create a new file ``composer.json`` with
|
||||
the following contents:
|
||||
``doctrine2-tutorial`` and create a new file ``composer.json`` inside
|
||||
that directory with the following contents:
|
||||
|
||||
::
|
||||
|
||||
{
|
||||
"require": {
|
||||
"doctrine/orm": "2.4.*",
|
||||
"doctrine/orm": "^2.6.2",
|
||||
"symfony/yaml": "2.*"
|
||||
},
|
||||
"autoload": {
|
||||
@@ -103,18 +102,21 @@ Install Doctrine using the Composer Dependency Management tool, by calling:
|
||||
$ composer install
|
||||
|
||||
This will install the packages Doctrine Common, Doctrine DBAL, Doctrine ORM,
|
||||
Symfony YAML and Symfony Console into the `vendor` directory. The Symfony
|
||||
dependencies are not required by Doctrine but will be used in this tutorial.
|
||||
into the ``vendor`` directory.
|
||||
|
||||
Add the following directories:
|
||||
::
|
||||
|
||||
doctrine2-tutorial
|
||||
|-- config
|
||||
| |-- xml
|
||||
| `-- xml
|
||||
| `-- yaml
|
||||
`-- src
|
||||
|
||||
.. note::
|
||||
The YAML driver is deprecated and will be removed in version 3.0.
|
||||
It is strongly recommended to switch to one of the other mappings.
|
||||
|
||||
Obtaining the EntityManager
|
||||
---------------------------
|
||||
|
||||
@@ -136,7 +138,10 @@ step:
|
||||
|
||||
// Create a simple "default" Doctrine ORM configuration for Annotations
|
||||
$isDevMode = true;
|
||||
$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/src"), $isDevMode);
|
||||
$proxyDir = null;
|
||||
$cache = null;
|
||||
$useSimpleAnnotationReader = false;
|
||||
$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/src"), $isDevMode, $proxyDir, $cache, $useSimpleAnnotationReader);
|
||||
// or if you prefer yaml or XML
|
||||
//$config = Setup::createXMLMetadataConfiguration(array(__DIR__."/config/xml"), $isDevMode);
|
||||
//$config = Setup::createYAMLMetadataConfiguration(array(__DIR__."/config/yaml"), $isDevMode);
|
||||
@@ -150,7 +155,15 @@ step:
|
||||
// obtaining the entity manager
|
||||
$entityManager = EntityManager::create($conn, $config);
|
||||
|
||||
The require_once statement sets up the class autoloading for Doctrine and
|
||||
.. note::
|
||||
The YAML driver is deprecated and will be removed in version 3.0.
|
||||
It is strongly recommended to switch to one of the other mappings.
|
||||
|
||||
.. note::
|
||||
It is recommended not to use the SimpleAnnotationReader because its
|
||||
usage will be removed for version 3.0.
|
||||
|
||||
The ``require_once`` statement sets up the class autoloading for Doctrine and
|
||||
its dependencies using Composer's autoloader.
|
||||
|
||||
The second block consists of the instantiation of the ORM
|
||||
@@ -162,7 +175,7 @@ read up on the configuration details in the
|
||||
The third block shows the configuration options required to connect to
|
||||
a database. In this case, we'll use a file-based SQLite database. All the
|
||||
configuration options for all the shipped drivers are given in the
|
||||
`DBAL Configuration section of the manual <http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/>`_.
|
||||
`DBAL Configuration section of the manual <https://www.doctrine-project.org/projects/doctrine-dbal/en/current/>`_.
|
||||
|
||||
The last block shows how the ``EntityManager`` is obtained from a
|
||||
factory method.
|
||||
@@ -173,7 +186,7 @@ Generating the Database Schema
|
||||
Doctrine has a command-line interface that allows you to access the SchemaTool,
|
||||
a component that can generate a relational database schema based entirely on the
|
||||
defined entity classes and their metadata. For this tool to work, a
|
||||
cli-config.php file must exist in the project root directory:
|
||||
``cli-config.php`` file must exist in the project root directory:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -183,20 +196,19 @@ cli-config.php file must exist in the project root directory:
|
||||
|
||||
return \Doctrine\ORM\Tools\Console\ConsoleRunner::createHelperSet($entityManager);
|
||||
|
||||
Change into your project directory and call the Doctrine command-line tool:
|
||||
Now call the Doctrine command-line tool:
|
||||
|
||||
::
|
||||
|
||||
$ cd project/
|
||||
$ vendor/bin/doctrine orm:schema-tool:create
|
||||
|
||||
Since we haven't added any entity metadata in `src` yet, you'll see a message
|
||||
Since we haven't added any entity metadata in ``src`` yet, you'll see a message
|
||||
stating "No Metadata Classes to process." In the next section, we'll create a
|
||||
Product entity along with the corresponding metadata, and run this command again.
|
||||
|
||||
Note that as you modify your entities' metadata during the development process,
|
||||
you'll need to update your database schema to stay in sync with the metadata.
|
||||
You can rasily recreate the database using the following commands:
|
||||
You can easily recreate the database using the following commands:
|
||||
|
||||
::
|
||||
|
||||
@@ -209,8 +221,8 @@ Or you can use the update functionality:
|
||||
|
||||
$ vendor/bin/doctrine orm:schema-tool:update --force
|
||||
|
||||
The updating of databases uses a Diff Algorithm for a given
|
||||
Database Schema. This is a cornerstone of the ``Doctrine\DBAL`` package,
|
||||
The updating of databases uses a diff algorithm for a given
|
||||
database schema. This is a cornerstone of the ``Doctrine\DBAL`` package,
|
||||
which can even be used without the Doctrine ORM package.
|
||||
|
||||
Starting with the Product Entity
|
||||
@@ -250,16 +262,16 @@ entity definition:
|
||||
}
|
||||
}
|
||||
|
||||
When creating entity classes, all of the fields should be protected or private
|
||||
(not public), with getter and setter methods for each one (except $id).
|
||||
When creating entity classes, all of the fields should be ``protected`` or ``private``
|
||||
(not ``public``), with getter and setter methods for each one (except ``$id``).
|
||||
The use of mutators allows Doctrine to hook into calls which
|
||||
manipulate the entities in ways that it could not if you just
|
||||
manipulate the entities in ways that it could not if you just
|
||||
directly set the values with ``entity#field = foo;``
|
||||
|
||||
The id field has no setter since, generally speaking, your code
|
||||
should not set this value since it represents a database id value.
|
||||
(Note that Doctrine itself can still set the value using the
|
||||
Reflection API instead of a defined setter function)
|
||||
The id field has no setter since, generally speaking, your code
|
||||
should not set this value since it represents a database id value.
|
||||
(Note that Doctrine itself can still set the value using the
|
||||
Reflection API instead of a defined setter function.)
|
||||
|
||||
The next step for persistence with Doctrine is to describe the
|
||||
structure of the ``Product`` entity to Doctrine using a metadata
|
||||
@@ -278,14 +290,24 @@ but you only need to choose one.
|
||||
|
||||
<?php
|
||||
// src/Product.php
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @Entity @Table(name="products")
|
||||
**/
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="products")
|
||||
*/
|
||||
class Product
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue **/
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\GeneratedValue
|
||||
*/
|
||||
protected $id;
|
||||
/** @Column(type="string") **/
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
// .. (other code)
|
||||
@@ -297,7 +319,7 @@ but you only need to choose one.
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Product" table="products">
|
||||
<id name="id" type="integer">
|
||||
@@ -308,6 +330,10 @@ but you only need to choose one.
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. note::
|
||||
The YAML driver is deprecated and will be removed in version 3.0.
|
||||
It is strongly recommended to switch to one of the other mappings.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# config/yaml/Product.dcm.yml
|
||||
@@ -323,8 +349,8 @@ but you only need to choose one.
|
||||
name:
|
||||
type: string
|
||||
|
||||
The top-level ``entity`` definition tag specifies information about
|
||||
the class and table-name. The primitive type ``Product#name`` is
|
||||
The top-level ``entity`` definition specifies information about
|
||||
the class and table name. The primitive type ``Product#name`` is
|
||||
defined as a ``field`` attribute. The ``id`` property is defined with
|
||||
the ``id`` tag. It has a ``generator`` tag nested inside, which
|
||||
specifies that the primary key generation mechanism should automatically
|
||||
@@ -458,28 +484,37 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
|
||||
|
||||
<?php
|
||||
// src/Bug.php
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @Entity(repositoryClass="BugRepository") @Table(name="bugs")
|
||||
* @ORM\Entity(repositoryClass="BugRepository")
|
||||
* @ORM\Table(name="bugs")
|
||||
*/
|
||||
class Bug
|
||||
{
|
||||
/**
|
||||
* @Id @Column(type="integer") @GeneratedValue
|
||||
* @ORM\Id
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\GeneratedValue
|
||||
* @var int
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @Column(type="string")
|
||||
* @ORM\Column(type="string")
|
||||
* @var string
|
||||
*/
|
||||
protected $description;
|
||||
|
||||
/**
|
||||
* @Column(type="datetime")
|
||||
* @ORM\Column(type="datetime")
|
||||
* @var DateTime
|
||||
*/
|
||||
protected $created;
|
||||
|
||||
/**
|
||||
* @Column(type="string")
|
||||
* @ORM\Column(type="string")
|
||||
* @var string
|
||||
*/
|
||||
protected $status;
|
||||
@@ -524,18 +559,25 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
|
||||
|
||||
<?php
|
||||
// src/User.php
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @Entity @Table(name="users")
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="users")
|
||||
*/
|
||||
class User
|
||||
{
|
||||
/**
|
||||
* @Id @GeneratedValue @Column(type="integer")
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
* @var int
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @Column(type="string")
|
||||
* @ORM\Column(type="string")
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
@@ -765,7 +807,7 @@ the database that points from Bugs to Products.
|
||||
{
|
||||
// ... (previous code)
|
||||
|
||||
protected $products = null;
|
||||
protected $products;
|
||||
|
||||
public function assignToProduct(Product $product)
|
||||
{
|
||||
@@ -787,41 +829,50 @@ the ``Product`` before:
|
||||
|
||||
<?php
|
||||
// src/Bug.php
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @Entity @Table(name="bugs")
|
||||
**/
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="bugs")
|
||||
*/
|
||||
class Bug
|
||||
{
|
||||
/**
|
||||
* @Id @Column(type="integer") @GeneratedValue
|
||||
**/
|
||||
* @ORM\Id
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\GeneratedValue
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @Column(type="string")
|
||||
**/
|
||||
* @ORM\Column(type="string")
|
||||
*/
|
||||
protected $description;
|
||||
|
||||
/**
|
||||
* @Column(type="datetime")
|
||||
**/
|
||||
* @ORM\Column(type="datetime")
|
||||
*/
|
||||
protected $created;
|
||||
|
||||
/**
|
||||
* @Column(type="string")
|
||||
**/
|
||||
* @ORM\Column(type="string")
|
||||
*/
|
||||
protected $status;
|
||||
|
||||
/**
|
||||
* @ManyToOne(targetEntity="User", inversedBy="assignedBugs")
|
||||
**/
|
||||
* @ORM\ManyToOne(targetEntity="User", inversedBy="assignedBugs")
|
||||
*/
|
||||
protected $engineer;
|
||||
|
||||
/**
|
||||
* @ManyToOne(targetEntity="User", inversedBy="reportedBugs")
|
||||
**/
|
||||
* @ORM\ManyToOne(targetEntity="User", inversedBy="reportedBugs")
|
||||
*/
|
||||
protected $reporter;
|
||||
|
||||
/**
|
||||
* @ManyToMany(targetEntity="Product")
|
||||
**/
|
||||
* @ORM\ManyToMany(targetEntity="Product")
|
||||
*/
|
||||
protected $products;
|
||||
|
||||
// ... (other code)
|
||||
@@ -833,7 +884,7 @@ the ``Product`` before:
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Bug" table="bugs">
|
||||
<id name="id" type="integer">
|
||||
@@ -851,6 +902,10 @@ the ``Product`` before:
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. note::
|
||||
The YAML driver is deprecated and will be removed in version 3.0.
|
||||
It is strongly recommended to switch to one of the other mappings.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# config/yaml/Bug.dcm.yml
|
||||
@@ -911,34 +966,40 @@ Finally, we'll add metadata mappings for the ``User`` entity.
|
||||
|
||||
<?php
|
||||
// src/User.php
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @Entity @Table(name="users")
|
||||
**/
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="users")
|
||||
*/
|
||||
class User
|
||||
{
|
||||
/**
|
||||
* @Id @GeneratedValue @Column(type="integer")
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
* @var int
|
||||
**/
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @Column(type="string")
|
||||
* @ORM\Column(type="string")
|
||||
* @var string
|
||||
**/
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @OneToMany(targetEntity="Bug", mappedBy="reporter")
|
||||
* @ORM\OneToMany(targetEntity="Bug", mappedBy="reporter")
|
||||
* @var Bug[] An ArrayCollection of Bug objects.
|
||||
**/
|
||||
protected $reportedBugs = null;
|
||||
*/
|
||||
protected $reportedBugs;
|
||||
|
||||
/**
|
||||
* @OneToMany(targetEntity="Bug", mappedBy="engineer")
|
||||
* @ORM\OneToMany(targetEntity="Bug", mappedBy="engineer")
|
||||
* @var Bug[] An ArrayCollection of Bug objects.
|
||||
**/
|
||||
protected $assignedBugs = null;
|
||||
*/
|
||||
protected $assignedBugs;
|
||||
|
||||
// .. (other code)
|
||||
}
|
||||
@@ -949,7 +1010,7 @@ Finally, we'll add metadata mappings for the ``User`` entity.
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="User" table="users">
|
||||
<id name="id" type="integer">
|
||||
@@ -963,6 +1024,10 @@ Finally, we'll add metadata mappings for the ``User`` entity.
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. note::
|
||||
The YAML driver is deprecated and will be removed in version 3.0.
|
||||
It is strongly recommended to switch to one of the other mappings.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# config/yaml/User.dcm.yml
|
||||
@@ -1145,8 +1210,7 @@ The console output of this script is then:
|
||||
throw your ORM into the dumpster, because it doesn't support some
|
||||
the more powerful SQL concepts.
|
||||
|
||||
|
||||
Instead of handwriting DQL you can use the ``QueryBuilder`` retrieved
|
||||
If you need to build your query dynamically, you can use the ``QueryBuilder`` retrieved
|
||||
by calling ``$entityManager->createQueryBuilder()``. There are more
|
||||
details about this in the relevant part of the documentation.
|
||||
|
||||
@@ -1472,9 +1536,12 @@ we have to adjust the metadata slightly.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @Entity(repositoryClass="BugRepository")
|
||||
* @Table(name="bugs")
|
||||
* @ORM\Entity(repositoryClass="BugRepository")
|
||||
* @ORM\Table(name="bugs")
|
||||
**/
|
||||
class Bug
|
||||
{
|
||||
@@ -1486,13 +1553,17 @@ we have to adjust the metadata slightly.
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Bug" table="bugs" repository-class="BugRepository">
|
||||
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. note::
|
||||
The YAML driver is deprecated and will be removed in version 3.0.
|
||||
It is strongly recommended to switch to one of the other mappings.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
Bug:
|
||||
|
||||
@@ -5,8 +5,8 @@ There are use-cases when you'll want to sort collections when they are
|
||||
retrieved from the database. In userland you do this as long as you
|
||||
haven't initially saved an entity with its associations into the
|
||||
database. To retrieve a sorted collection from the database you can
|
||||
use the ``@OrderBy`` annotation with an collection that specifies
|
||||
an DQL snippet that is appended to all queries with this
|
||||
use the ``@OrderBy`` annotation with a collection that specifies
|
||||
a DQL snippet that is appended to all queries with this
|
||||
collection.
|
||||
|
||||
Additional to any ``@OneToMany`` or ``@ManyToMany`` annotation you
|
||||
@@ -64,7 +64,7 @@ positional statement. Multiple Fields are separated by a comma (,).
|
||||
The referenced field names have to exist on the ``targetEntity``
|
||||
class of the ``@ManyToMany`` or ``@OneToMany`` annotation.
|
||||
|
||||
The semantics of this feature can be described as follows.
|
||||
The semantics of this feature can be described as follows:
|
||||
|
||||
|
||||
- ``@OrderBy`` acts as an implicit ORDER BY clause for the given
|
||||
@@ -73,7 +73,7 @@ The semantics of this feature can be described as follows.
|
||||
- All collections of the ordered type are always retrieved in an
|
||||
ordered fashion.
|
||||
- To keep the database impact low, these implicit ORDER BY items
|
||||
are only added to an DQL Query if the collection is fetch joined in
|
||||
are only added to a DQL Query if the collection is fetch joined in
|
||||
the DQL query.
|
||||
|
||||
Given our previously defined example, the following would not add
|
||||
|
||||
@@ -107,7 +107,7 @@ The code and mappings for the Market entity looks like this:
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Doctrine\Tests\Models\StockExchange\Market">
|
||||
<id name="id" type="integer">
|
||||
@@ -193,7 +193,7 @@ here are the code and mappings for it:
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Doctrine\Tests\Models\StockExchange\Stock">
|
||||
<id name="id" type="integer">
|
||||
|
||||
@@ -19,10 +19,12 @@
|
||||
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use Doctrine\Common\Persistence\Mapping\MappingException;
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
use Doctrine\ORM\Mapping\MappingException as ORMMappingException;
|
||||
use Doctrine\ORM\Query\Parameter;
|
||||
use Doctrine\ORM\Cache\QueryCacheKey;
|
||||
use Doctrine\DBAL\Cache\QueryCacheProfile;
|
||||
@@ -70,7 +72,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* The parameter map of this query.
|
||||
*
|
||||
* @var \Doctrine\Common\Collections\ArrayCollection
|
||||
* @var ArrayCollection|Parameter[]
|
||||
*/
|
||||
protected $parameters;
|
||||
|
||||
@@ -98,7 +100,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* The hydration mode.
|
||||
*
|
||||
* @var integer
|
||||
* @var string|int
|
||||
*/
|
||||
protected $_hydrationMode = self::HYDRATE_OBJECT;
|
||||
|
||||
@@ -304,7 +306,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Get all defined parameters.
|
||||
*
|
||||
* @return \Doctrine\Common\Collections\ArrayCollection The defined query parameters.
|
||||
* @return ArrayCollection The defined query parameters.
|
||||
*/
|
||||
public function getParameters()
|
||||
{
|
||||
@@ -334,7 +336,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Sets a collection of query parameters.
|
||||
*
|
||||
* @param \Doctrine\Common\Collections\ArrayCollection|array $parameters
|
||||
* @param ArrayCollection|mixed[] $parameters
|
||||
*
|
||||
* @return static This query instance.
|
||||
*/
|
||||
@@ -410,16 +412,24 @@ abstract class AbstractQuery
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value))) {
|
||||
if ($value instanceof Mapping\ClassMetadata) {
|
||||
return $value->name;
|
||||
}
|
||||
|
||||
if (! is_object($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
try {
|
||||
$value = $this->_em->getUnitOfWork()->getSingleIdentifierValue($value);
|
||||
|
||||
if ($value === null) {
|
||||
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
|
||||
}
|
||||
}
|
||||
|
||||
if ($value instanceof Mapping\ClassMetadata) {
|
||||
return $value->name;
|
||||
} catch (MappingException | ORMMappingException $e) {
|
||||
// Silence any mapping exceptions. These can occur if the object in
|
||||
// question is not a mapped entity, in which case we just don't do
|
||||
// any preparation on the value.
|
||||
}
|
||||
|
||||
return $value;
|
||||
@@ -573,21 +583,45 @@ abstract class AbstractQuery
|
||||
* Set whether or not to cache the results of this query and if so, for
|
||||
* how long and which ID to use for the cache entry.
|
||||
*
|
||||
* @param boolean $bool
|
||||
* @param integer $lifetime
|
||||
* @param string $resultCacheId
|
||||
* @deprecated 2.7 Use {@see enableResultCache} and {@see disableResultCache} instead.
|
||||
*
|
||||
* @param bool $useCache
|
||||
* @param int $lifetime
|
||||
* @param string $resultCacheId
|
||||
*
|
||||
* @return static This query instance.
|
||||
*/
|
||||
public function useResultCache($bool, $lifetime = null, $resultCacheId = null)
|
||||
public function useResultCache($useCache, $lifetime = null, $resultCacheId = null)
|
||||
{
|
||||
if ($bool) {
|
||||
$this->setResultCacheLifetime($lifetime);
|
||||
$this->setResultCacheId($resultCacheId);
|
||||
return $useCache
|
||||
? $this->enableResultCache($lifetime, $resultCacheId)
|
||||
: $this->disableResultCache();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* Enables caching of the results of this query, for given or default amount of seconds
|
||||
* and optionally specifies which ID to use for the cache entry.
|
||||
*
|
||||
* @param int|null $lifetime How long the cache entry is valid, in seconds.
|
||||
* @param string|null $resultCacheId ID to use for the cache entry.
|
||||
*
|
||||
* @return static This query instance.
|
||||
*/
|
||||
public function enableResultCache(?int $lifetime = null, ?string $resultCacheId = null) : self
|
||||
{
|
||||
$this->setResultCacheLifetime($lifetime);
|
||||
$this->setResultCacheId($resultCacheId);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables caching of the results of this query.
|
||||
*
|
||||
* @return static This query instance.
|
||||
*/
|
||||
public function disableResultCache() : self
|
||||
{
|
||||
$this->_queryCacheProfile = null;
|
||||
|
||||
return $this;
|
||||
@@ -680,8 +714,8 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Defines the processing mode to be used during hydration / result set transformation.
|
||||
*
|
||||
* @param integer $hydrationMode Doctrine processing mode to be used during hydration process.
|
||||
* One of the Query::HYDRATE_* constants.
|
||||
* @param string|int $hydrationMode Doctrine processing mode to be used during hydration process.
|
||||
* One of the Query::HYDRATE_* constants.
|
||||
*
|
||||
* @return static This query instance.
|
||||
*/
|
||||
@@ -695,7 +729,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Gets the hydration mode currently used by the query.
|
||||
*
|
||||
* @return integer
|
||||
* @return string|int
|
||||
*/
|
||||
public function getHydrationMode()
|
||||
{
|
||||
@@ -707,7 +741,7 @@ abstract class AbstractQuery
|
||||
*
|
||||
* Alias for execute(null, $hydrationMode = HYDRATE_OBJECT).
|
||||
*
|
||||
* @param int $hydrationMode
|
||||
* @param string|int $hydrationMode
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
@@ -743,7 +777,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Get exactly one result or null.
|
||||
*
|
||||
* @param int $hydrationMode
|
||||
* @param string|int $hydrationMode
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
@@ -781,7 +815,7 @@ abstract class AbstractQuery
|
||||
* If the result is not unique, a NonUniqueResultException is thrown.
|
||||
* If there is no result, a NoResultException is thrown.
|
||||
*
|
||||
* @param integer $hydrationMode
|
||||
* @param string|int $hydrationMode
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
@@ -812,8 +846,9 @@ abstract class AbstractQuery
|
||||
*
|
||||
* Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
|
||||
*
|
||||
* @return mixed The scalar result, or NULL if the query returned no result.
|
||||
* @return mixed The scalar result.
|
||||
*
|
||||
* @throws NoResultException If the query returned no result.
|
||||
* @throws NonUniqueResultException If the query result is not unique.
|
||||
*/
|
||||
public function getSingleScalarResult()
|
||||
@@ -875,7 +910,7 @@ abstract class AbstractQuery
|
||||
* iterate over the result.
|
||||
*
|
||||
* @param ArrayCollection|array|null $parameters The query parameters.
|
||||
* @param integer|null $hydrationMode The hydration mode to use.
|
||||
* @param string|int|null $hydrationMode The hydration mode to use.
|
||||
*
|
||||
* @return \Doctrine\ORM\Internal\Hydration\IterableResult
|
||||
*/
|
||||
@@ -899,7 +934,7 @@ abstract class AbstractQuery
|
||||
* Executes the query.
|
||||
*
|
||||
* @param ArrayCollection|array|null $parameters Query parameters.
|
||||
* @param integer|null $hydrationMode Processing mode to be used during the hydration process.
|
||||
* @param string|int|null $hydrationMode Processing mode to be used during the hydration process.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
@@ -916,7 +951,7 @@ abstract class AbstractQuery
|
||||
* Execute query ignoring second level cache.
|
||||
*
|
||||
* @param ArrayCollection|array|null $parameters
|
||||
* @param integer|null $hydrationMode
|
||||
* @param string|int|null $hydrationMode
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
@@ -974,7 +1009,7 @@ abstract class AbstractQuery
|
||||
* Load from second level cache or executes the query and put into cache.
|
||||
*
|
||||
* @param ArrayCollection|array|null $parameters
|
||||
* @param integer|null $hydrationMode
|
||||
* @param string|int|null $hydrationMode
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
|
||||
@@ -75,6 +75,10 @@ class DefaultEntityHydrator implements EntityHydrator
|
||||
$data = $this->uow->getOriginalEntityData($entity);
|
||||
$data = array_merge($data, $metadata->getIdentifierValues($entity)); // why update has no identifier values ?
|
||||
|
||||
if ($metadata->isVersioned) {
|
||||
$data[$metadata->versionField] = $metadata->getFieldValue($entity, $metadata->versionField);
|
||||
}
|
||||
|
||||
foreach ($metadata->associationMappings as $name => $assoc) {
|
||||
if ( ! isset($data[$name])) {
|
||||
continue;
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Doctrine\ORM\Cache;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\Cache\Persister\CachedPersister;
|
||||
use Doctrine\ORM\Cache\Persister\Entity\CachedEntityPersister;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
@@ -29,6 +30,7 @@ use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\Common\Proxy\Proxy;
|
||||
use Doctrine\ORM\Cache;
|
||||
use Doctrine\ORM\Query;
|
||||
use function assert;
|
||||
|
||||
/**
|
||||
* Default query cache implementation.
|
||||
@@ -106,25 +108,27 @@ class DefaultQueryCache implements QueryCache
|
||||
|
||||
$result = [];
|
||||
$entityName = reset($rsm->aliasMap);
|
||||
$hasRelation = ( ! empty($rsm->relationMap));
|
||||
$hasRelation = ! empty($rsm->relationMap);
|
||||
$persister = $this->uow->getEntityPersister($entityName);
|
||||
$region = $persister->getCacheRegion();
|
||||
$regionName = $region->getName();
|
||||
assert($persister instanceof CachedEntityPersister);
|
||||
|
||||
$region = $persister->getCacheRegion();
|
||||
$regionName = $region->getName();
|
||||
|
||||
$cm = $this->em->getClassMetadata($entityName);
|
||||
|
||||
$generateKeys = function (array $entry) use ($cm): EntityCacheKey {
|
||||
$generateKeys = static function (array $entry) use ($cm) : EntityCacheKey {
|
||||
return new EntityCacheKey($cm->rootEntityName, $entry['identifier']);
|
||||
};
|
||||
|
||||
$cacheKeys = new CollectionCacheEntry(array_map($generateKeys, $cacheEntry->result));
|
||||
$entries = $region->getMultiple($cacheKeys);
|
||||
$entries = $region->getMultiple($cacheKeys) ?? [];
|
||||
|
||||
// @TODO - move to cache hydration component
|
||||
foreach ($cacheEntry->result as $index => $entry) {
|
||||
$entityEntry = is_array($entries) && array_key_exists($index, $entries) ? $entries[$index] : null;
|
||||
$entityEntry = $entries[$index] ?? null;
|
||||
|
||||
if ($entityEntry === null) {
|
||||
if (! $entityEntry instanceof EntityCacheEntry) {
|
||||
if ($this->cacheLogger !== null) {
|
||||
$this->cacheLogger->entityCacheMiss($regionName, $cacheKeys->identifiers[$index]);
|
||||
}
|
||||
@@ -145,9 +149,11 @@ class DefaultQueryCache implements QueryCache
|
||||
$data = $entityEntry->data;
|
||||
|
||||
foreach ($entry['associations'] as $name => $assoc) {
|
||||
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
|
||||
$assocRegion = $assocPersister->getCacheRegion();
|
||||
$assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']);
|
||||
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
|
||||
assert($assocPersister instanceof CachedEntityPersister);
|
||||
|
||||
$assocRegion = $assocPersister->getCacheRegion();
|
||||
$assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']);
|
||||
|
||||
if ($assoc['type'] & ClassMetadata::TO_ONE) {
|
||||
|
||||
@@ -254,7 +260,7 @@ class DefaultQueryCache implements QueryCache
|
||||
throw new CacheException("Second-level cache query supports only select statements.");
|
||||
}
|
||||
|
||||
if (isset($hints[Query::HINT_FORCE_PARTIAL_LOAD]) && $hints[Query::HINT_FORCE_PARTIAL_LOAD]) {
|
||||
if (($hints[Query\SqlWalker::HINT_PARTIAL] ?? false) === true || ($hints[Query::HINT_FORCE_PARTIAL_LOAD] ?? false) === true) {
|
||||
throw new CacheException("Second level cache does not support partial entities.");
|
||||
}
|
||||
|
||||
@@ -267,15 +273,18 @@ class DefaultQueryCache implements QueryCache
|
||||
$rootAlias = key($rsm->aliasMap);
|
||||
$persister = $this->uow->getEntityPersister($entityName);
|
||||
|
||||
if ( ! ($persister instanceof CachedPersister)) {
|
||||
if (! $persister instanceof CachedEntityPersister) {
|
||||
throw CacheException::nonCacheableEntity($entityName);
|
||||
}
|
||||
|
||||
$region = $persister->getCacheRegion();
|
||||
|
||||
$cm = $this->em->getClassMetadata($entityName);
|
||||
assert($cm instanceof ClassMetadata);
|
||||
|
||||
foreach ($result as $index => $entity) {
|
||||
$identifier = $this->uow->getEntityIdentifier($entity);
|
||||
$entityKey = new EntityCacheKey($entityName, $identifier);
|
||||
$identifier = $this->uow->getEntityIdentifier($entity);
|
||||
$entityKey = new EntityCacheKey($cm->rootEntityName, $identifier);
|
||||
|
||||
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $region->contains($entityKey)) {
|
||||
// Cancel put result if entity put fail
|
||||
|
||||
@@ -243,20 +243,6 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
|
||||
return $this->persister->get($collection, $index);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeElement(PersistentCollection $collection, $element)
|
||||
{
|
||||
if ($persisterResult = $this->persister->removeElement($collection, $element)) {
|
||||
$this->evictCollectionCache($collection);
|
||||
$this->evictElementCache($this->sourceEntity->rootEntityName, $collection->getOwner());
|
||||
$this->evictElementCache($this->targetEntity->rootEntityName, $element);
|
||||
}
|
||||
|
||||
return $persisterResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
||||
@@ -452,12 +452,14 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
$class = $this->metadataFactory->getMetadataFor($cacheEntry->class);
|
||||
}
|
||||
|
||||
if (($entity = $this->hydrator->loadCacheEntry($class, $cacheKey, $cacheEntry, $entity)) !== null) {
|
||||
$cachedEntity = $this->hydrator->loadCacheEntry($class, $cacheKey, $cacheEntry, $entity);
|
||||
|
||||
if ($cachedEntity !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->entityCacheHit($this->regionName, $cacheKey);
|
||||
}
|
||||
|
||||
return $entity;
|
||||
return $cachedEntity;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
namespace Doctrine\ORM\Cache\Region;
|
||||
|
||||
use Doctrine\Common\Cache\MultiGetCache;
|
||||
use Doctrine\ORM\Cache\CacheEntry;
|
||||
use Doctrine\ORM\Cache\CollectionCacheEntry;
|
||||
|
||||
/**
|
||||
@@ -67,7 +68,12 @@ class DefaultMultiGetRegion extends DefaultRegion
|
||||
}
|
||||
|
||||
$returnableItems = [];
|
||||
|
||||
foreach ($keysToRetrieve as $index => $key) {
|
||||
if (! $items[$key] instanceof CacheEntry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$returnableItems[$index] = $items[$key];
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,13 @@ class DefaultRegion implements Region
|
||||
*/
|
||||
public function get(CacheKey $key)
|
||||
{
|
||||
return $this->cache->fetch($this->getCacheEntryKey($key)) ?: null;
|
||||
$entry = $this->cache->fetch($this->getCacheEntryKey($key));
|
||||
|
||||
if (! $entry instanceof CacheEntry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,7 +114,7 @@ class DefaultRegion implements Region
|
||||
$entryKey = $this->getCacheEntryKey($key);
|
||||
$entryValue = $this->cache->fetch($entryKey);
|
||||
|
||||
if ($entryValue === false) {
|
||||
if (! $entryValue instanceof CacheEntry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Gets the directory where Doctrine generates any necessary proxy class files.
|
||||
*
|
||||
* @return string|null
|
||||
*
|
||||
* @deprecated 2.7 We're switch to `ocramius/proxy-manager` and this method isn't applicable any longer
|
||||
* @see https://github.com/Ocramius/ProxyManager
|
||||
*/
|
||||
public function getProxyDir()
|
||||
{
|
||||
@@ -82,6 +85,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Gets the strategy for automatically generating proxy classes.
|
||||
*
|
||||
* @return int Possible values are constants of Doctrine\Common\Proxy\AbstractProxyFactory.
|
||||
*
|
||||
* @deprecated 2.7 We're switch to `ocramius/proxy-manager` and this method isn't applicable any longer
|
||||
* @see https://github.com/Ocramius/ProxyManager
|
||||
*/
|
||||
public function getAutoGenerateProxyClasses()
|
||||
{
|
||||
@@ -107,6 +113,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Gets the namespace where proxy classes reside.
|
||||
*
|
||||
* @return string|null
|
||||
*
|
||||
* @deprecated 2.7 We're switch to `ocramius/proxy-manager` and this method isn't applicable any longer
|
||||
* @see https://github.com/Ocramius/ProxyManager
|
||||
*/
|
||||
public function getProxyNamespace()
|
||||
{
|
||||
|
||||
@@ -23,11 +23,14 @@ use Doctrine\Common\EventManager;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\DriverManager;
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\ORM\Proxy\ProxyFactory;
|
||||
use Doctrine\ORM\Query\FilterCollection;
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
use Throwable;
|
||||
use const E_USER_DEPRECATED;
|
||||
use function trigger_error;
|
||||
|
||||
/**
|
||||
* The EntityManager is the central access point to ORM functionality.
|
||||
@@ -353,6 +356,13 @@ use Throwable;
|
||||
*/
|
||||
public function flush($entity = null)
|
||||
{
|
||||
if ($entity !== null) {
|
||||
@trigger_error(
|
||||
'Calling ' . __METHOD__ . '() with any arguments to flush specific entities is deprecated and will not be supported in Doctrine ORM 3.0.',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
}
|
||||
|
||||
$this->errorIfClosed();
|
||||
|
||||
$this->unitOfWork->commit($entity);
|
||||
@@ -380,6 +390,10 @@ use Throwable;
|
||||
{
|
||||
$class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
|
||||
|
||||
if ($lockMode !== null) {
|
||||
$this->checkLockRequirements($lockMode, $class);
|
||||
}
|
||||
|
||||
if ( ! is_array($id)) {
|
||||
if ($class->isIdentifierComposite) {
|
||||
throw ORMInvalidArgumentException::invalidCompositeIdentifier();
|
||||
@@ -441,10 +455,6 @@ use Throwable;
|
||||
|
||||
switch (true) {
|
||||
case LockMode::OPTIMISTIC === $lockMode:
|
||||
if ( ! $class->isVersioned) {
|
||||
throw OptimisticLockException::notVersioned($class->name);
|
||||
}
|
||||
|
||||
$entity = $persister->load($sortedId);
|
||||
|
||||
$unitOfWork->lock($entity, $lockMode, $lockVersion);
|
||||
@@ -453,10 +463,6 @@ use Throwable;
|
||||
|
||||
case LockMode::PESSIMISTIC_READ === $lockMode:
|
||||
case LockMode::PESSIMISTIC_WRITE === $lockMode:
|
||||
if ( ! $this->getConnection()->isTransactionActive()) {
|
||||
throw TransactionRequiredException::transactionRequired();
|
||||
}
|
||||
|
||||
return $persister->load($sortedId, null, null, [], $lockMode);
|
||||
|
||||
default:
|
||||
@@ -550,6 +556,13 @@ use Throwable;
|
||||
throw ORMInvalidArgumentException::invalidEntityName($entityName);
|
||||
}
|
||||
|
||||
if ($entityName !== null) {
|
||||
@trigger_error(
|
||||
'Calling ' . __METHOD__ . '() with any arguments to clear specific entities is deprecated and will not be supported in Doctrine ORM 3.0.',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
}
|
||||
|
||||
$this->unitOfWork->clear(
|
||||
null === $entityName
|
||||
? null
|
||||
@@ -652,9 +665,13 @@ use Throwable;
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMInvalidArgumentException
|
||||
*
|
||||
* @deprecated 2.7 This method is being removed from the ORM and won't have any replacement
|
||||
*/
|
||||
public function detach($entity)
|
||||
{
|
||||
@trigger_error('Method ' . __METHOD__ . '() is deprecated and will be removed in Doctrine ORM 3.0.', E_USER_DEPRECATED);
|
||||
|
||||
if ( ! is_object($entity)) {
|
||||
throw ORMInvalidArgumentException::invalidObject('EntityManager#detach()', $entity);
|
||||
}
|
||||
@@ -673,9 +690,13 @@ use Throwable;
|
||||
*
|
||||
* @throws ORMInvalidArgumentException
|
||||
* @throws ORMException
|
||||
*
|
||||
* @deprecated 2.7 This method is being removed from the ORM and won't have any replacement
|
||||
*/
|
||||
public function merge($entity)
|
||||
{
|
||||
@trigger_error('Method ' . __METHOD__ . '() is deprecated and will be removed in Doctrine ORM 3.0.', E_USER_DEPRECATED);
|
||||
|
||||
if ( ! is_object($entity)) {
|
||||
throw ORMInvalidArgumentException::invalidObject('EntityManager#merge()', $entity);
|
||||
}
|
||||
@@ -687,12 +708,11 @@ use Throwable;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @todo Implementation need. This is necessary since $e2 = clone $e1; throws an E_FATAL when access anything on $e:
|
||||
* Fatal error: Maximum function nesting level of '100' reached, aborting!
|
||||
*/
|
||||
public function copy($entity, $deep = false)
|
||||
{
|
||||
@trigger_error('Method ' . __METHOD__ . '() is deprecated and will be removed in Doctrine ORM 3.0.', E_USER_DEPRECATED);
|
||||
|
||||
throw new \BadMethodCallException("Not implemented.");
|
||||
}
|
||||
|
||||
@@ -915,4 +935,26 @@ use Throwable;
|
||||
{
|
||||
return null !== $this->filterCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $lockMode
|
||||
* @param ClassMetadata $class
|
||||
* @throws OptimisticLockException
|
||||
* @throws TransactionRequiredException
|
||||
*/
|
||||
private function checkLockRequirements(int $lockMode, ClassMetadata $class): void
|
||||
{
|
||||
switch ($lockMode) {
|
||||
case LockMode::OPTIMISTIC:
|
||||
if (!$class->isVersioned) {
|
||||
throw OptimisticLockException::notVersioned($class->name);
|
||||
}
|
||||
break;
|
||||
case LockMode::PESSIMISTIC_READ:
|
||||
case LockMode::PESSIMISTIC_WRITE:
|
||||
if (!$this->getConnection()->isTransactionActive()) {
|
||||
throw TransactionRequiredException::transactionRequired();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ interface EntityManagerInterface extends ObjectManager
|
||||
* @param string $entityName The name of the entity type.
|
||||
* @param mixed $identifier The entity identifier.
|
||||
*
|
||||
* @return object The (partial) entity reference.
|
||||
* @return object|null The (partial) entity reference.
|
||||
*/
|
||||
public function getPartialReference($entityName, $identifier);
|
||||
|
||||
@@ -190,6 +190,8 @@ interface EntityManagerInterface extends ObjectManager
|
||||
/**
|
||||
* Creates a copy of the given entity. Can create a shallow or a deep copy.
|
||||
*
|
||||
* @deprecated 2.7 This method is being removed from the ORM and won't have any replacement
|
||||
*
|
||||
* @param object $entity The entity to copy.
|
||||
* @param boolean $deep FALSE for a shallow copy, TRUE for a deep copy.
|
||||
*
|
||||
@@ -249,7 +251,7 @@ interface EntityManagerInterface extends ObjectManager
|
||||
*
|
||||
* @deprecated
|
||||
*
|
||||
* @param int $hydrationMode
|
||||
* @param string|int $hydrationMode
|
||||
*
|
||||
* @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator
|
||||
*/
|
||||
@@ -258,7 +260,7 @@ interface EntityManagerInterface extends ObjectManager
|
||||
/**
|
||||
* Create a new instance for the given hydration mode.
|
||||
*
|
||||
* @param int $hydrationMode
|
||||
* @param string|int $hydrationMode
|
||||
*
|
||||
* @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator
|
||||
*
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use Doctrine\Common\Util\Inflector;
|
||||
use Doctrine\Common\Inflector\Inflector;
|
||||
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
||||
use Doctrine\Common\Persistence\ObjectRepository;
|
||||
use Doctrine\Common\Collections\Selectable;
|
||||
@@ -57,9 +57,6 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
|
||||
/**
|
||||
* Initializes a new <tt>EntityRepository</tt>.
|
||||
*
|
||||
* @param EntityManager $em The EntityManager to use.
|
||||
* @param Mapping\ClassMetadata $class The class descriptor.
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $em, Mapping\ClassMetadata $class)
|
||||
{
|
||||
|
||||
@@ -27,7 +27,7 @@ abstract class AbstractIdGenerator
|
||||
* Generates an identifier for an entity.
|
||||
*
|
||||
* @param EntityManager $em
|
||||
* @param \Doctrine\ORM\Mapping\Entity $entity
|
||||
* @param object|null $entity
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function generate(EntityManager $em, $entity);
|
||||
|
||||
@@ -164,6 +164,17 @@ class CommitOrderCalculator
|
||||
case self::IN_PROGRESS:
|
||||
if (isset($adjacentVertex->dependencyList[$vertex->hash]) &&
|
||||
$adjacentVertex->dependencyList[$vertex->hash]->weight < $edge->weight) {
|
||||
|
||||
// If we have some non-visited dependencies in the in-progress dependency, we
|
||||
// need to visit them before adding the node.
|
||||
foreach ($adjacentVertex->dependencyList as $adjacentEdge) {
|
||||
$adjacentEdgeVertex = $this->nodeList[$adjacentEdge->to];
|
||||
|
||||
if ($adjacentEdgeVertex->state === self::NOT_VISITED) {
|
||||
$this->visit($adjacentEdgeVertex);
|
||||
}
|
||||
}
|
||||
|
||||
$adjacentVertex->state = self::VISITED;
|
||||
|
||||
$this->sortedNodeList[] = $adjacentVertex->value;
|
||||
|
||||
@@ -23,7 +23,10 @@ use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Events;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Tools\Pagination\LimitSubqueryWalker;
|
||||
use PDO;
|
||||
use function array_map;
|
||||
use function in_array;
|
||||
|
||||
/**
|
||||
* Base class for all hydrators. A hydrator is a class that provides some form
|
||||
@@ -296,11 +299,8 @@ abstract class AbstractHydrator
|
||||
|
||||
// If there are field name collisions in the child class, then we need
|
||||
// to only hydrate if we are looking at the correct discriminator value
|
||||
if(
|
||||
isset($cacheKeyInfo['discriminatorColumn']) &&
|
||||
isset($data[$cacheKeyInfo['discriminatorColumn']]) &&
|
||||
// Note: loose comparison required. See https://github.com/doctrine/doctrine2/pull/6304#issuecomment-323294442
|
||||
$data[$cacheKeyInfo['discriminatorColumn']] != $cacheKeyInfo['discriminatorValue']
|
||||
if (isset($cacheKeyInfo['discriminatorColumn'], $data[$cacheKeyInfo['discriminatorColumn']])
|
||||
&& ! in_array((string) $data[$cacheKeyInfo['discriminatorColumn']], $cacheKeyInfo['discriminatorValues'], true)
|
||||
) {
|
||||
break;
|
||||
}
|
||||
@@ -352,13 +352,11 @@ abstract class AbstractHydrator
|
||||
|
||||
// WARNING: BC break! We know this is the desired behavior to type convert values, but this
|
||||
// erroneous behavior exists since 2.0 and we're forced to keep compatibility.
|
||||
if ( ! isset($cacheKeyInfo['isScalar'])) {
|
||||
$dqlAlias = $cacheKeyInfo['dqlAlias'];
|
||||
$type = $cacheKeyInfo['type'];
|
||||
$fieldName = $dqlAlias . '_' . $fieldName;
|
||||
$value = $type
|
||||
? $type->convertToPHPValue($value, $this->_platform)
|
||||
: $value;
|
||||
if (! isset($cacheKeyInfo['isScalar'])) {
|
||||
$type = $cacheKeyInfo['type'];
|
||||
$value = $type ? $type->convertToPHPValue($value, $this->_platform) : $value;
|
||||
|
||||
$fieldName = $cacheKeyInfo['dqlAlias'] . '_' . $fieldName;
|
||||
}
|
||||
|
||||
$rowData[$fieldName] = $value;
|
||||
@@ -401,7 +399,8 @@ abstract class AbstractHydrator
|
||||
$columnInfo,
|
||||
[
|
||||
'discriminatorColumn' => $this->_rsm->discriminatorColumns[$ownerMap],
|
||||
'discriminatorValue' => $classMetadata->discriminatorValue
|
||||
'discriminatorValue' => $classMetadata->discriminatorValue,
|
||||
'discriminatorValues' => $this->getDiscriminatorValues($classMetadata),
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -422,6 +421,12 @@ abstract class AbstractHydrator
|
||||
'class' => new \ReflectionClass($mapping['className']),
|
||||
];
|
||||
|
||||
case isset($this->_rsm->scalarMappings[$key], $this->_hints[LimitSubqueryWalker::FORCE_DBAL_TYPE_CONVERSION]):
|
||||
return $this->_cache[$key] = [
|
||||
'fieldName' => $this->_rsm->scalarMappings[$key],
|
||||
'type' => Type::getType($this->_rsm->typeMappings[$key]),
|
||||
'dqlAlias' => '',
|
||||
];
|
||||
case (isset($this->_rsm->scalarMappings[$key])):
|
||||
return $this->_cache[$key] = [
|
||||
'isScalar' => true,
|
||||
@@ -454,6 +459,23 @@ abstract class AbstractHydrator
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getDiscriminatorValues(ClassMetadata $classMetadata) : array
|
||||
{
|
||||
$values = array_map(
|
||||
function (string $subClass) : string {
|
||||
return (string) $this->getClassMetadata($subClass)->discriminatorValue;
|
||||
},
|
||||
$classMetadata->subClasses
|
||||
);
|
||||
|
||||
$values[] = (string) $classMetadata->discriminatorValue;
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve ClassMetadata associated to entity class name.
|
||||
*
|
||||
|
||||
@@ -422,7 +422,7 @@ class ObjectHydrator extends AbstractHydrator
|
||||
$this->resultPointers[$dqlAlias] = $reflFieldValue[$index];
|
||||
}
|
||||
} else if ( ! $reflFieldValue) {
|
||||
$reflFieldValue = $this->initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias);
|
||||
$this->initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias);
|
||||
} else if ($reflFieldValue instanceof PersistentCollection && $reflFieldValue->isInitialized() === false) {
|
||||
$reflFieldValue->setInitialized(true);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Doctrine\ORM\Internal\Hydration;
|
||||
use PDO;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Query;
|
||||
use function in_array;
|
||||
|
||||
class SimpleObjectHydrator extends AbstractHydrator
|
||||
{
|
||||
@@ -78,8 +79,9 @@ class SimpleObjectHydrator extends AbstractHydrator
|
||||
*/
|
||||
protected function hydrateRowData(array $sqlResult, array &$result)
|
||||
{
|
||||
$entityName = $this->class->name;
|
||||
$data = [];
|
||||
$entityName = $this->class->name;
|
||||
$data = [];
|
||||
$discrColumnValue = null;
|
||||
|
||||
// We need to find the correct entity class name if we have inheritance in resultset
|
||||
if ($this->class->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) {
|
||||
@@ -104,7 +106,8 @@ class SimpleObjectHydrator extends AbstractHydrator
|
||||
throw HydrationException::invalidDiscriminatorValue($sqlResult[$discrColumnName], array_keys($discrMap));
|
||||
}
|
||||
|
||||
$entityName = $discrMap[$sqlResult[$discrColumnName]];
|
||||
$entityName = $discrMap[$sqlResult[$discrColumnName]];
|
||||
$discrColumnValue = $sqlResult[$discrColumnName];
|
||||
|
||||
unset($sqlResult[$discrColumnName]);
|
||||
}
|
||||
@@ -134,6 +137,11 @@ class SimpleObjectHydrator extends AbstractHydrator
|
||||
|
||||
// Prevent overwrite in case of inherit classes using same property name (See AbstractHydrator)
|
||||
if ( ! isset($data[$fieldName]) || ! $valueIsNull) {
|
||||
// If we have inheritance in resultset, make sure the field belongs to the correct class
|
||||
if (isset($cacheKeyInfo['discriminatorValues']) && ! in_array((string) $discrColumnValue, $cacheKeyInfo['discriminatorValues'], true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data[$fieldName] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ use Doctrine\ORM\Id\BigIntegerIdentityGenerator;
|
||||
use Doctrine\ORM\Id\IdentityGenerator;
|
||||
use Doctrine\ORM\ORMException;
|
||||
use ReflectionException;
|
||||
use function assert;
|
||||
|
||||
/**
|
||||
* The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
|
||||
@@ -401,10 +402,10 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass)
|
||||
{
|
||||
foreach ($parentClass->fieldMappings as $mapping) {
|
||||
if ( ! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
|
||||
if (! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass && ! $parentClass->isEmbeddedClass) {
|
||||
$mapping['inherited'] = $parentClass->name;
|
||||
}
|
||||
if ( ! isset($mapping['declared'])) {
|
||||
if (! isset($mapping['declared'])) {
|
||||
$mapping['declared'] = $parentClass->name;
|
||||
}
|
||||
$subClass->addInheritedFieldMapping($mapping);
|
||||
@@ -469,10 +470,6 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
private function addNestedEmbeddedClasses(ClassMetadata $subClass, ClassMetadata $parentClass, $prefix)
|
||||
{
|
||||
foreach ($subClass->embeddedClasses as $property => $embeddableClass) {
|
||||
if (isset($embeddableClass['inherited'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$embeddableMetadata = $this->getMetadataFor($embeddableClass['class']);
|
||||
|
||||
$parentClass->mapEmbedded(
|
||||
@@ -703,6 +700,9 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
|
||||
case ClassMetadata::GENERATOR_TYPE_CUSTOM:
|
||||
$definition = $class->customGeneratorDefinition;
|
||||
if ($definition === null) {
|
||||
throw new ORMException("Can't instantiate custom generator : no custom generator definition");
|
||||
}
|
||||
if ( ! class_exists($definition['class'])) {
|
||||
throw new ORMException("Can't instantiate custom generator : " .
|
||||
$definition['class']);
|
||||
@@ -777,7 +777,9 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
*/
|
||||
protected function isEntity(ClassMetadataInterface $class)
|
||||
{
|
||||
return isset($class->isMappedSuperclass) && $class->isMappedSuperclass === false;
|
||||
assert($class instanceof ClassMetadata);
|
||||
|
||||
return $class->isMappedSuperclass === false && $class->isEmbeddedClass === false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,7 +27,6 @@ use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use ReflectionClass;
|
||||
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
|
||||
use Doctrine\Common\ClassLoader;
|
||||
use Doctrine\ORM\Cache\CacheException;
|
||||
|
||||
/**
|
||||
@@ -242,9 +241,9 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
* )
|
||||
* </code>
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* @todo Merge with tableGeneratorDefinition into generic generatorDefinition
|
||||
*
|
||||
* @var array<string, string>|null
|
||||
*/
|
||||
public $customGeneratorDefinition;
|
||||
|
||||
@@ -1025,7 +1024,11 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
public function validateAssociations()
|
||||
{
|
||||
foreach ($this->associationMappings as $mapping) {
|
||||
if ( ! ClassLoader::classExists($mapping['targetEntity']) ) {
|
||||
if (
|
||||
! class_exists($mapping['targetEntity'])
|
||||
&& ! interface_exists($mapping['targetEntity'])
|
||||
&& ! trait_exists($mapping['targetEntity'])
|
||||
) {
|
||||
throw MappingException::invalidTargetEntityClass($mapping['targetEntity'], $this->name, $mapping['fieldName']);
|
||||
}
|
||||
}
|
||||
@@ -2014,7 +2017,7 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
*
|
||||
* @param string $fieldName
|
||||
*
|
||||
* @return \Doctrine\DBAL\Types\Type|string|null
|
||||
* @return string|null
|
||||
*
|
||||
* @todo 3.0 Remove this. PersisterHelper should fix it somehow
|
||||
*/
|
||||
@@ -2030,7 +2033,7 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
*
|
||||
* @param string $columnName
|
||||
*
|
||||
* @return \Doctrine\DBAL\Types\Type|string|null
|
||||
* @return string|null
|
||||
*
|
||||
* @deprecated 3.0 remove this. this method is bogus and unreliable, since it cannot resolve the type of a column
|
||||
* that is derived by a referenced field on a different entity.
|
||||
|
||||
@@ -99,7 +99,8 @@ class DefaultQuoteStrategy implements QuoteStrategy
|
||||
$schema = '';
|
||||
|
||||
if (isset($association['joinTable']['schema'])) {
|
||||
$schema = $association['joinTable']['schema'] . '.';
|
||||
$schema = $association['joinTable']['schema'];
|
||||
$schema .= ! $platform->supportsSchemas() && $platform->canEmulateSchemas() ? '__' : '.';
|
||||
}
|
||||
|
||||
$tableName = $association['joinTable']['name'];
|
||||
|
||||
@@ -44,6 +44,7 @@ class AnnotationDriver extends AbstractAnnotationDriver
|
||||
protected $entityAnnotationClasses = [
|
||||
Mapping\Entity::class => 1,
|
||||
Mapping\MappedSuperclass::class => 2,
|
||||
Mapping\Embeddable::class => 3,
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -276,6 +277,8 @@ class AnnotationDriver extends AbstractAnnotationDriver
|
||||
/* @var $property \ReflectionProperty */
|
||||
foreach ($class->getProperties() as $property) {
|
||||
if ($metadata->isMappedSuperclass && ! $property->isPrivate()
|
||||
||
|
||||
$metadata->isEmbeddedClass && $property->getDeclaringClass()->getName() !== $class->getName()
|
||||
||
|
||||
$metadata->isInheritedField($property->name)
|
||||
||
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
|
||||
namespace Doctrine\ORM\Mapping\Driver;
|
||||
|
||||
use Doctrine\Common\Inflector\Inflector;
|
||||
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;
|
||||
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
|
||||
use Doctrine\Common\Util\Inflector;
|
||||
use Doctrine\DBAL\Schema\AbstractSchemaManager;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
@@ -29,6 +29,7 @@ use Doctrine\DBAL\Schema\Column;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
use function preg_replace;
|
||||
|
||||
/**
|
||||
* The DatabaseDriver reverse engineers the mapping metadata from a database.
|
||||
@@ -548,7 +549,7 @@ class DatabaseDriver implements MappingDriver
|
||||
|
||||
// Replace _id if it is a foreignkey column
|
||||
if ($fk) {
|
||||
$columnName = str_replace('_id', '', $columnName);
|
||||
$columnName = preg_replace('/_id$/', '', $columnName);
|
||||
}
|
||||
|
||||
return Inflector::camelize($columnName);
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
namespace Doctrine\ORM\Mapping\Driver;
|
||||
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use SimpleXMLElement;
|
||||
use Doctrine\Common\Persistence\Mapping\Driver\FileDriver;
|
||||
use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
|
||||
@@ -429,7 +430,10 @@ class XmlDriver extends FileDriver
|
||||
if (isset($oneToManyElement->{'order-by'})) {
|
||||
$orderBy = [];
|
||||
foreach ($oneToManyElement->{'order-by'}->{'order-by-field'} as $orderByField) {
|
||||
$orderBy[(string) $orderByField['name']] = (string) $orderByField['direction'];
|
||||
$orderBy[(string) $orderByField['name']] = isset($orderByField['direction'])
|
||||
? (string) $orderByField['direction']
|
||||
: Criteria::ASC
|
||||
;
|
||||
}
|
||||
$mapping['orderBy'] = $orderBy;
|
||||
}
|
||||
@@ -545,7 +549,9 @@ class XmlDriver extends FileDriver
|
||||
if (isset($manyToManyElement->{'order-by'})) {
|
||||
$orderBy = [];
|
||||
foreach ($manyToManyElement->{'order-by'}->{'order-by-field'} as $orderByField) {
|
||||
$orderBy[(string) $orderByField['name']] = (string) $orderByField['direction'];
|
||||
$orderBy[(string) $orderByField['name']] = isset($orderByField['direction'])
|
||||
? (string) $orderByField['direction']
|
||||
: Criteria::ASC;
|
||||
}
|
||||
$mapping['orderBy'] = $orderBy;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ use Doctrine\Common\Persistence\Mapping\Driver\FileDriver;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata as Metadata;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use function trigger_error;
|
||||
|
||||
/**
|
||||
* The YamlDriver reads the mapping metadata from yaml schema files.
|
||||
@@ -34,6 +35,8 @@ use Symfony\Component\Yaml\Yaml;
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @author Jonathan H. Wage <jonwage@gmail.com>
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
*
|
||||
* @deprecated 2.7 This class is being removed from the ORM and won't have any replacement
|
||||
*/
|
||||
class YamlDriver extends FileDriver
|
||||
{
|
||||
@@ -44,6 +47,11 @@ class YamlDriver extends FileDriver
|
||||
*/
|
||||
public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENSION)
|
||||
{
|
||||
@trigger_error(
|
||||
'YAML mapping driver is deprecated and will be removed in Doctrine ORM 3.0, please migrate to annotation or XML driver.',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
|
||||
parent::__construct($locator, $fileExtension);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,30 +20,55 @@
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use const CASE_LOWER;
|
||||
use const CASE_UPPER;
|
||||
use const E_USER_DEPRECATED;
|
||||
use function preg_replace;
|
||||
use function strpos;
|
||||
use function strrpos;
|
||||
use function strtolower;
|
||||
use function strtoupper;
|
||||
use function substr;
|
||||
use function trigger_error;
|
||||
|
||||
/**
|
||||
* Naming strategy implementing the underscore naming convention.
|
||||
* Converts 'MyEntity' to 'my_entity' or 'MY_ENTITY'.
|
||||
*
|
||||
*
|
||||
*
|
||||
* @link www.doctrine-project.org
|
||||
* @since 2.3
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
*/
|
||||
class UnderscoreNamingStrategy implements NamingStrategy
|
||||
{
|
||||
private const DEFAULT_PATTERN = '/(?<=[a-z])([A-Z])/';
|
||||
private const NUMBER_AWARE_PATTERN = '/(?<=[a-z0-9])([A-Z])/';
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $case;
|
||||
|
||||
/** @var string */
|
||||
private $pattern;
|
||||
|
||||
/**
|
||||
* Underscore naming strategy construct.
|
||||
*
|
||||
* @param integer $case CASE_LOWER | CASE_UPPER
|
||||
* @param int $case CASE_LOWER | CASE_UPPER
|
||||
*/
|
||||
public function __construct($case = CASE_LOWER)
|
||||
public function __construct($case = CASE_LOWER, bool $numberAware = false)
|
||||
{
|
||||
$this->case = $case;
|
||||
if (! $numberAware) {
|
||||
@trigger_error(
|
||||
'Creating ' . self::class . ' without making it number aware is deprecated and will be removed in Doctrine ORM 3.0.',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
}
|
||||
|
||||
$this->case = $case;
|
||||
$this->pattern = $numberAware ? self::NUMBER_AWARE_PATTERN : self::DEFAULT_PATTERN;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +82,7 @@ class UnderscoreNamingStrategy implements NamingStrategy
|
||||
/**
|
||||
* Sets string case CASE_LOWER | CASE_UPPER.
|
||||
* Alphabetic characters converted to lowercase or uppercase.
|
||||
*
|
||||
*
|
||||
* @param integer $case
|
||||
*
|
||||
* @return void
|
||||
@@ -118,7 +143,7 @@ class UnderscoreNamingStrategy implements NamingStrategy
|
||||
{
|
||||
return $this->classToTableName($sourceEntity) . '_' . $this->classToTableName($targetEntity);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -127,15 +152,10 @@ class UnderscoreNamingStrategy implements NamingStrategy
|
||||
return $this->classToTableName($entityName) . '_' .
|
||||
($referencedColumnName ?: $this->referenceColumnName());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function underscore($string)
|
||||
|
||||
private function underscore(string $string) : string
|
||||
{
|
||||
$string = preg_replace('/(?<=[a-z])([A-Z])/', '_$1', $string);
|
||||
$string = preg_replace($this->pattern, '_$1', $string);
|
||||
|
||||
if ($this->case === CASE_UPPER) {
|
||||
return strtoupper($string);
|
||||
|
||||
@@ -25,6 +25,7 @@ use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Selectable;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use function get_class;
|
||||
|
||||
/**
|
||||
* A PersistentCollection represents a collection of elements that have persistent state.
|
||||
@@ -366,16 +367,6 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
|
||||
*/
|
||||
public function removeElement($element)
|
||||
{
|
||||
if ( ! $this->initialized && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
|
||||
if ($this->collection->contains($element)) {
|
||||
return $this->collection->removeElement($element);
|
||||
}
|
||||
|
||||
$persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
|
||||
|
||||
return $persister->removeElement($this, $element);
|
||||
}
|
||||
|
||||
$removed = parent::removeElement($element);
|
||||
|
||||
if ( ! $removed) {
|
||||
@@ -447,7 +438,7 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
if ( ! $this->initialized && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
|
||||
if (! $this->initialized && $this->association !== null && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
|
||||
$persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
|
||||
|
||||
return $persister->count($this) + ($this->isDirty ? $this->collection->count() : 0);
|
||||
@@ -668,6 +659,7 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
|
||||
|
||||
$criteria = clone $criteria;
|
||||
$criteria->where($expression);
|
||||
$criteria->orderBy($criteria->getOrderings() ?: $this->association['orderBy'] ?? []);
|
||||
|
||||
$persister = $this->em->getUnitOfWork()->getEntityPersister($this->association['targetEntity']);
|
||||
|
||||
|
||||
@@ -90,16 +90,6 @@ interface CollectionPersister
|
||||
*/
|
||||
public function containsKey(PersistentCollection $collection, $key);
|
||||
|
||||
/**
|
||||
* Removes an element.
|
||||
*
|
||||
* @param \Doctrine\ORM\PersistentCollection $collection
|
||||
* @param object $element
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function removeElement(PersistentCollection $collection, $element);
|
||||
|
||||
/**
|
||||
* Gets an element by key.
|
||||
*
|
||||
|
||||
@@ -211,22 +211,6 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
return (bool) $this->conn->fetchColumn($sql, $params, 0, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function removeElement(PersistentCollection $collection, $element)
|
||||
{
|
||||
if ( ! $this->isValidEntityState($element)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
list($quotedJoinTable, $whereClauses, $params, $types) = $this->getJoinTableRestrictions($collection, $element, false);
|
||||
|
||||
$sql = 'DELETE FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
|
||||
|
||||
return (bool) $this->conn->executeUpdate($sql, $params, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@@ -419,7 +403,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
foreach ($mapping['relationToSourceKeyColumns'] as $columnName => $refColumnName) {
|
||||
$params[] = isset($sourceClass->fieldNames[$refColumnName])
|
||||
? $identifier[$sourceClass->fieldNames[$refColumnName]]
|
||||
: $identifier[$sourceClass->getFieldForColumn($columnName)];
|
||||
: $identifier[$sourceClass->getFieldForColumn($refColumnName)];
|
||||
}
|
||||
|
||||
return $params;
|
||||
|
||||
@@ -166,28 +166,6 @@ class OneToManyPersister extends AbstractCollectionPersister
|
||||
return $persister->exists($element, $criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeElement(PersistentCollection $collection, $element)
|
||||
{
|
||||
$mapping = $collection->getMapping();
|
||||
|
||||
if ( ! $mapping['orphanRemoval']) {
|
||||
// no-op: this is not the owning side, therefore no operations should be applied
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $this->isValidEntityState($element)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this
|
||||
->uow
|
||||
->getEntityPersister($mapping['targetEntity'])
|
||||
->delete($element);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
||||
@@ -37,6 +37,10 @@ use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
use Doctrine\ORM\Utility\IdentifierFlattener;
|
||||
use Doctrine\ORM\Utility\PersisterHelper;
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function assert;
|
||||
use function reset;
|
||||
|
||||
/**
|
||||
* A BasicEntityPersister maps an entity to a single table in a relational database.
|
||||
@@ -451,23 +455,21 @@ class BasicEntityPersister implements EntityPersister
|
||||
continue;
|
||||
}
|
||||
|
||||
$params[] = $identifier[$idField];
|
||||
$where[] = $this->class->associationMappings[$idField]['joinColumns'][0]['name'];
|
||||
$targetMapping = $this->em->getClassMetadata($this->class->associationMappings[$idField]['targetEntity']);
|
||||
$params[] = $identifier[$idField];
|
||||
$where[] = $this->quoteStrategy->getJoinColumnName(
|
||||
$this->class->associationMappings[$idField]['joinColumns'][0],
|
||||
$this->class,
|
||||
$this->platform
|
||||
);
|
||||
|
||||
switch (true) {
|
||||
case (isset($targetMapping->fieldMappings[$targetMapping->identifier[0]])):
|
||||
$types[] = $targetMapping->fieldMappings[$targetMapping->identifier[0]]['type'];
|
||||
break;
|
||||
$targetMapping = $this->em->getClassMetadata($this->class->associationMappings[$idField]['targetEntity']);
|
||||
$targetType = PersisterHelper::getTypeOfField($targetMapping->identifier[0], $targetMapping, $this->em);
|
||||
|
||||
case (isset($targetMapping->associationMappings[$targetMapping->identifier[0]])):
|
||||
$types[] = $targetMapping->associationMappings[$targetMapping->identifier[0]]['type'];
|
||||
break;
|
||||
|
||||
default:
|
||||
throw ORMException::unrecognizedField($targetMapping->identifier[0]);
|
||||
if ($targetType === []) {
|
||||
throw ORMException::unrecognizedField($targetMapping->identifier[0]);
|
||||
}
|
||||
|
||||
$types[] = reset($targetType);
|
||||
}
|
||||
|
||||
if ($versioned) {
|
||||
@@ -569,29 +571,12 @@ class BasicEntityPersister implements EntityPersister
|
||||
*/
|
||||
public function delete($entity)
|
||||
{
|
||||
$self = $this;
|
||||
$class = $this->class;
|
||||
$identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
|
||||
$tableName = $this->quoteStrategy->getTableName($class, $this->platform);
|
||||
$idColumns = $this->quoteStrategy->getIdentifierColumnNames($class, $this->platform);
|
||||
$id = array_combine($idColumns, $identifier);
|
||||
$types = array_map(function ($identifier) use ($class, $self) {
|
||||
if (isset($class->fieldMappings[$identifier])) {
|
||||
return $class->fieldMappings[$identifier]['type'];
|
||||
}
|
||||
|
||||
$targetMapping = $self->em->getClassMetadata($class->associationMappings[$identifier]['targetEntity']);
|
||||
|
||||
if (isset($targetMapping->fieldMappings[$targetMapping->identifier[0]])) {
|
||||
return $targetMapping->fieldMappings[$targetMapping->identifier[0]]['type'];
|
||||
}
|
||||
|
||||
if (isset($targetMapping->associationMappings[$targetMapping->identifier[0]])) {
|
||||
return $targetMapping->associationMappings[$targetMapping->identifier[0]]['type'];
|
||||
}
|
||||
|
||||
throw ORMException::unrecognizedField($targetMapping->identifier[0]);
|
||||
}, $class->identifier);
|
||||
$types = $this->getClassIdentifiersTypes($class);
|
||||
|
||||
$this->deleteJoinTableRecords($identifier);
|
||||
|
||||
@@ -2089,4 +2074,22 @@ class BasicEntityPersister implements EntityPersister
|
||||
|
||||
$this->currentPersisterContext = $this->limitsHandlingContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getClassIdentifiersTypes(ClassMetadata $class) : array
|
||||
{
|
||||
$entityManager = $this->em;
|
||||
|
||||
return array_map(
|
||||
static function ($fieldName) use ($class, $entityManager) : string {
|
||||
$types = PersisterHelper::getTypeOfField($fieldName, $class, $entityManager);
|
||||
assert(isset($types[0]));
|
||||
|
||||
return $types[0];
|
||||
},
|
||||
$class->identifier
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,22 +278,25 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
// If the database platform supports FKs, just
|
||||
// delete the row from the root table. Cascades do the rest.
|
||||
if ($this->platform->supportsForeignKeyConstraints()) {
|
||||
$rootClass = $this->em->getClassMetadata($this->class->rootEntityName);
|
||||
$rootTable = $this->quoteStrategy->getTableName($rootClass, $this->platform);
|
||||
$rootClass = $this->em->getClassMetadata($this->class->rootEntityName);
|
||||
$rootTable = $this->quoteStrategy->getTableName($rootClass, $this->platform);
|
||||
$rootTypes = $this->getClassIdentifiersTypes($rootClass);
|
||||
|
||||
return (bool) $this->conn->delete($rootTable, $id);
|
||||
return (bool) $this->conn->delete($rootTable, $id, $rootTypes);
|
||||
}
|
||||
|
||||
// Delete from all tables individually, starting from this class' table up to the root table.
|
||||
$rootTable = $this->quoteStrategy->getTableName($this->class, $this->platform);
|
||||
$rootTypes = $this->getClassIdentifiersTypes($this->class);
|
||||
|
||||
$affectedRows = $this->conn->delete($rootTable, $id);
|
||||
$affectedRows = $this->conn->delete($rootTable, $id, $rootTypes);
|
||||
|
||||
foreach ($this->class->parentClasses as $parentClass) {
|
||||
$parentMetadata = $this->em->getClassMetadata($parentClass);
|
||||
$parentTable = $this->quoteStrategy->getTableName($parentMetadata, $this->platform);
|
||||
$parentTypes = $this->getClassIdentifiersTypes($parentMetadata);
|
||||
|
||||
$this->conn->delete($parentTable, $id);
|
||||
$this->conn->delete($parentTable, $id, $parentTypes);
|
||||
}
|
||||
|
||||
return (bool) $affectedRows;
|
||||
|
||||
@@ -26,6 +26,8 @@ use Doctrine\Common\Proxy\Proxy as BaseProxy;
|
||||
*
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
* @since 2.0
|
||||
*
|
||||
* @deprecated 2.7 This interface is being removed from the ORM and won't have any replacement, proxies will no longer implement it.
|
||||
*/
|
||||
interface Proxy extends BaseProxy
|
||||
{
|
||||
|
||||
@@ -37,6 +37,8 @@ use Doctrine\ORM\Utility\IdentifierFlattener;
|
||||
* @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
|
||||
* @author Marco Pivetta <ocramius@gmail.com>
|
||||
* @since 2.0
|
||||
*
|
||||
* @deprecated 2.7 This class is being removed from the ORM and won't have any replacement
|
||||
*/
|
||||
class ProxyFactory extends AbstractProxyFactory
|
||||
{
|
||||
|
||||
@@ -19,15 +19,18 @@
|
||||
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Query\Exec\AbstractSqlExecutor;
|
||||
use Doctrine\ORM\Query\Parameter;
|
||||
use Doctrine\ORM\Query\ParameterTypeInferer;
|
||||
use Doctrine\ORM\Query\Parser;
|
||||
use Doctrine\ORM\Query\ParserResult;
|
||||
use Doctrine\ORM\Query\QueryException;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Query\ParameterTypeInferer;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\Utility\HierarchyDiscriminatorResolver;
|
||||
use function array_keys;
|
||||
use function assert;
|
||||
|
||||
/**
|
||||
* A Query object represents a DQL query.
|
||||
@@ -130,7 +133,7 @@ final class Query extends AbstractQuery
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
private $_state = self::STATE_CLEAN;
|
||||
private $_state = self::STATE_DIRTY;
|
||||
|
||||
/**
|
||||
* A snapshot of the parameter types the query was parsed with.
|
||||
@@ -142,7 +145,7 @@ final class Query extends AbstractQuery
|
||||
/**
|
||||
* Cached DQL query.
|
||||
*
|
||||
* @var string
|
||||
* @var string|null
|
||||
*/
|
||||
private $_dql = null;
|
||||
|
||||
@@ -156,7 +159,7 @@ final class Query extends AbstractQuery
|
||||
/**
|
||||
* The first result to return (the "offset").
|
||||
*
|
||||
* @var integer
|
||||
* @var int|null
|
||||
*/
|
||||
private $_firstResult = null;
|
||||
|
||||
@@ -387,26 +390,13 @@ final class Query extends AbstractQuery
|
||||
$types = [];
|
||||
|
||||
foreach ($this->parameters as $parameter) {
|
||||
$key = $parameter->getName();
|
||||
$value = $parameter->getValue();
|
||||
$rsm = $this->getResultSetMapping();
|
||||
$key = $parameter->getName();
|
||||
|
||||
if ( ! isset($paramMappings[$key])) {
|
||||
throw QueryException::unknownParameter($key);
|
||||
}
|
||||
|
||||
if (isset($rsm->metadataParameterMapping[$key]) && $value instanceof ClassMetadata) {
|
||||
$value = $value->getMetadataValue($rsm->metadataParameterMapping[$key]);
|
||||
}
|
||||
|
||||
if (isset($rsm->discriminatorParameters[$key]) && $value instanceof ClassMetadata) {
|
||||
$value = array_keys(HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($value, $this->_em));
|
||||
}
|
||||
|
||||
$value = $this->processParameterValue($value);
|
||||
$type = ($parameter->getValue() === $value)
|
||||
? $parameter->getType()
|
||||
: ParameterTypeInferer::inferType($value);
|
||||
[$value, $type] = $this->resolveParameterValue($parameter);
|
||||
|
||||
foreach ($paramMappings[$key] as $position) {
|
||||
$types[$position] = $type;
|
||||
@@ -439,6 +429,38 @@ final class Query extends AbstractQuery
|
||||
return [$sqlParams, $types];
|
||||
}
|
||||
|
||||
/** @return mixed[] tuple of (value, type) */
|
||||
private function resolveParameterValue(Parameter $parameter) : array
|
||||
{
|
||||
if ($parameter->typeWasSpecified()) {
|
||||
return [$parameter->getValue(), $parameter->getType()];
|
||||
}
|
||||
|
||||
$key = $parameter->getName();
|
||||
$originalValue = $parameter->getValue();
|
||||
$value = $originalValue;
|
||||
$rsm = $this->getResultSetMapping();
|
||||
|
||||
assert($rsm !== null);
|
||||
|
||||
if ($value instanceof ClassMetadata && isset($rsm->metadataParameterMapping[$key])) {
|
||||
$value = $value->getMetadataValue($rsm->metadataParameterMapping[$key]);
|
||||
}
|
||||
|
||||
if ($value instanceof ClassMetadata && isset($rsm->discriminatorParameters[$key])) {
|
||||
$value = array_keys(HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($value, $this->_em));
|
||||
}
|
||||
|
||||
$processedValue = $this->processParameterValue($value);
|
||||
|
||||
return [
|
||||
$processedValue,
|
||||
$originalValue === $processedValue
|
||||
? $parameter->getType()
|
||||
: ParameterTypeInferer::inferType($processedValue),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a cache driver to be used for caching queries.
|
||||
*
|
||||
@@ -565,7 +587,7 @@ final class Query extends AbstractQuery
|
||||
/**
|
||||
* Returns the DQL query that is represented by this query object.
|
||||
*
|
||||
* @return string DQL query.
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDQL()
|
||||
{
|
||||
@@ -602,7 +624,7 @@ final class Query extends AbstractQuery
|
||||
/**
|
||||
* Sets the position of the first result to retrieve (the "offset").
|
||||
*
|
||||
* @param integer $firstResult The first result to return.
|
||||
* @param int|null $firstResult The first result to return.
|
||||
*
|
||||
* @return Query This query object.
|
||||
*/
|
||||
@@ -618,7 +640,7 @@ final class Query extends AbstractQuery
|
||||
* Gets the position of the first result the query object was set to retrieve (the "offset").
|
||||
* Returns NULL if {@link setFirstResult} was not applied to this query.
|
||||
*
|
||||
* @return integer The position of the first result.
|
||||
* @return int|null The position of the first result.
|
||||
*/
|
||||
public function getFirstResult()
|
||||
{
|
||||
@@ -656,7 +678,7 @@ final class Query extends AbstractQuery
|
||||
* iterated over the result.
|
||||
*
|
||||
* @param ArrayCollection|array|null $parameters The query parameters.
|
||||
* @param integer $hydrationMode The hydration mode to use.
|
||||
* @param string|int $hydrationMode The hydration mode to use.
|
||||
*
|
||||
* @return \Doctrine\ORM\Internal\Hydration\IterableResult
|
||||
*/
|
||||
|
||||
@@ -41,10 +41,8 @@ class Expr
|
||||
* // (u.type = ?1) AND (u.role = ?2)
|
||||
* $expr->andX($expr->eq('u.type', ':1'), $expr->eq('u.role', ':2'));
|
||||
*
|
||||
* @param \Doctrine\ORM\Query\Expr\Comparison |
|
||||
* \Doctrine\ORM\Query\Expr\Func |
|
||||
* \Doctrine\ORM\Query\Expr\Orx
|
||||
* $x Optional clause. Defaults to null, but requires at least one defined when converting to string.
|
||||
* @param Expr\Comparison|Expr\Func|Expr\Orx|string $x Optional clause. Defaults to null, but requires at least one
|
||||
* defined when converting to string.
|
||||
*
|
||||
* @return Expr\Andx
|
||||
*/
|
||||
|
||||
@@ -56,7 +56,7 @@ abstract class Base
|
||||
protected $parts = [];
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
* @param mixed $args
|
||||
*/
|
||||
public function __construct($args = [])
|
||||
{
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
namespace Doctrine\ORM\Query;
|
||||
|
||||
use Doctrine\Common\Lexer\AbstractLexer;
|
||||
|
||||
/**
|
||||
* Scans a DQL query for tokens.
|
||||
*
|
||||
@@ -27,7 +29,7 @@ namespace Doctrine\ORM\Query;
|
||||
* @author Roman Borschel <roman@code-factory.org>
|
||||
* @since 2.0
|
||||
*/
|
||||
class Lexer extends \Doctrine\Common\Lexer
|
||||
class Lexer extends AbstractLexer
|
||||
{
|
||||
// All tokens that are not valid identifiers must be < 100
|
||||
const T_NONE = 1;
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
namespace Doctrine\ORM\Query;
|
||||
|
||||
use function trim;
|
||||
|
||||
/**
|
||||
* Defines a Query Parameter.
|
||||
*
|
||||
@@ -49,6 +51,13 @@ class Parameter
|
||||
*/
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* Whether the parameter type was explicitly specified or not
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $typeSpecified;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
@@ -58,7 +67,8 @@ class Parameter
|
||||
*/
|
||||
public function __construct($name, $value, $type = null)
|
||||
{
|
||||
$this->name = trim($name, ':');
|
||||
$this->name = trim($name, ':');
|
||||
$this->typeSpecified = $type !== null;
|
||||
|
||||
$this->setValue($value, $type);
|
||||
}
|
||||
@@ -104,4 +114,9 @@ class Parameter
|
||||
$this->value = $value;
|
||||
$this->type = $type ?: ParameterTypeInferer::inferType($value);
|
||||
}
|
||||
|
||||
public function typeWasSpecified() : bool
|
||||
{
|
||||
return $this->typeSpecified;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ namespace Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\Query\AST\Functions;
|
||||
use function in_array;
|
||||
use function strpos;
|
||||
|
||||
/**
|
||||
* An LL(*) recursive-descent parser for the context-free grammar of the Doctrine Query Language.
|
||||
@@ -187,7 +189,7 @@ class Parser
|
||||
{
|
||||
$this->query = $query;
|
||||
$this->em = $query->getEntityManager();
|
||||
$this->lexer = new Lexer($query->getDQL());
|
||||
$this->lexer = new Lexer((string) $query->getDQL());
|
||||
$this->parserResult = new ParserResult();
|
||||
}
|
||||
|
||||
@@ -300,24 +302,27 @@ class Parser
|
||||
*/
|
||||
public function match($token)
|
||||
{
|
||||
$lookaheadType = $this->lexer->lookahead['type'];
|
||||
$lookaheadType = $this->lexer->lookahead['type'] ?? null;
|
||||
|
||||
// Short-circuit on first condition, usually types match
|
||||
if ($lookaheadType !== $token) {
|
||||
// If parameter is not identifier (1-99) must be exact match
|
||||
if ($token < Lexer::T_IDENTIFIER) {
|
||||
$this->syntaxError($this->lexer->getLiteral($token));
|
||||
}
|
||||
if ($lookaheadType === $token) {
|
||||
$this->lexer->moveNext();
|
||||
return;
|
||||
}
|
||||
|
||||
// If parameter is keyword (200+) must be exact match
|
||||
if ($token > Lexer::T_IDENTIFIER) {
|
||||
$this->syntaxError($this->lexer->getLiteral($token));
|
||||
}
|
||||
// If parameter is not identifier (1-99) must be exact match
|
||||
if ($token < Lexer::T_IDENTIFIER) {
|
||||
$this->syntaxError($this->lexer->getLiteral($token));
|
||||
}
|
||||
|
||||
// If parameter is T_IDENTIFIER, then matches T_IDENTIFIER (100) and keywords (200+)
|
||||
if ($token === Lexer::T_IDENTIFIER && $lookaheadType < Lexer::T_IDENTIFIER) {
|
||||
$this->syntaxError($this->lexer->getLiteral($token));
|
||||
}
|
||||
// If parameter is keyword (200+) must be exact match
|
||||
if ($token > Lexer::T_IDENTIFIER) {
|
||||
$this->syntaxError($this->lexer->getLiteral($token));
|
||||
}
|
||||
|
||||
// If parameter is T_IDENTIFIER, then matches T_IDENTIFIER (100) and keywords (200+)
|
||||
if ($token === Lexer::T_IDENTIFIER && $lookaheadType < Lexer::T_IDENTIFIER) {
|
||||
$this->syntaxError($this->lexer->getLiteral($token));
|
||||
}
|
||||
|
||||
$this->lexer->moveNext();
|
||||
@@ -465,7 +470,7 @@ class Parser
|
||||
public function semanticalError($message = '', $token = null)
|
||||
{
|
||||
if ($token === null) {
|
||||
$token = $this->lexer->lookahead;
|
||||
$token = $this->lexer->lookahead ?? ['position' => null];
|
||||
}
|
||||
|
||||
// Minimum exposed chars ahead of token
|
||||
@@ -532,7 +537,7 @@ class Parser
|
||||
*/
|
||||
private function isMathOperator($token)
|
||||
{
|
||||
return in_array($token['type'], [Lexer::T_PLUS, Lexer::T_MINUS, Lexer::T_DIVIDE, Lexer::T_MULTIPLY]);
|
||||
return $token !== null && in_array($token['type'], [Lexer::T_PLUS, Lexer::T_MINUS, Lexer::T_DIVIDE, Lexer::T_MULTIPLY]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -547,7 +552,7 @@ class Parser
|
||||
|
||||
$this->lexer->resetPeek();
|
||||
|
||||
return ($lookaheadType >= Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_OPEN_PARENTHESIS);
|
||||
return $lookaheadType >= Lexer::T_IDENTIFIER && $peek !== null && $peek['type'] === Lexer::T_OPEN_PARENTHESIS;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -838,7 +843,7 @@ class Parser
|
||||
|
||||
$this->lexer->moveNext();
|
||||
|
||||
switch ($this->lexer->lookahead['type']) {
|
||||
switch ($this->lexer->lookahead['type'] ?? null) {
|
||||
case Lexer::T_SELECT:
|
||||
$statement = $this->SelectStatement();
|
||||
break;
|
||||
@@ -958,20 +963,20 @@ class Parser
|
||||
if ($this->lexer->isNextToken(Lexer::T_FULLY_QUALIFIED_NAME)) {
|
||||
$this->match(Lexer::T_FULLY_QUALIFIED_NAME);
|
||||
|
||||
$schemaName = $this->lexer->token['value'];
|
||||
} else if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
|
||||
$this->match(Lexer::T_IDENTIFIER);
|
||||
|
||||
$schemaName = $this->lexer->token['value'];
|
||||
} else {
|
||||
$this->match(Lexer::T_ALIASED_NAME);
|
||||
|
||||
list($namespaceAlias, $simpleClassName) = explode(':', $this->lexer->token['value']);
|
||||
|
||||
$schemaName = $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
|
||||
return $this->lexer->token['value'];
|
||||
}
|
||||
|
||||
return $schemaName;
|
||||
if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
|
||||
$this->match(Lexer::T_IDENTIFIER);
|
||||
|
||||
return $this->lexer->token['value'];
|
||||
}
|
||||
|
||||
$this->match(Lexer::T_ALIASED_NAME);
|
||||
|
||||
[$namespaceAlias, $simpleClassName] = explode(':', $this->lexer->token['value']);
|
||||
|
||||
return $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1280,7 +1285,9 @@ class Parser
|
||||
$this->match(Lexer::T_AS);
|
||||
}
|
||||
|
||||
$aliasIdentificationVariable = $this->AliasIdentificationVariable();
|
||||
$aliasIdentificationVariable = $this->lexer->isNextToken(Lexer::T_IDENTIFIER)
|
||||
? $this->AliasIdentificationVariable()
|
||||
: 'alias_should_have_been_set';
|
||||
|
||||
$deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable;
|
||||
$class = $this->em->getClassMetadata($deleteClause->abstractSchemaName);
|
||||
@@ -1458,7 +1465,7 @@ class Parser
|
||||
// We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
|
||||
$glimpse = $this->lexer->glimpse();
|
||||
|
||||
if ($glimpse['type'] === Lexer::T_DOT) {
|
||||
if ($glimpse !== null && $glimpse['type'] === Lexer::T_DOT) {
|
||||
return $this->SingleValuedPathExpression();
|
||||
}
|
||||
|
||||
@@ -1502,7 +1509,7 @@ class Parser
|
||||
$expr = $this->SimpleArithmeticExpression();
|
||||
break;
|
||||
|
||||
case ($glimpse['type'] === Lexer::T_DOT):
|
||||
case $glimpse !== null && $glimpse['type'] === Lexer::T_DOT:
|
||||
$expr = $this->SingleValuedPathExpression();
|
||||
break;
|
||||
|
||||
@@ -2473,9 +2480,11 @@ class Parser
|
||||
// Peek beyond the matching closing parenthesis ')'
|
||||
$peek = $this->peekBeyondClosingParenthesis();
|
||||
|
||||
if (in_array($peek['value'], ["=", "<", "<=", "<>", ">", ">=", "!="]) ||
|
||||
if ($peek !== null && (
|
||||
in_array($peek['value'], ['=', '<', '<=', '<>', '>', '>=', '!=']) ||
|
||||
in_array($peek['type'], [Lexer::T_NOT, Lexer::T_BETWEEN, Lexer::T_LIKE, Lexer::T_IN, Lexer::T_IS, Lexer::T_EXISTS]) ||
|
||||
$this->isMathOperator($peek)) {
|
||||
$this->isMathOperator($peek)
|
||||
)) {
|
||||
$condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
|
||||
|
||||
return $condPrimary;
|
||||
@@ -2827,11 +2836,11 @@ class Parser
|
||||
case Lexer::T_IDENTIFIER:
|
||||
$peek = $this->lexer->glimpse();
|
||||
|
||||
if ($peek['value'] == '(') {
|
||||
if ($peek !== null && $peek['value'] === '(') {
|
||||
return $this->FunctionDeclaration();
|
||||
}
|
||||
|
||||
if ($peek['value'] == '.') {
|
||||
if ($peek !== null && $peek['value'] === '.') {
|
||||
return $this->SingleValuedPathExpression();
|
||||
}
|
||||
|
||||
@@ -2847,7 +2856,7 @@ class Parser
|
||||
default:
|
||||
$peek = $this->lexer->glimpse();
|
||||
|
||||
if ($peek['value'] == '(') {
|
||||
if ($peek !== null && $peek['value'] === '(') {
|
||||
return $this->FunctionDeclaration();
|
||||
}
|
||||
|
||||
@@ -2918,6 +2927,10 @@ class Parser
|
||||
case Lexer::T_COALESCE:
|
||||
case Lexer::T_NULLIF:
|
||||
return $this->CaseExpression();
|
||||
default:
|
||||
if ($this->isAggregateFunction($lookaheadType)) {
|
||||
return $this->AggregateExpression();
|
||||
}
|
||||
}
|
||||
|
||||
$this->syntaxError(
|
||||
@@ -3182,7 +3195,7 @@ class Parser
|
||||
|
||||
$escapeChar = null;
|
||||
|
||||
if ($this->lexer->lookahead['type'] === Lexer::T_ESCAPE) {
|
||||
if ($this->lexer->lookahead !== null && $this->lexer->lookahead['type'] === Lexer::T_ESCAPE) {
|
||||
$this->match(Lexer::T_ESCAPE);
|
||||
$this->match(Lexer::T_STRING);
|
||||
|
||||
|
||||
@@ -186,6 +186,8 @@ class QueryExpressionVisitor extends ExpressionVisitor
|
||||
$this->parameters[] = $parameter;
|
||||
|
||||
return $this->expr->like($field, $placeholder);
|
||||
case Comparison::MEMBER_OF:
|
||||
return $this->expr->isMemberOf($comparison->getField(), $comparison->getValue()->getValue());
|
||||
case Comparison::STARTS_WITH:
|
||||
$parameter->setValue($parameter->getValue() . '%', $parameter->getType());
|
||||
$this->parameters[] = $parameter;
|
||||
|
||||
@@ -584,4 +584,3 @@ class ResultSetMapping
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user