mirror of
https://github.com/doctrine/orm.git
synced 2026-03-30 02:42:18 +02:00
Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9fc5388f1 | ||
|
|
d2e51eacff | ||
|
|
5a6ae4686f | ||
|
|
8b8a1cbe81 | ||
|
|
27a5284899 | ||
|
|
0086d17afe | ||
|
|
ab62167c8a | ||
|
|
6d43195669 | ||
|
|
7065ff0ac9 | ||
|
|
aa61328e90 | ||
|
|
62719f2a97 | ||
|
|
66770c5bfe | ||
|
|
42691c21b4 | ||
|
|
596e895763 | ||
|
|
d5c82094df | ||
|
|
4148220f9c | ||
|
|
e173c930ec | ||
|
|
7071984559 | ||
|
|
216c466233 | ||
|
|
65f5777e60 | ||
|
|
6e3ce26429 | ||
|
|
752d4f9eac | ||
|
|
2983081a60 | ||
|
|
464b5fdbfb | ||
|
|
04254a8e34 | ||
|
|
1d213e6733 | ||
|
|
bc82e94afc | ||
|
|
b9af1c8fa5 | ||
|
|
d606efd4eb | ||
|
|
581e1638a2 | ||
|
|
17ae8d1b2d | ||
|
|
3a058f8522 | ||
|
|
567220ef71 | ||
|
|
39098ce415 | ||
|
|
1eb9c8a7f6 | ||
|
|
f2f53ba9dc | ||
|
|
ebbc443ec3 | ||
|
|
16802d2614 | ||
|
|
ef73249bc7 | ||
|
|
ed637e51b9 | ||
|
|
a3ece3b419 | ||
|
|
2d1bc78749 | ||
|
|
eaf8b1c7ca | ||
|
|
8070b50150 | ||
|
|
e6a83bedbe | ||
|
|
b6e5464b98 | ||
|
|
6366d190d7 | ||
|
|
89eed31e79 | ||
|
|
4ca00f7a9d | ||
|
|
6bc405455e | ||
|
|
173729e560 | ||
|
|
86abbb0e78 | ||
|
|
69ef75ff2d | ||
|
|
c68edec0c2 | ||
|
|
f9bbd953a7 | ||
|
|
9097014c3d | ||
|
|
12d178777a | ||
|
|
ed1c4de2b6 | ||
|
|
fff56c7f3f | ||
|
|
97e90ddefc | ||
|
|
d5adda954d | ||
|
|
c507b52f20 | ||
|
|
08be905fc3 | ||
|
|
d29cc3660f | ||
|
|
768c291cd1 | ||
|
|
10ed690d99 | ||
|
|
e4e59d8064 | ||
|
|
0e208f7538 | ||
|
|
3e6c6af845 | ||
|
|
584345397b | ||
|
|
786791b8fe | ||
|
|
724dfa0de3 | ||
|
|
39592ba59c | ||
|
|
58a6013d15 | ||
|
|
4580429616 | ||
|
|
6e563a313e | ||
|
|
e8c9cb2f23 | ||
|
|
4792b4f974 | ||
|
|
c2f6b09ee0 | ||
|
|
cb3179865b | ||
|
|
a1602bd91f | ||
|
|
34696126e0 | ||
|
|
aa4b2e59ce | ||
|
|
2cd86deeb4 | ||
|
|
52288abf48 |
4
.coveralls.yml
Normal file
4
.coveralls.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
# for php-coveralls
|
||||
service_name: travis-ci
|
||||
src_dir: lib
|
||||
coverage_clover: build/logs/clover*.xml
|
||||
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -10,4 +10,3 @@ build.xml export-ignore
|
||||
CONTRIBUTING.md export-ignore
|
||||
phpunit.xml.dist export-ignore
|
||||
run-all.sh export-ignore
|
||||
phpcs.xml.dist export-ignore
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -10,7 +10,5 @@ lib/Doctrine/DBAL
|
||||
.buildpath
|
||||
.project
|
||||
.idea
|
||||
*.iml
|
||||
vendor/
|
||||
/tests/Doctrine/Performance/history.db
|
||||
/.phpcs-cache
|
||||
composer.lock
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
build:
|
||||
nodes:
|
||||
analysis:
|
||||
environment:
|
||||
php:
|
||||
version: 7.1
|
||||
cache:
|
||||
disabled: false
|
||||
directories:
|
||||
- ~/.composer/cache
|
||||
|
||||
project_setup:
|
||||
override: true
|
||||
tests:
|
||||
override:
|
||||
- php-scrutinizer-run
|
||||
|
||||
before_commands:
|
||||
- "composer install --no-dev --prefer-source"
|
||||
|
||||
tools:
|
||||
external_code_coverage:
|
||||
timeout: 3600
|
||||
|
||||
filter:
|
||||
excluded_paths:
|
||||
- docs
|
||||
- tools
|
||||
|
||||
build_failure_conditions:
|
||||
- 'elements.rating(<= C).new.exists' # No new classes/methods with a rating of C or worse allowed
|
||||
- 'issues.severity(>= MAJOR).new.exists' # New issues of major or higher severity
|
||||
- 'project.metric_change("scrutinizer.test_coverage", < 0)' # Code Coverage decreased from previous inspection
|
||||
- 'patches.label("Unused Use Statements").new.exists' # No new unused imports patches allowed
|
||||
143
.travis.yml
143
.travis.yml
@@ -1,132 +1,41 @@
|
||||
dist: trusty
|
||||
sudo: false
|
||||
language: php
|
||||
|
||||
php:
|
||||
- 7.1
|
||||
- 7.2
|
||||
- 7.3
|
||||
- 7.4snapshot
|
||||
- nightly
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7.0
|
||||
- hhvm
|
||||
- hhvm-nightly
|
||||
|
||||
env:
|
||||
- DB=sqlite
|
||||
- DB=mysql
|
||||
- DB=pgsql
|
||||
- DB=sqlite
|
||||
|
||||
before_install:
|
||||
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{,.disabled} || echo "xdebug not available"
|
||||
before_script:
|
||||
- if [[ $TRAVIS_PHP_VERSION = '5.6' && $DB = 'sqlite' ]]; then PHPUNIT_FLAGS="--coverage-clover ./build/logs/clover.xml"; else PHPUNIT_FLAGS=""; fi
|
||||
- if [[ $TRAVIS_PHP_VERSION != '5.6' && $TRAVIS_PHP_VERSION != 'hhvm' && $TRAVIS_PHP_VERSION != 'hhvm-nightly' && $TRAVIS_PHP_VERSION != '7.0' ]]; then phpenv config-rm xdebug.ini; fi
|
||||
- composer self-update
|
||||
|
||||
install: travis_retry composer validate --strict && composer update --prefer-dist
|
||||
- composer install --prefer-source --dev
|
||||
|
||||
script:
|
||||
- if [[ "$DB" == "mysql" || "$DB" == "mariadb" ]]; then mysql -e "CREATE SCHEMA doctrine_tests; GRANT ALL PRIVILEGES ON doctrine_tests.* to travis@'%'"; fi
|
||||
- ENABLE_SECOND_LEVEL_CACHE=0 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml
|
||||
- ENABLE_SECOND_LEVEL_CACHE=0 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml $PHPUNIT_FLAGS
|
||||
- ENABLE_SECOND_LEVEL_CACHE=1 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml --exclude-group performance,non-cacheable,locking_functional
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: Test
|
||||
env: DB=mariadb
|
||||
addons:
|
||||
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: 7.4snapshot
|
||||
services:
|
||||
- mysql
|
||||
before_script:
|
||||
- ./tests/travis/install-mysql-$MYSQL_VERSION.sh
|
||||
sudo: required
|
||||
|
||||
- stage: Test
|
||||
env: DB=sqlite DEPENDENCIES=low
|
||||
install: travis_retry composer update --prefer-dist --prefer-lowest
|
||||
|
||||
- stage: Test
|
||||
if: type = cron
|
||||
php: 7.3
|
||||
env: DB=sqlite DEV_DEPENDENCIES
|
||||
install:
|
||||
- composer config minimum-stability dev
|
||||
- travis_retry composer update --prefer-dist
|
||||
|
||||
- stage: Test
|
||||
env: DB=sqlite COVERAGE
|
||||
before_script:
|
||||
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{.disabled,}
|
||||
- if [[ ! $(php -m | grep -si xdebug) ]]; then echo "xdebug required for coverage"; exit 1; fi
|
||||
script:
|
||||
- ENABLE_SECOND_LEVEL_CACHE=0 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml --coverage-clover ./build/logs/clover.xml
|
||||
after_script:
|
||||
- wget https://scrutinizer-ci.com/ocular.phar
|
||||
- php ocular.phar code-coverage:upload --format=php-clover build/logs/clover.xml
|
||||
|
||||
- stage: Code Quality
|
||||
env: DB=none STATIC_ANALYSIS
|
||||
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
|
||||
- travis_retry composer require --dev --prefer-dist --prefer-stable phpstan/phpstan:^0.9
|
||||
script: vendor/bin/phpstan analyse -l 1 -c phpstan.neon lib
|
||||
|
||||
- stage: Code Quality
|
||||
env: DB=none BENCHMARK
|
||||
before_script: wget https://phpbench.github.io/phpbench/phpbench.phar https://phpbench.github.io/phpbench/phpbench.phar.pubkey
|
||||
script: php phpbench.phar run -l dots --report=default
|
||||
|
||||
- stage: Code Quality
|
||||
if: NOT type = pull_request
|
||||
env: DB=none CODING_STANDARDS
|
||||
php: 7.4snapshot
|
||||
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
|
||||
after_script:
|
||||
- php vendor/bin/coveralls -v
|
||||
|
||||
matrix:
|
||||
exclude:
|
||||
- php: hhvm
|
||||
env: DB=pgsql # driver currently unsupported by HHVM
|
||||
- php: hhvm
|
||||
env: DB=mysqli # driver currently unsupported by HHVM
|
||||
- php: hhvm-nightly
|
||||
env: DB=pgsql # driver currently unsupported by HHVM
|
||||
- php: hhvm-nightly
|
||||
env: DB=mysqli # driver currently unsupported by HHVM
|
||||
allow_failures:
|
||||
- php: nightly
|
||||
- stage: Code Quality
|
||||
env: DB=none CODING_STANDARDS
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.composer/cache
|
||||
- php: 7.0
|
||||
- php: hhvm-nightly # hhvm-nightly currently chokes on composer installation
|
||||
|
||||
@@ -41,32 +41,19 @@ Please try to add a test for your pull-request.
|
||||
* If you want to contribute new functionality add unit- or functional tests
|
||||
depending on the scope of the feature.
|
||||
|
||||
You can run the unit-tests by calling ``vendor/bin/phpunit`` from the root of the project.
|
||||
You can run the unit-tests by calling ``phpunit`` from the root of the project.
|
||||
It will run all the tests with an in memory SQLite database.
|
||||
|
||||
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/orm.git
|
||||
cd orm
|
||||
curl -sS https://getcomposer.org/installer | php --
|
||||
./composer.phar install
|
||||
```
|
||||
|
||||
To run the testsuite against another database, copy the ``phpunit.xml.dist``
|
||||
to for example ``mysql.phpunit.xml`` and edit the parameters. You can
|
||||
take a look at the ``tests/travis`` folder for some examples. Then run:
|
||||
|
||||
vendor/bin/phpunit -c mysql.phpunit.xml
|
||||
|
||||
If you do not provide these parameters, the test suite will use an in-memory
|
||||
sqlite database.
|
||||
phpunit -c mysql.phpunit.xml
|
||||
|
||||
Tips for creating unit tests:
|
||||
Tips for creating unittests:
|
||||
|
||||
1. If you put a test into the `Ticket` namespace as described above, put the testcase and all entities into the same class.
|
||||
See `https://github.com/doctrine/orm/tree/master/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2306Test.php` for an
|
||||
See `https://github.com/doctrine/doctrine2/tree/master/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2306Test.php` for an
|
||||
example.
|
||||
|
||||
## Travis
|
||||
@@ -75,6 +62,13 @@ We automatically run your pull request through [Travis CI](http://www.travis-ci.
|
||||
against SQLite, MySQL and PostgreSQL. If you break the tests, we cannot merge your code,
|
||||
so please make sure that your code is working before opening up a Pull-Request.
|
||||
|
||||
## DoctrineBot, Tickets and Jira
|
||||
|
||||
DoctrineBot will synchronize your Pull-Request into our [Jira](http://www.doctrine-project.org).
|
||||
Make sure to add any existing Jira ticket into the Pull-Request Title, for example:
|
||||
|
||||
"[DDC-123] My Pull Request"
|
||||
|
||||
## Getting merged
|
||||
|
||||
Please allow us time to review your pull requests. We will give our best to review
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2006-2015 Doctrine Project
|
||||
Copyright (c) 2006-2012 Doctrine Project
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
||||
30
README.markdown
Normal file
30
README.markdown
Normal file
@@ -0,0 +1,30 @@
|
||||
| [Master][Master] | [2.4][2.4] | [2.3][2.3] | [2.2][2.2] | [2.1][2.1] |
|
||||
|:----------------:|:----------:|:----------:|:----------:|:----------:|
|
||||
| [![Build status][Master image]][Master] | [![Build status][2.4 image]][2.4] | [![Build status][2.3 image]][2.3] | [![Build status][2.2 image]][2.2] | [![Build status][2.1 image]][2.1] |
|
||||
| [![Coverage Status][Master coverage image]][Master coverage] |
|
||||
|
||||
Doctrine 2 is an object-relational mapper (ORM) for PHP 5.4+ that provides transparent persistence
|
||||
for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features
|
||||
is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL),
|
||||
inspired by Hibernate's HQL. This provides developers with a powerful alternative to SQL that maintains flexibility
|
||||
without requiring unnecessary code duplication.
|
||||
|
||||
## More resources:
|
||||
|
||||
* [Website](http://www.doctrine-project.org)
|
||||
* [Documentation](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/index.html)
|
||||
* [Issue Tracker](http://www.doctrine-project.org/jira/browse/DDC)
|
||||
* [Downloads](http://github.com/doctrine/doctrine2/downloads)
|
||||
|
||||
[Master image]: https://travis-ci.org/doctrine/doctrine2.svg?branch=master
|
||||
[Master]: https://travis-ci.org/doctrine/doctrine2
|
||||
[Master coverage image]: https://coveralls.io/repos/doctrine/doctrine2/badge.png?branch=master
|
||||
[Master coverage]: https://coveralls.io/r/doctrine/doctrine2?branch=master
|
||||
[2.4 image]: https://travis-ci.org/doctrine/doctrine2.svg?branch=2.4
|
||||
[2.4]: https://github.com/doctrine/doctrine2/tree/2.4
|
||||
[2.3 image]: https://travis-ci.org/doctrine/doctrine2.svg?branch=2.3
|
||||
[2.3]: https://github.com/doctrine/doctrine2/tree/2.3
|
||||
[2.2 image]: https://travis-ci.org/doctrine/doctrine2.svg?branch=2.2
|
||||
[2.2]: https://github.com/doctrine/doctrine2/tree/2.2
|
||||
[2.1 image]: https://travis-ci.org/doctrine/doctrine2.svg?branch=2.1.x
|
||||
[2.1]: https://github.com/doctrine/doctrine2/tree/2.1.x
|
||||
26
README.md
26
README.md
@@ -1,26 +0,0 @@
|
||||
| [Master][Master] | [2.5][2.5] |
|
||||
|:----------------:|:----------:|
|
||||
| [![Build status][Master image]][Master] | [![Build status][2.5 image]][2.5] |
|
||||
| [![Coverage Status][Master coverage image]][Master coverage] | [![Coverage Status][2.5 coverage image]][2.5 coverage] |
|
||||
|
||||
Doctrine 2 is an object-relational mapper (ORM) for PHP 7.1+ that provides transparent persistence
|
||||
for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features
|
||||
is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL),
|
||||
inspired by Hibernate's HQL. This provides developers with a powerful alternative to SQL that maintains flexibility
|
||||
without requiring unnecessary code duplication.
|
||||
|
||||
|
||||
## More resources:
|
||||
|
||||
* [Website](http://www.doctrine-project.org)
|
||||
* [Documentation](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/index.html)
|
||||
|
||||
|
||||
[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/orm/blob/master/docs/en/reference/security.rst)
|
||||
- [ORM Security Page](https://github.com/doctrine/doctrine2/blob/master/docs/en/reference/security.rst)
|
||||
|
||||
If you find a Security bug in Doctrine, please report it on Jira and change the
|
||||
Security Level to "Security Issues". It will be visible to Doctrine Core
|
||||
|
||||
190
UPGRADE.md
190
UPGRADE.md
@@ -1,195 +1,5 @@
|
||||
# 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.
|
||||
|
||||
## 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.
|
||||
|
||||
## Minor BC BREAK: removed `Doctrine\ORM\Query\QueryException::associationPathInverseSideNotSupported()`
|
||||
|
||||
Method `Doctrine\ORM\Query\QueryException::associationPathInverseSideNotSupported()`
|
||||
now has a required parameter `$pathExpr`.
|
||||
|
||||
## Minor BC BREAK: removed `Doctrine\ORM\Query\Parser#isInternalFunction()`
|
||||
|
||||
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/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/orm/pull/6500)
|
||||
|
||||
## PHP 7.1 is now required
|
||||
|
||||
Doctrine 2.6 now requires PHP 7.1 or newer.
|
||||
|
||||
As a consequence, automatic cache setup in Doctrine\ORM\Tools\Setup::create*Configuration() was changed:
|
||||
- APCu extension (ext-apcu) will now be used instead of abandoned APC (ext-apc).
|
||||
- Memcached extension (ext-memcached) will be used instead of obsolete Memcache (ext-memcache).
|
||||
- XCache support was dropped as it doesn't work with PHP 7.
|
||||
|
||||
# Upgrade to 2.5
|
||||
|
||||
## 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/orm/pull/5600).
|
||||
|
||||
## Minor BC BREAK: removed $className parameter on `AbstractEntityInheritancePersister#getSelectJoinColumnSQL()`
|
||||
|
||||
As `$className` parameter was not used in the method, it was safely removed.
|
||||
|
||||
## Minor BC BREAK: query cache key time is now a float
|
||||
|
||||
As of 2.5.5, the `QueryCacheEntry#time` property will contain a float value
|
||||
instead of an integer in order to have more precision and also to be consistent
|
||||
with the `TimestampCacheEntry#time`.
|
||||
|
||||
## Minor BC BREAK: discriminator map must now include all non-transient classes
|
||||
|
||||
It is now required that you declare the root of an inheritance in the
|
||||
|
||||
@@ -31,7 +31,7 @@ $helperSet = null;
|
||||
if (file_exists($configFile)) {
|
||||
if ( ! is_readable($configFile)) {
|
||||
trigger_error(
|
||||
'Configuration file [' . $configFile . '] does not have read permission.', E_USER_ERROR
|
||||
'Configuration file [' . $configFile . '] does not have read permission.', E_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
13
bin/doctrine.php
Normal file → Executable file
13
bin/doctrine.php
Normal file → Executable file
@@ -20,19 +20,16 @@
|
||||
use Symfony\Component\Console\Helper\HelperSet;
|
||||
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||
|
||||
$autoloadFiles = [
|
||||
__DIR__ . '/../vendor/autoload.php',
|
||||
__DIR__ . '/../../../autoload.php'
|
||||
];
|
||||
$autoloadFiles = array(__DIR__ . '/../vendor/autoload.php',
|
||||
__DIR__ . '/../../../autoload.php');
|
||||
|
||||
foreach ($autoloadFiles as $autoloadFile) {
|
||||
if (file_exists($autoloadFile)) {
|
||||
require_once $autoloadFile;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$directories = [getcwd(), getcwd() . DIRECTORY_SEPARATOR . 'config'];
|
||||
$directories = array(getcwd(), getcwd() . DIRECTORY_SEPARATOR . 'config');
|
||||
|
||||
$configFile = null;
|
||||
foreach ($directories as $directory) {
|
||||
@@ -53,7 +50,7 @@ if ( ! is_readable($configFile)) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$commands = [];
|
||||
$commands = array();
|
||||
|
||||
$helperSet = require $configFile;
|
||||
|
||||
@@ -66,4 +63,4 @@ if ( ! ($helperSet instanceof HelperSet)) {
|
||||
}
|
||||
}
|
||||
|
||||
ConsoleRunner::run($helperSet, $commands);
|
||||
\Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet, $commands);
|
||||
|
||||
@@ -3,55 +3,46 @@
|
||||
"type": "library",
|
||||
"description": "Object-Relational-Mapper for PHP",
|
||||
"keywords": ["orm", "database"],
|
||||
"homepage": "https://www.doctrine-project.org/projects/orm.html",
|
||||
"homepage": "http://www.doctrine-project.org",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
|
||||
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
|
||||
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
|
||||
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"},
|
||||
{"name": "Marco Pivetta", "email": "ocramius@gmail.com"}
|
||||
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"}
|
||||
],
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"require": {
|
||||
"php": "^7.1",
|
||||
"php": ">=5.4",
|
||||
"ext-pdo": "*",
|
||||
"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",
|
||||
"symfony/console": "^3.0|^4.0|^5.0"
|
||||
"doctrine/collections": "~1.2",
|
||||
"doctrine/dbal": ">=2.5-dev,<2.6-dev",
|
||||
"doctrine/instantiator": "~1.0.1",
|
||||
"doctrine/common": ">=2.5-dev,<2.7-dev",
|
||||
"doctrine/cache": "~1.4",
|
||||
"symfony/console": "~2.5|~3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^5.0",
|
||||
"phpunit/phpunit": "^7.5",
|
||||
"symfony/yaml": "^3.4|^4.0|^5.0"
|
||||
"symfony/yaml": "~2.3|~3.0",
|
||||
"phpunit/phpunit": "~4.0",
|
||||
"satooshi/php-coveralls": "dev-master"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/yaml": "If you want to use YAML Metadata Mapping Driver"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Doctrine\\ORM\\": "lib/Doctrine/ORM" }
|
||||
"psr-0": { "Doctrine\\ORM\\": "lib/" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Doctrine\\Tests\\": "tests/Doctrine/Tests",
|
||||
"Doctrine\\Performance\\": "tests/Doctrine/Performance"
|
||||
}
|
||||
"psr-0": { "Doctrine\\Tests\\": "tests/" }
|
||||
},
|
||||
"bin": ["bin/doctrine"],
|
||||
"bin": ["bin/doctrine", "bin/doctrine.php"],
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.7.x-dev"
|
||||
"dev-master": "2.6.x-dev"
|
||||
}
|
||||
},
|
||||
"archive": {
|
||||
"exclude": ["!vendor", "tests", "*phpunit.xml", ".travis.yml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp"]
|
||||
"exclude": ["!vendor", "tests", "*phpunit.xml", ".travis.yml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp", "*coveralls.yml"]
|
||||
}
|
||||
}
|
||||
|
||||
2744
composer.lock
generated
2744
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1,8 @@
|
||||
# Doctrine ORM Documentation
|
||||
|
||||
## How to Generate:
|
||||
Using Ubuntu 14.04 LTS:
|
||||
## How to Generate
|
||||
|
||||
1. Run ./bin/install-dependencies.sh
|
||||
2. Run ./bin/generate-docs.sh
|
||||
|
||||
It will generate the documentation into the build directory of the checkout.
|
||||
|
||||
|
||||
## Theme issues
|
||||
|
||||
If you get a "Theme error", check if the `en/_theme` subdirectory is empty,
|
||||
in which case you will need to run:
|
||||
|
||||
1. git submodule init
|
||||
2. git submodule update
|
||||
It will generate the documentation into the build directory of the checkout.
|
||||
@@ -1,2 +1,4 @@
|
||||
#!/bin/bash
|
||||
sudo apt-get update && sudo apt-get install -y python2.7 python-sphinx python-pygments
|
||||
sudo apt-get install python25 python25-dev texlive-full rubber
|
||||
sudo easy_install pygments
|
||||
sudo easy_install sphinx
|
||||
Submodule docs/en/_theme updated: 6f1bc8bead...dc294be1db
710
docs/en/changelog/migration_2_5.rst
Normal file
710
docs/en/changelog/migration_2_5.rst
Normal file
@@ -0,0 +1,710 @@
|
||||
What is new in Doctrine ORM 2.5?
|
||||
================================
|
||||
|
||||
This document describes changes between Doctrine ORM 2.4 and 2.5 (currently in
|
||||
Beta). It contains a description of all the new features and sections
|
||||
about behavioral changes and potential backwards compatibility breaks.
|
||||
Please review this document carefully when updating to Doctrine 2.5.
|
||||
|
||||
First note, that with the ORM 2.5 release we are dropping support
|
||||
for PHP 5.3. We are enforcing this with Composer, servers without
|
||||
at least PHP 5.4 will not allow installing Doctrine 2.5.
|
||||
|
||||
New Features and Improvements
|
||||
-----------------------------
|
||||
|
||||
Events: PostLoad now triggered after associations are loaded
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Before Doctrine 2.5 if you had an entity with a ``@PostLoad`` event
|
||||
defined then Doctrine would trigger listeners after the fields were
|
||||
loaded, but before assocations are available.
|
||||
|
||||
- `DDC-54 <http://doctrine-project.org/jira/browse/DDC-54>`_
|
||||
- `Commit <https://github.com/doctrine/doctrine2/commit/a906295c65f1516737458fbee2f6fa96254f27a5>`_
|
||||
|
||||
Events: Add API to programatically add event listeners to Entity
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When developing third party libraries or decoupled applications
|
||||
it can be interesting to develop an entity listener without knowing
|
||||
the entities that require this listener.
|
||||
|
||||
You can now attach entity listeners to entities using the
|
||||
``AttachEntityListenersListener`` class, which is listening to the
|
||||
``loadMetadata`` event that is fired once for every entity during
|
||||
metadata generation:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Tools\AttachEntityListenersListener;
|
||||
use Doctrine\ORM\Events;
|
||||
|
||||
$listener = new AttachEntityListenersListener();
|
||||
$listener->addEntityListener(
|
||||
'MyProject\Entity\User', 'MyProject\Listener\TimestampableListener',
|
||||
Events::prePersist, 'onPrePersist'
|
||||
);
|
||||
|
||||
$evm->addEventListener(Events::loadClassMetadata, $listener);
|
||||
|
||||
class TimestampableListener
|
||||
{
|
||||
public function onPrePersist($event)
|
||||
{
|
||||
$entity = $event->getEntity();
|
||||
$entity->setCreated(new \DateTime('now'));
|
||||
}
|
||||
}
|
||||
|
||||
Embeddedable Objects
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Doctrine now supports creating multiple PHP objects from one database table
|
||||
implementing a feature called "Embeddedable Objects". Next to an ``@Entity``
|
||||
class you can now define a class that is embeddable into a database table of an
|
||||
entity using the ``@Embeddable`` annotation. Embeddable objects can never be
|
||||
saved, updated or deleted on their own, only as part of an entity (called
|
||||
"root-entity" or "aggregate"). Consequently embeddables don't have a primary
|
||||
key, they are identified only by their values.
|
||||
|
||||
Example of defining and using embeddables classes:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
/** @Entity */
|
||||
class Product
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private $id;
|
||||
|
||||
/** @Embedded(class = "Money") */
|
||||
private $price;
|
||||
}
|
||||
|
||||
/** @Embeddable */
|
||||
class Money
|
||||
{
|
||||
/** @Column(type = "decimal") */
|
||||
private $value;
|
||||
|
||||
/** @Column(type = "string") */
|
||||
private $currency = 'EUR';
|
||||
}
|
||||
|
||||
You can read more on the features of Embeddables objects `in the documentation
|
||||
<http://docs.doctrine-project.org/en/latest/tutorials/embeddables.html>`_.
|
||||
|
||||
This feature was developed by external contributor `Johannes Schmitt
|
||||
<https://twitter.com/schmittjoh>`_
|
||||
|
||||
- `DDC-93 <http://doctrine-project.org/jira/browse/DDC-93>`_
|
||||
- `Pull Request <https://github.com/doctrine/doctrine2/pull/835>`_
|
||||
|
||||
Second-Level-Cache
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Since version 2.0 of Doctrine, fetching the same object twice by primary key
|
||||
would result in just one query. This was achieved by the identity map pattern
|
||||
(first-level-cache) that kept entities in memory.
|
||||
|
||||
The newly introduced second-level-cache works a bit differently. Instead
|
||||
of saving objects in memory, it saves them in a fast in-memory cache such
|
||||
as Memcache, Redis, Riak or MongoDB. Additionally it allows saving the result
|
||||
of more complex queries than by primary key. Summarized this feature works
|
||||
like the existing Query result cache, but it is much more powerful.
|
||||
|
||||
As an example lets cache an entity Country that is a relation to the User
|
||||
entity. We always want to display the country, but avoid the additional
|
||||
query to this table.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
* @Cache(usage="READ_ONLY", region="country_region")
|
||||
*/
|
||||
class Country
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @GeneratedValue
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @Column(unique=true)
|
||||
*/
|
||||
protected $name;
|
||||
}
|
||||
|
||||
In this example we have specified a caching region name called
|
||||
``country_region``, which we have to configure now on the EntityManager:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$config->setSecondLevelCacheEnabled();
|
||||
|
||||
$cacheConfig = $config->getSecondLevelCacheConfiguration();
|
||||
$regionConfig = $cacheConfig->getRegionsConfiguration();
|
||||
$regionConfig->setLifetime('country_region', 3600);
|
||||
|
||||
Now Doctrine will first check for the data of any country in the cache
|
||||
instead of the database.
|
||||
|
||||
- `Documentation
|
||||
<http://docs.doctrine-project.org/en/latest/reference/second-level-cache.html>`_
|
||||
- `Pull Request <https://github.com/doctrine/doctrine2/pull/808>`_
|
||||
|
||||
Criteria API: Support for ManyToMany assocations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
We introduced support for querying collections using the `Criteria API
|
||||
<http://docs.doctrine-project.org/en/latest/reference/working-with-associations.html#filtering-collections>`_
|
||||
in 2.4. This only worked efficently for One-To-Many assocations, not for
|
||||
Many-To-Many. With the start of 2.5 also Many-To-Many associations get queried
|
||||
instead of loading them into memory.
|
||||
|
||||
Criteria API: Add new contains() expression
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
It is now possible to use the Criteria API to check for string contains needle
|
||||
using ``contains()``. This translates to using a ``column LIKE '%needle%'`` SQL
|
||||
condition.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use \Doctrine\Common\Collections\Criteria;
|
||||
|
||||
$criteria = Criteria::create()
|
||||
->where(Criteria::expr()->contains('name', 'Benjamin'));
|
||||
|
||||
$users = $repository->matching($criteria);
|
||||
|
||||
Criteria API: Support for EXTRA_LAZY
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A collection that is marked as ``fetch="EXTRA_LAZY"`` will now return another
|
||||
lazy collection when using ``Collection::matching($criteria)``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
class Post
|
||||
{
|
||||
/** @OneToMany(targetEntity="Comment", fetch="EXTRA_LAZY") */
|
||||
private $comments;
|
||||
}
|
||||
|
||||
$criteria = Criteria::create()
|
||||
->where(Criteria->expr()->eq("published", 1));
|
||||
|
||||
$publishedComments = $post->getComments()->matching($criteria);
|
||||
|
||||
echo count($publishedComments);
|
||||
|
||||
The lazy criteria currently supports the ``count()`` and ``contains()``
|
||||
functionality lazily. All other operations of the ``Collection`` interface
|
||||
trigger a full load of the collection.
|
||||
|
||||
This feature was contributed by `Michaël Gallego <https://github.com/bakura10>`_.
|
||||
|
||||
- `Pull Request #1 <https://github.com/doctrine/doctrine2/pull/882>`_
|
||||
- `Pull Request #2 <https://github.com/doctrine/doctrine2/pull/1032>`_
|
||||
|
||||
Mapping: Allow configuring Index flags
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
It is now possible to control the index flags in the DBAL
|
||||
schema abstraction from the ORM using metadata. This was possible
|
||||
only with a schema event listener before.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @Table(name="product", indexes={@Index(columns={"description"},flags={"fulltext"})})
|
||||
*/
|
||||
class Product
|
||||
{
|
||||
private $description;
|
||||
}
|
||||
|
||||
This feature was contributed by `Adrian Olek <https://github.com/adrianolek>`_.
|
||||
|
||||
- `Pull Request <https://github.com/doctrine/doctrine2/pull/973>`_
|
||||
|
||||
SQLFilter API: Check if a parameter is set
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can now check in your SQLFilter if a parameter was set. This allows
|
||||
to more easily control which features of a filter to enable or disable.
|
||||
|
||||
Extending on the locale example of the documentation:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class MyLocaleFilter extends SQLFilter
|
||||
{
|
||||
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
|
||||
{
|
||||
if (!$targetEntity->reflClass->implementsInterface('LocaleAware')) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!$this->hasParameter('locale')) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return $targetTableAlias.'.locale = ' . $this->getParameter('locale');
|
||||
}
|
||||
}
|
||||
|
||||
This feature was contributed by `Miroslav Demovic <https://github.com/mdemo>`_
|
||||
|
||||
- `Pull Request <https://github.com/doctrine/doctrine2/pull/963>`_
|
||||
|
||||
|
||||
EXTRA_LAZY Improvements
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
1. Efficient query when using EXTRA_LAZY and containsKey
|
||||
|
||||
When calling ``Collection::containsKey($key)`` on one-to-many and many-to-many
|
||||
collections using ``indexBy`` and ``EXTRA_LAZY`` a query is now executed to check
|
||||
for the existance for the item. Prevoiusly this operation was performed in memory
|
||||
by loading all entities of the collection.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
class User
|
||||
{
|
||||
/** @OneToMany(targetEntity="Group", indexBy="id") */
|
||||
private $groups;
|
||||
}
|
||||
|
||||
if ($user->getGroups()->containsKey($groupId)) {
|
||||
echo "User is in group $groupId\n";
|
||||
}
|
||||
|
||||
This feature was contributed by `Asmir Mustafic <https://github.com/goetas>`_
|
||||
|
||||
- `Pull Request <https://github.com/doctrine/doctrine2/pull/937>`_
|
||||
|
||||
2. Add EXTRA_LAZY Support for get() for owning and inverse many-to-many
|
||||
|
||||
This was contributed by `Sander Marechal <https://github.com/sandermarechal>`_.
|
||||
|
||||
Improve efficiency of One-To-Many EAGER
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When marking a one-to-many association with ``fetch="EAGER"`` it will now
|
||||
execute one query less than before and work correctly in combination with
|
||||
``indexBy``.
|
||||
|
||||
Better support for EntityManagerInterface
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Many of the locations where previously only the ``Doctrine\ORM\EntityManager``
|
||||
was allowed are now changed to accept the ``EntityManagerInterface`` that was
|
||||
introduced in 2.4. This allows you to more easily use the decorator pattern
|
||||
to extend the EntityManager if you need. It's still not replaced everywhere,
|
||||
so you still have to be careful.
|
||||
|
||||
DQL Improvements
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
1. It is now possible to add functions to the ``ORDER BY`` clause in DQL statements:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$dql = "SELECT u FROM User u ORDER BY CONCAT(u.username, u.name)";
|
||||
|
||||
2. Support for functions in ``IS NULL`` expressions:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$dql = "SELECT u.name FROM User u WHERE MAX(u.name) IS NULL";
|
||||
|
||||
3. A ``LIKE`` expression is now suported in ``HAVING`` clause.
|
||||
|
||||
4. Subselects are now supported inside a ``NEW()`` expression:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$dql = "SELECT new UserDTO(u.name, SELECT count(g.id) FROM Group g WHERE g.id = u.id) FROM User u";
|
||||
|
||||
5. ``MEMBER OF`` expression now allows to filter for more than one result:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$dql = "SELECT u FROM User u WHERE :groups MEMBER OF u.groups";
|
||||
$query = $entityManager->createQuery($dql);
|
||||
$query->setParameter('groups', array(1, 2, 3));
|
||||
|
||||
$users = $query->getResult();
|
||||
|
||||
6. Expressions inside ``COUNT()`` now allowed
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$dql = "SELECT COUNT(DISTINCT CONCAT(u.name, u.lastname)) FROM User u";
|
||||
|
||||
7. Add support for ``HOUR`` in ``DATE_ADD()``/``DATE_SUB()`` functions
|
||||
|
||||
Custom DQL Functions: Add support for factories
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Previously custom DQL functions could only be provided with their
|
||||
full-qualified class-name, preventing runtime configuration through
|
||||
dependency injection.
|
||||
|
||||
A simplistic approach has been contributed by `Matthieu Napoli
|
||||
<https://github.com/mnapoli>`_ to pass a callback instead that resolves
|
||||
the function:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
|
||||
$config->addCustomNumericFunction(
|
||||
'IS_PUBLISHED', function($funcName) use ($currentSiteId) {
|
||||
return new IsPublishedFunction($currentSiteId);
|
||||
}
|
||||
);
|
||||
|
||||
Query API: WHERE IN Query using a Collection as parameter
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When performing a ``WHERE IN`` query for a collection of entities you can
|
||||
now pass the array collection of entities as a parameter value to the query
|
||||
object:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
$categories = $rootCategory->getChildren();
|
||||
|
||||
$queryBuilder
|
||||
->select('p')
|
||||
->from('Product', 'p')
|
||||
->where('p.category IN (:categories)')
|
||||
->setParameter('categories', $categories)
|
||||
;
|
||||
|
||||
This feature was contributed by `Michael Perrin
|
||||
<https://github.com/michaelperrin>`_.
|
||||
|
||||
- `Pull Request <https://github.com/doctrine/doctrine2/pull/590>`_
|
||||
- `DDC-2319 <http://doctrine-project.org/jira/browse/DDC-2319>`_
|
||||
|
||||
Query API: Add suport for default Query Hints
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To configure multiple different features such as custom AST Walker, fetch modes,
|
||||
locking and other features affecting DQL generation we have had a feature
|
||||
called "query hints" since version 2.0.
|
||||
|
||||
It is now possible to add query hints that are always enabled for every Query:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$config->setDefaultQueryHints(
|
||||
'doctrine.customOutputWalker' => 'MyProject\CustomOutputWalker'
|
||||
);
|
||||
|
||||
This feature was contributed by `Artur Eshenbrener
|
||||
<https://github.com/Strate>`_.
|
||||
|
||||
- `Pull Request <https://github.com/doctrine/doctrine2/pull/863>`_
|
||||
|
||||
ResultSetMappingBuilder: Add support for Single-Table Inheritance
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Before 2.5 the ResultSetMappingBuilder did not work with entities
|
||||
that are using Single-Table-Inheritance. This restriction was lifted
|
||||
by adding the missing support.
|
||||
|
||||
YAML Mapping: Many-To-Many doesnt require join column definition
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In Annotations and XML it was not necessary using conventions for naming
|
||||
the many-to-many join column names, in YAML it was not possible however.
|
||||
|
||||
A many-to-many definition in YAML is now possible using this minimal
|
||||
definition:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
manyToMany:
|
||||
groups:
|
||||
targetEntity: Group
|
||||
joinTable:
|
||||
name: users_groups
|
||||
|
||||
Schema Validator Command: Allow to skip sub-checks
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The Schema Validator command executes two independent checks
|
||||
for validity of the mappings and if the schema is synchronized
|
||||
correctly. It is now possible to skip any of the two steps
|
||||
when executing the command:
|
||||
|
||||
::
|
||||
|
||||
$ php vendor/bin/doctrine orm:validate-schema --skip-mapping
|
||||
$ php vendor/bin/doctrine orm:validate-schema --skip-sync
|
||||
|
||||
This allows you to write more specialized continuous integration and automation
|
||||
checks. When no changes are found the command returns the exit code 0
|
||||
and 1, 2 or 3 when failing because of mapping, sync or both.
|
||||
|
||||
EntityGenerator Command: Avoid backups
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When calling the EntityGenerator for an existing entity, Doctrine would
|
||||
create a backup file every time to avoid loosing changes to the code.
|
||||
You can now skip generating the backup file by passing the ``--no-backup``
|
||||
flag:
|
||||
|
||||
::
|
||||
|
||||
$ php vendor/bin/doctrine orm:generate-entities src/ --no-backup
|
||||
|
||||
Support for Objects as Identifiers
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
It is now possible to use Objects as identifiers for Entities
|
||||
as long as they implement the magic method ``__toString()``.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
class UserId
|
||||
{
|
||||
private $value;
|
||||
|
||||
public function __construct($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return (string)$this->value;
|
||||
}
|
||||
}
|
||||
|
||||
class User
|
||||
{
|
||||
/** @Id @Column(type="userid") */
|
||||
private $id;
|
||||
|
||||
public function __construct(UserId $id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
}
|
||||
|
||||
class UserIdType extends \Doctrine\DBAL\Types\Type
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
Doctrine\DBAL\Types\Type::addType('userid', 'MyProject\UserIdType');
|
||||
|
||||
Behavioral Changes (BC Breaks)
|
||||
------------------------------
|
||||
|
||||
NamingStrategy interface changed
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``Doctrine\ORM\Mapping\NamingStrategyInterface`` changed slightly
|
||||
to pass the Class Name of the entity into the join column name generation:
|
||||
|
||||
::
|
||||
|
||||
- function joinColumnName($propertyName);
|
||||
+ function joinColumnName($propertyName, $className = null);
|
||||
|
||||
It also received a new method for supporting embeddables:
|
||||
|
||||
::
|
||||
|
||||
public function embeddedFieldToColumnName($propertyName, $embeddedColumnName);
|
||||
|
||||
Minor BC BREAK: EntityManagerInterface instead of EntityManager in type-hints
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As of 2.5, classes requiring the ``EntityManager`` in any method signature will now require
|
||||
an ``EntityManagerInterface`` instead.
|
||||
If you are extending any of the following classes, then you need to check following
|
||||
signatures:
|
||||
|
||||
- ``Doctrine\ORM\Tools\DebugUnitOfWorkListener#dumpIdentityMap(EntityManagerInterface $em)``
|
||||
- ``Doctrine\ORM\Mapping\ClassMetadataFactory#setEntityManager(EntityManagerInterface $em)``
|
||||
|
||||
Minor BC BREAK: Custom Hydrators API change
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As of 2.5, ``AbstractHydrator`` does not enforce the usage of cache as part of
|
||||
API, and now provides you a clean API for column information through the method
|
||||
``hydrateColumnInfo($column)``.
|
||||
Cache variable being passed around by reference is no longer needed since
|
||||
Hydrators are per query instantiated since Doctrine 2.4.
|
||||
|
||||
- `DDC-3060 <http://doctrine-project.org/jira/browse/DDC-3060>`_
|
||||
|
||||
Minor BC BREAK: All non-transient classes in an inheritance must be part of the inheritance map
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As of 2.5, classes, if you define an inheritance map for an inheritance tree, you are required
|
||||
to map all non-transient classes in that inheritance, including the root of the inheritance.
|
||||
|
||||
So far, the root of the inheritance was allowed to be skipped in the inheritance map: this is
|
||||
not possible anymore, and if you don't plan to persist instances of that class, then you should
|
||||
either:
|
||||
|
||||
- make that class as ``abstract``
|
||||
- add that class to your inheritance map
|
||||
|
||||
If you fail to do so, then a ``Doctrine\ORM\Mapping\MappingException`` will be thrown.
|
||||
|
||||
|
||||
- `DDC-3300 <http://doctrine-project.org/jira/browse/DDC-3300>`_
|
||||
- `DDC-3503 <http://doctrine-project.org/jira/browse/DDC-3503>`_
|
||||
|
||||
Minor BC BREAK: Entity based EntityManager#clear() calls follow cascade detach
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Whenever ``EntityManager#clear()`` method gets called with a given entity class
|
||||
name, until 2.4, it was only detaching the specific requested entity.
|
||||
As of 2.5, ``EntityManager`` will follow configured cascades, providing a better
|
||||
memory management since associations will be garbage collected, optimizing
|
||||
resources consumption on long running jobs.
|
||||
|
||||
Updates on entities scheduled for deletion are no longer processed
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In Doctrine 2.4, if you modified properties of an entity scheduled for deletion, UnitOfWork would
|
||||
produce an ``UPDATE`` statement to be executed right before the ``DELETE`` statement. The entity in question
|
||||
was therefore present in ``UnitOfWork#entityUpdates``, which means that ``preUpdate`` and ``postUpdate``
|
||||
listeners were (quite pointlessly) called. In ``preFlush`` listeners, it used to be possible to undo
|
||||
the scheduled deletion for updated entities (by calling ``persist()`` if the entity was found in both
|
||||
``entityUpdates`` and ``entityDeletions``). This does not work any longer, because the entire changeset
|
||||
calculation logic is optimized away.
|
||||
|
||||
Minor BC BREAK: Default lock mode changed from LockMode::NONE to null in method signatures
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A misconception concerning default lock mode values in method signatures lead to unexpected behaviour
|
||||
in SQL statements on SQL Server. With a default lock mode of ``LockMode::NONE`` throughout the
|
||||
method signatures in ORM, the table lock hint ``WITH (NOLOCK)`` was appended to all locking related
|
||||
queries by default. This could result in unpredictable results because an explicit ``WITH (NOLOCK)``
|
||||
table hint tells SQL Server to run a specific query in transaction isolation level READ UNCOMMITTED
|
||||
instead of the default READ COMMITTED transaction isolation level.
|
||||
Therefore there now is a distinction between ``LockMode::NONE`` and ``null`` to be able to tell
|
||||
Doctrine whether to add table lock hints to queries by intention or not. To achieve this, the following
|
||||
method signatures have been changed to declare ``$lockMode = null`` instead of ``$lockMode = LockMode::NONE``:
|
||||
|
||||
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#getSelectSQL()``
|
||||
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#load()``
|
||||
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#refresh()``
|
||||
- ``Doctrine\ORM\Decorator\EntityManagerDecorator#find()``
|
||||
- ``Doctrine\ORM\EntityManager#find()``
|
||||
- ``Doctrine\ORM\EntityRepository#find()``
|
||||
- ``Doctrine\ORM\Persisters\BasicEntityPersister#getSelectSQL()``
|
||||
- ``Doctrine\ORM\Persisters\BasicEntityPersister#load()``
|
||||
- ``Doctrine\ORM\Persisters\BasicEntityPersister#refresh()``
|
||||
- ``Doctrine\ORM\Persisters\EntityPersister#getSelectSQL()``
|
||||
- ``Doctrine\ORM\Persisters\EntityPersister#load()``
|
||||
- ``Doctrine\ORM\Persisters\EntityPersister#refresh()``
|
||||
- ``Doctrine\ORM\Persisters\JoinedSubclassPersister#getSelectSQL()``
|
||||
|
||||
You should update signatures for these methods if you have subclassed one of the above classes.
|
||||
Please also check the calling code of these methods in your application and update if necessary.
|
||||
|
||||
.. note::
|
||||
|
||||
This in fact is really a minor BC BREAK and should not have any affect on database vendors
|
||||
other than SQL Server because it is the only one that supports and therefore cares about
|
||||
``LockMode::NONE``. It's really just a FIX for SQL Server environments using ORM.
|
||||
|
||||
Minor BC BREAK: __clone method not called anymore when entities are instantiated via metadata API
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As of PHP 5.6, instantiation of new entities is deferred to the
|
||||
`doctrine/instantiator <https://github.com/doctrine/instantiator>`_ library, which will avoid calling ``__clone``
|
||||
or any public API on instantiated objects.
|
||||
|
||||
BC BREAK: DefaultRepositoryFactory is now final
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Please implement the ``Doctrine\ORM\Repository\RepositoryFactory`` interface instead of extending
|
||||
the ``Doctrine\ORM\Repository\DefaultRepositoryFactory``.
|
||||
|
||||
BC BREAK: New object expression DQL queries now respects user provided aliasing and not return consumed fields
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When executing DQL queries with new object expressions, instead of returning
|
||||
DTOs numerically indexes, it will now respect user provided aliases. Consider
|
||||
the following query:
|
||||
|
||||
::
|
||||
|
||||
SELECT new UserDTO(u.id,u.name) as user,new AddressDTO(a.street,a.postalCode) as address, a.id as addressId
|
||||
FROM User u INNER JOIN u.addresses a WITH a.isPrimary = true
|
||||
|
||||
Previously, your result would be similar to this:
|
||||
|
||||
::
|
||||
|
||||
array(
|
||||
0=>array(
|
||||
0=>{UserDTO object},
|
||||
1=>{AddressDTO object},
|
||||
2=>{u.id scalar},
|
||||
3=>{u.name scalar},
|
||||
4=>{a.street scalar},
|
||||
5=>{a.postalCode scalar},
|
||||
'addressId'=>{a.id scalar},
|
||||
),
|
||||
...
|
||||
)
|
||||
|
||||
From now on, the resultset will look like this:
|
||||
|
||||
::
|
||||
|
||||
array(
|
||||
0=>array(
|
||||
'user'=>{UserDTO object},
|
||||
'address'=>{AddressDTO object},
|
||||
'addressId'=>{a.id scalar}
|
||||
),
|
||||
...
|
||||
)
|
||||
@@ -11,7 +11,7 @@
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os, datetime
|
||||
import sys, os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
@@ -38,7 +38,7 @@ master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Doctrine 2 ORM'
|
||||
copyright = u'2010-%y, Doctrine Project Team'.format(datetime.date.today)
|
||||
copyright = u'2010-12, Doctrine Project Team'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
|
||||
@@ -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 <https://dev.mysql.com/doc/refman/8.0/en/gis-class-point.html>`_.
|
||||
type `Point <http://dev.mysql.com/doc/refman/5.5/en/gis-class-point.html>`_.
|
||||
|
||||
The ``Point`` type is part of the `Spatial extension <https://dev.mysql.com/doc/refman/8.0/en/spatial-extensions.html>`_
|
||||
The ``Point`` type is part of the `Spatial extension <http://dev.mysql.com/doc/refman/5.5/en/spatial-extensions.html>`_
|
||||
of MySQL and enables you to store a single location in a coordinate space by
|
||||
using x and y coordinates. You can use the Point type to store a
|
||||
longitude/latitude pair to represent a geographic location.
|
||||
@@ -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 `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>`_
|
||||
MySQL functions `PointFromText <http://dev.mysql.com/doc/refman/5.5/en/creating-spatial-values.html#function_pointfromtext>`_
|
||||
and `AsText <http://dev.mysql.com/doc/refman/5.5/en/functions-to-convert-geometries-between-formats.html#function_astext>`_
|
||||
which convert WKT strings to and from the internal format of MySQL.
|
||||
|
||||
.. note::
|
||||
@@ -249,7 +249,7 @@ Example usage
|
||||
$em->clear();
|
||||
|
||||
// Fetch the Location object
|
||||
$query = $em->createQuery("SELECT l FROM Geo\Entity\Location l WHERE l.address = '1600 Amphitheatre Parkway, Mountain View, CA'");
|
||||
$query = $em->createQuery("SELECT l FROM Geo\Entity\Location WHERE l.address = '1600 Amphitheatre Parkway, Mountain View, CA'");
|
||||
$location = $query->getSingleResult();
|
||||
|
||||
/* @var Geo\ValueObject\Point */
|
||||
|
||||
@@ -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 safe-guard
|
||||
You can use both optimistic or pessimistic locking to save-guard
|
||||
your aggregate fields against this kind of race-conditions. Reading
|
||||
Eric Evans DDD carefully he mentions that the "Aggregate Root"
|
||||
(Account in our example) needs a locking mechanism.
|
||||
|
||||
@@ -49,6 +49,8 @@ you wish. Here is an example skeleton of such a custom type class:
|
||||
|
||||
The following assumptions are applied to mapping types by the ORM:
|
||||
|
||||
- If the value of the field is *NULL* the method
|
||||
``convertToDatabaseValue()`` is not called.
|
||||
- The ``UnitOfWork`` never passes values to the database convert
|
||||
method that did not change in the request.
|
||||
- The ``UnitOfWork`` internally assumes that entity identifiers are
|
||||
|
||||
@@ -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 User-Defined Functions API (UDF) of the Dql
|
||||
post explains the Used-Defined Functions API (UDF) of the Dql
|
||||
Parser and shows some examples to give you some hints how you would
|
||||
extend DQL.
|
||||
|
||||
@@ -70,7 +70,7 @@ methods, which are quite handy in my opinion:
|
||||
Date Diff
|
||||
---------
|
||||
|
||||
`Mysql's DateDiff function <https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_datediff>`_
|
||||
`Mysql's DateDiff function <http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_datediff>`_
|
||||
takes two dates as argument and calculates the difference in days
|
||||
with ``date1-date2``.
|
||||
|
||||
@@ -132,7 +132,7 @@ dql statement.
|
||||
|
||||
The ``ArithmeticPrimary`` method call is the most common
|
||||
denominator of valid EBNF tokens taken from the
|
||||
`DQL EBNF grammar <http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html#ebnf>`_
|
||||
`DQL EBNF grammar <http://www.doctrine-project.org/documentation/manual/2_0/en/dql-doctrine-query-language#ebnf>`_
|
||||
that matches our requirements for valid input into the DateDiff Dql
|
||||
function. Picking the right tokens for your methods is a tricky
|
||||
business, but the EBNF grammar is pretty helpful finding it, as is
|
||||
@@ -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 <https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_date-add>`_.
|
||||
`MySql's DATE\_ADD function <http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date-add>`_.
|
||||
|
||||
I'll skip the blah and show the code for this function:
|
||||
|
||||
@@ -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 the GitHub DoctrineExtensions repository <http://github.com/beberlei/DoctrineExtensions>`_.
|
||||
`in my Github DoctrineExtensions repository <http://github.com/beberlei/DoctrineExtensions>`_.
|
||||
|
||||
|
||||
|
||||
@@ -4,15 +4,15 @@ Implementing Wakeup or Clone
|
||||
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
|
||||
|
||||
As explained in the
|
||||
`restrictions for entity classes in the manual <http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/architecture.html#entities>`_,
|
||||
`restrictions for entity classes in the manual <http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities>`_,
|
||||
it is usually not allowed for an entity to implement ``__wakeup``
|
||||
or ``__clone``, because Doctrine makes special use of them.
|
||||
However, it is quite easy to make use of these methods in a safe
|
||||
way by guarding the custom wakeup or clone code with an entity
|
||||
identity check, as demonstrated in the following sections.
|
||||
|
||||
Safely implementing __wakeup
|
||||
----------------------------
|
||||
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:
|
||||
|
||||
|
||||
140
docs/en/cookbook/integrating-with-codeigniter.rst
Normal file
140
docs/en/cookbook/integrating-with-codeigniter.rst
Normal file
@@ -0,0 +1,140 @@
|
||||
Integrating with CodeIgniter
|
||||
============================
|
||||
|
||||
This is recipe for using Doctrine 2 in your
|
||||
`CodeIgniter <http://www.codeigniter.com>`_ framework.
|
||||
|
||||
.. note::
|
||||
|
||||
This might not work for all CodeIgniter versions and may require
|
||||
slight adjustments.
|
||||
|
||||
|
||||
Here is how to set it up:
|
||||
|
||||
Make a CodeIgniter library that is both a wrapper and a bootstrap
|
||||
for Doctrine 2.
|
||||
|
||||
Setting up the file structure
|
||||
-----------------------------
|
||||
|
||||
Here are the steps:
|
||||
|
||||
|
||||
- Add a php file to your system/application/libraries folder
|
||||
called Doctrine.php. This is going to be your wrapper/bootstrap for
|
||||
the D2 entity manager.
|
||||
- Put the Doctrine folder (the one that contains Common, DBAL, and
|
||||
ORM) inside that same libraries folder.
|
||||
- Your system/application/libraries folder now looks like this:
|
||||
|
||||
system/applications/libraries -Doctrine -Doctrine.php -index.html
|
||||
|
||||
- If you want, open your config/autoload.php file and autoload
|
||||
your Doctrine library.
|
||||
|
||||
<?php $autoload['libraries'] = array('doctrine');
|
||||
|
||||
|
||||
Creating your Doctrine CodeIgniter library
|
||||
------------------------------------------
|
||||
|
||||
Now, here is what your Doctrine.php file should look like.
|
||||
Customize it to your needs.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\Common\ClassLoader,
|
||||
Doctrine\ORM\Configuration,
|
||||
Doctrine\ORM\EntityManager,
|
||||
Doctrine\Common\Cache\ArrayCache,
|
||||
Doctrine\DBAL\Logging\EchoSQLLogger;
|
||||
|
||||
class Doctrine {
|
||||
|
||||
public $em = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// load database configuration from CodeIgniter
|
||||
require_once APPPATH.'config/database.php';
|
||||
|
||||
// Set up class loading. You could use different autoloaders, provided by your favorite framework,
|
||||
// if you want to.
|
||||
require_once APPPATH.'libraries/Doctrine/Common/ClassLoader.php';
|
||||
|
||||
$doctrineClassLoader = new ClassLoader('Doctrine', APPPATH.'libraries');
|
||||
$doctrineClassLoader->register();
|
||||
$entitiesClassLoader = new ClassLoader('models', rtrim(APPPATH, "/" ));
|
||||
$entitiesClassLoader->register();
|
||||
$proxiesClassLoader = new ClassLoader('Proxies', APPPATH.'models/proxies');
|
||||
$proxiesClassLoader->register();
|
||||
|
||||
// Set up caches
|
||||
$config = new Configuration;
|
||||
$cache = new ArrayCache;
|
||||
$config->setMetadataCacheImpl($cache);
|
||||
$driverImpl = $config->newDefaultAnnotationDriver(array(APPPATH.'models/Entities'));
|
||||
$config->setMetadataDriverImpl($driverImpl);
|
||||
$config->setQueryCacheImpl($cache);
|
||||
|
||||
$config->setQueryCacheImpl($cache);
|
||||
|
||||
// Proxy configuration
|
||||
$config->setProxyDir(APPPATH.'/models/proxies');
|
||||
$config->setProxyNamespace('Proxies');
|
||||
|
||||
// Set up logger
|
||||
$logger = new EchoSQLLogger;
|
||||
$config->setSQLLogger($logger);
|
||||
|
||||
$config->setAutoGenerateProxyClasses( TRUE );
|
||||
|
||||
// Database connection information
|
||||
$connectionOptions = array(
|
||||
'driver' => 'pdo_mysql',
|
||||
'user' => $db['default']['username'],
|
||||
'password' => $db['default']['password'],
|
||||
'host' => $db['default']['hostname'],
|
||||
'dbname' => $db['default']['database']
|
||||
);
|
||||
|
||||
// Create EntityManager
|
||||
$this->em = EntityManager::create($connectionOptions, $config);
|
||||
}
|
||||
}
|
||||
|
||||
Please note that this is a development configuration; for a
|
||||
production system you'll want to use a real caching system like
|
||||
APC, get rid of EchoSqlLogger, and turn off
|
||||
autoGenerateProxyClasses.
|
||||
|
||||
For more details, consult the
|
||||
`Doctrine 2 Configuration documentation <http://www.doctrine-project.org/documentation/manual/2_0/en/configuration#configuration-options>`_.
|
||||
|
||||
Now to use it
|
||||
-------------
|
||||
|
||||
Whenever you need a reference to the entity manager inside one of
|
||||
your controllers, views, or models you can do this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$em = $this->doctrine->em;
|
||||
|
||||
That's all there is to it. Once you get the reference to your
|
||||
EntityManager do your Doctrine 2.0 voodoo as normal.
|
||||
|
||||
Note: If you do not choose to autoload the Doctrine library, you
|
||||
will need to put this line before you get a reference to it:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$this->load->library('doctrine');
|
||||
|
||||
Good luck!
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ For example for the previous enum type:
|
||||
|
||||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||
{
|
||||
return "ENUM('visible', 'invisible')";
|
||||
return "ENUM('visible', 'invisible') COMMENT '(DC2Type:enumvisibility)'";
|
||||
}
|
||||
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||
@@ -118,11 +118,6 @@ For example for the previous enum type:
|
||||
{
|
||||
return self::ENUM_VISIBILITY;
|
||||
}
|
||||
|
||||
public function requiresSQLCommentHint(AbstractPlatform $platform)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
You can register this type with ``Type::addType('enumvisibility', 'MyProject\DBAL\EnumVisibilityType');``.
|
||||
@@ -157,7 +152,7 @@ You can generalize this approach easily to create a base class for enums:
|
||||
{
|
||||
$values = array_map(function($val) { return "'".$val."'"; }, $this->values);
|
||||
|
||||
return "ENUM(".implode(", ", $values).")";
|
||||
return "ENUM(".implode(", ", $values).") COMMENT '(DC2Type:".$this->name.")'";
|
||||
}
|
||||
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||
@@ -177,11 +172,6 @@ You can generalize this approach easily to create a base class for enums:
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function requiresSQLCommentHint(AbstractPlatform $platform)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
With this base class you can define an enum as easily as:
|
||||
|
||||
@@ -41,9 +41,7 @@ appropriate autoloaders.
|
||||
$classMetadata = $eventArgs->getClassMetadata();
|
||||
|
||||
if (!$classMetadata->isInheritanceTypeSingleTable() || $classMetadata->getName() === $classMetadata->rootEntityName) {
|
||||
$classMetadata->setPrimaryTable([
|
||||
'name' => $this->prefix . $classMetadata->getTableName()
|
||||
]);
|
||||
$classMetadata->setTableName($this->prefix . $classMetadata->getTableName());
|
||||
}
|
||||
|
||||
foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -89,7 +89,7 @@ events for one method, this will happen before Beta 1 though.
|
||||
Now validation is performed whenever you call
|
||||
``EntityManager#persist($order)`` or when you call
|
||||
``EntityManager#flush()`` and an order is about to be updated. Any
|
||||
Exception that happens in the lifecycle callbacks will be caught by
|
||||
Exception that happens in the lifecycle callbacks will be cached by
|
||||
the EntityManager and the current transaction is rolled back.
|
||||
|
||||
Of course you can do any type of primitive checks, not null,
|
||||
@@ -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: :ref:`reference-events-lifecycle-events`
|
||||
Further readings: :doc:`Lifecycle Events <../reference/events>`
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Working with DateTime Instances
|
||||
===============================
|
||||
|
||||
There are many nitty gritty details when working with PHPs DateTime instances. You have to know their inner
|
||||
There are many nitty gritty details when working with PHPs DateTime instances. You have know their inner
|
||||
workings pretty well not to make mistakes with date handling. This cookbook entry holds several
|
||||
interesting pieces of information on how to work with PHP DateTime instances in Doctrine 2.
|
||||
|
||||
@@ -49,15 +49,14 @@ By default Doctrine assumes that you are working with a default timezone. Each D
|
||||
is created by Doctrine will be assigned the timezone that is currently the default, either through
|
||||
the ``date.timezone`` ini setting or by calling ``date_default_timezone_set()``.
|
||||
|
||||
This is very important to handle correctly if your application runs on different servers or is moved from one to another server
|
||||
This is very important to handle correctly if your application runs on different serves or is moved from one to another server
|
||||
(with different timezone settings). You have to make sure that the timezone is the correct one
|
||||
on all this systems.
|
||||
|
||||
Handling different Timezones with the DateTime Type
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you first come across the requirement to save different timezones you may be still optimistic about how
|
||||
to manage this mess,
|
||||
If you first come across the requirement to save different you are still optimistic to manage this mess,
|
||||
however let me crush your expectations fast. There is not a single database out there (supported by Doctrine 2)
|
||||
that supports timezones correctly. Correctly here means that you can cover all the use-cases that
|
||||
can come up with timezones. If you don't believe me you should read up on `Storing DateTime
|
||||
@@ -86,71 +85,43 @@ the UTC time at the time of the booking and the timezone the event happened in.
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\ConversionException;
|
||||
use Doctrine\DBAL\Types\DateTimeType;
|
||||
|
||||
class UTCDateTimeType extends DateTimeType
|
||||
{
|
||||
/**
|
||||
* @var \DateTimeZone
|
||||
*/
|
||||
private static $utc;
|
||||
static private $utc = null;
|
||||
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
if ($value instanceof \DateTime) {
|
||||
$value->setTimezone(self::getUtc());
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return parent::convertToDatabaseValue($value, $platform);
|
||||
|
||||
return $value->format($platform->getDateTimeFormatString(),
|
||||
(self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone('UTC'))
|
||||
);
|
||||
}
|
||||
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
if (null === $value || $value instanceof \DateTime) {
|
||||
return $value;
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$converted = \DateTime::createFromFormat(
|
||||
$val = \DateTime::createFromFormat(
|
||||
$platform->getDateTimeFormatString(),
|
||||
$value,
|
||||
self::getUtc()
|
||||
(self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone('UTC'))
|
||||
);
|
||||
|
||||
if (! $converted) {
|
||||
throw ConversionException::conversionFailedFormat(
|
||||
$value,
|
||||
$this->getName(),
|
||||
$platform->getDateTimeFormatString()
|
||||
);
|
||||
if (!$val) {
|
||||
throw ConversionException::conversionFailed($value, $this->getName());
|
||||
}
|
||||
|
||||
return $converted;
|
||||
}
|
||||
|
||||
private static function getUtc(): \DateTimeZone
|
||||
{
|
||||
return self::$utc ?: self::$utc = new \DateTimeZone('UTC');
|
||||
return $val;
|
||||
}
|
||||
}
|
||||
|
||||
This database type makes sure that every DateTime instance is always saved in UTC, relative
|
||||
to the current timezone that the passed DateTime instance has.
|
||||
|
||||
To actually use this new type instead of the default ``datetime`` type, you need to run following
|
||||
code before bootstrapping the ORM:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use DoctrineExtensions\DBAL\Types\UTCDateTimeType;
|
||||
|
||||
Type::overrideType('datetime', UTCDateTimeType::class);
|
||||
Type::overrideType('datetimetz', UTCDateTimeType::class);
|
||||
|
||||
|
||||
To be able to transform these values
|
||||
to the current timezone that the passed DateTime instance has. To be able to transform these values
|
||||
back into their real timezone you have to save the timezone in a separate field of the entity
|
||||
requiring timezoned datetimes:
|
||||
|
||||
|
||||
@@ -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 <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>`_.
|
||||
- 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>`_.
|
||||
- On `Twitter <https://twitter.com/search/%23doctrine2>`_ with ``#doctrine2``
|
||||
- On `StackOverflow <https://stackoverflow.com/questions/tagged/doctrine-orm>`_
|
||||
- On `StackOverflow <http://stackoverflow.com/questions/tagged/doctrine2>`_
|
||||
|
||||
If you need more structure over the different topics you can browse the :doc:`table
|
||||
of contents <toc>`.
|
||||
@@ -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
|
||||
----------
|
||||
|
||||
* `Upgrade <https://github.com/doctrine/doctrine2/blob/master/UPGRADE.md>`_
|
||||
* :doc:`Migration to 2.5 <changelog/migration_2_5>`
|
||||
|
||||
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,6 +118,9 @@ 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>`
|
||||
|
||||
|
||||
@@ -152,7 +152,6 @@ The recommended implementations for production are:
|
||||
|
||||
|
||||
- ``Doctrine\Common\Cache\ApcCache``
|
||||
- ``Doctrine\Common\Cache\ApcuCache``
|
||||
- ``Doctrine\Common\Cache\MemcacheCache``
|
||||
- ``Doctrine\Common\Cache\XcacheCache``
|
||||
- ``Doctrine\Common\Cache\RedisCache``
|
||||
@@ -184,7 +183,6 @@ The recommended implementations for production are:
|
||||
|
||||
|
||||
- ``Doctrine\Common\Cache\ApcCache``
|
||||
- ``Doctrine\Common\Cache\ApcuCache``
|
||||
- ``Doctrine\Common\Cache\MemcacheCache``
|
||||
- ``Doctrine\Common\Cache\XcacheCache``
|
||||
- ``Doctrine\Common\Cache\RedisCache``
|
||||
@@ -292,7 +290,7 @@ instance of ``Doctrine\DBAL\Connection``. If an array is passed it
|
||||
is directly passed along to the DBAL Factory
|
||||
``Doctrine\DBAL\DriverManager::getConnection()``. The DBAL
|
||||
configuration is explained in the
|
||||
`DBAL section <https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/configuration.html>`_.
|
||||
`DBAL section <./../../../../../projects/doctrine-dbal/en/latest/reference/configuration.html>`_.
|
||||
|
||||
Proxy Objects
|
||||
-------------
|
||||
@@ -396,7 +394,7 @@ means that you have to register a special autoloader for these classes:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\Common\Proxy\Autoloader;
|
||||
use Doctrine\ORM\Proxy\Autoloader;
|
||||
|
||||
$proxyDir = "/path/to/proxies";
|
||||
$proxyNamespace = "MyProxies";
|
||||
|
||||
@@ -37,11 +37,8 @@ Index
|
||||
- :ref:`@ColumnResult <annref_column_result>`
|
||||
- :ref:`@Cache <annref_cache>`
|
||||
- :ref:`@ChangeTrackingPolicy <annref_changetrackingpolicy>`
|
||||
- :ref:`@CustomIdGenerator <annref_customidgenerator>`
|
||||
- :ref:`@DiscriminatorColumn <annref_discriminatorcolumn>`
|
||||
- :ref:`@DiscriminatorMap <annref_discriminatormap>`
|
||||
- :ref:`@Embeddable <annref_embeddable>`
|
||||
- :ref:`@Embedded <annref_embedded>`
|
||||
- :ref:`@Entity <annref_entity>`
|
||||
- :ref:`@EntityResult <annref_entity_result>`
|
||||
- :ref:`@FieldResult <annref_field_result>`
|
||||
@@ -113,7 +110,7 @@ Optional attributes:
|
||||
- **unique**: Boolean value to determine if the value of the column
|
||||
should be unique across all rows of the underlying entities table.
|
||||
|
||||
- **nullable**: Determines if NULL values allowed for this column. If not specified, default value is false.
|
||||
- **nullable**: Determines if NULL values allowed for this column.
|
||||
|
||||
- **options**: Array of additional options:
|
||||
|
||||
@@ -134,9 +131,6 @@ Optional attributes:
|
||||
|
||||
- ``collation``: The collation of the column (only supported by Drizzle, Mysql, PostgreSQL>=9.1, Sqlite and SQLServer).
|
||||
|
||||
- ``check``: Adds a check constraint type to the column (might not
|
||||
be supported by all vendors).
|
||||
|
||||
- **columnDefinition**: DDL SQL snippet that starts after the column
|
||||
name and specifies the complete (non-portable!) column definition.
|
||||
This attribute allows to make use of advanced RMDBS features.
|
||||
@@ -181,7 +175,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;
|
||||
|
||||
@@ -237,43 +231,16 @@ Example:
|
||||
*/
|
||||
class User {}
|
||||
|
||||
.. _annref_customidgenerator:
|
||||
|
||||
@CustomIdGenerator
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This annotations allows you to specify a user-provided class to generate identifiers. This annotation only works when both :ref:`@Id <annref_id>` and :ref:`@GeneratedValue(strategy="CUSTOM") <annref_generatedvalue>` are specified.
|
||||
|
||||
Required attributes:
|
||||
|
||||
- **class**: name of the class which should extend Doctrine\ORM\Id\AbstractIdGenerator
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Id
|
||||
* @Column(type="integer")
|
||||
* @GeneratedValue(strategy="CUSTOM")
|
||||
* @CustomIdGenerator(class="My\Namespace\MyIdGenerator")
|
||||
*/
|
||||
public $id;
|
||||
|
||||
.. _annref_discriminatorcolumn:
|
||||
|
||||
@DiscriminatorColumn
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This annotation is an optional annotation for the topmost/super
|
||||
This annotation is a required annotation for the topmost/super
|
||||
class of an inheritance hierarchy. It specifies the details of the
|
||||
column which saves the name of the class, which the entity is
|
||||
actually instantiated as.
|
||||
|
||||
If this annotation is not specified, the discriminator column defaults
|
||||
to a string column of length 255 called ``dtype``.
|
||||
|
||||
Required attributes:
|
||||
|
||||
|
||||
@@ -312,67 +279,6 @@ depending on whether the classes are in the namespace or not.
|
||||
// ...
|
||||
}
|
||||
|
||||
|
||||
.. _annref_embeddable:
|
||||
|
||||
@Embeddable
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The embeddable annotation is required on a class, in order to make it
|
||||
embeddable inside an entity. It works together with the :ref:`@Embedded <annref_embedded>`
|
||||
annotation to establish the relationship between the two classes.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @Embeddable
|
||||
*/
|
||||
class Address
|
||||
{
|
||||
// ...
|
||||
class User
|
||||
{
|
||||
/**
|
||||
* @Embedded(class = "Address")
|
||||
*/
|
||||
private $address;
|
||||
|
||||
|
||||
.. _annref_embedded:
|
||||
|
||||
@Embedded
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The embedded annotation is required on an entity's member variable,
|
||||
in order to specify that it is an embedded class.
|
||||
|
||||
Required attributes:
|
||||
|
||||
- **class**: The embeddable class
|
||||
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
// ...
|
||||
class User
|
||||
{
|
||||
/**
|
||||
* @Embedded(class = "Address")
|
||||
*/
|
||||
private $address;
|
||||
|
||||
/**
|
||||
* @Embeddable
|
||||
*/
|
||||
class Address
|
||||
{
|
||||
// ...
|
||||
|
||||
|
||||
.. _annref_entity:
|
||||
|
||||
@Entity
|
||||
@@ -451,12 +357,11 @@ conjunction with @Id.
|
||||
If this annotation is not specified with @Id the NONE strategy is
|
||||
used as default.
|
||||
|
||||
Optional attributes:
|
||||
Required attributes:
|
||||
|
||||
|
||||
- **strategy**: Set the name of the identifier generation strategy.
|
||||
Valid values are AUTO, SEQUENCE, TABLE, IDENTITY, UUID, CUSTOM and NONE.
|
||||
If not specified, default value is AUTO.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -1306,13 +1211,12 @@ Example with partial indexes:
|
||||
.. _annref_version:
|
||||
|
||||
@Version
|
||||
~~~~~~~~
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Marker annotation that defines a specified column as version attribute used in
|
||||
an :ref:`optimistic locking <transactions-and-concurrency_optimistic-locking>`
|
||||
scenario. It only works on :ref:`@Column <annref_column>` annotations that have
|
||||
the type ``integer`` or ``datetime``. Combining ``@Version`` with
|
||||
:ref:`@Id <annref_id>` is not supported.
|
||||
Marker annotation that defines a specified column as version
|
||||
attribute used in an optimistic locking scenario. It only works on
|
||||
:ref:`@Column <annref_column>` annotations that have the type integer or
|
||||
datetime. Combining @Version with :ref:`@Id <annref_id>` is not supported.
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ well.
|
||||
Requirements
|
||||
------------
|
||||
|
||||
Doctrine 2 requires a minimum of PHP 7.1. For greatly improved
|
||||
Doctrine 2 requires a minimum of PHP 5.4. For greatly improved
|
||||
performance it is also recommended that you use APC with PHP.
|
||||
|
||||
Doctrine 2 Packages
|
||||
|
||||
@@ -16,31 +16,20 @@ This chapter is split into three different sections.
|
||||
- :ref:`association_mapping_defaults` are explained that simplify the use-case examples.
|
||||
- :ref:`collections` are introduced that contain entities in associations.
|
||||
|
||||
One tip for working with relations is to read the relation from left to right, where the left word refers to the current Entity. For example:
|
||||
|
||||
- OneToMany - One instance of the current Entity has Many instances (references) to the refered Entity.
|
||||
- ManyToOne - Many instances of the current Entity refer to One instance of the refered Entity.
|
||||
- OneToOne - One instance of the current Entity refers to One instance of the refered Entity.
|
||||
|
||||
See below for all the possible relations.
|
||||
|
||||
An association is considered to be unidirectional if only one side of the association has
|
||||
a property referring to the other side.
|
||||
|
||||
To gain a full understanding of associations you should also read about :doc:`owning and
|
||||
inverse sides of associations <unitofwork-associations>`
|
||||
|
||||
Many-To-One, Unidirectional
|
||||
---------------------------
|
||||
|
||||
A many-to-one association is the most common association between objects. Example: Many Users have One Address:
|
||||
A many-to-one association is the most common association between objects.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
/** @Entity **/
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
@@ -48,11 +37,11 @@ A many-to-one association is the most common association between objects. Exampl
|
||||
/**
|
||||
* @ManyToOne(targetEntity="Address")
|
||||
* @JoinColumn(name="address_id", referencedColumnName="id")
|
||||
*/
|
||||
**/
|
||||
private $address;
|
||||
}
|
||||
|
||||
/** @Entity */
|
||||
/** @Entity **/
|
||||
class Address
|
||||
{
|
||||
// ...
|
||||
@@ -107,30 +96,31 @@ One-To-One, Unidirectional
|
||||
--------------------------
|
||||
|
||||
Here is an example of a one-to-one association with a ``Product`` entity that
|
||||
references one ``Shipment`` entity.
|
||||
references one ``Shipping`` entity. The ``Shipping`` does not reference back to
|
||||
the ``Product`` so that the reference is said to be unidirectional, in one
|
||||
direction only.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
/** @Entity **/
|
||||
class Product
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* One Product has One Shipment.
|
||||
* @OneToOne(targetEntity="Shipment")
|
||||
* @JoinColumn(name="shipment_id", referencedColumnName="id")
|
||||
*/
|
||||
private $shipment;
|
||||
* @OneToOne(targetEntity="Shipping")
|
||||
* @JoinColumn(name="shipping_id", referencedColumnName="id")
|
||||
**/
|
||||
private $shipping;
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
/** @Entity */
|
||||
class Shipment
|
||||
/** @Entity **/
|
||||
class Shipping
|
||||
{
|
||||
// ...
|
||||
}
|
||||
@@ -139,8 +129,8 @@ references one ``Shipment`` entity.
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity class="Product">
|
||||
<one-to-one field="shipment" target-entity="Shipment">
|
||||
<join-column name="shipment_id" referenced-column-name="id" />
|
||||
<one-to-one field="shipping" target-entity="Shipping">
|
||||
<join-column name="shipping_id" referenced-column-name="id" />
|
||||
</one-to-one>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
@@ -150,10 +140,10 @@ references one ``Shipment`` entity.
|
||||
Product:
|
||||
type: entity
|
||||
oneToOne:
|
||||
shipment:
|
||||
targetEntity: Shipment
|
||||
shipping:
|
||||
targetEntity: Shipping
|
||||
joinColumn:
|
||||
name: shipment_id
|
||||
name: shipping_id
|
||||
referencedColumnName: id
|
||||
|
||||
Note that the @JoinColumn is not really necessary in this example,
|
||||
@@ -165,15 +155,15 @@ Generated MySQL Schema:
|
||||
|
||||
CREATE TABLE Product (
|
||||
id INT AUTO_INCREMENT NOT NULL,
|
||||
shipment_id INT DEFAULT NULL,
|
||||
UNIQUE INDEX UNIQ_6FBC94267FE4B2B (shipment_id),
|
||||
shipping_id INT DEFAULT NULL,
|
||||
UNIQUE INDEX UNIQ_6FBC94267FE4B2B (shipping_id),
|
||||
PRIMARY KEY(id)
|
||||
) ENGINE = InnoDB;
|
||||
CREATE TABLE Shipment (
|
||||
CREATE TABLE Shipping (
|
||||
id INT AUTO_INCREMENT NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
) ENGINE = InnoDB;
|
||||
ALTER TABLE Product ADD FOREIGN KEY (shipment_id) REFERENCES Shipment(id);
|
||||
ALTER TABLE Product ADD FOREIGN KEY (shipping_id) REFERENCES Shipping(id);
|
||||
|
||||
One-To-One, Bidirectional
|
||||
-------------------------
|
||||
@@ -182,39 +172,33 @@ Here is a one-to-one relationship between a ``Customer`` and a
|
||||
``Cart``. The ``Cart`` has a reference back to the ``Customer`` so
|
||||
it is bidirectional.
|
||||
|
||||
Here we see the ``mappedBy`` and ``inversedBy`` annotations for the first time.
|
||||
They are used to tell Doctrine which property on the other side refers to the
|
||||
object.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
/** @Entity **/
|
||||
class Customer
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* One Customer has One Cart.
|
||||
* @OneToOne(targetEntity="Cart", mappedBy="customer")
|
||||
*/
|
||||
**/
|
||||
private $cart;
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
/** @Entity */
|
||||
/** @Entity **/
|
||||
class Cart
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* One Cart has One Customer.
|
||||
* @OneToOne(targetEntity="Customer", inversedBy="cart")
|
||||
* @JoinColumn(name="customer_id", referencedColumnName="id")
|
||||
*/
|
||||
**/
|
||||
private $customer;
|
||||
|
||||
// ...
|
||||
@@ -267,9 +251,8 @@ Generated MySQL Schema:
|
||||
) ENGINE = InnoDB;
|
||||
ALTER TABLE Cart ADD FOREIGN KEY (customer_id) REFERENCES Customer(id);
|
||||
|
||||
We had a choice of sides on which to place the ``inversedBy`` attribute. Because it
|
||||
is on the ``Cart``, that is the owning side of the relation, and thus holds the
|
||||
foreign key.
|
||||
See how the foreign key is defined on the owning side of the
|
||||
relation, the table ``Cart``.
|
||||
|
||||
One-To-One, Self-referencing
|
||||
----------------------------
|
||||
@@ -280,16 +263,15 @@ below.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
/** @Entity **/
|
||||
class Student
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* One Student has One Mentor.
|
||||
* @OneToOne(targetEntity="Student")
|
||||
* @JoinColumn(name="mentor_id", referencedColumnName="id")
|
||||
*/
|
||||
**/
|
||||
private $mentor;
|
||||
|
||||
// ...
|
||||
@@ -312,16 +294,15 @@ With the generated MySQL Schema:
|
||||
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
|
||||
defined in order to understand the association.
|
||||
A one-to-many association has to be bidirectional, unless you are using an
|
||||
additional join-table. This is necessary, because of the foreign key
|
||||
in a one-to-many association being defined on the "many" side. Doctrine
|
||||
needs a many-to-one association that defines the mapping of this
|
||||
foreign key.
|
||||
|
||||
This bidirectional mapping requires the ``mappedBy`` attribute on the
|
||||
"one" side and the ``inversedBy`` attribute on the "many" side.
|
||||
|
||||
This means there is no difference between a bidirectional one-to-many and a
|
||||
bidirectional many-to-one.
|
||||
``OneToMany`` association and the ``inversedBy`` attribute on the ``ManyToOne``
|
||||
association.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
@@ -330,14 +311,13 @@ bidirectional many-to-one.
|
||||
<?php
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
/** @Entity */
|
||||
/** @Entity **/
|
||||
class Product
|
||||
{
|
||||
// ...
|
||||
/**
|
||||
* One product has many features. This is the inverse side.
|
||||
* @OneToMany(targetEntity="Feature", mappedBy="product")
|
||||
*/
|
||||
**/
|
||||
private $features;
|
||||
// ...
|
||||
|
||||
@@ -346,15 +326,14 @@ bidirectional many-to-one.
|
||||
}
|
||||
}
|
||||
|
||||
/** @Entity */
|
||||
/** @Entity **/
|
||||
class Feature
|
||||
{
|
||||
// ...
|
||||
/**
|
||||
* Many features have one product. This is the owning side.
|
||||
* @ManyToOne(targetEntity="Product", inversedBy="features")
|
||||
* @JoinColumn(name="product_id", referencedColumnName="id")
|
||||
*/
|
||||
**/
|
||||
private $product;
|
||||
// ...
|
||||
}
|
||||
@@ -423,19 +402,18 @@ The following example sets up such a unidirectional one-to-many association:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
/** @Entity **/
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* Many User have Many Phonenumbers.
|
||||
* @ManyToMany(targetEntity="Phonenumber")
|
||||
* @JoinTable(name="users_phonenumbers",
|
||||
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
|
||||
* inverseJoinColumns={@JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)}
|
||||
* )
|
||||
*/
|
||||
**/
|
||||
private $phonenumbers;
|
||||
|
||||
public function __construct()
|
||||
@@ -446,7 +424,7 @@ The following example sets up such a unidirectional one-to-many association:
|
||||
// ...
|
||||
}
|
||||
|
||||
/** @Entity */
|
||||
/** @Entity **/
|
||||
class Phonenumber
|
||||
{
|
||||
// ...
|
||||
@@ -525,21 +503,19 @@ database perspective is known as an adjacency list approach.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
/** @Entity **/
|
||||
class Category
|
||||
{
|
||||
// ...
|
||||
/**
|
||||
* One Category has Many Categories.
|
||||
* @OneToMany(targetEntity="Category", mappedBy="parent")
|
||||
*/
|
||||
**/
|
||||
private $children;
|
||||
|
||||
/**
|
||||
* Many Categories have One Category.
|
||||
* @ManyToOne(targetEntity="Category", inversedBy="children")
|
||||
* @JoinColumn(name="parent_id", referencedColumnName="id")
|
||||
*/
|
||||
**/
|
||||
private $parent;
|
||||
// ...
|
||||
|
||||
@@ -596,19 +572,18 @@ entities:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
/** @Entity **/
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* Many Users have Many Groups.
|
||||
* @ManyToMany(targetEntity="Group")
|
||||
* @JoinTable(name="users_groups",
|
||||
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
|
||||
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
|
||||
* )
|
||||
*/
|
||||
**/
|
||||
private $groups;
|
||||
|
||||
// ...
|
||||
@@ -618,7 +593,7 @@ entities:
|
||||
}
|
||||
}
|
||||
|
||||
/** @Entity */
|
||||
/** @Entity **/
|
||||
class Group
|
||||
{
|
||||
// ...
|
||||
@@ -697,16 +672,15 @@ one is bidirectional.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
/** @Entity **/
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* Many Users have Many Groups.
|
||||
* @ManyToMany(targetEntity="Group", inversedBy="users")
|
||||
* @JoinTable(name="users_groups")
|
||||
*/
|
||||
**/
|
||||
private $groups;
|
||||
|
||||
public function __construct() {
|
||||
@@ -716,14 +690,13 @@ one is bidirectional.
|
||||
// ...
|
||||
}
|
||||
|
||||
/** @Entity */
|
||||
/** @Entity **/
|
||||
class Group
|
||||
{
|
||||
// ...
|
||||
/**
|
||||
* Many Groups have Many Users.
|
||||
* @ManyToMany(targetEntity="User", mappedBy="groups")
|
||||
*/
|
||||
**/
|
||||
private $users;
|
||||
|
||||
public function __construct() {
|
||||
@@ -781,22 +754,22 @@ one is bidirectional.
|
||||
The MySQL schema is exactly the same as for the Many-To-Many
|
||||
uni-directional case above.
|
||||
|
||||
Owning and Inverse Side on a ManyToMany Association
|
||||
Owning and Inverse Side on a ManyToMany association
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
For Many-To-Many associations you can chose which entity is the
|
||||
owning and which the inverse side. There is a very simple semantic
|
||||
rule to decide which side is more suitable to be the owning side
|
||||
from a developers perspective. You only have to ask yourself which
|
||||
entity is responsible for the connection management, and pick that
|
||||
from a developers perspective. You only have to ask yourself, which
|
||||
entity is responsible for the connection management and pick that
|
||||
as the owning side.
|
||||
|
||||
Take an example of two entities ``Article`` and ``Tag``. Whenever
|
||||
you want to connect an Article to a Tag and vice-versa, it is
|
||||
mostly the Article that is responsible for this relation. Whenever
|
||||
you add a new article, you want to connect it with existing or new
|
||||
tags. Your "Create Article" form will probably support this notion
|
||||
and allow specifying the tags directly. This is why you should pick
|
||||
tags. Your create Article form will probably support this notion
|
||||
and allow to specify the tags directly. This is why you should pick
|
||||
the Article as owning side, as it makes the code more
|
||||
understandable:
|
||||
|
||||
@@ -846,25 +819,23 @@ field named ``$friendsWithMe`` and ``$myFriends``.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
/** @Entity **/
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* Many Users have Many Users.
|
||||
* @ManyToMany(targetEntity="User", mappedBy="myFriends")
|
||||
*/
|
||||
**/
|
||||
private $friendsWithMe;
|
||||
|
||||
/**
|
||||
* Many Users have many Users.
|
||||
* @ManyToMany(targetEntity="User", inversedBy="friendsWithMe")
|
||||
* @JoinTable(name="friends",
|
||||
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
|
||||
* inverseJoinColumns={@JoinColumn(name="friend_user_id", referencedColumnName="id")}
|
||||
* )
|
||||
*/
|
||||
**/
|
||||
private $myFriends;
|
||||
|
||||
public function __construct() {
|
||||
@@ -912,14 +883,14 @@ As an example, consider this mapping:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @OneToOne(targetEntity="Shipment") */
|
||||
private $shipment;
|
||||
/** @OneToOne(targetEntity="Shipping") **/
|
||||
private $shipping;
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity class="Product">
|
||||
<one-to-one field="shipment" target-entity="Shipment" />
|
||||
<one-to-one field="shipping" target-entity="Shipping" />
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
@@ -928,8 +899,8 @@ As an example, consider this mapping:
|
||||
Product:
|
||||
type: entity
|
||||
oneToOne:
|
||||
shipment:
|
||||
targetEntity: Shipment
|
||||
shipping:
|
||||
targetEntity: Shipping
|
||||
|
||||
This is essentially the same as the following, more verbose,
|
||||
mapping:
|
||||
@@ -940,18 +911,17 @@ mapping:
|
||||
|
||||
<?php
|
||||
/**
|
||||
* One Product has One Shipment.
|
||||
* @OneToOne(targetEntity="Shipment")
|
||||
* @JoinColumn(name="shipment_id", referencedColumnName="id")
|
||||
*/
|
||||
private $shipment;
|
||||
* @OneToOne(targetEntity="Shipping")
|
||||
* @JoinColumn(name="shipping_id", referencedColumnName="id")
|
||||
**/
|
||||
private $shipping;
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity class="Product">
|
||||
<one-to-one field="shipment" target-entity="Shipment">
|
||||
<join-column name="shipment_id" referenced-column-name="id" />
|
||||
<one-to-one field="shipping" target-entity="Shipping">
|
||||
<join-column name="shipping_id" referenced-column-name="id" />
|
||||
</one-to-one>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
@@ -961,10 +931,10 @@ mapping:
|
||||
Product:
|
||||
type: entity
|
||||
oneToOne:
|
||||
shipment:
|
||||
targetEntity: Shipment
|
||||
shipping:
|
||||
targetEntity: Shipping
|
||||
joinColumn:
|
||||
name: shipment_id
|
||||
name: shipping_id
|
||||
referencedColumnName: id
|
||||
|
||||
The @JoinTable definition used for many-to-many mappings has
|
||||
@@ -978,7 +948,7 @@ similar defaults. As an example, consider this mapping:
|
||||
class User
|
||||
{
|
||||
//...
|
||||
/** @ManyToMany(targetEntity="Group") */
|
||||
/** @ManyToMany(targetEntity="Group") **/
|
||||
private $groups;
|
||||
//...
|
||||
}
|
||||
@@ -1010,13 +980,12 @@ This is essentially the same as the following, more verbose, mapping:
|
||||
{
|
||||
//...
|
||||
/**
|
||||
* Many Users have Many Groups.
|
||||
* @ManyToMany(targetEntity="Group")
|
||||
* @JoinTable(name="User_Group",
|
||||
* joinColumns={@JoinColumn(name="User_id", referencedColumnName="id")},
|
||||
* inverseJoinColumns={@JoinColumn(name="Group_id", referencedColumnName="id")}
|
||||
* )
|
||||
*/
|
||||
**/
|
||||
private $groups;
|
||||
//...
|
||||
}
|
||||
@@ -1095,17 +1064,12 @@ and ``@ManyToMany`` associations in the constructor of your entities:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
/** @Entity */
|
||||
/** @Entity **/
|
||||
class User
|
||||
{
|
||||
/**
|
||||
* Many Users have Many Groups.
|
||||
* @var Collection
|
||||
* @ManyToMany(targetEntity="Group")
|
||||
*/
|
||||
/** @ManyToMany(targetEntity="Group") **/
|
||||
private $groups;
|
||||
|
||||
public function __construct()
|
||||
|
||||
@@ -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://php.net/manual/en/function.date-default-timezone-set.php>`_
|
||||
set by `date_default_timezone_set() <http://docs.php.net/manual/en/function.date-default-timezone-set.php>`_
|
||||
or by the php.ini configuration ``date.timezone``. Working with
|
||||
different timezones will cause troubles and unexpected behavior.
|
||||
|
||||
@@ -295,8 +295,7 @@ annotation.
|
||||
class Message
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @Column(type="integer")
|
||||
* @Id @Column(type="integer")
|
||||
* @GeneratedValue
|
||||
*/
|
||||
private $id;
|
||||
@@ -355,8 +354,6 @@ Here is the list of possible generation strategies:
|
||||
strategy does currently not provide full portability and is
|
||||
supported by the following platforms: MySQL/SQLite/SQL Anywhere
|
||||
(AUTO\_INCREMENT), MSSQL (IDENTITY) and PostgreSQL (SERIAL).
|
||||
- ``UUID``: Tells Doctrine to use the built-in Universally Unique Identifier
|
||||
generator. This strategy provides full portability.
|
||||
- ``TABLE``: Tells Doctrine to use a separate table for ID
|
||||
generation. This strategy provides full portability.
|
||||
***This strategy is not yet implemented!***
|
||||
@@ -364,8 +361,6 @@ Here is the list of possible generation strategies:
|
||||
thus generated) by your code. The assignment must take place before
|
||||
a new entity is passed to ``EntityManager#persist``. NONE is the
|
||||
same as leaving off the @GeneratedValue entirely.
|
||||
- ``CUSTOM``: With this option, you can use the ``@CustomIdGenerator`` annotation.
|
||||
It will allow you to pass a :doc:`class of your own to generate the identifiers.<_annref_customidgenerator>`
|
||||
|
||||
Sequence Generator
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
@@ -451,7 +446,7 @@ need to access the sequence once to generate the identifiers for
|
||||
Composite Keys
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
With Doctrine 2 you can use composite primary keys, using ``@Id`` on more then
|
||||
with Doctrine 2 you can use composite primary keys, using ``@Id`` on more then
|
||||
one column. Some restrictions exist opposed to using a single identifier in
|
||||
this case: The use of the ``@GeneratedValue`` annotation is not supported,
|
||||
which means you can only use composite keys if you generate the primary key
|
||||
|
||||
@@ -16,15 +16,6 @@ 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
|
||||
------------
|
||||
|
||||
@@ -84,7 +75,7 @@ with the batching strategy that was already used for bulk inserts:
|
||||
|
||||
<?php
|
||||
$batchSize = 20;
|
||||
$i = 1;
|
||||
$i = 0;
|
||||
$q = $em->createQuery('select u from MyProject\Model\User u');
|
||||
$iterableResult = $q->iterate();
|
||||
foreach ($iterableResult as $row) {
|
||||
@@ -109,7 +100,7 @@ with the batching strategy that was already used for bulk inserts:
|
||||
|
||||
Results may be fully buffered by the database client/ connection allocating
|
||||
additional memory not visible to the PHP process. For large sets this
|
||||
may easily kill the process for no apparent reason.
|
||||
may easily kill the process for no apparant reason.
|
||||
|
||||
|
||||
Bulk Deletes
|
||||
@@ -145,7 +136,7 @@ The following example shows how to do this:
|
||||
|
||||
<?php
|
||||
$batchSize = 20;
|
||||
$i = 1;
|
||||
$i = 0;
|
||||
$q = $em->createQuery('select u from MyProject\Model\User u');
|
||||
$iterableResult = $q->iterate();
|
||||
while (($row = $iterableResult->next()) !== false) {
|
||||
|
||||
@@ -54,7 +54,7 @@ Don't use special characters
|
||||
|
||||
Avoid using any non-ASCII characters in class, field, table or
|
||||
column names. Doctrine itself is not unicode-safe in many places
|
||||
and will not be until PHP itself is fully unicode-aware.
|
||||
and will not be until PHP itself is fully unicode-aware (PHP6).
|
||||
|
||||
Don't use identifier quoting
|
||||
----------------------------
|
||||
|
||||
@@ -13,7 +13,7 @@ Cache Drivers
|
||||
|
||||
The cache drivers follow a simple interface that is defined in
|
||||
``Doctrine\Common\Cache\Cache``. All the cache drivers extend a
|
||||
base class ``Doctrine\Common\Cache\CacheProvider`` which implements
|
||||
base class ``Doctrine\Common\Cache\AbstractCache`` which implements
|
||||
this interface.
|
||||
|
||||
The interface defines the following public methods for you to implement:
|
||||
@@ -21,30 +21,26 @@ The interface defines the following public methods for you to implement:
|
||||
|
||||
- fetch($id) - Fetches an entry from the cache
|
||||
- contains($id) - Test if an entry exists in the cache
|
||||
- save($id, $data, $lifeTime = false) - Puts data into the cache for x seconds. 0 = infinite time
|
||||
- save($id, $data, $lifeTime = false) - Puts data into the cache
|
||||
- delete($id) - Deletes a cache entry
|
||||
|
||||
Each driver extends the ``CacheProvider`` class which defines a few
|
||||
Each driver extends the ``AbstractCache`` class which defines a few
|
||||
abstract protected methods that each of the drivers must
|
||||
implement:
|
||||
|
||||
|
||||
- doFetch($id)
|
||||
- doContains($id)
|
||||
- doSave($id, $data, $lifeTime = false)
|
||||
- doDelete($id)
|
||||
- \_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
|
||||
code is organized this way so that the protected methods in the
|
||||
drivers do the raw interaction with the cache implementation and
|
||||
the ``CacheProvider`` can build custom functionality on top of
|
||||
the ``AbstractCache`` can build custom functionality on top of
|
||||
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>`_.
|
||||
|
||||
APC
|
||||
~~~
|
||||
|
||||
@@ -63,24 +59,6 @@ by itself.
|
||||
$cacheDriver = new \Doctrine\Common\Cache\ApcCache();
|
||||
$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');
|
||||
|
||||
Memcache
|
||||
~~~~~~~~
|
||||
|
||||
@@ -104,7 +82,7 @@ driver by itself.
|
||||
$cacheDriver->save('cache_id', 'my_data');
|
||||
|
||||
Memcached
|
||||
~~~~~~~~~
|
||||
~~~~~~~~
|
||||
|
||||
Memcached is a more recent and complete alternative extension to
|
||||
Memcache.
|
||||
@@ -282,8 +260,6 @@ 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
|
||||
------------------------
|
||||
|
||||
@@ -307,7 +283,7 @@ use on your ORM configuration.
|
||||
|
||||
<?php
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$config->setQueryCacheImpl(new \Doctrine\Common\Cache\ApcuCache());
|
||||
$config->setQueryCacheImpl(new \Doctrine\Common\Cache\ApcCache());
|
||||
|
||||
Result Cache
|
||||
~~~~~~~~~~~~
|
||||
@@ -320,7 +296,7 @@ cache implementation.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config->setResultCacheImpl(new \Doctrine\Common\Cache\ApcuCache());
|
||||
$config->setResultCacheImpl(new \Doctrine\Common\Cache\ApcCache());
|
||||
|
||||
Now when you're executing DQL queries you can configure them to use
|
||||
the result cache.
|
||||
@@ -337,7 +313,7 @@ result cache driver.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query->setResultCacheDriver(new \Doctrine\Common\Cache\ApcuCache());
|
||||
$query->setResultCacheDriver(new \Doctrine\Common\Cache\ApcCache());
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -389,7 +365,7 @@ first.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcuCache());
|
||||
$config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcCache());
|
||||
|
||||
Now the metadata information will only be parsed once and stored in
|
||||
the cache driver.
|
||||
@@ -425,39 +401,6 @@ 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.
|
||||
|
||||
Cache Chaining
|
||||
--------------
|
||||
|
||||
A common pattern is to use a static cache to store data that is
|
||||
requested many times in a single PHP request. Even though this data
|
||||
may be stored in a fast memory cache, often that cache is over a
|
||||
network link leading to sizable network traffic.
|
||||
|
||||
The ChainCache class allows multiple caches to be registered at once.
|
||||
For example, a per-request ArrayCache can be used first, followed by
|
||||
a (relatively) slower MemcacheCache if the ArrayCache misses.
|
||||
ChainCache automatically handles pushing data up to faster caches in
|
||||
the chain and clearing data in the entire stack when it is deleted.
|
||||
|
||||
A ChainCache takes a simple array of CacheProviders in the order that
|
||||
they should be used.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$arrayCache = new \Doctrine\Common\Cache\ArrayCache();
|
||||
$memcache = new Memcache();
|
||||
$memcache->connect('memcache_host', 11211);
|
||||
$chainCache = new \Doctrine\Common\Cache\ChainCache([
|
||||
$arrayCache,
|
||||
$memcache,
|
||||
]);
|
||||
|
||||
ChainCache itself extends the CacheProvider interface, so it is
|
||||
possible to create chains of chains. While this may seem like an easy
|
||||
way to build a simple high-availability cache, ChainCache does not
|
||||
implement any exception handling so using it as a high-availability
|
||||
mechanism is not recommended.
|
||||
|
||||
Cache Slams
|
||||
-----------
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
Installation and Configuration
|
||||
==============================
|
||||
|
||||
Doctrine can be installed with `Composer <https://getcomposer.org>`_.
|
||||
Doctrine can be installed with `Composer <http://www.getcomposer.org>`_. For
|
||||
older versions we still have `PEAR packages
|
||||
<http://pear.doctrine-project.org>`_.
|
||||
|
||||
Define the following requirement in your ``composer.json`` file:
|
||||
|
||||
@@ -14,7 +16,8 @@ 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 <https://getcomposer.org/doc/00-intro.md>`_ to set up.
|
||||
how Composer works, check out their `Getting Started
|
||||
<http://getcomposer.org/doc/00-intro.md>`_ to set up.
|
||||
|
||||
Class loading
|
||||
-------------
|
||||
@@ -76,13 +79,6 @@ Or if you prefer YAML:
|
||||
$config = Setup::createYAMLMetadataConfiguration($paths, $isDevMode);
|
||||
$entityManager = EntityManager::create($dbParams, $config);
|
||||
|
||||
.. note::
|
||||
If you want to use yml mapping you should add yaml dependency to your `composer.json`:
|
||||
|
||||
::
|
||||
|
||||
"symfony/yaml": "*"
|
||||
|
||||
Inside the ``Setup`` methods several assumptions are made:
|
||||
|
||||
- If `$isDevMode` is true caching is done in memory with the ``ArrayCache``. Proxy objects are recreated on every request.
|
||||
@@ -90,7 +86,8 @@ 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::
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ Doctrine Query Language
|
||||
===========================
|
||||
|
||||
DQL stands for Doctrine Query Language and is an Object
|
||||
Query Language derivative that is very similar to the Hibernate
|
||||
Query Language derivate that is very similar to the Hibernate
|
||||
Query Language (HQL) or the Java Persistence Query Language (JPQL).
|
||||
|
||||
In essence, DQL provides powerful querying capabilities over your
|
||||
@@ -34,9 +34,9 @@ object model.
|
||||
|
||||
DQL SELECT statements are a very powerful way of retrieving parts
|
||||
of your domain model that are not accessible via associations.
|
||||
Additionally they allow you to retrieve entities and their associations
|
||||
Additionally they allow to retrieve entities and their associations
|
||||
in one single SQL select statement which can make a huge difference
|
||||
in performance compared to using several queries.
|
||||
in performance in contrast to using several queries.
|
||||
|
||||
DQL UPDATE and DELETE statements offer a way to execute bulk
|
||||
changes on the entities of your domain model. This is often
|
||||
@@ -49,6 +49,10 @@ SELECT queries
|
||||
DQL SELECT clause
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The select clause of a DQL query specifies what appears in the
|
||||
query result. The composition of all the expressions in the select
|
||||
clause also influences the nature of the query result.
|
||||
|
||||
Here is an example that selects all users with an age > 20:
|
||||
|
||||
.. code-block:: php
|
||||
@@ -79,58 +83,14 @@ Lets examine the query:
|
||||
The result of this query would be a list of User objects where all
|
||||
users are older than 20.
|
||||
|
||||
Result format
|
||||
~~~~~~~~~~~~~
|
||||
The composition of the expressions in the SELECT clause also
|
||||
influences the nature of the query result. There are three
|
||||
cases:
|
||||
|
||||
**All objects**
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
SELECT u, p, n FROM Users u...
|
||||
|
||||
In this case, the result will be an array of User objects because of
|
||||
the FROM clause, with children ``p`` and ``n`` hydrated because of
|
||||
their inclusion in the SELECT clause.
|
||||
|
||||
**All scalars**
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
SELECT u.name, u.address FROM Users u...
|
||||
|
||||
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.
|
||||
|
||||
You can select scalars from any entity in the query.
|
||||
|
||||
**Mixed**
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
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
|
||||
``p.quantity``.
|
||||
|
||||
Multiple FROM clauses are allowed, which would cause the result
|
||||
array elements to cycle through the classes included in the
|
||||
multiple FROM clauses.
|
||||
|
||||
.. note::
|
||||
|
||||
You cannot select other entities unless you also select the
|
||||
root of the selection (which is the first entity in FROM).
|
||||
|
||||
For example, ``SELECT p,n FROM Users u...`` would be wrong because
|
||||
``u`` is not part of the SELECT
|
||||
|
||||
Doctrine throws an exception if you violate this constraint.
|
||||
|
||||
The SELECT clause allows to specify both class identification
|
||||
variables that signal the hydration of a complete entity class or
|
||||
just fields of the entity using the syntax ``u.name``. Combinations
|
||||
of both are also allowed and it is possible to wrap both fields and
|
||||
identification values into aggregation and DQL functions. Numerical
|
||||
fields can be part of computations using mathematical operations.
|
||||
See the sub-section on `Functions, Operators, Aggregates`_ for
|
||||
more information.
|
||||
|
||||
Joins
|
||||
~~~~~
|
||||
@@ -359,8 +319,7 @@ article-ids:
|
||||
$query = $em->createQuery('SELECT u.id, a.id as article_id FROM CmsUser u LEFT JOIN u.articles a');
|
||||
$results = $query->getResult(); // array of user ids and every article_id for each user
|
||||
|
||||
Restricting a JOIN clause by additional conditions specified by
|
||||
WITH:
|
||||
Restricting a JOIN clause by additional conditions:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -493,18 +452,6 @@ Joins between entities without associations were not possible until version
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT u FROM User u JOIN Blacklist b WITH u.email = b.email');
|
||||
|
||||
.. note::
|
||||
The differences between WHERE, WITH and HAVING clauses may be
|
||||
confusing.
|
||||
|
||||
- WHERE is applied to the results of an entire query
|
||||
- WITH is applied to a join as an additional condition. For
|
||||
arbitrary joins (SELECT f, b FROM Foo f, Bar b WITH f.id = b.id)
|
||||
the WITH is required, even if it is 1 = 1
|
||||
- HAVING is applied to the results of a query after
|
||||
aggregation (GROUP BY)
|
||||
|
||||
|
||||
Partial Object Syntax
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -539,9 +486,9 @@ You use the partial syntax when joining as well:
|
||||
Using the ``NEW`` operator you can construct Data Transfer Objects (DTOs) directly from DQL queries.
|
||||
|
||||
- When using ``SELECT NEW`` you don't need to specify a mapped entity.
|
||||
- You can specify any PHP class, it only requires that the constructor of this class matches the ``NEW`` statement.
|
||||
- You can specify any PHP class, it's only require that the constructor of this class matches the ``NEW`` statement.
|
||||
- This approach involves determining exactly which columns you really need,
|
||||
and instantiating a data-transfer object that contains a constructor with those arguments.
|
||||
and instantiating data-transfer object that containing a constructor with those arguments.
|
||||
|
||||
If you want to select data-transfer objects you should create a class:
|
||||
|
||||
@@ -654,15 +601,12 @@ The same restrictions apply for the reference of related entities.
|
||||
DQL DELETE statements are ported directly into a
|
||||
Database DELETE statement and therefore bypass any events and checks for the
|
||||
version column if they are not explicitly added to the WHERE clause
|
||||
of the query. Additionally Deletes of specified entities are *NOT*
|
||||
of the query. Additionally Deletes of specifies entities are *NOT*
|
||||
cascaded to related entities even if specified in the metadata.
|
||||
|
||||
|
||||
Functions, Operators, Aggregates
|
||||
--------------------------------
|
||||
It is possible to wrap both fields and identification values into
|
||||
aggregation and DQL functions. Numerical fields can be part of
|
||||
computations using mathematical operations.
|
||||
|
||||
DQL Functions
|
||||
~~~~~~~~~~~~~
|
||||
@@ -691,8 +635,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, 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_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_DIFF(date1, date2) - Calculate the difference in days between date1-date2.
|
||||
|
||||
Arithmetic operators
|
||||
@@ -775,6 +719,8 @@ classes have to implement the base class :
|
||||
|
||||
public function parse(\Doctrine\ORM\Query\Parser $parser)
|
||||
{
|
||||
$lexer = $parser->getLexer();
|
||||
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
@@ -965,7 +911,7 @@ An instance of the ``Doctrine\ORM\Query`` class represents a DQL
|
||||
query. You create a Query instance be calling
|
||||
``EntityManager#createQuery($dql)``, passing the DQL query string.
|
||||
Alternatively you can create an empty ``Query`` instance and invoke
|
||||
``Query#setDQL($dql)`` afterwards. Here are some examples:
|
||||
``Query#setDql($dql)`` afterwards. Here are some examples:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -975,9 +921,9 @@ Alternatively you can create an empty ``Query`` instance and invoke
|
||||
// example1: passing a DQL string
|
||||
$q = $em->createQuery('select u from MyProject\Model\User u');
|
||||
|
||||
// example2: using setDQL
|
||||
// example2: using setDql
|
||||
$q = $em->createQuery();
|
||||
$q->setDQL('select u from MyProject\Model\User u');
|
||||
$q->setDql('select u from MyProject\Model\User u');
|
||||
|
||||
Query Result Formats
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -996,9 +942,8 @@ 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 the
|
||||
result contains more than one object, a ``NonUniqueResultException``
|
||||
is thrown. If no object is found null will be returned.
|
||||
- ``Query#getOneOrNullResult()``: Retrieve a single object. 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.
|
||||
@@ -1061,7 +1006,7 @@ structure:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$dql = "SELECT u, 'some scalar string', count(g.id) AS num FROM User u JOIN u.groups g GROUP BY u.id";
|
||||
$dql = "SELECT u, 'some scalar string', count(u.groups) AS num FROM User u JOIN u.groups g GROUP BY u.id";
|
||||
|
||||
array
|
||||
[0]
|
||||
@@ -1161,22 +1106,6 @@ Object hydration hydrates the result set into the object graph:
|
||||
$query = $em->createQuery('SELECT u FROM CmsUser u');
|
||||
$users = $query->getResult(Query::HYDRATE_OBJECT);
|
||||
|
||||
Sometimes the behavior in the object hydrator can be confusing, which is
|
||||
why we are listing as many of the assumptions here for reference:
|
||||
|
||||
- Objects fetched in a FROM clause are returned as a Set, that means every
|
||||
object is only ever included in the resulting array once. This is the case
|
||||
even when using JOIN or GROUP BY in ways that return the same row for an
|
||||
object multiple times. If the hydrator sees the same object multiple times,
|
||||
then it makes sure it is only returned once.
|
||||
|
||||
- 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.
|
||||
|
||||
This list might be incomplete.
|
||||
|
||||
Array Hydration
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -1453,13 +1382,7 @@ 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,
|
||||
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.
|
||||
Changing the fetch mode during a query is only possible for one-to-one and many-to-one relations.
|
||||
|
||||
|
||||
EBNF
|
||||
@@ -1488,9 +1411,7 @@ Terminals
|
||||
~~~~~~~~~
|
||||
|
||||
|
||||
- identifier (name, email, ...) must match ``[a-z_][a-z0-9_]*``
|
||||
- fully_qualified_name (Doctrine\Tests\Models\CMS\CmsUser) matches PHP's fully qualified class names
|
||||
- aliased_name (CMS:CmsUser) uses two identifiers, one for the namespace alias and one for the class inside it
|
||||
- identifier (name, email, ...)
|
||||
- string ('foo', 'bar''s house', '%ninja%', ...)
|
||||
- char ('/', '\\', ' ', ...)
|
||||
- integer (-1, 0, 1, 34, ...)
|
||||
@@ -1524,8 +1445,8 @@ Identifiers
|
||||
/* Alias Identification declaration (the "u" of "FROM User u") */
|
||||
AliasIdentificationVariable :: = identifier
|
||||
|
||||
/* identifier that must be a class name (the "User" of "FROM User u"), possibly as a fully qualified class name or namespace-aliased */
|
||||
AbstractSchemaName ::= fully_qualified_name | aliased_name | identifier
|
||||
/* identifier that must be a class name (the "User" of "FROM User u") */
|
||||
AbstractSchemaName ::= identifier
|
||||
|
||||
/* Alias ResultVariable declaration (the "total" of "COUNT(*) AS total") */
|
||||
AliasResultVariable = identifier
|
||||
@@ -1624,7 +1545,7 @@ Select Expressions
|
||||
SimpleSelectExpression ::= (StateFieldPathExpression | IdentificationVariable | FunctionDeclaration | AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable]
|
||||
PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
|
||||
PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
|
||||
NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")"
|
||||
NewObjectExpression ::= "NEW" IdentificationVariable "(" NewObjectArg {"," NewObjectArg}* ")"
|
||||
NewObjectArg ::= ScalarExpression | "(" Subselect ")"
|
||||
|
||||
Conditional Expressions
|
||||
|
||||
@@ -164,8 +164,7 @@ the life-time of their registered entities.
|
||||
database insert operations. Generated primary key values are
|
||||
available in the postPersist event.
|
||||
- preUpdate - The preUpdate event occurs before the database
|
||||
update operations to entity data. It is not called for a DQL UPDATE statement
|
||||
nor when the computed changeset is empty.
|
||||
update operations to entity data. It is not called for a DQL UPDATE statement.
|
||||
- postUpdate - The postUpdate event occurs after the database
|
||||
update operations to entity data. It is not called for a DQL UPDATE statement.
|
||||
- postLoad - The postLoad event occurs for an entity after the
|
||||
@@ -179,7 +178,7 @@ the life-time of their registered entities.
|
||||
allows providing fallback metadata even when no actual metadata exists
|
||||
or could be found. This event is not a lifecycle callback.
|
||||
- preFlush - The preFlush event occurs at the very beginning of a flush
|
||||
operation.
|
||||
operation. This event is not a lifecycle callback.
|
||||
- onFlush - The onFlush event occurs after the change-sets of all
|
||||
managed entities are computed. This event is not a lifecycle
|
||||
callback.
|
||||
@@ -323,7 +322,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
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
/Users/robo/dev/php/Doctrine/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="User">
|
||||
|
||||
@@ -406,8 +405,8 @@ behaviors across different entity classes.
|
||||
|
||||
Note that they require much more detailed knowledge about the inner
|
||||
workings of the EntityManager and UnitOfWork. Please read the
|
||||
:ref:`reference-events-implementing-listeners` section carefully if you
|
||||
are trying to write your own listener.
|
||||
*Implementing Event Listeners* section carefully if you are trying
|
||||
to write your own listener.
|
||||
|
||||
For event subscribers, there are no surprises. They declare the
|
||||
lifecycle events in their ``getSubscribedEvents`` method and provide
|
||||
@@ -434,7 +433,7 @@ A lifecycle event listener looks like the following:
|
||||
}
|
||||
}
|
||||
|
||||
A lifecycle event subscriber may look like this:
|
||||
A lifecycle event subscriber may looks like this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -548,9 +547,8 @@ preFlush
|
||||
~~~~~~~~
|
||||
|
||||
``preFlush`` is called at ``EntityManager#flush()`` before
|
||||
anything else. ``EntityManager#flush()`` should not be called inside
|
||||
its listeners, since `preFlush` event is dispatched in it, which would
|
||||
result in infinite loop.
|
||||
anything else. ``EntityManager#flush()`` can be called safely
|
||||
inside its listeners.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -654,8 +652,7 @@ preUpdate
|
||||
|
||||
PreUpdate is the most restrictive to use event, since it is called
|
||||
right before an update statement is called for an entity inside the
|
||||
``EntityManager#flush()`` method. Note that this event is not
|
||||
triggered when the computed changeset is empty.
|
||||
``EntityManager#flush()`` method.
|
||||
|
||||
Changes to associations of the updated entity are never allowed in
|
||||
this event, since Doctrine cannot guarantee to correctly handle
|
||||
@@ -741,7 +738,7 @@ The three post events are called inside ``EntityManager#flush()``.
|
||||
Changes in here are not relevant to the persistence in the
|
||||
database, but you can use these events to alter non-persistable items,
|
||||
like non-mapped fields, logging or even associated classes that are
|
||||
not directly mapped by Doctrine.
|
||||
directly mapped by Doctrine.
|
||||
|
||||
postLoad
|
||||
~~~~~~~~
|
||||
@@ -889,9 +886,6 @@ you need to map the listener method using the event type mapping:
|
||||
preRemove: [preRemoveHandler]
|
||||
# ....
|
||||
|
||||
.. note::
|
||||
|
||||
The order of execution of multiple methods for the same event (e.g. multiple @PrePersist) is not guaranteed.
|
||||
|
||||
|
||||
Entity listeners resolver
|
||||
|
||||
@@ -21,6 +21,12 @@ 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?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -198,21 +204,6 @@ 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 OPcache.
|
||||
It is highly recommended to make use of a bytecode cache like APC.
|
||||
A bytecode cache removes the need for parsing PHP code on every
|
||||
request and can greatly improve performance.
|
||||
|
||||
@@ -26,8 +26,6 @@ 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
|
||||
--------------------------------
|
||||
|
||||
@@ -44,8 +42,6 @@ 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
|
||||
----------------------
|
||||
|
||||
@@ -56,7 +52,7 @@ for more information on how this fetch mode works.
|
||||
Temporarily change fetch mode in DQL
|
||||
------------------------------------
|
||||
|
||||
See :ref:`dql-temporarily-change-fetch-mode`
|
||||
See :ref:`Doctrine Query Language chapter <dql-temporarily-change-fetch-mode>`
|
||||
|
||||
|
||||
Apply Best Practices
|
||||
@@ -65,7 +61,6 @@ 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
|
||||
------------------------
|
||||
|
||||
@@ -125,8 +125,9 @@ Example:
|
||||
Things to note:
|
||||
|
||||
|
||||
- The @InheritanceType and @DiscriminatorColumn must be specified
|
||||
on the topmost class that is part of the mapped entity hierarchy.
|
||||
- The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap
|
||||
must be specified on the topmost class that is part of the mapped
|
||||
entity hierarchy.
|
||||
- The @DiscriminatorMap specifies which values of the
|
||||
discriminator column identify a row as being of a certain type. In
|
||||
the case above a value of "person" identifies a row as being of
|
||||
@@ -159,7 +160,7 @@ This strategy is very efficient for querying across all types in
|
||||
the hierarchy or for specific types. No table joins are required,
|
||||
only a WHERE clause listing the type identifiers. In particular,
|
||||
relationships involving types that employ this mapping strategy are
|
||||
very performing.
|
||||
very performant.
|
||||
|
||||
There is a general performance consideration with Single Table
|
||||
Inheritance: If the target-entity of a many-to-one or one-to-one
|
||||
@@ -174,7 +175,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 allow
|
||||
root entity but in any of the different sub-entities has to allows
|
||||
null values. Columns that have NOT NULL constraints have to be on
|
||||
the root entity of the single-table inheritance hierarchy.
|
||||
|
||||
@@ -454,8 +455,6 @@ Things to note:
|
||||
- This feature is available for all kind of associations. (OneToOne, OneToMany, ManyToOne, ManyToMany)
|
||||
- The association type *CANNOT* be changed.
|
||||
- The override could redefine the joinTables or joinColumns depending on the association type.
|
||||
- The override could redefine inversedBy to reference more than one extended entity.
|
||||
- The override could redefine fetch to modify the fetch strategy of the extended entity.
|
||||
|
||||
Attribute Override
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -493,7 +492,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 +500,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,4 +1,5 @@
|
||||
Installation
|
||||
============
|
||||
|
||||
The installation chapter has moved to :doc:`Installation and Configuration <reference/configuration>`_.
|
||||
The installation chapter has moved to `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 <https://github.com/doctrine/orm/issues/3743>`_.
|
||||
`is described in the DDC-298 ticket <http://www.doctrine-project.org/jira/browse/DDC-298>`_.
|
||||
|
||||
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 <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"
|
||||
- `DDC-875 <http://www.doctrine-project.org/jira/browse/DDC-875>`_ Merge can sometimes add the same entity twice into a collection
|
||||
- `DDC-763 <http://www.doctrine-project.org/jira/browse/DDC-763>`_ Cascade merge on associated entities can insert too many rows through "Persistence by Reachability"
|
||||
|
||||
Custom Persisters
|
||||
~~~~~~~~~~~~~~~~~
|
||||
@@ -83,8 +83,10 @@ 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 <https://github.com/doctrine/orm/issues/5178>`_
|
||||
- `Evaluate possible ways in which stored-procedures can be used <https://github.com/doctrine/orm/issues/4946>`_
|
||||
|
||||
- `Add Upsert Support <http://www.doctrine-project.org/jira/browse/DDC-668>`_
|
||||
- `Evaluate possible ways in which stored-procedures can be used <http://www.doctrine-project.org/jira/browse/DDC-445>`_
|
||||
- The previous Filter Rules Feature Request
|
||||
|
||||
Persist Keys of Collections
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -94,7 +96,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 <https://github.com/doctrine/orm/issues/2817>`_
|
||||
`Ticket DDC-213 <http://www.doctrine-project.org/jira/browse/DDC-213>`_
|
||||
|
||||
Mapping many tables to one entity
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -112,10 +114,11 @@ in the core library. We don't think behaviors add more value than
|
||||
they cost pain and debugging hell. Please see the many different
|
||||
blog posts we have written on this topics:
|
||||
|
||||
- `Doctrine2 "Behaviors" in a Nutshell <http://www.doctrine-project.org/2010/02/17/doctrine2-behaviours-nutshell.html>`_
|
||||
- `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>`_
|
||||
- `Doctrine2 "Behaviors" in a Nutshell <http://www.doctrine-project.org/blog/doctrine2-behaviours-nutshell>`_
|
||||
- `A re-usable Versionable behavior for Doctrine2 <http://www.doctrine-project.org/blog/doctrine2-versionable>`_
|
||||
- `Write your own ORM on top of Doctrine2 <http://www.doctrine-project.org/blog/your-own-orm-doctrine2>`_
|
||||
- `Doctrine 2 Behavioral Extensions <http://www.doctrine-project.org/blog/doctrine2-behavioral-extensions>`_
|
||||
- `Doctrator <https://github.com/pablodip/doctrator`>_
|
||||
|
||||
Doctrine 2 has enough hooks and extension points so that **you** can
|
||||
add whatever you want on top of it. None of this will ever become
|
||||
@@ -143,7 +146,9 @@ 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 bugs, improvement and feature requests on `Github issues <https://github.com/doctrine/orm/issues>`_.
|
||||
See the Open Bugs on Jira for more details on `bugs, improvement and feature
|
||||
requests
|
||||
<http://www.doctrine-project.org/jira/secure/IssueNavigator.jspa?reset=true&mode=hide&pid=10032&resolution=-1&sorter/field=updated&sorter/order=DESC>`_.
|
||||
|
||||
Identifier Quoting and Legacy Databases
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -177,27 +182,3 @@ MySQL with MyISAM tables
|
||||
Doctrine cannot provide atomic operations when calling ``EntityManager#flush()`` if one
|
||||
of the tables involved uses the storage engine MyISAM. You must use InnoDB or
|
||||
other storage engines that support transactions if you need integrity.
|
||||
|
||||
Entities, Proxies and Reflection
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Using methods for Reflection on entities can be prone to error, when the entity
|
||||
is actually a proxy the following methods will not work correctly:
|
||||
|
||||
- ``new ReflectionClass``
|
||||
- ``new ReflectionObject``
|
||||
- ``get_class()``
|
||||
- ``get_parent_class()``
|
||||
|
||||
This is why ``Doctrine\Common\Util\ClassUtils`` class exists that has similar
|
||||
methods, which resolve the proxy problem beforehand.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
|
||||
$bookProxy = $entityManager->getReference('Acme\Book');
|
||||
|
||||
$reflection = ClassUtils::newReflectionClass($bookProxy);
|
||||
$class = ClassUtils::getClass($bookProxy)¸
|
||||
|
||||
@@ -35,7 +35,7 @@ an entity.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$em->getConfiguration()->setMetadataCacheImpl(new ApcuCache());
|
||||
$em->getConfiguration()->setMetadataCacheImpl(new ApcCache());
|
||||
|
||||
|
||||
If you want to use one of the included core metadata drivers you
|
||||
|
||||
@@ -3,43 +3,48 @@ 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
|
||||
reduce the verbosity of the mapping document, eliminating repetitive noise (eg: ``TABLE_``).
|
||||
Using a naming strategy you can provide rules for automatically generating
|
||||
database identifiers, columns and tables names
|
||||
when the table/column name is not given.
|
||||
This feature helps reduce the verbosity of the mapping document,
|
||||
eliminating repetitive noise (eg: ``TABLE_``).
|
||||
|
||||
|
||||
Configuring a naming strategy
|
||||
-----------------------------
|
||||
The default strategy used by Doctrine is quite minimal.
|
||||
|
||||
By default the ``Doctrine\ORM\Mapping\DefaultNamingStrategy``
|
||||
uses the simple class name and the attribute names to generate tables and columns.
|
||||
uses the simple class name and the attributes names to generate tables and columns
|
||||
|
||||
You can specify a different strategy by calling ``Doctrine\ORM\Configuration#setNamingStrategy()``:
|
||||
You can specify a different strategy by calling ``Doctrine\ORM\Configuration#setNamingStrategy()`` :
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$namingStrategy = new MyNamingStrategy();
|
||||
$configuration->setNamingStrategy($namingStrategy);
|
||||
$configuration()->setNamingStrategy($namingStrategy);
|
||||
|
||||
Underscore naming strategy
|
||||
---------------------------
|
||||
|
||||
``\Doctrine\ORM\Mapping\UnderscoreNamingStrategy`` is a built-in strategy.
|
||||
``\Doctrine\ORM\Mapping\UnderscoreNamingStrategy`` is a built-in strategy
|
||||
that might be a useful if you want to use a underlying convention.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$namingStrategy = new \Doctrine\ORM\Mapping\UnderscoreNamingStrategy(CASE_UPPER);
|
||||
$configuration->setNamingStrategy($namingStrategy);
|
||||
$configuration()->setNamingStrategy($namingStrategy);
|
||||
|
||||
Then SomeEntityName will generate the table SOME_ENTITY_NAME when CASE_UPPER
|
||||
or some_entity_name using CASE_LOWER is given.
|
||||
|
||||
For SomeEntityName the strategy will generate the table SOME_ENTITY_NAME with the
|
||||
``CASE_UPPER`` option, or some_entity_name with the ``CASE_LOWER`` option.
|
||||
|
||||
Naming strategy interface
|
||||
-------------------------
|
||||
The interface ``Doctrine\ORM\Mapping\NamingStrategy`` allows you to specify
|
||||
a naming strategy for database tables and columns.
|
||||
a "naming standard" for database tables and columns.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -96,11 +101,10 @@ a naming strategy for database tables and columns.
|
||||
|
||||
Implementing a naming strategy
|
||||
-------------------------------
|
||||
If you have database naming standards, like all table names should be prefixed
|
||||
by the application prefix, all column names should be lower case, you can easily
|
||||
achieve such standards by implementing a naming strategy.
|
||||
|
||||
You need to create a class which implements ``Doctrine\ORM\Mapping\NamingStrategy``.
|
||||
If you have database naming standards like all tables names should be prefixed
|
||||
by the application prefix, all column names should be upper case,
|
||||
you can easily achieve such standards by implementing a naming strategy.
|
||||
You need to implements NamingStrategy first. Following is an example
|
||||
|
||||
|
||||
.. code-block:: php
|
||||
@@ -135,3 +139,12 @@ You need to create a class which implements ``Doctrine\ORM\Mapping\NamingStrateg
|
||||
($referencedColumnName ?: $this->referenceColumnName()));
|
||||
}
|
||||
}
|
||||
|
||||
Configuring the namingstrategy is easy if.
|
||||
Just set your naming strategy calling ``Doctrine\ORM\Configuration#setNamingStrategy()`` :.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$namingStrategy = new MyAppNamingStrategy();
|
||||
$configuration()->setNamingStrategy($namingStrategy);
|
||||
|
||||
@@ -63,7 +63,7 @@ This has several benefits:
|
||||
- The API is much simpler than the usual ``ResultSetMapping`` API.
|
||||
|
||||
One downside is that the builder API does not yet support entities
|
||||
with inheritance hierarchies.
|
||||
with inheritance hierachies.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -92,7 +92,7 @@ a mapping from DQL alias (key) to SQL alias (value)
|
||||
|
||||
<?php
|
||||
|
||||
$selectClause = $rsm->generateSelectClause(array(
|
||||
$selectClause = $builder->generateSelectClause(array(
|
||||
'u' => 't1',
|
||||
'g' => 't2'
|
||||
));
|
||||
|
||||
@@ -7,20 +7,14 @@ conditionally constructing a DQL query in several steps.
|
||||
It provides a set of classes and methods that is able to
|
||||
programmatically build queries, and also provides a fluent API.
|
||||
This means that you can change between one methodology to the other
|
||||
as you want, 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>`_.
|
||||
as you want, and also pick one if you prefer.
|
||||
|
||||
Constructing a new QueryBuilder object
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The same way you build a normal Query, you build a ``QueryBuilder``
|
||||
object. Here is an example of how to build a ``QueryBuilder``
|
||||
object:
|
||||
object, just providing the correct method name. Here is an example
|
||||
how to build a ``QueryBuilder`` object:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -30,9 +24,9 @@ object:
|
||||
// example1: creating a QueryBuilder instance
|
||||
$qb = $em->createQueryBuilder();
|
||||
|
||||
An instance of QueryBuilder has several informative methods. One
|
||||
good example is to inspect what type of object the
|
||||
``QueryBuilder`` is.
|
||||
Once you have created an instance of QueryBuilder, it provides a
|
||||
set of useful informative functions that you can use. One good
|
||||
example is to inspect what type of object the ``QueryBuilder`` is.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -86,11 +80,11 @@ Working with QueryBuilder
|
||||
High level API methods
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
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
|
||||
``QueryBuilder`` helper methods:
|
||||
To simplify even more the way you build a query in Doctrine, we can take
|
||||
advantage of what we call Helper methods. For all base code, there
|
||||
is a set of useful methods to simplify a programmer's life. To
|
||||
illustrate how to work with them, here is the same example 6
|
||||
re-written using ``QueryBuilder`` helper methods:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -103,9 +97,10 @@ 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
|
||||
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:
|
||||
build DQL queries. Although it is supported, it should be avoided
|
||||
to use string based queries and greatly encouraged to use
|
||||
``$qb->expr()->*`` methods. Here is a converted example 8 to
|
||||
suggested standard way to build queries:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -118,7 +113,7 @@ suggested way to build queries with dynamic conditions:
|
||||
$qb->expr()->eq('u.id', '?1'),
|
||||
$qb->expr()->like('u.nickname', '?2')
|
||||
))
|
||||
->orderBy('u.surname', 'ASC');
|
||||
->orderBy('u.surname', 'ASC'));
|
||||
|
||||
Here is a complete list of helper methods available in ``QueryBuilder``:
|
||||
|
||||
@@ -131,7 +126,7 @@ Here is a complete list of helper methods available in ``QueryBuilder``:
|
||||
// Example - $qb->select(array('u', 'p'))
|
||||
// Example - $qb->select($qb->expr()->select('u', 'p'))
|
||||
public function select($select = null);
|
||||
|
||||
|
||||
// addSelect does not override previous calls to select
|
||||
//
|
||||
// Example - $qb->select('u');
|
||||
@@ -322,7 +317,7 @@ the Query object which can be retrieved from ``EntityManager#createQuery()``.
|
||||
Executing a Query
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
The QueryBuilder is a builder object only - it has no means of actually
|
||||
The QueryBuilder is a builder object only, it has no means of actually
|
||||
executing the Query. Additionally a set of parameters such as query hints
|
||||
cannot be set on the QueryBuilder itself. This is why you always have to convert
|
||||
a querybuilder instance into a Query object:
|
||||
@@ -504,32 +499,14 @@ complete list of supported helper methods available:
|
||||
public function countDistinct($x); // Returns Expr\Func
|
||||
}
|
||||
|
||||
Adding a Criteria to a Query
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can also add a :ref:`filtering-collections` to a QueryBuilder by
|
||||
using ``addCriteria``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
// ...
|
||||
|
||||
$criteria = Criteria::create()
|
||||
->orderBy(['firstName', 'ASC']);
|
||||
|
||||
// $qb instanceof QueryBuilder
|
||||
$qb->addCriteria($criteria);
|
||||
// then execute your query like normal
|
||||
|
||||
Low Level API
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Now we will describe the low level method of creating queries.
|
||||
It may be useful to work at this level for optimization purposes,
|
||||
but most of the time it is preferred to work at a higher level of
|
||||
abstraction.
|
||||
Now we have describe the low level (thought of as the
|
||||
hardcore method) of creating queries. It may be useful to work at
|
||||
this level for optimization purposes, but most of the time it is
|
||||
preferred to work at a higher level of abstraction.
|
||||
|
||||
All helper methods in ``QueryBuilder`` actually rely on a single
|
||||
one: ``add()``. This method is responsible of building every piece
|
||||
@@ -582,3 +559,7 @@ same query of example 6 written using
|
||||
->add('where', new Expr\Comparison('u.id', '=', '?1'))
|
||||
->add('orderBy', new Expr\OrderBy('u.name', 'ASC'));
|
||||
|
||||
Of course this is the hardest way to build a DQL query in Doctrine.
|
||||
To simplify some of these efforts, we introduce what we call as
|
||||
``Expr`` helper class.
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ The Second Level Cache is designed to reduce the amount of necessary database ac
|
||||
It sits between your application and the database to avoid the number of database hits as much as possible.
|
||||
|
||||
When turned on, entities will be first searched in cache and if they are not found,
|
||||
a database query will be fired and then the entity result will be stored in a cache provider.
|
||||
a database query will be fired an then the entity result will be stored in a cache provider.
|
||||
|
||||
There are some flavors of caching available, but is better to cache read-only data.
|
||||
|
||||
@@ -97,7 +97,7 @@ Defines a contract for accessing a particular region.
|
||||
|
||||
Defines a contract for accessing a particular cache region.
|
||||
|
||||
`See API Doc <https://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/Region.html>`_.
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-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 <https://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/ConcurrentRegion.html>`_.
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-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/current/Doctrine/ORM/Cache/TimestampRegion.html>`_.
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.TimestampRegion.html/>`_.
|
||||
|
||||
.. _reference-second-level-cache-mode:
|
||||
|
||||
@@ -149,25 +149,25 @@ Caching mode
|
||||
|
||||
|
||||
Built-in cached persisters
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Cached persisters are responsible to access cache regions.
|
||||
|
||||
+-----------------------+-------------------------------------------------------------------------------------------+
|
||||
| Cache Usage | Persister |
|
||||
+=======================+===========================================================================================+
|
||||
| READ_ONLY | Doctrine\\ORM\\Cache\\Persister\\Entity\\ReadOnlyCachedEntityPersister |
|
||||
+-----------------------+-------------------------------------------------------------------------------------------+
|
||||
| READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\Entity\\ReadWriteCachedEntityPersister |
|
||||
+-----------------------+-------------------------------------------------------------------------------------------+
|
||||
| NONSTRICT_READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\Entity\\NonStrictReadWriteCachedEntityPersister |
|
||||
+-----------------------+-------------------------------------------------------------------------------------------+
|
||||
| READ_ONLY | Doctrine\\ORM\\Cache\\Persister\\Collection\\ReadOnlyCachedCollectionPersister |
|
||||
+-----------------------+-------------------------------------------------------------------------------------------+
|
||||
| READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\Collection\\ReadWriteCachedCollectionPersister |
|
||||
+-----------------------+-------------------------------------------------------------------------------------------+
|
||||
| NONSTRICT_READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\Collection\\NonStrictReadWriteCachedCollectionPersister |
|
||||
+-----------------------+-------------------------------------------------------------------------------------------+
|
||||
+-----------------------+-------------------------------------------------------------------------------+
|
||||
| Cache Usage | Persister |
|
||||
+=======================+===============================================================================+
|
||||
| READ_ONLY | Doctrine\\ORM\\Cache\\Persister\\ReadOnlyCachedEntityPersister |
|
||||
+-----------------------+-------------------------------------------------------------------------------+
|
||||
| READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\ReadWriteCachedEntityPersister |
|
||||
+-----------------------+-------------------------------------------------------------------------------+
|
||||
| NONSTRICT_READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\NonStrictReadWriteCachedEntityPersister |
|
||||
+-----------------------+-------------------------------------------------------------------------------+
|
||||
| READ_ONLY | Doctrine\\ORM\\Cache\\Persister\\ReadOnlyCachedCollectionPersister |
|
||||
+-----------------------+-------------------------------------------------------------------------------+
|
||||
| READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\ReadWriteCachedCollectionPersister |
|
||||
+-----------------------+-------------------------------------------------------------------------------+
|
||||
| NONSTRICT_READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\NonStrictReadWriteCacheCollectionPersister |
|
||||
+-----------------------+-------------------------------------------------------------------------------+
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
@@ -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/current/Doctrine/ORM/Cache/DefaultCacheFactory.html>`_.
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.DefaultCacheFactory.html/>`_.
|
||||
|
||||
Region Lifetime
|
||||
~~~~~~~~~~~~~~~
|
||||
@@ -220,7 +220,7 @@ To specify a default lifetime for all regions or specify a different lifetime fo
|
||||
|
||||
<?php
|
||||
/* @var $config \Doctrine\ORM\Configuration */
|
||||
/* @var $cacheConfig \Doctrine\ORM\Cache\CacheConfiguration */
|
||||
/* @var $cacheConfig \Doctrine\ORM\Configuration */
|
||||
$cacheConfig = $config->getSecondLevelCacheConfiguration();
|
||||
$regionConfig = $cacheConfig->getRegionsConfiguration();
|
||||
|
||||
@@ -239,7 +239,7 @@ By providing a cache logger you should be able to get information about all cach
|
||||
|
||||
<?php
|
||||
/* @var $config \Doctrine\ORM\Configuration */
|
||||
$logger = new \Doctrine\ORM\Cache\Logging\StatisticsCacheLogger();
|
||||
$logger = \Doctrine\ORM\Cache\Logging\StatisticsCacheLogger();
|
||||
|
||||
// Cache logger
|
||||
$config->setSecondLevelCacheEnabled(true);
|
||||
@@ -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/current/Doctrine/ORM/Cache/Logging/CacheLogger.html>`_.
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.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 https://www.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 http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
<entity name="Country">
|
||||
<cache usage="READ_ONLY" region="my_entity_region" />
|
||||
<id name="id" type="integer" column="id">
|
||||
@@ -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 https://www.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 http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
<entity name="State">
|
||||
|
||||
<cache usage="NONSTRICT_READ_WRITE" />
|
||||
@@ -619,7 +619,7 @@ Execute the ``UPDATE`` and invalidate ``a specific cache entry`` using the cache
|
||||
$em->getCache()->evictEntity('Entity\Country', 1);
|
||||
|
||||
Using the repository query cache
|
||||
--------------------------------
|
||||
---------------------
|
||||
|
||||
As well as ``Query Cache`` all persister queries store only identifier values for an individual query.
|
||||
All persister use a single timestamps cache region keeps track of the last update for each persister,
|
||||
|
||||
@@ -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 <http://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/security.html>`
|
||||
- [DBAL Security Page](https://github.com/doctrine/dbal/blob/master/docs/en/reference/security.rst)
|
||||
|
||||
If you find a Security bug in Doctrine, please report it on Jira and change the
|
||||
Security Level to "Security Issues". It will be visible to Doctrine Core
|
||||
@@ -32,7 +32,7 @@ You can consider the following APIs to be safe from SQL injection:
|
||||
- Queries through the Criteria API on ``Doctrine\ORM\PersistentCollection`` and
|
||||
``Doctrine\ORM\EntityRepository``.
|
||||
|
||||
You are **NOT** safe from SQL injection when using user input with:
|
||||
You are **NOT** save from SQL injection when using user input with:
|
||||
|
||||
- Expression API of ``Doctrine\ORM\QueryBuilder``
|
||||
- Concatenating user input into DQL SELECT, UPDATE or DELETE statements or
|
||||
@@ -119,7 +119,7 @@ entity might look like this:
|
||||
}
|
||||
|
||||
Now the possiblity of mass-asignment exists on this entity and can
|
||||
be exploited by attackers to set the "isAdmin" flag to true on any
|
||||
be exploitet by attackers to set the "isAdmin" flag to true on any
|
||||
object when you pass the whole request data to this method like:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -205,7 +205,7 @@ tables of the current model to clean up with orphaned tables.
|
||||
You can also use database introspection to update your schema
|
||||
easily with the ``updateSchema()`` method. It will compare your
|
||||
existing database schema to the passed array of
|
||||
``ClassMetadataInfo`` instances.
|
||||
``ClassMetdataInfo`` instances.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -252,6 +252,15 @@ 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
|
||||
-----------------
|
||||
|
||||
@@ -376,7 +385,7 @@ First you need to retrieve the metadata instances with the
|
||||
)
|
||||
);
|
||||
|
||||
$cmf = new \Doctrine\ORM\Tools\DisconnectedClassMetadataFactory();
|
||||
$cmf = new DisconnectedClassMetadataFactory();
|
||||
$cmf->setEntityManager($em);
|
||||
$metadata = $cmf->getAllMetadata();
|
||||
|
||||
@@ -386,7 +395,6 @@ to yml:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cme = new \Doctrine\ORM\Tools\Export\ClassMetadataExporter();
|
||||
$exporter = $cme->getExporter('yml', '/path/to/export/yml');
|
||||
$exporter->setMetadata($metadata);
|
||||
$exporter->export();
|
||||
@@ -468,7 +476,7 @@ To include a new command on Doctrine Console, you need to do modify the
|
||||
|
||||
<?php
|
||||
// doctrine.php
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Helper\Application;
|
||||
|
||||
// as before ...
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
Transactions and Concurrency
|
||||
============================
|
||||
|
||||
.. _transactions-and-concurrency_transaction-demarcation:
|
||||
|
||||
Transaction Demarcation
|
||||
-----------------------
|
||||
|
||||
@@ -28,8 +26,6 @@ and control transaction demarcation yourself.
|
||||
These are two ways to deal with transactions when using the
|
||||
Doctrine ORM and are now described in more detail.
|
||||
|
||||
.. _transactions-and-concurrency_approach-implicitly:
|
||||
|
||||
Approach 1: Implicitly
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -53,8 +49,6 @@ the DML operations by the Doctrine ORM and is sufficient if all the
|
||||
data manipulation that is part of a unit of work happens through
|
||||
the domain model and thus the ORM.
|
||||
|
||||
.. _transactions-and-concurrency_approach-explicitly:
|
||||
|
||||
Approach 2: Explicitly
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -75,7 +69,7 @@ looks like this:
|
||||
$em->flush();
|
||||
$em->getConnection()->commit();
|
||||
} catch (Exception $e) {
|
||||
$em->getConnection()->rollBack();
|
||||
$em->getConnection()->rollback();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
@@ -104,21 +98,12 @@ functionally equivalent to the previously shown code looks as follows:
|
||||
$em->persist($user);
|
||||
});
|
||||
|
||||
.. warning::
|
||||
|
||||
For historical reasons, ``EntityManager#transactional($func)`` will return
|
||||
``true`` whenever the return value of ``$func`` is loosely false.
|
||||
Some examples of this include ``array()``, ``"0"``, ``""``, ``0``, and
|
||||
``null``.
|
||||
|
||||
The difference between ``Connection#transactional($func)`` and
|
||||
``EntityManager#transactional($func)`` is that the latter
|
||||
abstraction flushes the ``EntityManager`` prior to transaction
|
||||
commit and rolls back the transaction when an
|
||||
exception occurs.
|
||||
|
||||
.. _transactions-and-concurrency_exception-handling:
|
||||
|
||||
Exception Handling
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -149,8 +134,6 @@ knowing that their state is potentially no longer accurate.
|
||||
If you intend to start another unit of work after an exception has
|
||||
occurred you should do that with a new ``EntityManager``.
|
||||
|
||||
.. _transactions-and-concurrency_locking-support:
|
||||
|
||||
Locking Support
|
||||
---------------
|
||||
|
||||
@@ -159,8 +142,6 @@ strategies natively. This allows to take very fine-grained control
|
||||
over what kind of locking is required for your Entities in your
|
||||
application.
|
||||
|
||||
.. _transactions-and-concurrency_optimistic-locking:
|
||||
|
||||
Optimistic Locking
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -187,68 +168,30 @@ has been modified by someone else already.
|
||||
You designate a version field in an entity as follows. In this
|
||||
example we'll use an integer.
|
||||
|
||||
.. configuration-block::
|
||||
.. code-block:: php
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
/** @Version @Column(type="integer") */
|
||||
private $version;
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="User">
|
||||
<field name="version" type="integer" version="true" />
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
User:
|
||||
type: entity
|
||||
fields:
|
||||
version:
|
||||
type: integer
|
||||
version: true
|
||||
<?php
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
/** @Version @Column(type="integer") */
|
||||
private $version;
|
||||
// ...
|
||||
}
|
||||
|
||||
Alternatively a datetime type can be used (which maps to a SQL
|
||||
timestamp or datetime):
|
||||
|
||||
.. configuration-block::
|
||||
.. code-block:: php
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
/** @Version @Column(type="datetime") */
|
||||
private $version;
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="User">
|
||||
<field name="version" type="datetime" version="true" />
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
User:
|
||||
type: entity
|
||||
fields:
|
||||
version:
|
||||
type: datetime
|
||||
version: true
|
||||
<?php
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
/** @Version @Column(type="datetime") */
|
||||
private $version;
|
||||
// ...
|
||||
}
|
||||
|
||||
Version numbers (not timestamps) should however be preferred as
|
||||
they can not potentially conflict in a highly concurrent
|
||||
@@ -362,8 +305,6 @@ And the change headline action (POST Request):
|
||||
|
||||
$post = $em->find('BlogPost', $postId, \Doctrine\DBAL\LockMode::OPTIMISTIC, $postVersion);
|
||||
|
||||
.. _transactions-and-concurrency_pessimistic-locking:
|
||||
|
||||
Pessimistic Locking
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -15,10 +15,10 @@ Bidirectional Associations
|
||||
|
||||
The following rules apply to **bidirectional** associations:
|
||||
|
||||
- The inverse side has to have the ``mappedBy`` attribute of the OneToOne,
|
||||
- The inverse side has to use the ``mappedBy`` attribute of the OneToOne,
|
||||
OneToMany, or ManyToMany mapping declaration. The mappedBy
|
||||
attribute contains the name of the association-field on the owning side.
|
||||
- The owning side has to have the ``inversedBy`` attribute of the
|
||||
- The owning side has to use the ``inversedBy`` attribute of the
|
||||
OneToOne, ManyToOne, or ManyToMany mapping declaration.
|
||||
The inversedBy attribute contains the name of the association-field
|
||||
on the inverse-side.
|
||||
|
||||
@@ -156,7 +156,7 @@ wishes to be hydrated. Default result-types include:
|
||||
- SQL to simple scalar result arrays
|
||||
- SQL to a single result variable
|
||||
|
||||
Hydration to entities and arrays is one of the most complex parts of Doctrine
|
||||
Hydration to entities and arrays is one of 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 association is updated, Doctrine only checks
|
||||
- When a bidirectional assocation is updated, Doctrine only checks
|
||||
on one of both sides for these changes. This is called the :doc:`owning side <unitofwork-associations>`
|
||||
of the association.
|
||||
- A property with a reference to many entities has to be instances of the
|
||||
@@ -238,14 +238,14 @@ the database permanently.
|
||||
|
||||
Notice how both sides of the bidirectional association are always
|
||||
updated. Unidirectional associations are consequently simpler to
|
||||
handle.
|
||||
|
||||
Also note that if you use type-hinting in your methods, you will
|
||||
have to specify a nullable type, i.e. ``setAddress(?Address $address)``,
|
||||
otherwise ``setAddress(null)`` will fail to remove the association.
|
||||
Another way to deal with this is to provide a special method, like
|
||||
``removeAddress()``. This can also provide better encapsulation as
|
||||
it hides the internal meaning of not having an address.
|
||||
handle. Also note that if you use type-hinting in your methods, i.e.
|
||||
``setAddress(Address $address)``, PHP will only allow null
|
||||
values if ``null`` is set as default value. Otherwise
|
||||
setAddress(null) will fail for removing the association. If you
|
||||
insist on type-hinting a typical way to deal with this is to
|
||||
provide a special method, like ``removeAddress()``. This can also
|
||||
provide better encapsulation as it hides the internal meaning of
|
||||
not having an address.
|
||||
|
||||
When working with collections, keep in mind that a Collection is
|
||||
essentially an ordered map (just like a PHP array). That is why the
|
||||
@@ -396,25 +396,54 @@ There are two approaches to handle this problem in your code:
|
||||
|
||||
1. Ignore updating the inverse side of bidirectional collections,
|
||||
BUT never read from them in requests that changed their state. In
|
||||
the next request Doctrine hydrates the consistent collection state
|
||||
the next Request Doctrine hydrates the consistent collection state
|
||||
again.
|
||||
2. Always keep the bidirectional collections in sync through
|
||||
association management methods. Reads of the Collections directly
|
||||
after changes are consistent then.
|
||||
|
||||
.. _transitive-persistence:
|
||||
|
||||
Transitive persistence / Cascade Operations
|
||||
-------------------------------------------
|
||||
|
||||
Doctrine 2 provides a mechanism for transitive persistence through cascading of certain operations.
|
||||
Each association to another entity or a collection of
|
||||
entities can be configured to automatically cascade the following operations to the associated entities:
|
||||
``persist``, ``remove``, ``merge``, ``detach``, ``refresh`` or ``all``.
|
||||
Persisting, removing, detaching, refreshing and merging individual entities can
|
||||
become pretty cumbersome, especially when a highly interweaved object graph
|
||||
is involved. Therefore Doctrine 2 provides a
|
||||
mechanism for transitive persistence through cascading of these
|
||||
operations. Each association to another entity or a collection of
|
||||
entities can be configured to automatically cascade certain
|
||||
operations. By default, no operations are cascaded.
|
||||
|
||||
The main use case for ``cascade: persist`` is to avoid "exposing" associated entities to your PHP application.
|
||||
Continuing with the User-Comment example of this chapter, this is how the creation of a new user and a new
|
||||
comment might look like in your controller (without ``cascade: persist``):
|
||||
The following cascade options exist:
|
||||
|
||||
|
||||
- persist : Cascades persist operations to the associated
|
||||
entities.
|
||||
- remove : Cascades remove operations to the associated entities.
|
||||
- merge : Cascades merge operations to the associated entities.
|
||||
- detach : Cascades detach operations to the associated entities.
|
||||
- refresh : Cascades refresh operations to the associated entities.
|
||||
- all : Cascades persist, remove, merge, refresh and detach operations to
|
||||
associated entities.
|
||||
|
||||
.. note::
|
||||
|
||||
Cascade operations are performed in memory. That means collections and related entities
|
||||
are fetched into memory, even if they are still marked as lazy when
|
||||
the cascade operation is about to be performed. However this approach allows
|
||||
entity lifecycle events to be performed for each of these operations.
|
||||
|
||||
However, pulling objects graph into memory on cascade can cause considerable performance
|
||||
overhead, especially when cascading collections are large. Makes sure
|
||||
to weigh the benefits and downsides of each cascade operation that you define.
|
||||
|
||||
To rely on the database level cascade operations for the delete operation instead, you can
|
||||
configure each join column with the **onDelete** option. See the respective
|
||||
mapping driver chapters for more information.
|
||||
|
||||
The following example is an extension to the User-Comment example
|
||||
of this chapter. Suppose in our application a user is created
|
||||
whenever he writes his first comment. In this case we would use the
|
||||
following code:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -424,39 +453,37 @@ comment might look like in your controller (without ``cascade: persist``):
|
||||
$user->addComment($myFirstComment);
|
||||
|
||||
$em->persist($user);
|
||||
$em->persist($myFirstComment); // required, if `cascade: persist` is not set
|
||||
$em->persist($myFirstComment);
|
||||
$em->flush();
|
||||
|
||||
Note that the Comment entity is instantiated right here in the controller.
|
||||
To avoid this, ``cascade: persist`` allows you to "hide" the Comment entity from the controller,
|
||||
only accessing it through the User entity:
|
||||
Even if you *persist* a new User that contains our new Comment this
|
||||
code would fail if you removed the call to
|
||||
``EntityManager#persist($myFirstComment)``. Doctrine 2 does not
|
||||
cascade the persist operation to all nested entities that are new
|
||||
as well.
|
||||
|
||||
More complicated is the deletion of all of a user's comments when he is
|
||||
removed from the system:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// User entity
|
||||
class User
|
||||
{
|
||||
private $id;
|
||||
private $comments;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->id = User::new();
|
||||
$this->comments = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function comment(string $text, DateTimeInterface $time) : void
|
||||
{
|
||||
$newComment = Comment::create($text, $time);
|
||||
$newComment->setUser($this);
|
||||
$this->comments->add($newComment);
|
||||
}
|
||||
|
||||
// ...
|
||||
$user = $em->find('User', $deleteUserId);
|
||||
|
||||
foreach ($user->getAuthoredComments() as $comment) {
|
||||
$em->remove($comment);
|
||||
}
|
||||
$em->remove($user);
|
||||
$em->flush();
|
||||
|
||||
If you then set up the cascading to the ``User#commentsAuthored`` property...
|
||||
Without the loop over all the authored comments Doctrine would use
|
||||
an UPDATE statement only to set the foreign key to NULL and only
|
||||
the User would be deleted from the database during the
|
||||
flush()-Operation.
|
||||
|
||||
To have Doctrine handle both cases automatically we can change the
|
||||
``User#commentsAuthored`` property to cascade both the "persist"
|
||||
and the "remove" operation.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -473,51 +500,10 @@ If you then set up the cascading to the ``User#commentsAuthored`` property...
|
||||
//...
|
||||
}
|
||||
|
||||
...you can now create a user and an associated comment like this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$user = new User();
|
||||
$user->comment('Lorem ipsum', new DateTime());
|
||||
|
||||
$em->persist($user);
|
||||
$em->flush();
|
||||
|
||||
.. note::
|
||||
|
||||
The idea of ``cascade: persist`` is not to save you any lines of code in the controller.
|
||||
If you instantiate the comment object in the controller (i.e. don't set up the user entity as shown above),
|
||||
even with ``cascade: persist`` you still have to call ``$myFirstComment->setUser($user);``.
|
||||
|
||||
Thanks to ``cascade: remove``, you can easily delete a user and all linked comments without having to loop through them:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$user = $em->find('User', $deleteUserId);
|
||||
|
||||
$em->remove($user);
|
||||
$em->flush();
|
||||
|
||||
.. note::
|
||||
|
||||
Cascade operations are performed in memory. That means collections and related entities
|
||||
are fetched into memory (even if they are marked as lazy) when
|
||||
the cascade operation is about to be performed. This approach allows
|
||||
entity lifecycle events to be performed for each of these operations.
|
||||
|
||||
However, pulling object graphs into memory on cascade can cause considerable performance
|
||||
overhead, especially when the cascaded collections are large. Make sure
|
||||
to weigh the benefits and downsides of each cascade operation that you define.
|
||||
|
||||
To rely on the database level cascade operations for the delete operation instead, you can
|
||||
configure each join column with :doc:`the onDelete option <working-with-objects>`.
|
||||
|
||||
Even though automatic cascading is convenient, it should be used
|
||||
with care. Do not blindly apply ``cascade=all`` to all associations as
|
||||
Even though automatic cascading is convenient it should be used
|
||||
with care. Do not blindly apply cascade=all to all associations as
|
||||
it will unnecessarily degrade the performance of your application.
|
||||
For each cascade operation that gets activated, Doctrine also
|
||||
For each cascade operation that gets activated Doctrine also
|
||||
applies that operation to the association, be it single or
|
||||
collection valued.
|
||||
|
||||
@@ -525,20 +511,21 @@ Persistence by Reachability: Cascade Persist
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
There are additional semantics that apply to the Cascade Persist
|
||||
operation. During each ``flush()`` operation Doctrine detects if there
|
||||
operation. During each flush() operation Doctrine detects if there
|
||||
are new entities in any collection and three possible cases can
|
||||
happen:
|
||||
|
||||
|
||||
1. New entities in a collection marked as ``cascade: persist`` will be
|
||||
1. New entities in a collection marked as cascade persist will be
|
||||
directly persisted by Doctrine.
|
||||
2. New entities in a collection not marked as ``cascade: persist`` will
|
||||
produce an Exception and rollback the ``flush()`` operation.
|
||||
2. New entities in a collection not marked as cascade persist will
|
||||
produce an Exception and rollback the flush() operation.
|
||||
3. Collections without new entities are skipped.
|
||||
|
||||
This concept is called Persistence by Reachability: New entities
|
||||
that are found on already managed entities are automatically
|
||||
persisted as long as the association is defined as ``cascade: persist``.
|
||||
persisted as long as the association is defined as cascade
|
||||
persist.
|
||||
|
||||
Orphan Removal
|
||||
--------------
|
||||
@@ -616,11 +603,11 @@ address reference. When flush is called not only are the references removed
|
||||
but both the old standing data and the one address entity are also deleted
|
||||
from the database.
|
||||
|
||||
.. _filtering-collections:
|
||||
|
||||
Filtering Collections
|
||||
---------------------
|
||||
|
||||
.. filtering-collections:
|
||||
|
||||
Collections have a filtering API that allows to slice parts of data from
|
||||
a collection. If the collection has not been loaded from the database yet,
|
||||
the filtering API can work on the SQL level to make optimized access to
|
||||
@@ -716,9 +703,6 @@ methods:
|
||||
* ``in($field, array $values)``
|
||||
* ``notIn($field, array $values)``
|
||||
* ``contains($field, $value)``
|
||||
* ``memberOf($value, $field)``
|
||||
* ``startsWith($field, $value)``
|
||||
* ``endsWith($field, $value)``
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -25,13 +25,6 @@ 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
|
||||
-----------------------------
|
||||
@@ -245,7 +238,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").
|
||||
- If X is a removed entity, it becomes managed.
|
||||
- If X is a detached entity, an exception will be thrown on
|
||||
flush.
|
||||
@@ -286,12 +279,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`").
|
||||
with cascade=REMOVE or cascade=ALL (see "Transitive Persistence").
|
||||
- If X is a managed entity, the remove operation causes it to
|
||||
become removed. The remove operation is cascaded to entities
|
||||
referenced by X, if the relationships from X to these other
|
||||
entities is mapped with cascade=REMOVE or cascade=ALL (see
|
||||
":ref:`transitive-persistence`").
|
||||
"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.
|
||||
@@ -357,14 +350,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`"). Entities which previously referenced X
|
||||
"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`"). Entities which previously referenced X
|
||||
"Transitive Persistence"). Entities which previously referenced X
|
||||
will continue to reference X.
|
||||
|
||||
There are several situations in which an entity is detached
|
||||
@@ -423,7 +416,8 @@ 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`").
|
||||
the cascade element value MERGE or ALL (see "Transitive
|
||||
Persistence").
|
||||
- For all entities Y referenced by relationships from X having the
|
||||
cascade element value MERGE or ALL, Y is merged recursively as Y'.
|
||||
For all such Y referenced by X, X' is set to reference Y'. (Note
|
||||
@@ -705,6 +699,8 @@ You can also load by owning side associations through the repository:
|
||||
$number = $em->find('MyProject\Domain\Phonenumber', 1234);
|
||||
$user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('phone' => $number->getId()));
|
||||
|
||||
Be careful that this only works by passing the ID of the associated entity, not yet by passing the associated entity itself.
|
||||
|
||||
The ``EntityRepository#findBy()`` method additionally accepts orderings, limit and offset as second to fourth parameters:
|
||||
|
||||
.. code-block:: php
|
||||
@@ -733,14 +729,6 @@ examples are equivalent:
|
||||
// A single user by its nickname (__call magic)
|
||||
$user = $em->getRepository('MyProject\Domain\User')->findOneByNickname('romanb');
|
||||
|
||||
Additionally, you can just count the result of the provided conditions when you don't really need the data:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// Check there is no user with nickname
|
||||
$availableNickname = 0 === $em->getRepository('MyProject\Domain\User')->count(['nickname' => 'nonexistent']);
|
||||
|
||||
By Criteria
|
||||
~~~~~~~~~~~
|
||||
|
||||
@@ -750,7 +738,8 @@ The Repository implement the ``Doctrine\Common\Collections\Selectable``
|
||||
interface. That means you can build ``Doctrine\Common\Collections\Criteria``
|
||||
and pass them to the ``matching($criteria)`` method.
|
||||
|
||||
See section `Filtering collections` of chapter :doc:`Working with Associations <working-with-associations>`
|
||||
See the :ref:`Working with Associations: Filtering collections
|
||||
<filtering-collections>`.
|
||||
|
||||
By Eager Loading
|
||||
~~~~~~~~~~~~~~~~
|
||||
@@ -800,9 +789,7 @@ 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. 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
|
||||
``Doctrine\ORM\QueryBuilder`` class. 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
|
||||
`https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd <https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd>`_.
|
||||
`http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd <http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd>`_.
|
||||
In order to point to the latest version of the document of a
|
||||
particular stable release branch, just append the release number,
|
||||
i.e.: doctrine-mapping-2.0.xsd The most convenient way to work with
|
||||
@@ -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://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
https://raw.github.com/doctrine/doctrine2/master/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
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Doctrine\Tests\ORM\Mapping\User" table="cms_users">
|
||||
|
||||
@@ -321,12 +321,12 @@ Using the simplified definition above Doctrine will use no
|
||||
identifier strategy for this entity. That means you have to
|
||||
manually set the identifier before calling
|
||||
``EntityManager#persist($entity)``. This is the so called
|
||||
``NONE`` strategy.
|
||||
``ASSIGNED`` strategy.
|
||||
|
||||
If you want to switch the identifier generation strategy you have
|
||||
to nest a ``<generator />`` element inside the id-element. This of
|
||||
course only works for surrogate keys. For composite keys you always
|
||||
have to use the ``NONE`` strategy.
|
||||
have to use the ``ASSIGNED`` strategy.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
@@ -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
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Application\Model\ArticleAttribute">
|
||||
<id name="article" association-key="true" />
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
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.
|
||||
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
.. 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,6 +75,7 @@ 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
|
||||
|
||||
@@ -13,7 +13,7 @@ This tutorial shows how the semantics of composite primary keys work and how the
|
||||
General Considerations
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Every entity with a composite key cannot use an id generator other than "NONE". That means
|
||||
Every entity with a composite key cannot use an id generator other than "ASSIGNED". That means
|
||||
the ID fields have to have their values set before you call ``EntityManager#persist($entity)``.
|
||||
|
||||
Primitive Types only
|
||||
@@ -38,7 +38,7 @@ and year of production as primary keys:
|
||||
/** @Id @Column(type="string") */
|
||||
private $name;
|
||||
/** @Id @Column(type="integer") */
|
||||
private $year;
|
||||
private $year
|
||||
|
||||
public function __construct($name, $year)
|
||||
{
|
||||
@@ -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
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
http://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
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Application\Model\ArticleAttribute">
|
||||
<id name="article" association-key="true" />
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
Separating Concerns using Embeddables
|
||||
-------------------------------------
|
||||
|
||||
Embeddables are classes which are not entities themselves, but are embedded
|
||||
Embeddables are classes which are not entities themself, but are embedded
|
||||
in entities and can also be queried in DQL. You'll mostly want to use them
|
||||
to reduce duplication or separating concerns. Value objects such as date range
|
||||
or address are the primary use case for this feature.
|
||||
|
||||
.. note::
|
||||
|
||||
Embeddables can only contain properties with basic ``@Column`` mapping.
|
||||
or address are the primary use case for this feature. Embeddables can only
|
||||
contain properties with basic ``@Column`` mapping.
|
||||
|
||||
For the purposes of this tutorial, we will assume that you have a ``User``
|
||||
class in your application and you would like to store an address in
|
||||
@@ -79,20 +76,6 @@ In terms of your database schema, Doctrine will automatically inline all
|
||||
columns from the ``Address`` class into the table of the ``User`` class,
|
||||
just as if you had declared them directly there.
|
||||
|
||||
Initializing embeddables
|
||||
------------------------
|
||||
|
||||
In case all fields in the embeddable are ``nullable``, you might want
|
||||
to initialize the embeddable, to avoid getting a null value instead of
|
||||
the embedded object.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->address = new Address();
|
||||
}
|
||||
|
||||
Column Prefixing
|
||||
----------------
|
||||
|
||||
|
||||
@@ -65,7 +65,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
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
http://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 Database First, you already have a database schema
|
||||
When you have a :doc:`Database First <getting-started-database>`, you already have a database schema
|
||||
and generate the corresponding PHP code from it.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -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 Model First, you are modelling your application using tools (for
|
||||
you :doc:`Model First <getting-started-models>`, you are modelling your application using tools (for
|
||||
example UML) and generate database schema and PHP code from this model.
|
||||
When you have a :doc:`Database First <getting-started-database>`, then you already have a database schema
|
||||
and generate the corresponding PHP code from it.
|
||||
|
||||
@@ -14,25 +14,26 @@ Guide Assumptions
|
||||
-----------------
|
||||
|
||||
This guide is designed for beginners that haven't worked with Doctrine ORM
|
||||
before. There are some prerequisites for the tutorial that have to be
|
||||
before. There are some prerequesites for the tutorial that have to be
|
||||
installed:
|
||||
|
||||
- PHP (latest stable version)
|
||||
- PHP 5.4 or above
|
||||
- Composer Package Manager (`Install Composer
|
||||
<https://getcomposer.org/doc/00-intro.md>`_)
|
||||
<http://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.6** and above.
|
||||
This tutorial assumes you work with **Doctrine 2.4** and above.
|
||||
Some of the code will not work with lower versions.
|
||||
|
||||
What is Doctrine?
|
||||
-----------------
|
||||
|
||||
Doctrine 2 is an `object-relational mapper (ORM) <https://en.wikipedia.org/wiki/Object-relational_mapping>`_
|
||||
for PHP 7.1+ that provides transparent persistence for PHP objects. It uses the Data Mapper
|
||||
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
|
||||
pattern at the heart, aiming for a complete separation of your domain/business
|
||||
logic from the persistence in a relational database management system.
|
||||
|
||||
@@ -50,7 +51,7 @@ Entities are PHP Objects that can be identified over many requests
|
||||
by a unique identifier or primary key. These classes don't need to extend any
|
||||
abstract base class or interface. An entity class must not be final
|
||||
or contain final methods. Additionally it must not implement
|
||||
**clone** nor **wakeup**, unless it :doc:`does so safely <../cookbook/implementing-wakeup-or-clone>`.
|
||||
**clone** nor **wakeup** or :doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
|
||||
|
||||
An entity contains persistable properties. A persistable property
|
||||
is an instance variable of the entity that is saved into and retrieved from the database
|
||||
@@ -61,7 +62,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 <https://framework.zend.com/manual/1.12/en/zend.db.adapter.html>`_
|
||||
`Zend\_Db\_Table <http://framework.zend.com/manual/en/zend.db.table.html>`_
|
||||
documentation. Reading their documentation we can extract the
|
||||
requirements:
|
||||
|
||||
@@ -79,14 +80,14 @@ Project Setup
|
||||
-------------
|
||||
|
||||
Create a new empty folder for this tutorial project, for example
|
||||
``doctrine2-tutorial`` and create a new file ``composer.json`` inside
|
||||
that directory with the following contents:
|
||||
``doctrine2-tutorial`` and create a new file ``composer.json`` with
|
||||
the following contents:
|
||||
|
||||
::
|
||||
|
||||
{
|
||||
"require": {
|
||||
"doctrine/orm": "^2.6.2",
|
||||
"doctrine/orm": "2.4.*",
|
||||
"symfony/yaml": "2.*"
|
||||
},
|
||||
"autoload": {
|
||||
@@ -102,26 +103,23 @@ Install Doctrine using the Composer Dependency Management tool, by calling:
|
||||
$ composer install
|
||||
|
||||
This will install the packages Doctrine Common, Doctrine DBAL, Doctrine ORM,
|
||||
into the ``vendor`` directory.
|
||||
Symfony YAML and Symfony Console into the `vendor` directory. The Symfony
|
||||
dependencies are not required by Doctrine but will be used in this tutorial.
|
||||
|
||||
Add the following directories:
|
||||
::
|
||||
|
||||
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
|
||||
---------------------------
|
||||
|
||||
Doctrine's public interface is through the ``EntityManager``. This class
|
||||
provides access points to the complete lifecycle management for your entities,
|
||||
Doctrine's public interface is the EntityManager, it provides the
|
||||
access point to the complete lifecycle management of your entities
|
||||
and transforms entities from and back to persistence. You have to
|
||||
configure and create it to use your entities with Doctrine 2. I
|
||||
will show the configuration steps and then discuss them step by
|
||||
@@ -138,10 +136,7 @@ step:
|
||||
|
||||
// Create a simple "default" Doctrine ORM configuration for Annotations
|
||||
$isDevMode = true;
|
||||
$proxyDir = null;
|
||||
$cache = null;
|
||||
$useSimpleAnnotationReader = false;
|
||||
$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/src"), $isDevMode, $proxyDir, $cache, $useSimpleAnnotationReader);
|
||||
$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/src"), $isDevMode);
|
||||
// or if you prefer yaml or XML
|
||||
//$config = Setup::createXMLMetadataConfiguration(array(__DIR__."/config/xml"), $isDevMode);
|
||||
//$config = Setup::createYAMLMetadataConfiguration(array(__DIR__."/config/yaml"), $isDevMode);
|
||||
@@ -155,16 +150,8 @@ step:
|
||||
// obtaining the entity manager
|
||||
$entityManager = EntityManager::create($conn, $config);
|
||||
|
||||
.. 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 first require statement sets up the autoloading capabilities of Doctrine
|
||||
using the Composer autoload.
|
||||
|
||||
The second block consists of the instantiation of the ORM
|
||||
``Configuration`` object using the Setup helper. It assumes a bunch
|
||||
@@ -172,10 +159,10 @@ of defaults that you don't have to bother about for now. You can
|
||||
read up on the configuration details in the
|
||||
:doc:`reference chapter on configuration <../reference/configuration>`.
|
||||
|
||||
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
|
||||
The third block shows the configuration options required to connect
|
||||
to a database, in my case a file-based sqlite database. All the
|
||||
configuration options for all the shipped drivers are given in the
|
||||
`DBAL Configuration section of the manual <https://www.doctrine-project.org/projects/doctrine-dbal/en/current/>`_.
|
||||
`DBAL Configuration section of the manual <http://www.doctrine-project.org/documentation/manual/2_0/en/dbal>`_.
|
||||
|
||||
The last block shows how the ``EntityManager`` is obtained from a
|
||||
factory method.
|
||||
@@ -183,10 +170,15 @@ factory method.
|
||||
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:
|
||||
Now that we have defined the Metadata mappings and bootstrapped the
|
||||
EntityManager we want to generate the relational database schema
|
||||
from it. Doctrine has a Command-Line Interface that allows you to
|
||||
access the SchemaTool, a component that generates the required
|
||||
tables to work with the metadata.
|
||||
|
||||
For the command-line tool to work a cli-config.php file has to be
|
||||
present in the project root directory, where you will execute the
|
||||
doctrine command. Its a fairly simple file:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -196,37 +188,40 @@ defined entity classes and their metadata. For this tool to work, a
|
||||
|
||||
return \Doctrine\ORM\Tools\Console\ConsoleRunner::createHelperSet($entityManager);
|
||||
|
||||
Now call the Doctrine command-line tool:
|
||||
You can then change into your project directory and 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
|
||||
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.
|
||||
At this point no entity metadata exists in `src` so you will see a message like
|
||||
"No Metadata Classes to process." Don't worry, we'll create a Product entity and
|
||||
corresponding metadata in the next section.
|
||||
|
||||
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 easily recreate the database using the following commands:
|
||||
You should be aware that during the development process you'll periodically need
|
||||
to update your database schema to be in sync with your Entities metadata.
|
||||
|
||||
You can easily recreate the database:
|
||||
|
||||
::
|
||||
|
||||
$ vendor/bin/doctrine orm:schema-tool:drop --force
|
||||
$ vendor/bin/doctrine orm:schema-tool:create
|
||||
|
||||
Or you can use the update functionality:
|
||||
Or 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, a cornerstone of the ``Doctrine\DBAL`` package,
|
||||
which can even be used without the Doctrine ORM package.
|
||||
|
||||
Starting with the Product Entity
|
||||
--------------------------------
|
||||
Starting with the Product
|
||||
-------------------------
|
||||
|
||||
We start with the simplest entity, the Product. Create a ``src/Product.php`` file to contain the ``Product``
|
||||
entity definition:
|
||||
@@ -262,16 +257,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``).
|
||||
The use of mutators allows Doctrine to hook into calls which
|
||||
manipulate the entities in ways that it could not if you just
|
||||
Note that all fields are set to protected (not public) with a
|
||||
mutator (getter and setter) defined for every field except $id.
|
||||
The use of mutators allows Doctrine to hook into calls which
|
||||
manipulate the entities in ways that it could not if you just
|
||||
directly set the values with ``entity#field = foo;``
|
||||
|
||||
The id field has no setter since, generally speaking, your code
|
||||
should not set this value since it represents a database id value.
|
||||
(Note that Doctrine itself can still set the value using the
|
||||
Reflection API instead of a defined setter function.)
|
||||
The 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
|
||||
@@ -279,10 +274,9 @@ language. The metadata language describes how entities, their
|
||||
properties and references should be persisted and what constraints
|
||||
should be applied to them.
|
||||
|
||||
Metadata for an Entity can be configured using DocBlock annotations directly
|
||||
in the Entity class itself, or in an external XML or YAML file. This Getting
|
||||
Started guide will demonstrate metadata mappings using all three methods,
|
||||
but you only need to choose one.
|
||||
Metadata for entities are configured using a XML, YAML or Docblock Annotations.
|
||||
This Getting Started Guide will show the mappings for all Mapping Drivers.
|
||||
References in the text will be made to the XML mapping.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
@@ -290,24 +284,14 @@ but you only need to choose one.
|
||||
|
||||
<?php
|
||||
// src/Product.php
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="products")
|
||||
*/
|
||||
* @Entity @Table(name="products")
|
||||
**/
|
||||
class Product
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\GeneratedValue
|
||||
*/
|
||||
/** @Id @Column(type="integer") @GeneratedValue **/
|
||||
protected $id;
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
*/
|
||||
/** @Column(type="string") **/
|
||||
protected $name;
|
||||
|
||||
// .. (other code)
|
||||
@@ -319,7 +303,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
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Product" table="products">
|
||||
<id name="id" type="integer">
|
||||
@@ -330,10 +314,6 @@ 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
|
||||
@@ -349,31 +329,30 @@ but you only need to choose one.
|
||||
name:
|
||||
type: string
|
||||
|
||||
The top-level ``entity`` definition specifies information about
|
||||
the class and table name. The primitive type ``Product#name`` is
|
||||
The top-level ``entity`` definition tag 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
|
||||
use the database platform's native id generation strategy (for
|
||||
example, AUTO INCREMENT in the case of MySql, or Sequences in the
|
||||
the ``id`` tag, this has a ``generator`` tag nested inside which
|
||||
defines that the primary key generation mechanism automatically
|
||||
uses the database platforms native id generation strategy (for
|
||||
example AUTO INCREMENT in the case of MySql or Sequences in the
|
||||
case of PostgreSql and Oracle).
|
||||
|
||||
Now that we have defined our first entity and its metadata,
|
||||
let's update the database schema:
|
||||
Now that we have defined our first entity, let's update the database:
|
||||
|
||||
::
|
||||
|
||||
$ vendor/bin/doctrine orm:schema-tool:update --force --dump-sql
|
||||
|
||||
Specifying both flags ``--force`` and ``--dump-sql`` will cause the DDL
|
||||
statements to be executed and then printed to the screen.
|
||||
Specifying both flags ``--force`` and ``-dump-sql`` prints and executes the DDL
|
||||
statements.
|
||||
|
||||
Now, we'll create a new script to insert products into the database:
|
||||
Now create a new script that will insert products into the database:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// create_product.php <name>
|
||||
// create_product.php
|
||||
require_once "bootstrap.php";
|
||||
|
||||
$newProductName = $argv[1];
|
||||
@@ -393,19 +372,22 @@ Call this script from the command-line to see how new products are created:
|
||||
$ php create_product.php ORM
|
||||
$ php create_product.php DBAL
|
||||
|
||||
What is happening here? Using the ``Product`` class is pretty standard OOP.
|
||||
What is happening here? Using the ``Product`` is pretty standard OOP.
|
||||
The interesting bits are the use of the ``EntityManager`` service. To
|
||||
notify the EntityManager that a new entity should be inserted into the database,
|
||||
you have to call ``persist()``. To initiate a transaction to actually *perform*
|
||||
the insertion, you have to explicitly call ``flush()`` on the ``EntityManager``.
|
||||
notify the EntityManager that a new entity should be inserted into the database
|
||||
you have to call ``persist()``. To initiate a transaction to actually perform
|
||||
the insertion, You have to explicitly call ``flush()`` on the ``EntityManager``.
|
||||
|
||||
This distinction between persist and flush is what allows the aggregation of
|
||||
all database writes (INSERT, UPDATE, DELETE) into one single transaction, which
|
||||
is executed when ``flush()`` is called. Using this approach, the write-performance
|
||||
is significantly better than in a scenario in which writes are performed on
|
||||
each entity in isolation.
|
||||
This distinction between persist and flush is allows to aggregate all writes
|
||||
(INSERT, UPDATE, DELETE) into one single transaction, which is executed when
|
||||
flush is called. Using this approach the write-performance is significantly
|
||||
better than in a scenario where updates are done for each entity in isolation.
|
||||
|
||||
Next, we'll fetch a list of all the Products in the database. Let's create a
|
||||
Doctrine follows the UnitOfWork pattern which additionally detects all entities
|
||||
that were fetched and have changed during the request. You don't have to keep track of
|
||||
entities yourself, when Doctrine already knows about them.
|
||||
|
||||
As a next step we want to fetch a list of all the Products. Let's create a
|
||||
new script for this:
|
||||
|
||||
.. code-block:: php
|
||||
@@ -422,10 +404,10 @@ new script for this:
|
||||
}
|
||||
|
||||
The ``EntityManager#getRepository()`` method can create a finder object (called
|
||||
a repository) for every type of entity. It is provided by Doctrine and contains
|
||||
some finder methods like ``findAll()``.
|
||||
a repository) for every entity. It is provided by Doctrine and contains some
|
||||
finder methods such as ``findAll()``.
|
||||
|
||||
Let's continue by creating a script to display the name of a product based on its ID:
|
||||
Let's continue with displaying the name of a product based on its ID:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -443,13 +425,9 @@ Let's continue by creating a script to display the name of a product based on it
|
||||
|
||||
echo sprintf("-%s\n", $product->getName());
|
||||
|
||||
Next we'll update a product's name, given its id. This simple example will
|
||||
help demonstrate Doctrine's implementation of the UnitOfWork pattern. Doctrine
|
||||
keeps track of all the entities that were retrieved from the Entity Manager,
|
||||
and can detect when any of those entities' properties have been modified.
|
||||
As a result, rather than needing to call ``persist($entity)`` for each individual
|
||||
entity whose properties were changed, a single call to ``flush()`` at the end of a
|
||||
request is sufficient to update the database for all of the modified entities.
|
||||
Updating a product name demonstrates the functionality UnitOfWork of pattern
|
||||
discussed before. We only need to find a product entity and all changes to its
|
||||
properties are written to the database:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -477,44 +455,36 @@ product name changed by calling the ``show_product.php`` script.
|
||||
Adding Bug and User Entities
|
||||
----------------------------
|
||||
|
||||
We continue with the bug tracker example by creating the ``Bug`` and ``User``
|
||||
classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
|
||||
We continue with the bug tracker domain, by creating the missing classes
|
||||
``Bug`` and ``User`` and putting them into ``src/Bug.php`` and
|
||||
``src/User.php`` respectively.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// src/Bug.php
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity(repositoryClass="BugRepository")
|
||||
* @ORM\Table(name="bugs")
|
||||
* @Entity(repositoryClass="BugRepository") @Table(name="bugs")
|
||||
*/
|
||||
class Bug
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\GeneratedValue
|
||||
* @Id @Column(type="integer") @GeneratedValue
|
||||
* @var int
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
* @Column(type="string")
|
||||
* @var string
|
||||
*/
|
||||
protected $description;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="datetime")
|
||||
* @Column(type="datetime")
|
||||
* @var DateTime
|
||||
*/
|
||||
protected $created;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
* @Column(type="string")
|
||||
* @var string
|
||||
*/
|
||||
protected $status;
|
||||
@@ -559,25 +529,18 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
|
||||
|
||||
<?php
|
||||
// src/User.php
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="users")
|
||||
* @Entity @Table(name="users")
|
||||
*/
|
||||
class User
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
* @Id @GeneratedValue @Column(type="integer")
|
||||
* @var int
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
* @Column(type="string")
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
@@ -598,15 +561,14 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
|
||||
}
|
||||
}
|
||||
|
||||
All of the properties we've seen so far are of simple types (integer, string,
|
||||
and datetime). But now, we'll add properties that will store objects of
|
||||
specific *entity types* in order to model the relationships between different
|
||||
entities.
|
||||
All of the properties discussed so far are simple string and integer values,
|
||||
for example the id fields of the entities, their names, description, status and
|
||||
change dates. Next we will model the dynamic relationships between the entities
|
||||
by defining the references between entities.
|
||||
|
||||
At the database level, relationships between entities are represented by foreign
|
||||
keys. But with Doctrine, you'll never have to (and never should) work with
|
||||
the foreign keys directly. You should only work with objects that represent
|
||||
foreign keys through their own identities.
|
||||
References between objects are foreign keys in the database. You never have to
|
||||
(and never should) work with the foreign keys directly, only with the objects
|
||||
that represent the foreign key through their own identity.
|
||||
|
||||
For every foreign key you either have a Doctrine ManyToOne or OneToOne
|
||||
association. On the inverse sides of these foreign keys you can have
|
||||
@@ -640,7 +602,6 @@ domain model to match the requirements:
|
||||
<?php
|
||||
// src/User.php
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
class User
|
||||
{
|
||||
// ... (previous code)
|
||||
@@ -655,13 +616,12 @@ domain model to match the requirements:
|
||||
}
|
||||
}
|
||||
|
||||
.. note::
|
||||
|
||||
Whenever an entity is created from the database, a ``Collection``
|
||||
implementation of the type ``PersistentCollection`` will be injected into
|
||||
your entity instead of an ``ArrayCollection``. This helps Doctrine ORM
|
||||
understand the changes that have happened to the collection that are
|
||||
noteworthy for persistence.
|
||||
Whenever an entity is recreated from the database, an Collection
|
||||
implementation of the type Doctrine is injected into your entity
|
||||
instead of an array. Compared to the ArrayCollection this
|
||||
implementation helps the Doctrine ORM understand the changes that
|
||||
have happened to the collection which are noteworthy for
|
||||
persistence.
|
||||
|
||||
.. warning::
|
||||
|
||||
@@ -684,22 +644,24 @@ able to work with Doctrine 2. These assumptions are not unique to
|
||||
Doctrine 2 but are best practices in handling database relations
|
||||
and Object-Relational Mapping.
|
||||
|
||||
- In a one-to-one relation, the entity holding the foreign key of
|
||||
the related entity on its own database table is *always* the owning
|
||||
side of the relation.
|
||||
- In a many-to-one relation, the Many-side is the owning side by
|
||||
default because it holds the foreign key. Accordingly, the One-side
|
||||
is the inverse side by default.
|
||||
- In a many-to-one relation, the One-side can only be the owning side if
|
||||
the relation is implemented as a ManyToMany with a join table, and the
|
||||
One-side is restricted to allow only UNIQUE values per database constraint.
|
||||
- In a many-to-many relation, both sides can be the owning side of
|
||||
the relation. However, in a bi-directional many-to-many relation,
|
||||
only one side is allowed to be the owning side.
|
||||
|
||||
- Changes to Collections are saved or updated, when the entity on
|
||||
the *owning* side of the collection is saved or updated.
|
||||
- Saving an Entity at the inverse side of a relation never
|
||||
triggers a persist operation to changes to the collection.
|
||||
- In a one-to-one relation the entity holding the foreign key of
|
||||
the related entity on its own database table is *always* the owning
|
||||
side of the relation.
|
||||
- In a many-to-many relation, both sides can be the owning side of
|
||||
the relation. However in a bi-directional many-to-many relation
|
||||
only one is allowed to be.
|
||||
- In a many-to-one relation the Many-side is the owning side by
|
||||
default, because it holds the foreign key.
|
||||
- The OneToMany side of a relation is inverse by default, since
|
||||
the foreign key is saved on the Many side. A OneToMany relation can
|
||||
only be the owning side, if its implemented using a ManyToMany
|
||||
relation with join table and restricting the one side to allow only
|
||||
UNIQUE values per database constraint.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -724,13 +686,13 @@ the bi-directional reference:
|
||||
protected $engineer;
|
||||
protected $reporter;
|
||||
|
||||
public function setEngineer(User $engineer)
|
||||
public function setEngineer($engineer)
|
||||
{
|
||||
$engineer->assignedToBug($this);
|
||||
$this->engineer = $engineer;
|
||||
}
|
||||
|
||||
public function setReporter(User $reporter)
|
||||
public function setReporter($reporter)
|
||||
{
|
||||
$reporter->addReportedBug($this);
|
||||
$this->reporter = $reporter;
|
||||
@@ -755,15 +717,15 @@ the bi-directional reference:
|
||||
{
|
||||
// ... (previous code)
|
||||
|
||||
protected $reportedBugs;
|
||||
protected $assignedBugs;
|
||||
private $reportedBugs = null;
|
||||
private $assignedBugs = null;
|
||||
|
||||
public function addReportedBug(Bug $bug)
|
||||
public function addReportedBug($bug)
|
||||
{
|
||||
$this->reportedBugs[] = $bug;
|
||||
}
|
||||
|
||||
public function assignedToBug(Bug $bug)
|
||||
public function assignedToBug($bug)
|
||||
{
|
||||
$this->assignedBugs[] = $bug;
|
||||
}
|
||||
@@ -779,7 +741,7 @@ You can see from ``User#addReportedBug()`` and
|
||||
``User#assignedToBug()`` that using this method in userland alone
|
||||
would not add the Bug to the collection of the owning side in
|
||||
``Bug#reporter`` or ``Bug#engineer``. Using these methods and
|
||||
calling Doctrine for persistence would not update the Collections'
|
||||
calling Doctrine for persistence would not update the collections
|
||||
representation in the database.
|
||||
|
||||
Only using ``Bug#setEngineer()`` or ``Bug#setReporter()``
|
||||
@@ -787,7 +749,7 @@ correctly saves the relation information.
|
||||
|
||||
The ``Bug#reporter`` and ``Bug#engineer`` properties are
|
||||
Many-To-One relations, which point to a User. In a normalized
|
||||
relational model, the foreign key is saved on the Bug's table, hence
|
||||
relational model the foreign key is saved on the Bug's table, hence
|
||||
in our object-relation model the Bug is at the owning side of the
|
||||
relation. You should always make sure that the use-cases of your
|
||||
domain model should drive which side is an inverse or owning one in
|
||||
@@ -796,7 +758,7 @@ or an engineer is assigned to the bug, we don't want to update the
|
||||
User to persist the reference, but the Bug. This is the case with
|
||||
the Bug being at the owning side of the relation.
|
||||
|
||||
Bugs reference Products by a uni-directional ManyToMany relation in
|
||||
Bugs reference Products by an uni-directional ManyToMany relation in
|
||||
the database that points from Bugs to Products.
|
||||
|
||||
.. code-block:: php
|
||||
@@ -807,9 +769,9 @@ the database that points from Bugs to Products.
|
||||
{
|
||||
// ... (previous code)
|
||||
|
||||
protected $products;
|
||||
protected $products = null;
|
||||
|
||||
public function assignToProduct(Product $product)
|
||||
public function assignToProduct($product)
|
||||
{
|
||||
$this->products[] = $product;
|
||||
}
|
||||
@@ -821,7 +783,7 @@ the database that points from Bugs to Products.
|
||||
}
|
||||
|
||||
We are now finished with the domain model given the requirements.
|
||||
Lets add metadata mappings for the ``Bug`` entity, as we did for
|
||||
Lets add metadata mappings for the ``User`` and ``Bug`` as we did for
|
||||
the ``Product`` before:
|
||||
|
||||
.. configuration-block::
|
||||
@@ -829,50 +791,41 @@ the ``Product`` before:
|
||||
|
||||
<?php
|
||||
// src/Bug.php
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="bugs")
|
||||
*/
|
||||
* @Entity @Table(name="bugs")
|
||||
**/
|
||||
class Bug
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\GeneratedValue
|
||||
*/
|
||||
* @Id @Column(type="integer") @GeneratedValue
|
||||
**/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
*/
|
||||
* @Column(type="string")
|
||||
**/
|
||||
protected $description;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="datetime")
|
||||
*/
|
||||
* @Column(type="datetime")
|
||||
**/
|
||||
protected $created;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
*/
|
||||
* @Column(type="string")
|
||||
**/
|
||||
protected $status;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity="User", inversedBy="assignedBugs")
|
||||
*/
|
||||
* @ManyToOne(targetEntity="User", inversedBy="assignedBugs")
|
||||
**/
|
||||
protected $engineer;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity="User", inversedBy="reportedBugs")
|
||||
*/
|
||||
* @ManyToOne(targetEntity="User", inversedBy="reportedBugs")
|
||||
**/
|
||||
protected $reporter;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToMany(targetEntity="Product")
|
||||
*/
|
||||
* @ManyToMany(targetEntity="Product")
|
||||
**/
|
||||
protected $products;
|
||||
|
||||
// ... (other code)
|
||||
@@ -884,7 +837,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
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Bug" table="bugs">
|
||||
<id name="id" type="integer">
|
||||
@@ -902,10 +855,6 @@ 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
|
||||
@@ -941,13 +890,13 @@ For the "created" field we have used the ``datetime`` type,
|
||||
which translates the YYYY-mm-dd HH:mm:ss database format
|
||||
into a PHP DateTime instance and back.
|
||||
|
||||
After the field definitions, the two qualified references to the
|
||||
After the field definitions the two qualified references to the
|
||||
user entity are defined. They are created by the ``many-to-one``
|
||||
tag. The class name of the related entity has to be specified with
|
||||
the ``target-entity`` attribute, which is enough information for
|
||||
the database mapper to access the foreign-table. Since
|
||||
``reporter`` and ``engineer`` are on the owning side of a
|
||||
bi-directional relation, we also have to specify the ``inversed-by``
|
||||
bi-directional relation we also have to specify the ``inversed-by``
|
||||
attribute. They have to point to the field names on the inverse
|
||||
side of the relationship. We will see in the next example that the ``inversed-by``
|
||||
attribute has a counterpart ``mapped-by`` which makes that
|
||||
@@ -958,7 +907,7 @@ holds all products where the specific bug occurs. Again
|
||||
you have to define the ``target-entity`` and ``field`` attributes
|
||||
on the ``many-to-many`` tag.
|
||||
|
||||
Finally, we'll add metadata mappings for the ``User`` entity.
|
||||
The last missing definition is that of the User entity:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
@@ -966,40 +915,34 @@ Finally, we'll add metadata mappings for the ``User`` entity.
|
||||
|
||||
<?php
|
||||
// src/User.php
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="users")
|
||||
*/
|
||||
* @Entity @Table(name="users")
|
||||
**/
|
||||
class User
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
* @Id @GeneratedValue @Column(type="integer")
|
||||
* @var int
|
||||
*/
|
||||
**/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
* @Column(type="string")
|
||||
* @var string
|
||||
*/
|
||||
**/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity="Bug", mappedBy="reporter")
|
||||
* @var Bug[] An ArrayCollection of Bug objects.
|
||||
*/
|
||||
protected $reportedBugs;
|
||||
* @OneToMany(targetEntity="Bug", mappedBy="reporter")
|
||||
* @var Bug[]
|
||||
**/
|
||||
protected $reportedBugs = null;
|
||||
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity="Bug", mappedBy="engineer")
|
||||
* @var Bug[] An ArrayCollection of Bug objects.
|
||||
*/
|
||||
protected $assignedBugs;
|
||||
* @OneToMany(targetEntity="Bug", mappedBy="engineer")
|
||||
* @var Bug[]
|
||||
**/
|
||||
protected $assignedBugs = null;
|
||||
|
||||
// .. (other code)
|
||||
}
|
||||
@@ -1010,7 +953,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
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="User" table="users">
|
||||
<id name="id" type="integer">
|
||||
@@ -1024,13 +967,9 @@ 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
|
||||
# config/xml/User.dcm.yml
|
||||
User:
|
||||
type: entity
|
||||
table: users
|
||||
@@ -1057,7 +996,10 @@ means the join details have already been defined on the owning
|
||||
side. Therefore we only have to specify the property on the Bug
|
||||
class that holds the owning sides.
|
||||
|
||||
Update your database schema by running:
|
||||
This example has a fair overview of the most basic features of the
|
||||
metadata definition language.
|
||||
|
||||
Update your database running:
|
||||
::
|
||||
|
||||
$ vendor/bin/doctrine orm:schema-tool:update --force
|
||||
@@ -1066,8 +1008,7 @@ Update your database schema by running:
|
||||
Implementing more Requirements
|
||||
------------------------------
|
||||
|
||||
So far, we've seen the most basic features of the metadata definition language.
|
||||
To explore additional functionality, let's first create new ``User`` entities:
|
||||
For starters we need a create user entities:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -1091,22 +1032,23 @@ Now call:
|
||||
|
||||
$ php create_user.php beberlei
|
||||
|
||||
We now have the necessary data to create a new Bug entity:
|
||||
We now have the data to create a bug and the code for this scenario may look
|
||||
like this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// create_bug.php <reporter-id> <engineer-id> <product-ids>
|
||||
// create_bug.php
|
||||
require_once "bootstrap.php";
|
||||
|
||||
$reporterId = $argv[1];
|
||||
$engineerId = $argv[2];
|
||||
$theReporterId = $argv[1];
|
||||
$theDefaultEngineerId = $argv[2];
|
||||
$productIds = explode(",", $argv[3]);
|
||||
|
||||
$reporter = $entityManager->find("User", $reporterId);
|
||||
$engineer = $entityManager->find("User", $engineerId);
|
||||
$reporter = $entityManager->find("User", $theReporterId);
|
||||
$engineer = $entityManager->find("User", $theDefaultEngineerId);
|
||||
if (!$reporter || !$engineer) {
|
||||
echo "No reporter and/or engineer found for the given id(s).\n";
|
||||
echo "No reporter and/or engineer found for the input.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -1128,17 +1070,22 @@ We now have the necessary data to create a new Bug entity:
|
||||
|
||||
echo "Your new Bug Id: ".$bug->getId()."\n";
|
||||
|
||||
Since we only have one user and product, probably with the ID of 1, we can
|
||||
call this script as follows:
|
||||
Since we only have one user and product, probably with the ID of 1, we can call this script with:
|
||||
|
||||
::
|
||||
|
||||
php create_bug.php 1 1 1
|
||||
|
||||
See how simple it is to relate a Bug, Reporter, Engineer and Products?
|
||||
Also recall that thanks to the UnitOfWork pattern, Doctrine will detect
|
||||
these relations and update all of the modified entities in the database
|
||||
automatically when ``flush()`` is called.
|
||||
This is the first contact with the read API of the EntityManager,
|
||||
showing that a call to ``EntityManager#find($name, $id)`` returns a
|
||||
single instance of an entity queried by primary key. Besides this
|
||||
we see the persist + flush pattern again to save the Bug into the
|
||||
database.
|
||||
|
||||
See how simple relating Bug, Reporter, Engineer and Products is
|
||||
done by using the discussed methods in the "A first prototype"
|
||||
section. The UnitOfWork will detect this relations when flush is
|
||||
called and relate them in the database appropriately.
|
||||
|
||||
Queries for Application Use-Cases
|
||||
---------------------------------
|
||||
@@ -1147,7 +1094,7 @@ List of Bugs
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Using the previous examples we can fill up the database quite a
|
||||
bit. However, we now need to discuss how to query the underlying
|
||||
bit, however we now need to discuss how to query the underlying
|
||||
mapper for the required view representations. When opening the
|
||||
application, bugs can be paginated through a list-view, which is
|
||||
the first read-only use-case:
|
||||
@@ -1203,14 +1150,15 @@ The console output of this script is then:
|
||||
|
||||
An important reason why DQL is favourable to the Query API of most
|
||||
ORMs is its similarity to SQL. The DQL language allows query
|
||||
constructs that most ORMs don't: GROUP BY even with HAVING,
|
||||
constructs that most ORMs don't, GROUP BY even with HAVING,
|
||||
Sub-selects, Fetch-Joins of nested classes, mixed results with
|
||||
entities and scalar data such as COUNT() results and much more.
|
||||
Using DQL you should seldom come to the point where you want to
|
||||
throw your ORM into the dumpster, because it doesn't support some
|
||||
the more powerful SQL concepts.
|
||||
|
||||
If you need to build your query dynamically, you can use the ``QueryBuilder`` retrieved
|
||||
|
||||
Instead of handwriting DQL you can use the ``QueryBuilder`` retrieved
|
||||
by calling ``$entityManager->createQueryBuilder()``. There are more
|
||||
details about this in the relevant part of the documentation.
|
||||
|
||||
@@ -1277,7 +1225,7 @@ write scenarios:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// show_bug.php <id>
|
||||
// show_bug.php
|
||||
require_once "bootstrap.php";
|
||||
|
||||
$theBugId = $argv[1];
|
||||
@@ -1352,7 +1300,7 @@ and usage of bound parameters:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// dashboard.php <user-id>
|
||||
// dashboard.php
|
||||
require_once "bootstrap.php";
|
||||
|
||||
$theUserId = $argv[1];
|
||||
@@ -1418,7 +1366,7 @@ should be able to close a bug. This looks like:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// close_bug.php <bug-id>
|
||||
// close_bug.php
|
||||
require_once "bootstrap.php";
|
||||
|
||||
$theBugId = $argv[1];
|
||||
@@ -1536,12 +1484,9 @@ we have to adjust the metadata slightly.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity(repositoryClass="BugRepository")
|
||||
* @ORM\Table(name="bugs")
|
||||
* @Entity(repositoryClass="BugRepository")
|
||||
* @Table(name="bugs")
|
||||
**/
|
||||
class Bug
|
||||
{
|
||||
@@ -1553,17 +1498,13 @@ 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
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
http://raw.github.com/doctrine/doctrine2/master/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:
|
||||
@@ -1594,16 +1535,6 @@ As an example here is the code of the first use case "List of Bugs":
|
||||
Using EntityRepositories you can avoid coupling your model with specific query logic.
|
||||
You can also re-use query logic easily throughout your application.
|
||||
|
||||
The method ``count()`` takes an array of fields or association keys and the values to match against.
|
||||
This provides you with a convenient and lightweight way to count a resultset when you don't need to
|
||||
deal with it:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$productCount = $entityManager->getRepository(Product::class)
|
||||
->count(['name' => $productName]);
|
||||
|
||||
Conclusion
|
||||
----------
|
||||
|
||||
|
||||
@@ -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 a collection that specifies
|
||||
a DQL snippet that is appended to all queries with this
|
||||
use the ``@OrderBy`` annotation with an collection that specifies
|
||||
an DQL snippet that is appended to all queries with this
|
||||
collection.
|
||||
|
||||
Additional to any ``@OneToMany`` or ``@ManyToMany`` annotation you
|
||||
@@ -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 a DQL Query if the collection is fetch joined in
|
||||
are only added to an DQL Query if the collection is fetch joined in
|
||||
the DQL query.
|
||||
|
||||
Given our previously defined example, the following would not add
|
||||
|
||||
@@ -58,7 +58,6 @@ which has mapping metadata that is overridden by the annotation above:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Trait class
|
||||
*/
|
||||
@@ -83,7 +82,6 @@ The case for just extending a class would be just the same but:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class ExampleEntityWithOverride extends BaseEntityWithSomeMapping
|
||||
{
|
||||
// ...
|
||||
|
||||
@@ -3,7 +3,7 @@ Working with Indexed Associations
|
||||
|
||||
.. note::
|
||||
|
||||
This feature is available from version 2.1 of Doctrine.
|
||||
This feature is scheduled for version 2.1 of Doctrine and not included in the 2.0.x series.
|
||||
|
||||
Doctrine 2 collections are modelled after PHPs native arrays. PHP arrays are an ordered hashmap, but in
|
||||
the first version of Doctrine keys retrieved from the database were always numerical unless ``INDEX BY``
|
||||
@@ -13,8 +13,8 @@ The feature works like an implicit ``INDEX BY`` for the selected association but
|
||||
downsides also:
|
||||
|
||||
- You have to manage both the key and field if you want to change the index by field value.
|
||||
- On each request the keys are regenerated from the field value, and not from the previous collection key.
|
||||
- Values of the Index-By keys are never considered during persistence. They only exist for accessing purposes.
|
||||
- On each request the keys are regenerated from the field value not from the previous collection key.
|
||||
- Values of the Index-By keys are never considered during persistence, it only exists for accessing purposes.
|
||||
- Fields that are used for the index by feature **HAVE** to be unique in the database. The behavior for multiple entities
|
||||
with the same index-by field value is undefined.
|
||||
|
||||
@@ -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
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Doctrine\Tests\Models\StockExchange\Market">
|
||||
<id name="id" type="integer">
|
||||
@@ -164,6 +164,8 @@ here are the code and mappings for it:
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* For real this column would have to be unique=true. But I want to test behavior of non-unique overrides.
|
||||
*
|
||||
* @Column(type="string", unique=true)
|
||||
*/
|
||||
private $symbol;
|
||||
@@ -193,7 +195,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
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Doctrine\Tests\Models\StockExchange\Stock">
|
||||
<id name="id" type="integer">
|
||||
@@ -225,7 +227,7 @@ here are the code and mappings for it:
|
||||
Querying indexed associations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Now that we defined the stocks collection to be indexed by symbol, we can take a look at some code
|
||||
Now that we defined the stocks collection to be indexed by symbol we can take a look at some code,
|
||||
that makes use of the indexing.
|
||||
|
||||
First we will populate our database with two example stocks traded on a single market:
|
||||
@@ -261,7 +263,7 @@ now query for the market:
|
||||
|
||||
echo $stock->getSymbol(); // will print "AAPL"
|
||||
|
||||
The implementation of ``Market::addStock()``, in combination with ``indexBy``, allows us to access the collection
|
||||
The implementation ``Market::addStock()`` in combination with ``indexBy`` allows to access the collection
|
||||
consistently by the Stock symbol. It does not matter if Stock is managed by Doctrine or not.
|
||||
|
||||
The same applies to DQL queries: The ``indexBy`` configuration acts as implicit "INDEX BY" to a join association.
|
||||
@@ -283,8 +285,8 @@ The same applies to DQL queries: The ``indexBy`` configuration acts as implicit
|
||||
|
||||
echo $stock->getSymbol(); // will print "AAPL"
|
||||
|
||||
If you want to use ``INDEX BY`` explicitly on an indexed association you are free to do so. Additionally,
|
||||
indexed associations also work with the ``Collection::slice()`` functionality, even if the association's fetch mode is
|
||||
If you want to use ``INDEX BY`` explicitly on an indexed association you are free to do so. Additionally
|
||||
indexed associations also work with the ``Collection::slice()`` functionality, no matter if marked as
|
||||
LAZY or EXTRA_LAZY.
|
||||
|
||||
Outlook into the Future
|
||||
@@ -292,5 +294,5 @@ Outlook into the Future
|
||||
|
||||
For the inverse side of a many-to-many associations there will be a way to persist the keys and the order
|
||||
as a third and fourth parameter into the join table. This feature is discussed in `DDC-213 <http://www.doctrine-project.org/jira/browse/DDC-213>`_
|
||||
This feature cannot be implemented for one-to-many associations, because they are never the owning side.
|
||||
This feature cannot be implemented for One-To-Many associations, because they are never the owning side.
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:string" use="required" />
|
||||
<xs:attribute name="result-class" type="orm:fqcn" />
|
||||
<xs:attribute name="result-class" type="xs:string" />
|
||||
<xs:attribute name="result-set-mapping" type="xs:string" />
|
||||
</xs:complexType>
|
||||
|
||||
@@ -117,7 +117,7 @@
|
||||
<xs:element name="lifecycle-callback" type="orm:lifecycle-callback" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="class" type="orm:fqcn"/>
|
||||
<xs:attribute name="class" type="xs:string"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="entity-listeners">
|
||||
@@ -139,7 +139,7 @@
|
||||
<xs:sequence>
|
||||
<xs:element name="field-result" type="orm:field-result" minOccurs="0" maxOccurs="unbounded" />
|
||||
</xs:sequence>
|
||||
<xs:attribute name="entity-class" type="orm:fqcn" use="required" />
|
||||
<xs:attribute name="entity-class" type="xs:string" use="required" />
|
||||
<xs:attribute name="discriminator-column" type="xs:string" use="optional" />
|
||||
</xs:complexType>
|
||||
|
||||
@@ -167,7 +167,7 @@
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="entity">
|
||||
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:sequence>
|
||||
<xs:element name="cache" type="orm:cache" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="options" type="orm:options" minOccurs="0" />
|
||||
<xs:element name="indexes" type="orm:indexes" minOccurs="0"/>
|
||||
@@ -189,24 +189,17 @@
|
||||
<xs:element name="association-overrides" type="orm:association-overrides" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xs:element name="attribute-overrides" type="orm:attribute-overrides" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:choice>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:string" use="required" />
|
||||
<xs:attribute name="table" type="orm:tablename" />
|
||||
<xs:attribute name="table" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="schema" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="repository-class" type="orm:fqcn"/>
|
||||
<xs:attribute name="repository-class" type="xs:string"/>
|
||||
<xs:attribute name="inheritance-type" type="orm:inheritance-type"/>
|
||||
<xs:attribute name="change-tracking-policy" type="orm:change-tracking-policy" />
|
||||
<xs:attribute name="read-only" type="xs:boolean" default="false" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:simpleType name="tablename" id="tablename">
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:pattern value="[a-zA-Z_u01-uff.]+" id="tablename.pattern">
|
||||
</xs:pattern>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:complexType name="option" mixed="true">
|
||||
<xs:sequence minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:element name="option" type="orm:option"/>
|
||||
@@ -309,7 +302,7 @@
|
||||
|
||||
<xs:complexType name="embedded">
|
||||
<xs:attribute name="name" type="xs:string" use="required" />
|
||||
<xs:attribute name="class" type="orm:fqcn" use="required" />
|
||||
<xs:attribute name="class" type="xs:string" use="required" />
|
||||
<xs:attribute name="column-prefix" type="xs:string" use="optional" />
|
||||
<xs:attribute name="use-column-prefix" type="xs:boolean" default="true" use="optional" />
|
||||
</xs:complexType>
|
||||
@@ -368,7 +361,7 @@
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="value" type="xs:NMTOKEN" use="required"/>
|
||||
<xs:attribute name="class" type="orm:fqcn" use="required"/>
|
||||
<xs:attribute name="class" type="xs:string" use="required"/>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
@@ -419,16 +412,9 @@
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="class" type="orm:fqcn" use="required" />
|
||||
<xs:attribute name="class" type="xs:NMTOKEN" use="required" />
|
||||
</xs:complexType>
|
||||
|
||||
<xs:simpleType name="fqcn" id="fqcn">
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:pattern value="[a-zA-Z_u01-uff][a-zA-Z0-9_u01-uff]+" id="fqcn.pattern">
|
||||
</xs:pattern>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:complexType name="inverse-join-columns">
|
||||
<xs:sequence>
|
||||
<xs:element name="join-column" type="orm:join-column" minOccurs="1" maxOccurs="unbounded" />
|
||||
@@ -501,11 +487,11 @@
|
||||
<xs:element name="order-by" type="orm:order-by" minOccurs="0" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="target-entity" type="xs:string" use="required" />
|
||||
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="mapped-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="index-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
||||
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
@@ -518,12 +504,12 @@
|
||||
<xs:element name="order-by" type="orm:order-by" minOccurs="0" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="target-entity" type="xs:string" use="required" />
|
||||
<xs:attribute name="mapped-by" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="index-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
||||
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
@@ -538,10 +524,11 @@
|
||||
</xs:choice>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="target-entity" type="xs:string" use="required" />
|
||||
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
||||
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
@@ -560,8 +547,8 @@
|
||||
<xs:attribute name="target-entity" type="xs:string" use="required" />
|
||||
<xs:attribute name="mapped-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
||||
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
@@ -576,15 +563,9 @@
|
||||
<xs:sequence>
|
||||
<xs:element name="join-table" type="orm:join-table" minOccurs="0" />
|
||||
<xs:element name="join-columns" type="orm:join-columns" minOccurs="0" />
|
||||
<xs:element name="inversed-by" type="orm:inversed-by-override" minOccurs="0" maxOccurs="1" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="fetch" type="orm:fetch-type" use="optional" />
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="inversed-by-override">
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="attribute-overrides">
|
||||
|
||||
@@ -19,16 +19,17 @@
|
||||
|
||||
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;
|
||||
|
||||
use Doctrine\ORM\Cache;
|
||||
use Doctrine\ORM\Query\QueryException;
|
||||
|
||||
/**
|
||||
* Base contract for ORM queries. Base class for Query and NativeQuery.
|
||||
*
|
||||
@@ -72,7 +73,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* The parameter map of this query.
|
||||
*
|
||||
* @var ArrayCollection|Parameter[]
|
||||
* @var \Doctrine\Common\Collections\ArrayCollection
|
||||
*/
|
||||
protected $parameters;
|
||||
|
||||
@@ -95,17 +96,17 @@ abstract class AbstractQuery
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_hints = [];
|
||||
protected $_hints = array();
|
||||
|
||||
/**
|
||||
* The hydration mode.
|
||||
*
|
||||
* @var string|int
|
||||
* @var integer
|
||||
*/
|
||||
protected $_hydrationMode = self::HYDRATE_OBJECT;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\DBAL\Cache\QueryCacheProfile
|
||||
* @param \Doctrine\DBAL\Cache\QueryCacheProfile
|
||||
*/
|
||||
protected $_queryCacheProfile;
|
||||
|
||||
@@ -117,7 +118,7 @@ abstract class AbstractQuery
|
||||
protected $_expireResultCache = false;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\DBAL\Cache\QueryCacheProfile
|
||||
* @param \Doctrine\DBAL\Cache\QueryCacheProfile
|
||||
*/
|
||||
protected $_hydrationCacheProfile;
|
||||
|
||||
@@ -177,6 +178,7 @@ abstract class AbstractQuery
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Enable/disable second level query (result) caching for this query.
|
||||
*
|
||||
* @param boolean $cacheable
|
||||
@@ -213,7 +215,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Obtain the name of the second level query cache region in which query results will be stored
|
||||
*
|
||||
* @return string|null The cache region name; NULL indicates the default region.
|
||||
* @return The cache region name; NULL indicates the default region.
|
||||
*/
|
||||
public function getCacheRegion()
|
||||
{
|
||||
@@ -241,7 +243,7 @@ abstract class AbstractQuery
|
||||
*
|
||||
* @param integer $lifetime
|
||||
*
|
||||
* @return \Doctrine\ORM\AbstractQuery This query instance.
|
||||
* @return static This query instance.
|
||||
*/
|
||||
public function setLifetime($lifetime)
|
||||
{
|
||||
@@ -261,7 +263,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* @param integer $cacheMode
|
||||
*
|
||||
* @return \Doctrine\ORM\AbstractQuery This query instance.
|
||||
* @return static This query instance.
|
||||
*/
|
||||
public function setCacheMode($cacheMode)
|
||||
{
|
||||
@@ -306,7 +308,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Get all defined parameters.
|
||||
*
|
||||
* @return ArrayCollection The defined query parameters.
|
||||
* @return \Doctrine\Common\Collections\ArrayCollection The defined query parameters.
|
||||
*/
|
||||
public function getParameters()
|
||||
{
|
||||
@@ -323,20 +325,20 @@ abstract class AbstractQuery
|
||||
public function getParameter($key)
|
||||
{
|
||||
$filteredParameters = $this->parameters->filter(
|
||||
function (Query\Parameter $parameter) use ($key) : bool {
|
||||
$parameterName = $parameter->getName();
|
||||
|
||||
return $key === $parameterName || (string) $key === (string) $parameterName;
|
||||
function ($parameter) use ($key)
|
||||
{
|
||||
// Must not be identical because of string to integer conversion
|
||||
return ($key == $parameter->getName());
|
||||
}
|
||||
);
|
||||
|
||||
return ! $filteredParameters->isEmpty() ? $filteredParameters->first() : null;
|
||||
return count($filteredParameters) ? $filteredParameters->first() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a collection of query parameters.
|
||||
*
|
||||
* @param ArrayCollection|mixed[] $parameters
|
||||
* @param \Doctrine\Common\Collections\ArrayCollection|array $parameters
|
||||
*
|
||||
* @return static This query instance.
|
||||
*/
|
||||
@@ -371,10 +373,17 @@ abstract class AbstractQuery
|
||||
*/
|
||||
public function setParameter($key, $value, $type = null)
|
||||
{
|
||||
$existingParameter = $this->getParameter($key);
|
||||
$filteredParameters = $this->parameters->filter(
|
||||
function ($parameter) use ($key)
|
||||
{
|
||||
// Must not be identical because of string to integer conversion
|
||||
return ($key == $parameter->getName());
|
||||
}
|
||||
);
|
||||
|
||||
if ($existingParameter !== null) {
|
||||
$existingParameter->setValue($value, $type);
|
||||
if (count($filteredParameters)) {
|
||||
$parameter = $filteredParameters->first();
|
||||
$parameter->setValue($value, $type);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -389,7 +398,7 @@ abstract class AbstractQuery
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return array|string
|
||||
* @return array
|
||||
*
|
||||
* @throws \Doctrine\ORM\ORMInvalidArgumentException
|
||||
*/
|
||||
@@ -412,24 +421,16 @@ abstract class AbstractQuery
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ($value instanceof Mapping\ClassMetadata) {
|
||||
return $value->name;
|
||||
}
|
||||
|
||||
if (! is_object($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
try {
|
||||
if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value))) {
|
||||
$value = $this->_em->getUnitOfWork()->getSingleIdentifierValue($value);
|
||||
|
||||
if ($value === null) {
|
||||
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
|
||||
}
|
||||
} 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.
|
||||
}
|
||||
|
||||
if ($value instanceof Mapping\ClassMetadata) {
|
||||
return $value->name;
|
||||
}
|
||||
|
||||
return $value;
|
||||
@@ -501,7 +502,7 @@ abstract class AbstractQuery
|
||||
*/
|
||||
public function setHydrationCacheProfile(QueryCacheProfile $profile = null)
|
||||
{
|
||||
if ($profile !== null && ! $profile->getResultCacheDriver()) {
|
||||
if ( ! $profile->getResultCacheDriver()) {
|
||||
$resultCacheDriver = $this->_em->getConfiguration()->getHydrationCacheImpl();
|
||||
$profile = $profile->setResultCacheDriver($resultCacheDriver);
|
||||
}
|
||||
@@ -531,7 +532,7 @@ abstract class AbstractQuery
|
||||
*/
|
||||
public function setResultCacheProfile(QueryCacheProfile $profile = null)
|
||||
{
|
||||
if ($profile !== null && ! $profile->getResultCacheDriver()) {
|
||||
if ( ! $profile->getResultCacheDriver()) {
|
||||
$resultCacheDriver = $this->_em->getConfiguration()->getResultCacheImpl();
|
||||
$profile = $profile->setResultCacheDriver($resultCacheDriver);
|
||||
}
|
||||
@@ -583,45 +584,21 @@ 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.
|
||||
*
|
||||
* @deprecated 2.7 Use {@see enableResultCache} and {@see disableResultCache} instead.
|
||||
*
|
||||
* @param bool $useCache
|
||||
* @param int $lifetime
|
||||
* @param string $resultCacheId
|
||||
* @param boolean $bool
|
||||
* @param integer $lifetime
|
||||
* @param string $resultCacheId
|
||||
*
|
||||
* @return static This query instance.
|
||||
*/
|
||||
public function useResultCache($useCache, $lifetime = null, $resultCacheId = null)
|
||||
public function useResultCache($bool, $lifetime = null, $resultCacheId = null)
|
||||
{
|
||||
return $useCache
|
||||
? $this->enableResultCache($lifetime, $resultCacheId)
|
||||
: $this->disableResultCache();
|
||||
}
|
||||
if ($bool) {
|
||||
$this->setResultCacheLifetime($lifetime);
|
||||
$this->setResultCacheId($resultCacheId);
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables caching of the results of this query.
|
||||
*
|
||||
* @return static This query instance.
|
||||
*/
|
||||
public function disableResultCache() : self
|
||||
{
|
||||
$this->_queryCacheProfile = null;
|
||||
|
||||
return $this;
|
||||
@@ -714,8 +691,8 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Defines the processing mode to be used during hydration / result set transformation.
|
||||
*
|
||||
* @param string|int $hydrationMode Doctrine processing mode to be used during hydration process.
|
||||
* One of the Query::HYDRATE_* constants.
|
||||
* @param integer $hydrationMode Doctrine processing mode to be used during hydration process.
|
||||
* One of the Query::HYDRATE_* constants.
|
||||
*
|
||||
* @return static This query instance.
|
||||
*/
|
||||
@@ -729,7 +706,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Gets the hydration mode currently used by the query.
|
||||
*
|
||||
* @return string|int
|
||||
* @return integer
|
||||
*/
|
||||
public function getHydrationMode()
|
||||
{
|
||||
@@ -741,9 +718,9 @@ abstract class AbstractQuery
|
||||
*
|
||||
* Alias for execute(null, $hydrationMode = HYDRATE_OBJECT).
|
||||
*
|
||||
* @param string|int $hydrationMode
|
||||
* @param int $hydrationMode
|
||||
*
|
||||
* @return mixed
|
||||
* @return array
|
||||
*/
|
||||
public function getResult($hydrationMode = self::HYDRATE_OBJECT)
|
||||
{
|
||||
@@ -777,7 +754,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Get exactly one result or null.
|
||||
*
|
||||
* @param string|int $hydrationMode
|
||||
* @param int $hydrationMode
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
@@ -815,12 +792,12 @@ abstract class AbstractQuery
|
||||
* If the result is not unique, a NonUniqueResultException is thrown.
|
||||
* If there is no result, a NoResultException is thrown.
|
||||
*
|
||||
* @param string|int $hydrationMode
|
||||
* @param integer $hydrationMode
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws NonUniqueResultException If the query result is not unique.
|
||||
* @throws NoResultException If the query returned no result and hydration mode is not HYDRATE_SINGLE_SCALAR.
|
||||
* @throws NoResultException If the query returned no result.
|
||||
*/
|
||||
public function getSingleResult($hydrationMode = null)
|
||||
{
|
||||
@@ -846,10 +823,10 @@ abstract class AbstractQuery
|
||||
*
|
||||
* Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
|
||||
*
|
||||
* @return mixed The scalar result.
|
||||
* @return mixed
|
||||
*
|
||||
* @throws NoResultException If the query returned no result.
|
||||
* @throws NonUniqueResultException If the query result is not unique.
|
||||
* @throws NoResultException If the query returned no result.
|
||||
*/
|
||||
public function getSingleScalarResult()
|
||||
{
|
||||
@@ -886,7 +863,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Check if the query has a hint
|
||||
*
|
||||
* @param string $name The name of the hint
|
||||
* @param string $name The name of the hint
|
||||
*
|
||||
* @return bool False if the query does not have any hint
|
||||
*/
|
||||
@@ -910,7 +887,7 @@ abstract class AbstractQuery
|
||||
* iterate over the result.
|
||||
*
|
||||
* @param ArrayCollection|array|null $parameters The query parameters.
|
||||
* @param string|int|null $hydrationMode The hydration mode to use.
|
||||
* @param integer|null $hydrationMode The hydration mode to use.
|
||||
*
|
||||
* @return \Doctrine\ORM\Internal\Hydration\IterableResult
|
||||
*/
|
||||
@@ -934,7 +911,7 @@ abstract class AbstractQuery
|
||||
* Executes the query.
|
||||
*
|
||||
* @param ArrayCollection|array|null $parameters Query parameters.
|
||||
* @param string|int|null $hydrationMode Processing mode to be used during the hydration process.
|
||||
* @param integer|null $hydrationMode Processing mode to be used during the hydration process.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
@@ -951,7 +928,7 @@ abstract class AbstractQuery
|
||||
* Execute query ignoring second level cache.
|
||||
*
|
||||
* @param ArrayCollection|array|null $parameters
|
||||
* @param string|int|null $hydrationMode
|
||||
* @param integer|null $hydrationMode
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
@@ -979,7 +956,7 @@ abstract class AbstractQuery
|
||||
}
|
||||
|
||||
if ( ! $result) {
|
||||
$result = [];
|
||||
$result = array();
|
||||
}
|
||||
|
||||
$setCacheEntry = function($data) use ($cache, $result, $cacheKey, $realCacheKey, $queryCacheProfile) {
|
||||
@@ -1009,61 +986,39 @@ abstract class AbstractQuery
|
||||
* Load from second level cache or executes the query and put into cache.
|
||||
*
|
||||
* @param ArrayCollection|array|null $parameters
|
||||
* @param string|int|null $hydrationMode
|
||||
* @param integer|null $hydrationMode
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function executeUsingQueryCache($parameters = null, $hydrationMode = null)
|
||||
{
|
||||
$rsm = $this->getResultSetMapping();
|
||||
$querykey = new QueryCacheKey($this->getHash(), $this->lifetime, $this->cacheMode ?: Cache::MODE_NORMAL);
|
||||
$queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion);
|
||||
$queryKey = new QueryCacheKey(
|
||||
$this->getHash(),
|
||||
$this->lifetime,
|
||||
$this->cacheMode ?: Cache::MODE_NORMAL,
|
||||
$this->getTimestampKey()
|
||||
);
|
||||
|
||||
$result = $queryCache->get($queryKey, $rsm, $this->_hints);
|
||||
$result = $queryCache->get($querykey, $rsm, $this->_hints);
|
||||
|
||||
if ($result !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $queryKey);
|
||||
$this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $querykey);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
$result = $this->executeIgnoreQueryCache($parameters, $hydrationMode);
|
||||
$cached = $queryCache->put($queryKey, $rsm, $result, $this->_hints);
|
||||
$cached = $queryCache->put($querykey, $rsm, $result, $this->_hints);
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $queryKey);
|
||||
$this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $querykey);
|
||||
|
||||
if ($cached) {
|
||||
$this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $queryKey);
|
||||
$this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $querykey);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Doctrine\ORM\Cache\TimestampCacheKey|null
|
||||
*/
|
||||
private function getTimestampKey()
|
||||
{
|
||||
$entityName = reset($this->_resultSetMapping->aliasMap);
|
||||
|
||||
if (empty($entityName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$metadata = $this->_em->getClassMetadata($entityName);
|
||||
|
||||
return new Cache\TimestampCacheKey($metadata->rootEntityName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the result cache id to use to store the result set cache entry.
|
||||
* Will return the configured id if it exists otherwise a hash will be
|
||||
@@ -1073,7 +1028,7 @@ abstract class AbstractQuery
|
||||
*/
|
||||
protected function getHydrationCacheId()
|
||||
{
|
||||
$parameters = [];
|
||||
$parameters = array();
|
||||
|
||||
foreach ($this->getParameters() as $parameter) {
|
||||
$parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
|
||||
@@ -1135,7 +1090,7 @@ abstract class AbstractQuery
|
||||
{
|
||||
$this->parameters = new ArrayCollection();
|
||||
|
||||
$this->_hints = [];
|
||||
$this->_hints = array();
|
||||
$this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints();
|
||||
}
|
||||
|
||||
|
||||
@@ -43,8 +43,8 @@ class AssociationCacheEntry implements CacheEntry
|
||||
public $class;
|
||||
|
||||
/**
|
||||
* @param string $class The entity class.
|
||||
* @param array $identifier The entity identifier.
|
||||
* @param string $class The entity class.
|
||||
* @param array $identifier The entity identifier.
|
||||
*/
|
||||
public function __construct($class, array $identifier)
|
||||
{
|
||||
@@ -58,8 +58,6 @@ class AssociationCacheEntry implements CacheEntry
|
||||
* This method allow Doctrine\Common\Cache\PhpFileCache compatibility
|
||||
*
|
||||
* @param array $values array containing property values
|
||||
*
|
||||
* @return AssociationCacheEntry
|
||||
*/
|
||||
public static function __set_state(array $values)
|
||||
{
|
||||
|
||||
@@ -110,9 +110,7 @@ class CacheConfiguration
|
||||
public function getQueryValidator()
|
||||
{
|
||||
if ($this->queryValidator === null) {
|
||||
$this->queryValidator = new TimestampQueryCacheValidator(
|
||||
$this->cacheFactory->getTimestampRegion()
|
||||
);
|
||||
$this->queryValidator = new TimestampQueryCacheValidator();
|
||||
}
|
||||
|
||||
return $this->queryValidator;
|
||||
|
||||
@@ -62,9 +62,8 @@ class CacheException extends ORMException
|
||||
|
||||
/**
|
||||
* @param string $entityName
|
||||
* @param string $field
|
||||
*
|
||||
* @return CacheException
|
||||
* @return \Doctrine\ORM\Cache\CacheException
|
||||
*/
|
||||
public static function nonCacheableEntityAssociation($entityName, $field)
|
||||
{
|
||||
|
||||
@@ -50,7 +50,7 @@ class CollectionCacheEntry implements CacheEntry
|
||||
*
|
||||
* @param array $values array containing property values
|
||||
*
|
||||
* @return CollectionCacheEntry
|
||||
* @return self
|
||||
*/
|
||||
public static function __set_state(array $values)
|
||||
{
|
||||
|
||||
@@ -53,7 +53,7 @@ class DefaultCache implements Cache
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\QueryCache[]
|
||||
*/
|
||||
private $queryCaches = [];
|
||||
private $queryCaches = array();
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\QueryCache
|
||||
@@ -314,7 +314,7 @@ class DefaultCache implements Cache
|
||||
private function buildCollectionCacheKey(ClassMetadata $metadata, $association, $ownerIdentifier)
|
||||
{
|
||||
if ( ! is_array($ownerIdentifier)) {
|
||||
$ownerIdentifier = $this->toIdentifierArray($metadata, $ownerIdentifier);
|
||||
$ownerIdentifier = $this->toIdentifierArray($metadata, $ownerIdentifier);;
|
||||
}
|
||||
|
||||
return new CollectionCacheKey($metadata->rootEntityName, $association, $ownerIdentifier);
|
||||
@@ -336,7 +336,7 @@ class DefaultCache implements Cache
|
||||
}
|
||||
}
|
||||
|
||||
return [$metadata->identifier[0] => $identifier];
|
||||
return array($metadata->identifier[0] => $identifier);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ use Doctrine\ORM\Cache\Persister\Collection\ReadWriteCachedCollectionPersister;
|
||||
use Doctrine\ORM\Cache\Persister\Entity\NonStrictReadWriteCachedEntityPersister;
|
||||
use Doctrine\ORM\Cache\Persister\Entity\ReadOnlyCachedEntityPersister;
|
||||
use Doctrine\ORM\Cache\Persister\Entity\ReadWriteCachedEntityPersister;
|
||||
use Doctrine\ORM\Cache\Region;
|
||||
use Doctrine\ORM\Cache\Region\DefaultMultiGetRegion;
|
||||
use Doctrine\ORM\Cache\Region\DefaultRegion;
|
||||
use Doctrine\ORM\Cache\Region\FileLockRegion;
|
||||
@@ -63,7 +64,7 @@ class DefaultCacheFactory implements CacheFactory
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\Region[]
|
||||
*/
|
||||
private $regions = [];
|
||||
private $regions = array();
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
@@ -166,10 +167,10 @@ class DefaultCacheFactory implements CacheFactory
|
||||
return new DefaultQueryCache(
|
||||
$em,
|
||||
$this->getRegion(
|
||||
[
|
||||
array(
|
||||
'region' => $regionName ?: Cache::DEFAULT_QUERY_REGION_NAME,
|
||||
'usage' => ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE
|
||||
]
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -199,9 +200,14 @@ class DefaultCacheFactory implements CacheFactory
|
||||
return $this->regions[$cache['region']];
|
||||
}
|
||||
|
||||
$name = $cache['region'];
|
||||
$cacheAdapter = $this->createRegionCache($name);
|
||||
$lifetime = $this->regionsConfig->getLifetime($cache['region']);
|
||||
$cacheAdapter = clone $this->cache;
|
||||
|
||||
if ($cacheAdapter instanceof CacheProvider) {
|
||||
$cacheAdapter->setNamespace($cache['region']);
|
||||
}
|
||||
|
||||
$name = $cache['region'];
|
||||
$lifetime = $this->regionsConfig->getLifetime($cache['region']);
|
||||
|
||||
$region = ($cacheAdapter instanceof MultiGetCache)
|
||||
? new DefaultMultiGetRegion($name, $cacheAdapter, $lifetime)
|
||||
@@ -209,13 +215,10 @@ class DefaultCacheFactory implements CacheFactory
|
||||
|
||||
if ($cache['usage'] === ClassMetadata::CACHE_USAGE_READ_WRITE) {
|
||||
|
||||
if (
|
||||
'' === $this->fileLockRegionDirectory ||
|
||||
null === $this->fileLockRegionDirectory
|
||||
) {
|
||||
if ( ! $this->fileLockRegionDirectory) {
|
||||
throw new \LogicException(
|
||||
'If you want to use a "READ_WRITE" cache an implementation of "Doctrine\ORM\Cache\ConcurrentRegion" is required, ' .
|
||||
'The default implementation provided by doctrine is "Doctrine\ORM\Cache\Region\FileLockRegion" if you want to use it please provide a valid directory, DefaultCacheFactory#setFileLockRegionDirectory(). '
|
||||
'If you what to use a "READ_WRITE" cache an implementation of "Doctrine\ORM\Cache\ConcurrentRegion" is required, ' .
|
||||
'The default implementation provided by doctrine is "Doctrine\ORM\Cache\Region\FileLockRegion" if you what to use it please provide a valid directory, DefaultCacheFactory#setFileLockRegionDirectory(). '
|
||||
);
|
||||
}
|
||||
|
||||
@@ -226,30 +229,6 @@ class DefaultCacheFactory implements CacheFactory
|
||||
return $this->regions[$cache['region']] = $region;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return CacheAdapter
|
||||
*/
|
||||
private function createRegionCache($name)
|
||||
{
|
||||
$cacheAdapter = clone $this->cache;
|
||||
|
||||
if (!$cacheAdapter instanceof CacheProvider) {
|
||||
return $cacheAdapter;
|
||||
}
|
||||
|
||||
$namespace = $cacheAdapter->getNamespace();
|
||||
|
||||
if ('' !== $namespace) {
|
||||
$namespace .= ':';
|
||||
}
|
||||
|
||||
$cacheAdapter->setNamespace($namespace . $name);
|
||||
|
||||
return $cacheAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
||||
@@ -46,7 +46,7 @@ class DefaultCollectionHydrator implements CollectionHydrator
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $hints = [Query::HINT_CACHE_ENABLED => true];
|
||||
private static $hints = array(Query::HINT_CACHE_ENABLED => true);
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
|
||||
@@ -62,12 +62,11 @@ class DefaultCollectionHydrator implements CollectionHydrator
|
||||
*/
|
||||
public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, $collection)
|
||||
{
|
||||
$data = [];
|
||||
$data = array();
|
||||
|
||||
foreach ($collection as $index => $entity) {
|
||||
$data[$index] = new EntityCacheKey($metadata->rootEntityName, $this->uow->getEntityIdentifier($entity));
|
||||
$data[$index] = new EntityCacheKey($metadata->name, $this->uow->getEntityIdentifier($entity));
|
||||
}
|
||||
|
||||
return new CollectionCacheEntry($data);
|
||||
}
|
||||
|
||||
@@ -80,7 +79,7 @@ class DefaultCollectionHydrator implements CollectionHydrator
|
||||
/* @var $targetPersister \Doctrine\ORM\Cache\Persister\CachedPersister */
|
||||
$targetPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
|
||||
$targetRegion = $targetPersister->getCacheRegion();
|
||||
$list = [];
|
||||
$list = array();
|
||||
|
||||
$entityEntries = $targetRegion->getMultiple($entry);
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ use Doctrine\ORM\Utility\IdentifierFlattener;
|
||||
class DefaultEntityHydrator implements EntityHydrator
|
||||
{
|
||||
/**
|
||||
* @var \Doctrine\ORM\EntityManagerInterface
|
||||
* @var \Doctrine\ORM\EntityManager
|
||||
*/
|
||||
private $em;
|
||||
|
||||
@@ -55,7 +55,7 @@ class DefaultEntityHydrator implements EntityHydrator
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $hints = [Query::HINT_CACHE_ENABLED => true];
|
||||
private static $hints = array(Query::HINT_CACHE_ENABLED => true);
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
|
||||
@@ -73,50 +73,37 @@ class DefaultEntityHydrator implements EntityHydrator
|
||||
public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $entity)
|
||||
{
|
||||
$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);
|
||||
}
|
||||
$data = array_merge($data, $key->identifier); // why update has no identifier values ?
|
||||
|
||||
foreach ($metadata->associationMappings as $name => $assoc) {
|
||||
|
||||
if ( ! isset($data[$name])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! ($assoc['type'] & ClassMetadata::TO_ONE)) {
|
||||
if (! ($assoc['type'] & ClassMetadata::TO_ONE)) {
|
||||
unset($data[$name]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! isset($assoc['cache'])) {
|
||||
$targetClassMetadata = $this->em->getClassMetadata($assoc['targetEntity']);
|
||||
$owningAssociation = ( ! $assoc['isOwningSide'])
|
||||
? $targetClassMetadata->associationMappings[$assoc['mappedBy']]
|
||||
: $assoc;
|
||||
$associationIds = $this->identifierFlattener->flattenIdentifier(
|
||||
$targetClassMetadata,
|
||||
$targetClassMetadata->getIdentifierValues($data[$name])
|
||||
);
|
||||
|
||||
$associationIds = $this->identifierFlattener->flattenIdentifier($targetClassMetadata, $targetClassMetadata->getIdentifierValues($data[$name]));
|
||||
unset($data[$name]);
|
||||
|
||||
foreach ($associationIds as $fieldName => $fieldValue) {
|
||||
if (isset($targetClassMetadata->fieldMappings[$fieldName])) {
|
||||
$fieldMapping = $targetClassMetadata->fieldMappings[$fieldName];
|
||||
|
||||
$data[$owningAssociation['targetToSourceKeyColumns'][$fieldMapping['columnName']]] = $fieldValue;
|
||||
if (isset($targetClassMetadata->associationMappings[$fieldName])){
|
||||
$targetAssoc = $targetClassMetadata->associationMappings[$fieldName];
|
||||
|
||||
continue;
|
||||
}
|
||||
foreach($assoc['targetToSourceKeyColumns'] as $referencedColumn => $localColumn) {
|
||||
|
||||
$targetAssoc = $targetClassMetadata->associationMappings[$fieldName];
|
||||
|
||||
foreach($assoc['targetToSourceKeyColumns'] as $referencedColumn => $localColumn) {
|
||||
if (isset($targetAssoc['sourceToTargetKeyColumns'][$referencedColumn])) {
|
||||
$data[$localColumn] = $fieldValue;
|
||||
if (isset($targetAssoc['sourceToTargetKeyColumns'][$referencedColumn])) {
|
||||
$data[$localColumn] = $fieldValue;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
$data[$assoc['targetToSourceKeyColumns'][$targetClassMetadata->columnNames[$fieldName]]] = $fieldValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,10 +126,11 @@ class DefaultEntityHydrator implements EntityHydrator
|
||||
// @TODO - fix it !
|
||||
// handle UnitOfWork#createEntity hash generation
|
||||
if ( ! is_array($targetId)) {
|
||||
|
||||
$data[reset($assoc['joinColumnFieldNames'])] = $targetId;
|
||||
|
||||
$targetEntity = $this->em->getClassMetadata($assoc['targetEntity']);
|
||||
$targetId = [$targetEntity->identifier[0] => $targetId];
|
||||
$targetId = array($targetEntity->identifier[0] => $targetId);
|
||||
}
|
||||
|
||||
$data[$name] = new AssociationCacheEntry($assoc['targetEntity'], $targetId);
|
||||
@@ -179,8 +167,7 @@ class DefaultEntityHydrator implements EntityHydrator
|
||||
continue;
|
||||
}
|
||||
|
||||
$assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']);
|
||||
$assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocId);
|
||||
$assocKey = new EntityCacheKey($assoc['targetEntity'], $assocId);
|
||||
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
|
||||
$assocRegion = $assocPersister->getCacheRegion();
|
||||
$assocEntry = $assocRegion->get($assocKey);
|
||||
|
||||
@@ -22,7 +22,6 @@ 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;
|
||||
@@ -30,7 +29,6 @@ use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\Common\Proxy\Proxy;
|
||||
use Doctrine\ORM\Cache;
|
||||
use Doctrine\ORM\Query;
|
||||
use function assert;
|
||||
|
||||
/**
|
||||
* Default query cache implementation.
|
||||
@@ -68,7 +66,7 @@ class DefaultQueryCache implements QueryCache
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $hints = [Query::HINT_CACHE_ENABLED => true];
|
||||
private static $hints = array(Query::HINT_CACHE_ENABLED => true);
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
|
||||
@@ -88,59 +86,49 @@ class DefaultQueryCache implements QueryCache
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = [])
|
||||
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = array())
|
||||
{
|
||||
if ( ! ($key->cacheMode & Cache::MODE_GET)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$cacheEntry = $this->region->get($key);
|
||||
$entry = $this->region->get($key);
|
||||
|
||||
if ( ! $cacheEntry instanceof QueryCacheEntry) {
|
||||
if ( ! $entry instanceof QueryCacheEntry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( ! $this->validator->isValid($key, $cacheEntry)) {
|
||||
if ( ! $this->validator->isValid($key, $entry)) {
|
||||
$this->region->evict($key);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$result = [];
|
||||
$result = array();
|
||||
$entityName = reset($rsm->aliasMap);
|
||||
$hasRelation = ! empty($rsm->relationMap);
|
||||
$hasRelation = ( ! empty($rsm->relationMap));
|
||||
$persister = $this->uow->getEntityPersister($entityName);
|
||||
assert($persister instanceof CachedEntityPersister);
|
||||
|
||||
$region = $persister->getCacheRegion();
|
||||
$regionName = $region->getName();
|
||||
|
||||
$cm = $this->em->getClassMetadata($entityName);
|
||||
|
||||
$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) ?? [];
|
||||
$region = $persister->getCacheRegion();
|
||||
$regionName = $region->getName();
|
||||
|
||||
// @TODO - move to cache hydration component
|
||||
foreach ($cacheEntry->result as $index => $entry) {
|
||||
$entityEntry = $entries[$index] ?? null;
|
||||
foreach ($entry->result as $index => $entry) {
|
||||
|
||||
if (($entityEntry = $region->get($entityKey = new EntityCacheKey($entityName, $entry['identifier']))) === null) {
|
||||
|
||||
if (! $entityEntry instanceof EntityCacheEntry) {
|
||||
if ($this->cacheLogger !== null) {
|
||||
$this->cacheLogger->entityCacheMiss($regionName, $cacheKeys->identifiers[$index]);
|
||||
$this->cacheLogger->entityCacheMiss($regionName, $entityKey);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->cacheLogger !== null) {
|
||||
$this->cacheLogger->entityCacheHit($regionName, $cacheKeys->identifiers[$index]);
|
||||
$this->cacheLogger->entityCacheHit($regionName, $entityKey);
|
||||
}
|
||||
|
||||
if ( ! $hasRelation) {
|
||||
|
||||
$result[$index] = $this->uow->createEntity($entityEntry->class, $entityEntry->resolveAssociationEntries($this->em), self::$hints);
|
||||
|
||||
continue;
|
||||
@@ -149,15 +137,13 @@ class DefaultQueryCache implements QueryCache
|
||||
$data = $entityEntry->data;
|
||||
|
||||
foreach ($entry['associations'] as $name => $assoc) {
|
||||
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
|
||||
assert($assocPersister instanceof CachedEntityPersister);
|
||||
|
||||
$assocRegion = $assocPersister->getCacheRegion();
|
||||
$assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']);
|
||||
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
|
||||
$assocRegion = $assocPersister->getCacheRegion();
|
||||
|
||||
if ($assoc['type'] & ClassMetadata::TO_ONE) {
|
||||
|
||||
if (($assocEntry = $assocRegion->get($assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assoc['identifier']))) === null) {
|
||||
if (($assocEntry = $assocRegion->get($assocKey = new EntityCacheKey($assoc['targetEntity'], $assoc['identifier']))) === null) {
|
||||
|
||||
if ($this->cacheLogger !== null) {
|
||||
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey);
|
||||
@@ -181,20 +167,15 @@ class DefaultQueryCache implements QueryCache
|
||||
continue;
|
||||
}
|
||||
|
||||
$generateKeys = function ($id) use ($assocMetadata): EntityCacheKey {
|
||||
return new EntityCacheKey($assocMetadata->rootEntityName, $id);
|
||||
};
|
||||
|
||||
$collection = new PersistentCollection($this->em, $assocMetadata, new ArrayCollection());
|
||||
$assocKeys = new CollectionCacheEntry(array_map($generateKeys, $assoc['list']));
|
||||
$assocEntries = $assocRegion->getMultiple($assocKeys);
|
||||
$targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
|
||||
$collection = new PersistentCollection($this->em, $targetClass, new ArrayCollection());
|
||||
|
||||
foreach ($assoc['list'] as $assocIndex => $assocId) {
|
||||
$assocEntry = is_array($assocEntries) && array_key_exists($assocIndex, $assocEntries) ? $assocEntries[$assocIndex] : null;
|
||||
|
||||
if ($assocEntry === null) {
|
||||
if (($assocEntry = $assocRegion->get($assocKey = new EntityCacheKey($assoc['targetEntity'], $assocId))) === null) {
|
||||
|
||||
if ($this->cacheLogger !== null) {
|
||||
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKeys->identifiers[$assocIndex]);
|
||||
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey);
|
||||
}
|
||||
|
||||
$this->uow->hydrationComplete();
|
||||
@@ -207,7 +188,7 @@ class DefaultQueryCache implements QueryCache
|
||||
$collection->hydrateSet($assocIndex, $element);
|
||||
|
||||
if ($this->cacheLogger !== null) {
|
||||
$this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKeys->identifiers[$assocIndex]);
|
||||
$this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,25 +197,6 @@ class DefaultQueryCache implements QueryCache
|
||||
$collection->setInitialized(true);
|
||||
}
|
||||
|
||||
foreach ($data as $fieldName => $unCachedAssociationData) {
|
||||
// In some scenarios, such as EAGER+ASSOCIATION+ID+CACHE, the
|
||||
// cache key information in `$cacheEntry` will not contain details
|
||||
// for fields that are associations.
|
||||
//
|
||||
// This means that `$data` keys for some associations that may
|
||||
// actually not be cached will not be converted to actual association
|
||||
// data, yet they contain L2 cache AssociationCacheEntry objects.
|
||||
//
|
||||
// We need to unwrap those associations into proxy references,
|
||||
// since we don't have actual data for them except for identifiers.
|
||||
if ($unCachedAssociationData instanceof AssociationCacheEntry) {
|
||||
$data[$fieldName] = $this->em->getReference(
|
||||
$unCachedAssociationData->class,
|
||||
$unCachedAssociationData->identifier
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$result[$index] = $this->uow->createEntity($entityEntry->class, $data, self::$hints);
|
||||
}
|
||||
|
||||
@@ -246,7 +208,7 @@ class DefaultQueryCache implements QueryCache
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = [])
|
||||
public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = array())
|
||||
{
|
||||
if ($rsm->scalarMappings) {
|
||||
throw new CacheException("Second level cache does not support scalar results.");
|
||||
@@ -268,12 +230,13 @@ class DefaultQueryCache implements QueryCache
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = [];
|
||||
$data = array();
|
||||
$entityName = reset($rsm->aliasMap);
|
||||
$rootAlias = key($rsm->aliasMap);
|
||||
$hasRelation = ( ! empty($rsm->relationMap));
|
||||
$metadata = $this->em->getClassMetadata($entityName);
|
||||
$persister = $this->uow->getEntityPersister($entityName);
|
||||
|
||||
if (! $persister instanceof CachedEntityPersister) {
|
||||
if ( ! ($persister instanceof CachedPersister)) {
|
||||
throw CacheException::nonCacheableEntity($entityName);
|
||||
}
|
||||
|
||||
@@ -281,185 +244,86 @@ class DefaultQueryCache implements QueryCache
|
||||
|
||||
foreach ($result as $index => $entity) {
|
||||
$identifier = $this->uow->getEntityIdentifier($entity);
|
||||
$entityKey = new EntityCacheKey($entityName, $identifier);
|
||||
$data[$index]['identifier'] = $identifier;
|
||||
$data[$index]['associations'] = array();
|
||||
|
||||
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $region->contains($entityKey)) {
|
||||
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $region->contains($entityKey = new EntityCacheKey($entityName, $identifier))) {
|
||||
// Cancel put result if entity put fail
|
||||
if ( ! $persister->storeEntityCache($entity, $entityKey)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$data[$index]['identifier'] = $identifier;
|
||||
$data[$index]['associations'] = [];
|
||||
if ( ! $hasRelation) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// @TODO - move to cache hydration components
|
||||
foreach ($rsm->relationMap as $alias => $name) {
|
||||
$parentAlias = $rsm->parentAliasMap[$alias];
|
||||
$parentClass = $rsm->aliasMap[$parentAlias];
|
||||
$metadata = $this->em->getClassMetadata($parentClass);
|
||||
$assoc = $metadata->associationMappings[$name];
|
||||
$assocValue = $this->getAssociationValue($rsm, $alias, $entity);
|
||||
foreach ($rsm->relationMap as $name) {
|
||||
$assoc = $metadata->associationMappings[$name];
|
||||
|
||||
if ($assocValue === null) {
|
||||
if (($assocValue = $metadata->getFieldValue($entity, $name)) === null || $assocValue instanceof Proxy) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// root entity association
|
||||
if ($rootAlias === $parentAlias) {
|
||||
// Cancel put result if association put fail
|
||||
if ( ($assocInfo = $this->storeAssociationCache($key, $assoc, $assocValue)) === null) {
|
||||
return false;
|
||||
if ( ! isset($assoc['cache'])) {
|
||||
throw CacheException::nonCacheableEntityAssociation($entityName, $name);
|
||||
}
|
||||
|
||||
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
|
||||
$assocRegion = $assocPersister->getCacheRegion();
|
||||
$assocMetadata = $assocPersister->getClassMetadata();
|
||||
|
||||
// Handle *-to-one associations
|
||||
if ($assoc['type'] & ClassMetadata::TO_ONE) {
|
||||
|
||||
$assocIdentifier = $this->uow->getEntityIdentifier($assocValue);
|
||||
|
||||
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier))) {
|
||||
|
||||
// Cancel put result if association entity put fail
|
||||
if ( ! $assocPersister->storeEntityCache($assocValue, $entityKey)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$data[$index]['associations'][$name] = $assocInfo;
|
||||
$data[$index]['associations'][$name] = array(
|
||||
'targetEntity' => $assocMetadata->rootEntityName,
|
||||
'identifier' => $assocIdentifier,
|
||||
'type' => $assoc['type']
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// store single nested association
|
||||
if ( ! is_array($assocValue)) {
|
||||
// Cancel put result if association put fail
|
||||
if ($this->storeAssociationCache($key, $assoc, $assocValue) === null) {
|
||||
return false;
|
||||
// Handle *-to-many associations
|
||||
$list = array();
|
||||
|
||||
foreach ($assocValue as $assocItemIndex => $assocItem) {
|
||||
$assocIdentifier = $this->uow->getEntityIdentifier($assocItem);
|
||||
|
||||
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier))) {
|
||||
|
||||
// Cancel put result if entity put fail
|
||||
if ( ! $assocPersister->storeEntityCache($assocItem, $entityKey)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
$list[$assocItemIndex] = $assocIdentifier;
|
||||
}
|
||||
|
||||
// store array of nested association
|
||||
foreach ($assocValue as $aVal) {
|
||||
// Cancel put result if association put fail
|
||||
if ($this->storeAssociationCache($key, $assoc, $aVal) === null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$data[$index]['associations'][$name] = array(
|
||||
'targetEntity' => $assocMetadata->rootEntityName,
|
||||
'type' => $assoc['type'],
|
||||
'list' => $list,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->region->put($key, new QueryCacheEntry($data));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Cache\QueryCacheKey $key
|
||||
* @param array $assoc
|
||||
* @param mixed $assocValue
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
private function storeAssociationCache(QueryCacheKey $key, array $assoc, $assocValue)
|
||||
{
|
||||
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
|
||||
$assocMetadata = $assocPersister->getClassMetadata();
|
||||
$assocRegion = $assocPersister->getCacheRegion();
|
||||
|
||||
// Handle *-to-one associations
|
||||
if ($assoc['type'] & ClassMetadata::TO_ONE) {
|
||||
$assocIdentifier = $this->uow->getEntityIdentifier($assocValue);
|
||||
$entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier);
|
||||
|
||||
if ( ! $assocValue instanceof Proxy && ($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) {
|
||||
// Entity put fail
|
||||
if ( ! $assocPersister->storeEntityCache($assocValue, $entityKey)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'targetEntity' => $assocMetadata->rootEntityName,
|
||||
'identifier' => $assocIdentifier,
|
||||
'type' => $assoc['type']
|
||||
];
|
||||
}
|
||||
|
||||
// Handle *-to-many associations
|
||||
$list = [];
|
||||
|
||||
foreach ($assocValue as $assocItemIndex => $assocItem) {
|
||||
$assocIdentifier = $this->uow->getEntityIdentifier($assocItem);
|
||||
$entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier);
|
||||
|
||||
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) {
|
||||
// Entity put fail
|
||||
if ( ! $assocPersister->storeEntityCache($assocItem, $entityKey)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
$list[$assocItemIndex] = $assocIdentifier;
|
||||
}
|
||||
|
||||
return [
|
||||
'targetEntity' => $assocMetadata->rootEntityName,
|
||||
'type' => $assoc['type'],
|
||||
'list' => $list,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Query\ResultSetMapping $rsm
|
||||
* @param string $assocAlias
|
||||
* @param object $entity
|
||||
*
|
||||
* @return array|object
|
||||
*/
|
||||
private function getAssociationValue(ResultSetMapping $rsm, $assocAlias, $entity)
|
||||
{
|
||||
$path = [];
|
||||
$alias = $assocAlias;
|
||||
|
||||
while (isset($rsm->parentAliasMap[$alias])) {
|
||||
$parent = $rsm->parentAliasMap[$alias];
|
||||
$field = $rsm->relationMap[$alias];
|
||||
$class = $rsm->aliasMap[$parent];
|
||||
|
||||
array_unshift($path, [
|
||||
'field' => $field,
|
||||
'class' => $class
|
||||
]
|
||||
);
|
||||
|
||||
$alias = $parent;
|
||||
}
|
||||
|
||||
return $this->getAssociationPathValue($entity, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param array $path
|
||||
*
|
||||
* @return array|object|null
|
||||
*/
|
||||
private function getAssociationPathValue($value, array $path)
|
||||
{
|
||||
$mapping = array_shift($path);
|
||||
$metadata = $this->em->getClassMetadata($mapping['class']);
|
||||
$assoc = $metadata->associationMappings[$mapping['field']];
|
||||
$value = $metadata->getFieldValue($value, $mapping['field']);
|
||||
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (empty($path)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
// Handle *-to-one associations
|
||||
if ($assoc['type'] & ClassMetadata::TO_ONE) {
|
||||
return $this->getAssociationPathValue($value, $path);
|
||||
}
|
||||
|
||||
$values = [];
|
||||
|
||||
foreach ($value as $item) {
|
||||
$values[] = $this->getAssociationPathValue($item, $path);
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
||||
@@ -60,8 +60,6 @@ class EntityCacheEntry implements CacheEntry
|
||||
* This method allow Doctrine\Common\Cache\PhpFileCache compatibility
|
||||
*
|
||||
* @param array $values array containing property values
|
||||
*
|
||||
* @return EntityCacheEntry
|
||||
*/
|
||||
public static function __set_state(array $values)
|
||||
{
|
||||
@@ -71,7 +69,7 @@ class EntityCacheEntry implements CacheEntry
|
||||
/**
|
||||
* Retrieves the entity data resolving cache entries
|
||||
*
|
||||
* @param \Doctrine\ORM\EntityManagerInterface $em
|
||||
* @param \Doctrine\ORM\EntityManagerInterfac $em
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
|
||||
@@ -53,6 +53,6 @@ class Lock
|
||||
*/
|
||||
public static function createLockRead()
|
||||
{
|
||||
return new self(uniqid(time(), true));
|
||||
return new self(uniqid(time()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class CacheLoggerChain implements CacheLogger
|
||||
/**
|
||||
* @var array<\Doctrine\ORM\Cache\Logging\CacheLogger>
|
||||
*/
|
||||
private $loggers = [];
|
||||
private $loggers = array();
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
|
||||
@@ -35,17 +35,17 @@ class StatisticsCacheLogger implements CacheLogger
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $cacheMissCountMap = [];
|
||||
private $cacheMissCountMap = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $cacheHitCountMap = [];
|
||||
private $cacheHitCountMap = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $cachePutCountMap = [];
|
||||
private $cachePutCountMap = array();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
@@ -214,9 +214,9 @@ class StatisticsCacheLogger implements CacheLogger
|
||||
*/
|
||||
public function clearStats()
|
||||
{
|
||||
$this->cachePutCountMap = [];
|
||||
$this->cacheHitCountMap = [];
|
||||
$this->cacheMissCountMap = [];
|
||||
$this->cachePutCountMap = array();
|
||||
$this->cacheHitCountMap = array();
|
||||
$this->cacheMissCountMap = array();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,7 +32,6 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
|
||||
/**
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @since 2.5
|
||||
*/
|
||||
abstract class AbstractCollectionPersister implements CachedCollectionPersister
|
||||
@@ -70,7 +69,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $queuedCache = [];
|
||||
protected $queuedCache = array();
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\Region
|
||||
@@ -165,18 +164,10 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
|
||||
public function storeCollectionCache(CollectionCacheKey $key, $elements)
|
||||
{
|
||||
/* @var $targetPersister CachedEntityPersister */
|
||||
$associationMapping = $this->sourceEntity->associationMappings[$key->association];
|
||||
$targetPersister = $this->uow->getEntityPersister($this->targetEntity->rootEntityName);
|
||||
$targetRegion = $targetPersister->getCacheRegion();
|
||||
$targetHydrator = $targetPersister->getEntityHydrator();
|
||||
|
||||
// Only preserve ordering if association configured it
|
||||
if ( ! (isset($associationMapping['indexBy']) && $associationMapping['indexBy'])) {
|
||||
// Elements may be an array or a Collection
|
||||
$elements = array_values(is_array($elements) ? $elements : $elements->getValues());
|
||||
}
|
||||
|
||||
$entry = $this->hydrator->buildCacheEntry($this->targetEntity, $key, $elements);
|
||||
$entry = $this->hydrator->buildCacheEntry($this->targetEntity, $key, $elements);
|
||||
|
||||
foreach ($entry->identifiers as $index => $entityKey) {
|
||||
if ($targetRegion->contains($entityKey)) {
|
||||
|
||||
@@ -46,7 +46,7 @@ class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPers
|
||||
}
|
||||
}
|
||||
|
||||
$this->queuedCache = [];
|
||||
$this->queuedCache = array();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,7 +54,7 @@ class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPers
|
||||
*/
|
||||
public function afterTransactionRolledBack()
|
||||
{
|
||||
$this->queuedCache = [];
|
||||
$this->queuedCache = array();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,9 +96,9 @@ class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPers
|
||||
|
||||
$this->persister->update($collection);
|
||||
|
||||
$this->queuedCache['update'][spl_object_hash($collection)] = [
|
||||
$this->queuedCache['update'][spl_object_hash($collection)] = array(
|
||||
'key' => $key,
|
||||
'list' => $collection
|
||||
];
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class ReadOnlyCachedCollectionPersister extends NonStrictReadWriteCachedCollecti
|
||||
*/
|
||||
public function update(PersistentCollection $collection)
|
||||
{
|
||||
if ($collection->isDirty() && $collection->getSnapshot()) {
|
||||
if ($collection->isDirty() && count($collection->getSnapshot()) > 0) {
|
||||
throw CacheException::updateReadOnlyCollection(ClassUtils::getClass($collection->getOwner()), $this->association['fieldName']);
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
|
||||
}
|
||||
}
|
||||
|
||||
$this->queuedCache = [];
|
||||
$this->queuedCache = array();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,7 +80,7 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
|
||||
}
|
||||
}
|
||||
|
||||
$this->queuedCache = [];
|
||||
$this->queuedCache = array();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,10 +98,10 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
|
||||
return;
|
||||
}
|
||||
|
||||
$this->queuedCache['delete'][spl_object_hash($collection)] = [
|
||||
$this->queuedCache['delete'][spl_object_hash($collection)] = array(
|
||||
'key' => $key,
|
||||
'lock' => $lock
|
||||
];
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,9 +126,9 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
|
||||
return;
|
||||
}
|
||||
|
||||
$this->queuedCache['update'][spl_object_hash($collection)] = [
|
||||
$this->queuedCache['update'][spl_object_hash($collection)] = array(
|
||||
'key' => $key,
|
||||
'lock' => $lock
|
||||
];
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ use Doctrine\ORM\Cache\CollectionCacheKey;
|
||||
use Doctrine\ORM\Cache\TimestampCacheKey;
|
||||
use Doctrine\ORM\Cache\QueryCacheKey;
|
||||
use Doctrine\ORM\Cache\Persister\CachedPersister;
|
||||
use Doctrine\ORM\Cache\CacheException;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
@@ -64,7 +65,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $queuedCache = [];
|
||||
protected $queuedCache = array();
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\Region
|
||||
@@ -104,7 +105,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
/**
|
||||
* Associations configured as FETCH_EAGER, as well as all inverse one-to-one associations.
|
||||
*
|
||||
* @var array|null
|
||||
* @var array
|
||||
*/
|
||||
protected $joinedAssociations;
|
||||
|
||||
@@ -130,7 +131,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
$this->cacheLogger = $cacheConfig->getCacheLogger();
|
||||
$this->timestampRegion = $cacheFactory->getTimestampRegion();
|
||||
$this->hydrator = $cacheFactory->buildEntityHydrator($em, $class);
|
||||
$this->timestampKey = new TimestampCacheKey($this->class->rootEntityName);
|
||||
$this->timestampKey = new TimestampCacheKey($this->class->getTableName());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,7 +161,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getCountSQL($criteria = [])
|
||||
public function getCountSQL($criteria = array())
|
||||
{
|
||||
return $this->persister->getCountSQL($criteria);
|
||||
}
|
||||
@@ -233,6 +234,14 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
$class = $this->metadataFactory->getMetadataFor($className);
|
||||
}
|
||||
|
||||
if ($class->containsForeignIdentifier) {
|
||||
foreach ($class->associationMappings as $name => $assoc) {
|
||||
if (!empty($assoc['id']) && !isset($assoc['cache'])) {
|
||||
throw CacheException::nonCacheableEntityAssociation($class->name, $name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$entry = $this->hydrator->buildCacheEntry($class, $key, $entity);
|
||||
$cached = $this->region->put($key, $entry);
|
||||
|
||||
@@ -249,7 +258,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
private function storeJoinedAssociations($entity)
|
||||
{
|
||||
if ($this->joinedAssociations === null) {
|
||||
$associations = [];
|
||||
$associations = array();
|
||||
|
||||
foreach ($this->class->associationMappings as $name => $assoc) {
|
||||
if (isset($assoc['cache']) &&
|
||||
@@ -272,8 +281,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
}
|
||||
|
||||
$assocId = $this->uow->getEntityIdentifier($assocEntity);
|
||||
$assocMetadata = $this->metadataFactory->getMetadataFor($assoc['targetEntity']);
|
||||
$assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocId);
|
||||
$assocKey = new EntityCacheKey($assoc['targetEntity'], $assocId);
|
||||
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
|
||||
|
||||
$assocPersister->storeEntityCache($assocEntity, $assocKey);
|
||||
@@ -283,21 +291,22 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
/**
|
||||
* Generates a string of currently query
|
||||
*
|
||||
* @param array $query
|
||||
* @param string $criteria
|
||||
* @param array $orderBy
|
||||
* @param array $query
|
||||
* @param string $criteria
|
||||
* @param array $orderBy
|
||||
* @param integer $limit
|
||||
* @param integer $offset
|
||||
* @param integer $timestamp
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getHash($query, $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
protected function getHash($query, $criteria, array $orderBy = null, $limit = null, $offset = null, $timestamp = null)
|
||||
{
|
||||
list($params) = ($criteria instanceof Criteria)
|
||||
? $this->persister->expandCriteriaParameters($criteria)
|
||||
: $this->persister->expandParameters($criteria);
|
||||
|
||||
return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset);
|
||||
return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset . $timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -361,23 +370,24 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load(array $criteria, $entity = null, $assoc = null, array $hints = [], $lockMode = null, $limit = null, array $orderBy = null)
|
||||
public function load(array $criteria, $entity = null, $assoc = null, array $hints = array(), $lockMode = null, $limit = null, array $orderBy = null)
|
||||
{
|
||||
if ($entity !== null || $assoc !== null || ! empty($hints) || $lockMode !== null) {
|
||||
return $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy);
|
||||
}
|
||||
|
||||
//handle only EntityRepository#findOneBy
|
||||
$timestamp = $this->timestampRegion->get($this->timestampKey);
|
||||
$query = $this->persister->getSelectSQL($criteria, null, null, $limit, null, $orderBy);
|
||||
$hash = $this->getHash($query, $criteria, null, null, null);
|
||||
$hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null);
|
||||
$rsm = $this->getResultSetMapping();
|
||||
$queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey);
|
||||
$querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL);
|
||||
$queryCache = $this->cache->getQueryCache($this->regionName);
|
||||
$result = $queryCache->get($queryKey, $rsm);
|
||||
$result = $queryCache->get($querykey, $rsm);
|
||||
|
||||
if ($result !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->queryCacheHit($this->regionName, $queryKey);
|
||||
$this->cacheLogger->queryCacheHit($this->regionName, $querykey);
|
||||
}
|
||||
|
||||
return $result[0];
|
||||
@@ -387,15 +397,15 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
return null;
|
||||
}
|
||||
|
||||
$cached = $queryCache->put($queryKey, $rsm, [$result]);
|
||||
$cached = $queryCache->put($querykey, $rsm, array($result));
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
if ($result) {
|
||||
$this->cacheLogger->queryCacheMiss($this->regionName, $queryKey);
|
||||
$this->cacheLogger->queryCacheMiss($this->regionName, $querykey);
|
||||
}
|
||||
|
||||
if ($cached) {
|
||||
$this->cacheLogger->queryCachePut($this->regionName, $queryKey);
|
||||
$this->cacheLogger->queryCachePut($this->regionName, $querykey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,33 +415,34 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadAll(array $criteria = [], array $orderBy = null, $limit = null, $offset = null)
|
||||
public function loadAll(array $criteria = array(), array $orderBy = null, $limit = null, $offset = null)
|
||||
{
|
||||
$timestamp = $this->timestampRegion->get($this->timestampKey);
|
||||
$query = $this->persister->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy);
|
||||
$hash = $this->getHash($query, $criteria, null, null, null);
|
||||
$hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null);
|
||||
$rsm = $this->getResultSetMapping();
|
||||
$queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey);
|
||||
$querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL);
|
||||
$queryCache = $this->cache->getQueryCache($this->regionName);
|
||||
$result = $queryCache->get($queryKey, $rsm);
|
||||
$result = $queryCache->get($querykey, $rsm);
|
||||
|
||||
if ($result !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->queryCacheHit($this->regionName, $queryKey);
|
||||
$this->cacheLogger->queryCacheHit($this->regionName, $querykey);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
$result = $this->persister->loadAll($criteria, $orderBy, $limit, $offset);
|
||||
$cached = $queryCache->put($queryKey, $rsm, $result);
|
||||
$cached = $queryCache->put($querykey, $rsm, $result);
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
if ($result) {
|
||||
$this->cacheLogger->queryCacheMiss($this->regionName, $queryKey);
|
||||
$this->cacheLogger->queryCacheMiss($this->regionName, $querykey);
|
||||
}
|
||||
|
||||
if ($cached) {
|
||||
$this->cacheLogger->queryCachePut($this->regionName, $queryKey);
|
||||
$this->cacheLogger->queryCachePut($this->regionName, $querykey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,14 +463,12 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
$class = $this->metadataFactory->getMetadataFor($cacheEntry->class);
|
||||
}
|
||||
|
||||
$cachedEntity = $this->hydrator->loadCacheEntry($class, $cacheKey, $cacheEntry, $entity);
|
||||
|
||||
if ($cachedEntity !== null) {
|
||||
if (($entity = $this->hydrator->loadCacheEntry($class, $cacheKey, $cacheEntry, $entity)) !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->entityCacheHit($this->regionName, $cacheKey);
|
||||
}
|
||||
|
||||
return $cachedEntity;
|
||||
return $entity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,7 +488,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
$cacheEntry = $this->hydrator->buildCacheEntry($class, $cacheKey, $entity);
|
||||
$cached = $this->region->put($cacheKey, $cacheEntry);
|
||||
|
||||
if ($cached && (null === $this->joinedAssociations || $this->joinedAssociations)) {
|
||||
if ($cached && ($this->joinedAssociations === null || count($this->joinedAssociations) > 0)) {
|
||||
$this->storeJoinedAssociations($entity);
|
||||
}
|
||||
|
||||
@@ -497,7 +506,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function count($criteria = [])
|
||||
public function count($criteria = array())
|
||||
{
|
||||
return $this->persister->count($criteria);
|
||||
}
|
||||
@@ -507,34 +516,32 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
*/
|
||||
public function loadCriteria(Criteria $criteria)
|
||||
{
|
||||
$orderBy = $criteria->getOrderings();
|
||||
$limit = $criteria->getMaxResults();
|
||||
$offset = $criteria->getFirstResult();
|
||||
$query = $this->persister->getSelectSQL($criteria);
|
||||
$hash = $this->getHash($query, $criteria, $orderBy, $limit, $offset);
|
||||
$timestamp = $this->timestampRegion->get($this->timestampKey);
|
||||
$hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null);
|
||||
$rsm = $this->getResultSetMapping();
|
||||
$queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey);
|
||||
$querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL);
|
||||
$queryCache = $this->cache->getQueryCache($this->regionName);
|
||||
$cacheResult = $queryCache->get($queryKey, $rsm);
|
||||
$cacheResult = $queryCache->get($querykey, $rsm);
|
||||
|
||||
if ($cacheResult !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->queryCacheHit($this->regionName, $queryKey);
|
||||
$this->cacheLogger->queryCacheHit($this->regionName, $querykey);
|
||||
}
|
||||
|
||||
return $cacheResult;
|
||||
}
|
||||
|
||||
$result = $this->persister->loadCriteria($criteria);
|
||||
$cached = $queryCache->put($queryKey, $rsm, $result);
|
||||
$cached = $queryCache->put($querykey, $rsm, $result);
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
if ($result) {
|
||||
$this->cacheLogger->queryCacheMiss($this->regionName, $queryKey);
|
||||
$this->cacheLogger->queryCacheMiss($this->regionName, $querykey);
|
||||
}
|
||||
|
||||
if ($cached) {
|
||||
$this->cacheLogger->queryCachePut($this->regionName, $queryKey);
|
||||
$this->cacheLogger->queryCachePut($this->regionName, $querykey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -550,28 +557,28 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
$hasCache = ($persister instanceof CachedPersister);
|
||||
$key = null;
|
||||
|
||||
if ( ! $hasCache) {
|
||||
return $this->persister->loadManyToManyCollection($assoc, $sourceEntity, $coll);
|
||||
}
|
||||
if ($hasCache) {
|
||||
$ownerId = $this->uow->getEntityIdentifier($coll->getOwner());
|
||||
$key = new CollectionCacheKey($assoc['sourceEntity'], $assoc['fieldName'], $ownerId);
|
||||
$list = $persister->loadCollectionCache($coll, $key);
|
||||
|
||||
$ownerId = $this->uow->getEntityIdentifier($coll->getOwner());
|
||||
$key = $this->buildCollectionCacheKey($assoc, $ownerId);
|
||||
$list = $persister->loadCollectionCache($coll, $key);
|
||||
if ($list !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key);
|
||||
}
|
||||
|
||||
if ($list !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key);
|
||||
return $list;
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
$list = $this->persister->loadManyToManyCollection($assoc, $sourceEntity, $coll);
|
||||
|
||||
$persister->storeCollectionCache($key, $list);
|
||||
if ($hasCache) {
|
||||
$persister->storeCollectionCache($key, $list);
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
|
||||
}
|
||||
}
|
||||
|
||||
return $list;
|
||||
@@ -585,28 +592,28 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
$persister = $this->uow->getCollectionPersister($assoc);
|
||||
$hasCache = ($persister instanceof CachedPersister);
|
||||
|
||||
if ( ! $hasCache) {
|
||||
return $this->persister->loadOneToManyCollection($assoc, $sourceEntity, $coll);
|
||||
}
|
||||
if ($hasCache) {
|
||||
$ownerId = $this->uow->getEntityIdentifier($coll->getOwner());
|
||||
$key = new CollectionCacheKey($assoc['sourceEntity'], $assoc['fieldName'], $ownerId);
|
||||
$list = $persister->loadCollectionCache($coll, $key);
|
||||
|
||||
$ownerId = $this->uow->getEntityIdentifier($coll->getOwner());
|
||||
$key = $this->buildCollectionCacheKey($assoc, $ownerId);
|
||||
$list = $persister->loadCollectionCache($coll, $key);
|
||||
if ($list !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key);
|
||||
}
|
||||
|
||||
if ($list !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key);
|
||||
return $list;
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
$list = $this->persister->loadOneToManyCollection($assoc, $sourceEntity, $coll);
|
||||
|
||||
$persister->storeCollectionCache($key, $list);
|
||||
if ($hasCache) {
|
||||
$persister->storeCollectionCache($key, $list);
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
|
||||
}
|
||||
}
|
||||
|
||||
return $list;
|
||||
@@ -615,7 +622,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = [])
|
||||
public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = array())
|
||||
{
|
||||
return $this->persister->loadOneToOneEntity($assoc, $sourceEntity, $identifier);
|
||||
}
|
||||
@@ -636,17 +643,4 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
$this->persister->refresh($id, $entity, $lockMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $association
|
||||
* @param array $ownerId
|
||||
*
|
||||
* @return CollectionCacheKey
|
||||
*/
|
||||
protected function buildCollectionCacheKey(array $association, $ownerId)
|
||||
{
|
||||
/** @var ClassMetadata $metadata */
|
||||
$metadata = $this->metadataFactory->getMetadataFor($association['sourceEntity']);
|
||||
|
||||
return new CollectionCacheKey($metadata->rootEntityName, $association['fieldName'], $ownerId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,9 +38,8 @@ interface CachedEntityPersister extends CachedPersister, EntityPersister
|
||||
public function getEntityHydrator();
|
||||
|
||||
/**
|
||||
* @param object $entity
|
||||
* @param \Doctrine\ORM\Cache\EntityCacheKey $key
|
||||
*
|
||||
* @param object $entity
|
||||
* @param \Doctrine\ORM\Cache\EntityCacheKey $key
|
||||
* @return boolean
|
||||
*/
|
||||
public function storeEntityCache($entity, EntityCacheKey $key);
|
||||
|
||||
@@ -20,13 +20,13 @@
|
||||
|
||||
namespace Doctrine\ORM\Cache\Persister\Entity;
|
||||
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
use Doctrine\ORM\Cache\EntityCacheKey;
|
||||
|
||||
/**
|
||||
* Specific non-strict read/write cached entity persister
|
||||
*
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @since 2.5
|
||||
*/
|
||||
class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
@@ -62,7 +62,7 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
$this->timestampRegion->update($this->timestampKey);
|
||||
}
|
||||
|
||||
$this->queuedCache = [];
|
||||
$this->queuedCache = array();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,7 +70,7 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
*/
|
||||
public function afterTransactionRolledBack()
|
||||
{
|
||||
$this->queuedCache = [];
|
||||
$this->queuedCache = array();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,16 +78,13 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
*/
|
||||
public function delete($entity)
|
||||
{
|
||||
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
|
||||
$deleted = $this->persister->delete($entity);
|
||||
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
|
||||
|
||||
if ($deleted) {
|
||||
if ($this->persister->delete($entity)) {
|
||||
$this->region->evict($key);
|
||||
}
|
||||
|
||||
$this->queuedCache['delete'][] = $key;
|
||||
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,7 +30,6 @@ use Doctrine\ORM\Cache\EntityCacheKey;
|
||||
* Specific read-write entity persister
|
||||
*
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @since 2.5
|
||||
*/
|
||||
class ReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
@@ -73,7 +72,7 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
$this->timestampRegion->update($this->timestampKey);
|
||||
}
|
||||
|
||||
$this->queuedCache = [];
|
||||
$this->queuedCache = array();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,7 +92,7 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
}
|
||||
}
|
||||
|
||||
$this->queuedCache = [];
|
||||
$this->queuedCache = array();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,24 +100,21 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
*/
|
||||
public function delete($entity)
|
||||
{
|
||||
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
|
||||
$lock = $this->region->lock($key);
|
||||
$deleted = $this->persister->delete($entity);
|
||||
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
|
||||
$lock = $this->region->lock($key);
|
||||
|
||||
if ($deleted) {
|
||||
if ($this->persister->delete($entity)) {
|
||||
$this->region->evict($key);
|
||||
}
|
||||
|
||||
if ($lock === null) {
|
||||
return $deleted;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->queuedCache['delete'][] = [
|
||||
$this->queuedCache['delete'][] = array(
|
||||
'lock' => $lock,
|
||||
'key' => $key
|
||||
];
|
||||
|
||||
return $deleted;
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,9 +131,9 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
return;
|
||||
}
|
||||
|
||||
$this->queuedCache['update'][] = [
|
||||
$this->queuedCache['update'][] = array(
|
||||
'lock' => $lock,
|
||||
'key' => $key
|
||||
];
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ interface QueryCache
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = []);
|
||||
public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = array());
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Cache\QueryCacheKey $key
|
||||
@@ -53,7 +53,7 @@ interface QueryCache
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = []);
|
||||
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = array());
|
||||
|
||||
/**
|
||||
* @return \Doctrine\ORM\Cache\Region
|
||||
|
||||
@@ -38,24 +38,22 @@ class QueryCacheEntry implements CacheEntry
|
||||
/**
|
||||
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
|
||||
*
|
||||
* @var float Time creation of this cache entry
|
||||
* @var integer Time creation of this cache entry
|
||||
*/
|
||||
public $time;
|
||||
|
||||
/**
|
||||
* @param array $result
|
||||
* @param float $time
|
||||
* @param integer $time
|
||||
*/
|
||||
public function __construct($result, $time = null)
|
||||
{
|
||||
$this->result = $result;
|
||||
$this->time = $time ?: microtime(true);
|
||||
$this->time = $time ?: time();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $values
|
||||
*
|
||||
* @return QueryCacheEntry
|
||||
*/
|
||||
public static function __set_state(array $values)
|
||||
{
|
||||
|
||||
@@ -45,27 +45,14 @@ class QueryCacheKey extends CacheKey
|
||||
public $cacheMode;
|
||||
|
||||
/**
|
||||
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
|
||||
*
|
||||
* @var TimestampCacheKey|null
|
||||
* @param string $hash Result cache id
|
||||
* @param integer $lifetime Query lifetime
|
||||
* @param integer $cacheMode Query cache mode
|
||||
*/
|
||||
public $timestampKey;
|
||||
|
||||
/**
|
||||
* @param string $hash Result cache id
|
||||
* @param integer $lifetime Query lifetime
|
||||
* @param int $cacheMode Query cache mode
|
||||
* @param TimestampCacheKey|null $timestampKey
|
||||
*/
|
||||
public function __construct(
|
||||
$hash,
|
||||
$lifetime = 0,
|
||||
$cacheMode = Cache::MODE_NORMAL,
|
||||
TimestampCacheKey $timestampKey = null
|
||||
) {
|
||||
$this->hash = $hash;
|
||||
$this->lifetime = $lifetime;
|
||||
$this->cacheMode = $cacheMode;
|
||||
$this->timestampKey = $timestampKey;
|
||||
public function __construct($hash, $lifetime = 0, $cacheMode = Cache::MODE_NORMAL)
|
||||
{
|
||||
$this->hash = $hash;
|
||||
$this->lifetime = $lifetime;
|
||||
$this->cacheMode = $cacheMode;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user