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
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -10,8 +10,5 @@ lib/Doctrine/DBAL
|
||||
.buildpath
|
||||
.project
|
||||
.idea
|
||||
*.iml
|
||||
vendor/
|
||||
composer.lock
|
||||
/tests/Doctrine/Performance/history.db
|
||||
/.phpcs-cache
|
||||
|
||||
@@ -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
|
||||
114
.travis.yml
114
.travis.yml
@@ -1,103 +1,41 @@
|
||||
dist: trusty
|
||||
sudo: false
|
||||
language: php
|
||||
|
||||
php:
|
||||
- 7.1
|
||||
- 7.2
|
||||
- 7.3
|
||||
- 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 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
|
||||
env: DB=mysql MYSQL_VERSION=5.7
|
||||
php: 7.1
|
||||
before_script:
|
||||
- ./tests/travis/install-mysql-$MYSQL_VERSION.sh
|
||||
sudo: required
|
||||
|
||||
- stage: Test
|
||||
env: DB=mysql MYSQL_VERSION=5.7
|
||||
php: 7.2
|
||||
before_script:
|
||||
- ./tests/travis/install-mysql-$MYSQL_VERSION.sh
|
||||
sudo: required
|
||||
|
||||
- stage: Test
|
||||
env: DB=mysql MYSQL_VERSION=5.7
|
||||
php: nightly
|
||||
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 update --prefer-dist --prefer-stable
|
||||
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
|
||||
env: DB=none CODING_STANDARDS
|
||||
php: nightly
|
||||
script:
|
||||
- ./vendor/bin/phpcs
|
||||
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
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.composer/cache
|
||||
- php: 7.0
|
||||
- php: hhvm-nightly # hhvm-nightly currently chokes on composer installation
|
||||
|
||||
@@ -41,29 +41,16 @@ 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 doctrine2, and you
|
||||
will have to run a composer installation in the project:
|
||||
|
||||
```sh
|
||||
git clone git@github.com:doctrine/doctrine2.git
|
||||
cd doctrine2
|
||||
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/doctrine2/tree/master/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2306Test.php` for an
|
||||
@@ -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/doctrine2/master.svg?style=flat-square
|
||||
[Master]: https://travis-ci.org/doctrine/doctrine2
|
||||
[Master coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/doctrine2/master.svg?style=flat-square
|
||||
[Master coverage]: https://scrutinizer-ci.com/g/doctrine/doctrine2/?branch=master
|
||||
[2.5 image]: https://img.shields.io/travis/doctrine/doctrine2/2.5.svg?style=flat-square
|
||||
[2.5]: https://github.com/doctrine/doctrine2/tree/2.5
|
||||
[2.5 coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/doctrine2/2.5.svg?style=flat-square
|
||||
[2.5 coverage]: https://scrutinizer-ci.com/g/doctrine/doctrine2/?branch=2.5
|
||||
54
UPGRADE.md
54
UPGRADE.md
@@ -1,59 +1,5 @@
|
||||
# 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/doctrine2/pull/6500)
|
||||
|
||||
## Minor BC BREAK: removed `Doctrine\ORM\ORMException#overwriteInternalDQLFunctionNotAllowed()`
|
||||
|
||||
Method `Doctrine\ORM\Query\Parser#overwriteInternalDQLFunctionNotAllowed()` was
|
||||
removed because of the choice to allow users to overwrite internal functions, ie
|
||||
`AVG`, `SUM`, `COUNT`, `MIN` and `MAX`. [#6500](https://github.com/doctrine/doctrine2/pull/6500)
|
||||
|
||||
## 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/doctrine2/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);
|
||||
|
||||
@@ -9,48 +9,40 @@
|
||||
{"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.5",
|
||||
"doctrine/cache": "~1.6",
|
||||
"doctrine/collections": "^1.4",
|
||||
"doctrine/common": "^2.7.1",
|
||||
"doctrine/dbal": "^2.6",
|
||||
"doctrine/instantiator": "~1.1",
|
||||
"symfony/console": "~3.0|~4.0"
|
||||
"doctrine/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": "^1.0",
|
||||
"phpunit/phpunit": "^6.5",
|
||||
"squizlabs/php_codesniffer": "^3.2",
|
||||
"symfony/yaml": "~3.4|~4.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.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"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -1,9 +1,9 @@
|
||||
What is new in Doctrine ORM 2.5?
|
||||
================================
|
||||
|
||||
This document describes changes between Doctrine ORM 2.4 and 2.5.
|
||||
It contains a description of all the new features and sections about
|
||||
behavioral changes and potential backwards compatibility breaks.
|
||||
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
|
||||
@@ -21,7 +21,7 @@ defined then Doctrine would trigger listeners after the fields were
|
||||
loaded, but before assocations are available.
|
||||
|
||||
- `DDC-54 <http://doctrine-project.org/jira/browse/DDC-54>`_
|
||||
- `Commit #a90629 <https://github.com/doctrine/doctrine2/commit/a906295c65f1516737458fbee2f6fa96254f27a5>`_
|
||||
- `Commit <https://github.com/doctrine/doctrine2/commit/a906295c65f1516737458fbee2f6fa96254f27a5>`_
|
||||
|
||||
Events: Add API to programatically add event listeners to Entity
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -58,11 +58,11 @@ metadata generation:
|
||||
}
|
||||
}
|
||||
|
||||
Embeddable Objects
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
Embeddedable Objects
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Doctrine now supports creating multiple PHP objects from one database table
|
||||
implementing a feature called "Embeddable Objects". Next to an ``@Entity``
|
||||
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
|
||||
@@ -102,7 +102,7 @@ This feature was developed by external contributor `Johannes Schmitt
|
||||
<https://twitter.com/schmittjoh>`_
|
||||
|
||||
- `DDC-93 <http://doctrine-project.org/jira/browse/DDC-93>`_
|
||||
- `Pull Request #835 <https://github.com/doctrine/doctrine2/pull/835>`_
|
||||
- `Pull Request <https://github.com/doctrine/doctrine2/pull/835>`_
|
||||
|
||||
Second-Level-Cache
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
@@ -160,7 +160,7 @@ instead of the database.
|
||||
|
||||
- `Documentation
|
||||
<http://docs.doctrine-project.org/en/latest/reference/second-level-cache.html>`_
|
||||
- `Pull Request #808 <https://github.com/doctrine/doctrine2/pull/808>`_
|
||||
- `Pull Request <https://github.com/doctrine/doctrine2/pull/808>`_
|
||||
|
||||
Criteria API: Support for ManyToMany assocations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -217,8 +217,8 @@ trigger a full load of the collection.
|
||||
|
||||
This feature was contributed by `Michaël Gallego <https://github.com/bakura10>`_.
|
||||
|
||||
- `Pull Request #882 <https://github.com/doctrine/doctrine2/pull/882>`_
|
||||
- `Pull Request #1032 <https://github.com/doctrine/doctrine2/pull/1032>`_
|
||||
- `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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -241,7 +241,7 @@ only with a schema event listener before.
|
||||
|
||||
This feature was contributed by `Adrian Olek <https://github.com/adrianolek>`_.
|
||||
|
||||
- `Pull Request #973 <https://github.com/doctrine/doctrine2/pull/973>`_
|
||||
- `Pull Request <https://github.com/doctrine/doctrine2/pull/973>`_
|
||||
|
||||
SQLFilter API: Check if a parameter is set
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -272,7 +272,7 @@ Extending on the locale example of the documentation:
|
||||
|
||||
This feature was contributed by `Miroslav Demovic <https://github.com/mdemo>`_
|
||||
|
||||
- `Pull Request #963 <https://github.com/doctrine/doctrine2/pull/963>`_
|
||||
- `Pull Request <https://github.com/doctrine/doctrine2/pull/963>`_
|
||||
|
||||
|
||||
EXTRA_LAZY Improvements
|
||||
@@ -301,7 +301,7 @@ EXTRA_LAZY Improvements
|
||||
|
||||
This feature was contributed by `Asmir Mustafic <https://github.com/goetas>`_
|
||||
|
||||
- `Pull Request #937 <https://github.com/doctrine/doctrine2/pull/937>`_
|
||||
- `Pull Request <https://github.com/doctrine/doctrine2/pull/937>`_
|
||||
|
||||
2. Add EXTRA_LAZY Support for get() for owning and inverse many-to-many
|
||||
|
||||
@@ -415,11 +415,11 @@ object:
|
||||
This feature was contributed by `Michael Perrin
|
||||
<https://github.com/michaelperrin>`_.
|
||||
|
||||
- `Pull Request #590 <https://github.com/doctrine/doctrine2/pull/590>`_
|
||||
- `Pull Request <https://github.com/doctrine/doctrine2/pull/590>`_
|
||||
- `DDC-2319 <http://doctrine-project.org/jira/browse/DDC-2319>`_
|
||||
|
||||
Query API: Add support for default Query Hints
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
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
|
||||
@@ -439,7 +439,7 @@ It is now possible to add query hints that are always enabled for every Query:
|
||||
This feature was contributed by `Artur Eshenbrener
|
||||
<https://github.com/Strate>`_.
|
||||
|
||||
- `Pull Request #863 <https://github.com/doctrine/doctrine2/pull/863>`_
|
||||
- `Pull Request <https://github.com/doctrine/doctrine2/pull/863>`_
|
||||
|
||||
ResultSetMappingBuilder: Add support for Single-Table Inheritance
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -486,8 +486,8 @@ EntityGenerator Command: Avoid backups
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When calling the EntityGenerator for an existing entity, Doctrine would
|
||||
create a backup file every time to avoid losing changes to the code. You
|
||||
can now skip generating the backup file by passing the ``--no-backup``
|
||||
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:
|
||||
|
||||
::
|
||||
@@ -708,4 +708,3 @@ From now on, the resultset will look like this:
|
||||
),
|
||||
...
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,7 +4,7 @@ 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
|
||||
|
||||
@@ -111,7 +111,7 @@ APC, get rid of EchoSqlLogger, and turn off
|
||||
autoGenerateProxyClasses.
|
||||
|
||||
For more details, consult the
|
||||
`Doctrine 2 Configuration documentation <http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/configuration.html>`_.
|
||||
`Doctrine 2 Configuration documentation <http://www.doctrine-project.org/documentation/manual/2_0/en/configuration#configuration-options>`_.
|
||||
|
||||
Now to use it
|
||||
-------------
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,63 +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
|
||||
{
|
||||
static private $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::$utc ? self::$utc : self::$utc = new \DateTimeZone('UTC')
|
||||
(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;
|
||||
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:
|
||||
|
||||
|
||||
@@ -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``
|
||||
@@ -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.
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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 Student.
|
||||
* @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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -100,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
|
||||
|
||||
@@ -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,10 +21,10 @@ 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:
|
||||
|
||||
@@ -38,13 +38,9 @@ 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.
|
||||
@@ -305,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
|
||||
~~~~~~~~~~~~
|
||||
@@ -318,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.
|
||||
@@ -335,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::
|
||||
|
||||
@@ -387,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.
|
||||
@@ -423,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
|
||||
-----------
|
||||
|
||||
|
||||
@@ -79,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.
|
||||
|
||||
@@ -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
|
||||
~~~~~~~~~~~~~
|
||||
@@ -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
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -1060,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]
|
||||
@@ -1160,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
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -1452,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
|
||||
@@ -1487,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, ...)
|
||||
@@ -1523,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
|
||||
@@ -1623,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
|
||||
|
||||
@@ -653,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
|
||||
@@ -740,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
|
||||
~~~~~~~~
|
||||
@@ -888,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?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
* )
|
||||
* )
|
||||
* })
|
||||
|
||||
@@ -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/doctrine2/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/doctrine2/issues/5398>`_ Merge can sometimes add the same entity twice into a collection
|
||||
- `DDC-763 <https://github.com/doctrine/doctrine2/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/doctrine2/issues/5178>`_
|
||||
- `Evaluate possible ways in which stored-procedures can be used <https://github.com/doctrine/doctrine2/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/doctrine2/issues/2817>`_
|
||||
`Ticket DDC-213 <http://www.doctrine-project.org/jira/browse/DDC-213>`_
|
||||
|
||||
Mapping many tables to one entity
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -112,10 +114,10 @@ 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
|
||||
@@ -144,8 +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/doctrine2/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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -179,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
|
||||
|
||||
|
||||
@@ -7,14 +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.
|
||||
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
|
||||
|
||||
@@ -24,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
|
||||
|
||||
@@ -80,11 +80,11 @@ Working with QueryBuilder
|
||||
High level API methods
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To simplify even more the way you build a query in Doctrine, you can take
|
||||
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
|
||||
|
||||
@@ -97,8 +97,8 @@ to work with them, here is the same example 6 re-written using
|
||||
->orderBy('u.name', 'ASC');
|
||||
|
||||
``QueryBuilder`` helper methods are considered the standard way to
|
||||
build DQL queries. Although it is supported, using string-based
|
||||
queries should be avoided. You are greatly encouraged to use
|
||||
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:
|
||||
|
||||
@@ -113,7 +113,7 @@ suggested standard way to build queries:
|
||||
$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``:
|
||||
|
||||
@@ -126,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');
|
||||
@@ -317,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:
|
||||
@@ -499,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:`Criteria <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
|
||||
@@ -577,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 <http://www.doctrine-project.org/api/orm/2.5/class-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 <http://www.doctrine-project.org/api/orm/2.5/class-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/2.5/class-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/2.5/class-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/2.5/class-Doctrine.ORM.Cache.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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -385,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();
|
||||
|
||||
@@ -395,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();
|
||||
@@ -477,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.
|
||||
|
||||
@@ -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,8 +703,6 @@ methods:
|
||||
* ``in($field, array $values)``
|
||||
* ``notIn($field, array $values)``
|
||||
* ``contains($field, $value)``
|
||||
* ``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>`").
|
||||
"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 <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>`").
|
||||
"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 <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 <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 <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
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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">
|
||||
<!-- ... -->
|
||||
|
||||
@@ -14,10 +14,10 @@ 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
|
||||
<http://getcomposer.org/doc/00-intro.md>`_)
|
||||
|
||||
@@ -25,7 +25,7 @@ The code of this tutorial is `available on Github <https://github.com/doctrine/d
|
||||
|
||||
.. 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?
|
||||
@@ -51,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
|
||||
@@ -62,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 <http://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:
|
||||
|
||||
@@ -80,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": {
|
||||
@@ -103,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
|
||||
@@ -153,12 +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.
|
||||
|
||||
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
|
||||
@@ -166,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 <http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/>`_.
|
||||
`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.
|
||||
@@ -177,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
|
||||
|
||||
@@ -190,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:
|
||||
@@ -256,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
|
||||
@@ -273,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::
|
||||
|
||||
@@ -303,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">
|
||||
@@ -314,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
|
||||
@@ -333,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];
|
||||
@@ -377,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
|
||||
@@ -406,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
|
||||
|
||||
@@ -427,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
|
||||
|
||||
@@ -461,8 +455,9 @@ 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
|
||||
|
||||
@@ -566,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
|
||||
@@ -608,7 +602,6 @@ domain model to match the requirements:
|
||||
<?php
|
||||
// src/User.php
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
class User
|
||||
{
|
||||
// ... (previous code)
|
||||
@@ -623,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::
|
||||
|
||||
@@ -652,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::
|
||||
|
||||
@@ -692,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;
|
||||
@@ -723,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;
|
||||
}
|
||||
@@ -747,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()``
|
||||
@@ -755,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
|
||||
@@ -764,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
|
||||
@@ -777,7 +771,7 @@ the database that points from Bugs to Products.
|
||||
|
||||
protected $products = null;
|
||||
|
||||
public function assignToProduct(Product $product)
|
||||
public function assignToProduct($product)
|
||||
{
|
||||
$this->products[] = $product;
|
||||
}
|
||||
@@ -789,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::
|
||||
@@ -843,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">
|
||||
@@ -861,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
|
||||
@@ -900,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
|
||||
@@ -917,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::
|
||||
|
||||
@@ -944,13 +934,13 @@ Finally, we'll add metadata mappings for the ``User`` entity.
|
||||
|
||||
/**
|
||||
* @OneToMany(targetEntity="Bug", mappedBy="reporter")
|
||||
* @var Bug[] An ArrayCollection of Bug objects.
|
||||
* @var Bug[]
|
||||
**/
|
||||
protected $reportedBugs = null;
|
||||
|
||||
/**
|
||||
* @OneToMany(targetEntity="Bug", mappedBy="engineer")
|
||||
* @var Bug[] An ArrayCollection of Bug objects.
|
||||
* @var Bug[]
|
||||
**/
|
||||
protected $assignedBugs = null;
|
||||
|
||||
@@ -963,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">
|
||||
@@ -977,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
|
||||
@@ -1010,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
|
||||
@@ -1019,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
|
||||
|
||||
@@ -1044,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);
|
||||
}
|
||||
|
||||
@@ -1081,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
|
||||
---------------------------------
|
||||
@@ -1100,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:
|
||||
@@ -1156,7 +1150,7 @@ 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
|
||||
@@ -1231,7 +1225,7 @@ write scenarios:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// show_bug.php <id>
|
||||
// show_bug.php
|
||||
require_once "bootstrap.php";
|
||||
|
||||
$theBugId = $argv[1];
|
||||
@@ -1306,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];
|
||||
@@ -1372,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];
|
||||
@@ -1504,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:
|
||||
@@ -1545,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
|
||||
----------
|
||||
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
@@ -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)
|
||||
{
|
||||
@@ -323,14 +325,14 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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);
|
||||
}
|
||||
@@ -690,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.
|
||||
*/
|
||||
@@ -705,7 +706,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Gets the hydration mode currently used by the query.
|
||||
*
|
||||
* @return string|int
|
||||
* @return integer
|
||||
*/
|
||||
public function getHydrationMode()
|
||||
{
|
||||
@@ -717,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)
|
||||
{
|
||||
@@ -753,7 +754,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Get exactly one result or null.
|
||||
*
|
||||
* @param string|int $hydrationMode
|
||||
* @param int $hydrationMode
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
@@ -791,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)
|
||||
{
|
||||
@@ -822,9 +823,10 @@ abstract class AbstractQuery
|
||||
*
|
||||
* Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
|
||||
*
|
||||
* @return mixed The scalar result, or NULL if the query returned no result.
|
||||
* @return mixed
|
||||
*
|
||||
* @throws NonUniqueResultException If the query result is not unique.
|
||||
* @throws NoResultException If the query returned no result.
|
||||
*/
|
||||
public function getSingleScalarResult()
|
||||
{
|
||||
@@ -861,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
|
||||
*/
|
||||
@@ -885,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
|
||||
*/
|
||||
@@ -909,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
|
||||
*/
|
||||
@@ -926,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
|
||||
*/
|
||||
@@ -954,7 +956,7 @@ abstract class AbstractQuery
|
||||
}
|
||||
|
||||
if ( ! $result) {
|
||||
$result = [];
|
||||
$result = array();
|
||||
}
|
||||
|
||||
$setCacheEntry = function($data) use ($cache, $result, $cacheKey, $realCacheKey, $queryCacheProfile) {
|
||||
@@ -984,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
|
||||
@@ -1048,7 +1028,7 @@ abstract class AbstractQuery
|
||||
*/
|
||||
protected function getHydrationCacheId()
|
||||
{
|
||||
$parameters = [];
|
||||
$parameters = array();
|
||||
|
||||
foreach ($this->getParameters() as $parameter) {
|
||||
$parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
|
||||
@@ -1110,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);
|
||||
|
||||
@@ -66,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.
|
||||
@@ -86,57 +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));
|
||||
$persister = $this->uow->getEntityPersister($entityName);
|
||||
$region = $persister->getCacheRegion();
|
||||
$regionName = $region->getName();
|
||||
|
||||
$cm = $this->em->getClassMetadata($entityName);
|
||||
|
||||
$generateKeys = 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);
|
||||
|
||||
// @TODO - move to cache hydration component
|
||||
foreach ($cacheEntry->result as $index => $entry) {
|
||||
$entityEntry = is_array($entries) && array_key_exists($index, $entries) ? $entries[$index] : null;
|
||||
foreach ($entry->result as $index => $entry) {
|
||||
|
||||
if (($entityEntry = $region->get($entityKey = new EntityCacheKey($entityName, $entry['identifier']))) === null) {
|
||||
|
||||
if ($entityEntry === null) {
|
||||
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;
|
||||
@@ -145,13 +137,13 @@ class DefaultQueryCache implements QueryCache
|
||||
$data = $entityEntry->data;
|
||||
|
||||
foreach ($entry['associations'] as $name => $assoc) {
|
||||
|
||||
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
|
||||
$assocRegion = $assocPersister->getCacheRegion();
|
||||
$assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']);
|
||||
|
||||
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);
|
||||
@@ -175,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();
|
||||
@@ -201,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,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);
|
||||
}
|
||||
|
||||
@@ -240,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.");
|
||||
@@ -262,9 +230,10 @@ 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 CachedPersister)) {
|
||||
@@ -275,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,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);
|
||||
}
|
||||
|
||||
@@ -495,7 +506,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function count($criteria = [])
|
||||
public function count($criteria = array())
|
||||
{
|
||||
return $this->persister->count($criteria);
|
||||
}
|
||||
@@ -505,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -548,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;
|
||||
@@ -583,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;
|
||||
@@ -613,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);
|
||||
}
|
||||
@@ -634,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
namespace Doctrine\ORM\Cache\Region;
|
||||
|
||||
use Doctrine\Common\Cache\MultiGetCache;
|
||||
use Doctrine\ORM\Cache\Region;
|
||||
use Doctrine\ORM\Cache\CollectionCacheEntry;
|
||||
|
||||
/**
|
||||
@@ -55,7 +56,7 @@ class DefaultMultiGetRegion extends DefaultRegion
|
||||
*/
|
||||
public function getMultiple(CollectionCacheEntry $collection)
|
||||
{
|
||||
$keysToRetrieve = [];
|
||||
$keysToRetrieve = array();
|
||||
|
||||
foreach ($collection->identifiers as $index => $key) {
|
||||
$keysToRetrieve[$index] = $this->getCacheEntryKey($key);
|
||||
@@ -66,7 +67,7 @@ class DefaultMultiGetRegion extends DefaultRegion
|
||||
return null;
|
||||
}
|
||||
|
||||
$returnableItems = [];
|
||||
$returnableItems = array();
|
||||
foreach ($keysToRetrieve as $index => $key) {
|
||||
$returnableItems[$index] = $items[$key];
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ class DefaultRegion implements Region
|
||||
*/
|
||||
public function getMultiple(CollectionCacheEntry $collection)
|
||||
{
|
||||
$result = [];
|
||||
$result = array();
|
||||
|
||||
foreach ($collection->identifiers as $key) {
|
||||
$entryKey = $this->getCacheEntryKey($key);
|
||||
|
||||
@@ -114,7 +114,7 @@ class FileLockRegion implements ConcurrentRegion
|
||||
/**
|
||||
* @param \Doctrine\ORM\Cache\CacheKey $key
|
||||
*
|
||||
* @return string
|
||||
* return string
|
||||
*/
|
||||
private function getLockFileName(CacheKey $key)
|
||||
{
|
||||
@@ -124,7 +124,7 @@ class FileLockRegion implements ConcurrentRegion
|
||||
/**
|
||||
* @param string $filename
|
||||
*
|
||||
* @return string
|
||||
* return string
|
||||
*/
|
||||
private function getLockContent($filename)
|
||||
{
|
||||
@@ -134,7 +134,7 @@ class FileLockRegion implements ConcurrentRegion
|
||||
/**
|
||||
* @param string $filename
|
||||
*
|
||||
* @return integer
|
||||
* return integer
|
||||
*/
|
||||
private function getLockTime($filename)
|
||||
{
|
||||
@@ -142,7 +142,7 @@ class FileLockRegion implements ConcurrentRegion
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* {inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
@@ -150,7 +150,7 @@ class FileLockRegion implements ConcurrentRegion
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* {inheritdoc}
|
||||
*/
|
||||
public function contains(CacheKey $key)
|
||||
{
|
||||
@@ -162,7 +162,7 @@ class FileLockRegion implements ConcurrentRegion
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* {inheritdoc}
|
||||
*/
|
||||
public function get(CacheKey $key)
|
||||
{
|
||||
@@ -186,7 +186,7 @@ class FileLockRegion implements ConcurrentRegion
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* {inheritdoc}
|
||||
*/
|
||||
public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null)
|
||||
{
|
||||
@@ -198,7 +198,7 @@ class FileLockRegion implements ConcurrentRegion
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* {inheritdoc}
|
||||
*/
|
||||
public function evict(CacheKey $key)
|
||||
{
|
||||
@@ -210,7 +210,7 @@ class FileLockRegion implements ConcurrentRegion
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* {inheritdoc}
|
||||
*/
|
||||
public function evictAll()
|
||||
{
|
||||
@@ -228,7 +228,7 @@ class FileLockRegion implements ConcurrentRegion
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* {inheritdoc}
|
||||
*/
|
||||
public function lock(CacheKey $key)
|
||||
{
|
||||
@@ -248,7 +248,7 @@ class FileLockRegion implements ConcurrentRegion
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* {inheritdoc}
|
||||
*/
|
||||
public function unlock(CacheKey $key, Lock $lock)
|
||||
{
|
||||
|
||||
@@ -31,12 +31,12 @@ class RegionsConfiguration
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $lifetimes = [];
|
||||
private $lifetimes = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $lockLifetimes = [];
|
||||
private $lockLifetimes = array();
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
|
||||
@@ -40,7 +40,7 @@ class TimestampCacheEntry implements CacheEntry
|
||||
*/
|
||||
public function __construct($time = null)
|
||||
{
|
||||
$this->time = $time ? (float) $time : microtime(true);
|
||||
$this->time = $time ? (float)$time : microtime(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,8 +49,6 @@ class TimestampCacheEntry implements CacheEntry
|
||||
* This method allow Doctrine\Common\Cache\PhpFileCache compatibility
|
||||
*
|
||||
* @param array $values array containing property values
|
||||
*
|
||||
* @return TimestampCacheEntry
|
||||
*/
|
||||
public static function __set_state(array $values)
|
||||
{
|
||||
|
||||
@@ -26,49 +26,15 @@ namespace Doctrine\ORM\Cache;
|
||||
*/
|
||||
class TimestampQueryCacheValidator implements QueryCacheValidator
|
||||
{
|
||||
/**
|
||||
* @var TimestampRegion
|
||||
*/
|
||||
private $timestampRegion;
|
||||
|
||||
/**
|
||||
* @param TimestampRegion $timestampRegion
|
||||
*/
|
||||
public function __construct(TimestampRegion $timestampRegion)
|
||||
{
|
||||
$this->timestampRegion = $timestampRegion;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isValid(QueryCacheKey $key, QueryCacheEntry $entry)
|
||||
{
|
||||
if ($this->regionUpdated($key, $entry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($key->lifetime == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ($entry->time + $key->lifetime) > microtime(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param QueryCacheKey $key
|
||||
* @param QueryCacheEntry $entry
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function regionUpdated(QueryCacheKey $key, QueryCacheEntry $entry)
|
||||
{
|
||||
if ($key->timestampKey === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$timestamp = $this->timestampRegion->get($key->timestampKey);
|
||||
|
||||
return $timestamp && $timestamp->time > $entry->time;
|
||||
return ($entry->time + $key->lifetime) > time();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,11 +25,9 @@ use Doctrine\Common\Annotations\CachedReader;
|
||||
use Doctrine\Common\Annotations\SimpleAnnotationReader;
|
||||
use Doctrine\Common\Cache\ArrayCache;
|
||||
use Doctrine\Common\Cache\Cache as CacheDriver;
|
||||
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;
|
||||
use Doctrine\Common\Persistence\ObjectRepository;
|
||||
use Doctrine\Common\Proxy\AbstractProxyFactory;
|
||||
use Doctrine\ORM\Cache\CacheConfiguration;
|
||||
use Doctrine\ORM\Mapping\ClassMetadataFactory;
|
||||
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;
|
||||
use Doctrine\ORM\Mapping\DefaultEntityListenerResolver;
|
||||
use Doctrine\ORM\Mapping\DefaultNamingStrategy;
|
||||
use Doctrine\ORM\Mapping\DefaultQuoteStrategy;
|
||||
@@ -100,7 +98,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function setAutoGenerateProxyClasses($autoGenerate)
|
||||
{
|
||||
$this->_attributes['autoGenerateProxyClasses'] = (int) $autoGenerate;
|
||||
$this->_attributes['autoGenerateProxyClasses'] = (int)$autoGenerate;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,7 +149,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*
|
||||
* @return AnnotationDriver
|
||||
*/
|
||||
public function newDefaultAnnotationDriver($paths = [], $useSimpleAnnotationReader = true)
|
||||
public function newDefaultAnnotationDriver($paths = array(), $useSimpleAnnotationReader = true)
|
||||
{
|
||||
AnnotationRegistry::registerFile(__DIR__ . '/Mapping/Driver/DoctrineAnnotations.php');
|
||||
|
||||
@@ -351,7 +349,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function addNamedNativeQuery($name, $sql, Query\ResultSetMapping $rsm)
|
||||
{
|
||||
$this->_attributes['namedNativeQueries'][$name] = [$sql, $rsm];
|
||||
$this->_attributes['namedNativeQueries'][$name] = array($sql, $rsm);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -420,9 +418,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* @param string|callable $className Class name or a callable that returns the function.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMException
|
||||
*/
|
||||
public function addCustomStringFunction($name, $className)
|
||||
{
|
||||
if (Query\Parser::isInternalFunction($name)) {
|
||||
throw ORMException::overwriteInternalDQLFunctionNotAllowed($name);
|
||||
}
|
||||
|
||||
$this->_attributes['customStringFunctions'][strtolower($name)] = $className;
|
||||
}
|
||||
|
||||
@@ -472,9 +476,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* @param string|callable $className Class name or a callable that returns the function.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMException
|
||||
*/
|
||||
public function addCustomNumericFunction($name, $className)
|
||||
{
|
||||
if (Query\Parser::isInternalFunction($name)) {
|
||||
throw ORMException::overwriteInternalDQLFunctionNotAllowed($name);
|
||||
}
|
||||
|
||||
$this->_attributes['customNumericFunctions'][strtolower($name)] = $className;
|
||||
}
|
||||
|
||||
@@ -524,9 +534,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* @param string|callable $className Class name or a callable that returns the function.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMException
|
||||
*/
|
||||
public function addCustomDatetimeFunction($name, $className)
|
||||
{
|
||||
if (Query\Parser::isInternalFunction($name)) {
|
||||
throw ORMException::overwriteInternalDQLFunctionNotAllowed($name);
|
||||
}
|
||||
|
||||
$this->_attributes['customDatetimeFunctions'][strtolower($name)] = $className;
|
||||
}
|
||||
|
||||
@@ -574,7 +590,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function setCustomHydrationModes($modes)
|
||||
{
|
||||
$this->_attributes['customHydrationModes'] = [];
|
||||
$this->_attributes['customHydrationModes'] = array();
|
||||
|
||||
foreach ($modes as $modeName => $hydrator) {
|
||||
$this->addCustomHydrationMode($modeName, $hydrator);
|
||||
@@ -626,7 +642,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
public function getClassMetadataFactoryName()
|
||||
{
|
||||
if ( ! isset($this->_attributes['classMetadataFactoryName'])) {
|
||||
$this->_attributes['classMetadataFactoryName'] = ClassMetadataFactory::class;
|
||||
$this->_attributes['classMetadataFactoryName'] = 'Doctrine\ORM\Mapping\ClassMetadataFactory';
|
||||
}
|
||||
|
||||
return $this->_attributes['classMetadataFactoryName'];
|
||||
@@ -648,7 +664,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*
|
||||
* @param string $name The name of the filter.
|
||||
*
|
||||
* @return string The class name of the filter, or null if it is not
|
||||
* @return string The class name of the filter, or null of it is not
|
||||
* defined.
|
||||
*/
|
||||
public function getFilterClassName($name)
|
||||
@@ -673,7 +689,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
{
|
||||
$reflectionClass = new \ReflectionClass($className);
|
||||
|
||||
if ( ! $reflectionClass->implementsInterface(ObjectRepository::class)) {
|
||||
if ( ! $reflectionClass->implementsInterface('Doctrine\Common\Persistence\ObjectRepository')) {
|
||||
throw ORMException::invalidEntityRepository($className);
|
||||
}
|
||||
|
||||
@@ -691,7 +707,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
{
|
||||
return isset($this->_attributes['defaultRepositoryClassName'])
|
||||
? $this->_attributes['defaultRepositoryClassName']
|
||||
: EntityRepository::class;
|
||||
: 'Doctrine\ORM\EntityRepository';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -865,7 +881,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function getDefaultQueryHints()
|
||||
{
|
||||
return isset($this->_attributes['defaultQueryHints']) ? $this->_attributes['defaultQueryHints'] : [];
|
||||
return isset($this->_attributes['defaultQueryHints']) ? $this->_attributes['defaultQueryHints'] : array();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,16 +19,14 @@
|
||||
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use Exception;
|
||||
use Doctrine\Common\EventManager;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\DriverManager;
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\ORM\Proxy\ProxyFactory;
|
||||
use Doctrine\ORM\Query\FilterCollection;
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* The EntityManager is the central access point to ORM functionality.
|
||||
@@ -238,9 +236,9 @@ use Throwable;
|
||||
$this->conn->commit();
|
||||
|
||||
return $return ?: true;
|
||||
} catch (Throwable $e) {
|
||||
} catch (Exception $e) {
|
||||
$this->close();
|
||||
$this->conn->rollBack();
|
||||
$this->conn->rollback();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
@@ -259,7 +257,7 @@ use Throwable;
|
||||
*/
|
||||
public function rollback()
|
||||
{
|
||||
$this->conn->rollBack();
|
||||
$this->conn->rollback();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -291,7 +289,7 @@ use Throwable;
|
||||
$query = new Query($this);
|
||||
|
||||
if ( ! empty($dql)) {
|
||||
$query->setDQL($dql);
|
||||
$query->setDql($dql);
|
||||
}
|
||||
|
||||
return $query;
|
||||
@@ -312,7 +310,7 @@ use Throwable;
|
||||
{
|
||||
$query = new NativeQuery($this);
|
||||
|
||||
$query->setSQL($sql);
|
||||
$query->setSql($sql);
|
||||
$query->setResultSetMapping($rsm);
|
||||
|
||||
return $query;
|
||||
@@ -350,7 +348,6 @@ use Throwable;
|
||||
*
|
||||
* @throws \Doctrine\ORM\OptimisticLockException If a version check on an entity that
|
||||
* makes use of optimistic locking fails.
|
||||
* @throws ORMException
|
||||
*/
|
||||
public function flush($entity = null)
|
||||
{
|
||||
@@ -381,16 +378,12 @@ use Throwable;
|
||||
{
|
||||
$class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
|
||||
|
||||
if ($lockMode !== null) {
|
||||
$this->checkLockRequirements($lockMode, $class);
|
||||
}
|
||||
|
||||
if ( ! is_array($id)) {
|
||||
if ($class->isIdentifierComposite) {
|
||||
throw ORMInvalidArgumentException::invalidCompositeIdentifier();
|
||||
}
|
||||
|
||||
$id = [$class->identifier[0] => $id];
|
||||
$id = array($class->identifier[0] => $id);
|
||||
}
|
||||
|
||||
foreach ($id as $i => $value) {
|
||||
@@ -403,7 +396,7 @@ use Throwable;
|
||||
}
|
||||
}
|
||||
|
||||
$sortedId = [];
|
||||
$sortedId = array();
|
||||
|
||||
foreach ($class->identifier as $identifier) {
|
||||
if ( ! isset($id[$identifier])) {
|
||||
@@ -446,15 +439,24 @@ use Throwable;
|
||||
|
||||
switch (true) {
|
||||
case LockMode::OPTIMISTIC === $lockMode:
|
||||
if ( ! $class->isVersioned) {
|
||||
throw OptimisticLockException::notVersioned($class->name);
|
||||
}
|
||||
|
||||
$entity = $persister->load($sortedId);
|
||||
|
||||
$unitOfWork->lock($entity, $lockMode, $lockVersion);
|
||||
|
||||
return $entity;
|
||||
|
||||
case LockMode::NONE === $lockMode:
|
||||
case LockMode::PESSIMISTIC_READ === $lockMode:
|
||||
case LockMode::PESSIMISTIC_WRITE === $lockMode:
|
||||
return $persister->load($sortedId, null, null, [], $lockMode);
|
||||
if ( ! $this->getConnection()->isTransactionActive()) {
|
||||
throw TransactionRequiredException::transactionRequired();
|
||||
}
|
||||
|
||||
return $persister->load($sortedId, null, null, array(), $lockMode);
|
||||
|
||||
default:
|
||||
return $persister->loadById($sortedId);
|
||||
@@ -469,10 +471,10 @@ use Throwable;
|
||||
$class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
|
||||
|
||||
if ( ! is_array($id)) {
|
||||
$id = [$class->identifier[0] => $id];
|
||||
$id = array($class->identifier[0] => $id);
|
||||
}
|
||||
|
||||
$sortedId = [];
|
||||
$sortedId = array();
|
||||
|
||||
foreach ($class->identifier as $identifier) {
|
||||
if ( ! isset($id[$identifier])) {
|
||||
@@ -480,11 +482,6 @@ use Throwable;
|
||||
}
|
||||
|
||||
$sortedId[$identifier] = $id[$identifier];
|
||||
unset($id[$identifier]);
|
||||
}
|
||||
|
||||
if ($id) {
|
||||
throw ORMException::unrecognizedIdentifierFields($class->name, array_keys($id));
|
||||
}
|
||||
|
||||
// Check identity map first, if its already in there just return it.
|
||||
@@ -496,9 +493,13 @@ use Throwable;
|
||||
return $this->find($entityName, $sortedId);
|
||||
}
|
||||
|
||||
if ( ! is_array($sortedId)) {
|
||||
$sortedId = array($class->identifier[0] => $sortedId);
|
||||
}
|
||||
|
||||
$entity = $this->proxyFactory->getProxy($class->name, $sortedId);
|
||||
|
||||
$this->unitOfWork->registerManaged($entity, $sortedId, []);
|
||||
$this->unitOfWork->registerManaged($entity, $sortedId, array());
|
||||
|
||||
return $entity;
|
||||
}
|
||||
@@ -516,14 +517,14 @@ use Throwable;
|
||||
}
|
||||
|
||||
if ( ! is_array($identifier)) {
|
||||
$identifier = [$class->identifier[0] => $identifier];
|
||||
$identifier = array($class->identifier[0] => $identifier);
|
||||
}
|
||||
|
||||
$entity = $class->newInstance();
|
||||
|
||||
$class->setIdentifierValues($entity, $identifier);
|
||||
|
||||
$this->unitOfWork->registerManaged($entity, $identifier, []);
|
||||
$this->unitOfWork->registerManaged($entity, $identifier, array());
|
||||
$this->unitOfWork->markReadOnly($entity);
|
||||
|
||||
return $entity;
|
||||
@@ -536,22 +537,10 @@ use Throwable;
|
||||
* @param string|null $entityName if given, only entities of this type will get detached
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMInvalidArgumentException if a non-null non-string value is given
|
||||
* @throws \Doctrine\Common\Persistence\Mapping\MappingException if a $entityName is given, but that entity is not
|
||||
* found in the mappings
|
||||
*/
|
||||
public function clear($entityName = null)
|
||||
{
|
||||
if (null !== $entityName && ! is_string($entityName)) {
|
||||
throw ORMInvalidArgumentException::invalidEntityName($entityName);
|
||||
}
|
||||
|
||||
$this->unitOfWork->clear(
|
||||
null === $entityName
|
||||
? null
|
||||
: $this->metadataFactory->getMetadataFor($entityName)->getName()
|
||||
);
|
||||
$this->unitOfWork->clear($entityName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -578,12 +567,11 @@ use Throwable;
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMInvalidArgumentException
|
||||
* @throws ORMException
|
||||
*/
|
||||
public function persist($entity)
|
||||
{
|
||||
if ( ! is_object($entity)) {
|
||||
throw ORMInvalidArgumentException::invalidObject('EntityManager#persist()', $entity);
|
||||
throw ORMInvalidArgumentException::invalidObject('EntityManager#persist()' , $entity);
|
||||
}
|
||||
|
||||
$this->errorIfClosed();
|
||||
@@ -602,12 +590,11 @@ use Throwable;
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMInvalidArgumentException
|
||||
* @throws ORMException
|
||||
*/
|
||||
public function remove($entity)
|
||||
{
|
||||
if ( ! is_object($entity)) {
|
||||
throw ORMInvalidArgumentException::invalidObject('EntityManager#remove()', $entity);
|
||||
throw ORMInvalidArgumentException::invalidObject('EntityManager#remove()' , $entity);
|
||||
}
|
||||
|
||||
$this->errorIfClosed();
|
||||
@@ -624,12 +611,11 @@ use Throwable;
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMInvalidArgumentException
|
||||
* @throws ORMException
|
||||
*/
|
||||
public function refresh($entity)
|
||||
{
|
||||
if ( ! is_object($entity)) {
|
||||
throw ORMInvalidArgumentException::invalidObject('EntityManager#refresh()', $entity);
|
||||
throw ORMInvalidArgumentException::invalidObject('EntityManager#refresh()' , $entity);
|
||||
}
|
||||
|
||||
$this->errorIfClosed();
|
||||
@@ -653,7 +639,7 @@ use Throwable;
|
||||
public function detach($entity)
|
||||
{
|
||||
if ( ! is_object($entity)) {
|
||||
throw ORMInvalidArgumentException::invalidObject('EntityManager#detach()', $entity);
|
||||
throw ORMInvalidArgumentException::invalidObject('EntityManager#detach()' , $entity);
|
||||
}
|
||||
|
||||
$this->unitOfWork->detach($entity);
|
||||
@@ -669,12 +655,11 @@ use Throwable;
|
||||
* @return object The managed copy of the entity.
|
||||
*
|
||||
* @throws ORMInvalidArgumentException
|
||||
* @throws ORMException
|
||||
*/
|
||||
public function merge($entity)
|
||||
{
|
||||
if ( ! is_object($entity)) {
|
||||
throw ORMInvalidArgumentException::invalidObject('EntityManager#merge()', $entity);
|
||||
throw ORMInvalidArgumentException::invalidObject('EntityManager#merge()' , $entity);
|
||||
}
|
||||
|
||||
$this->errorIfClosed();
|
||||
@@ -706,7 +691,7 @@ use Throwable;
|
||||
*
|
||||
* @param string $entityName The name of the entity.
|
||||
*
|
||||
* @return \Doctrine\Common\Persistence\ObjectRepository|\Doctrine\ORM\EntityRepository The repository class.
|
||||
* @return \Doctrine\ORM\EntityRepository The repository class.
|
||||
*/
|
||||
public function getRepository($entityName)
|
||||
{
|
||||
@@ -830,59 +815,39 @@ use Throwable;
|
||||
/**
|
||||
* Factory method to create EntityManager instances.
|
||||
*
|
||||
* @param array|Connection $connection An array with the connection parameters or an existing Connection instance.
|
||||
* @param Configuration $config The Configuration instance to use.
|
||||
* @param EventManager $eventManager The EventManager instance to use.
|
||||
* @param mixed $conn An array with the connection parameters or an existing Connection instance.
|
||||
* @param Configuration $config The Configuration instance to use.
|
||||
* @param EventManager $eventManager The EventManager instance to use.
|
||||
*
|
||||
* @return EntityManager The created EntityManager.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws ORMException
|
||||
*/
|
||||
public static function create($connection, Configuration $config, EventManager $eventManager = null)
|
||||
public static function create($conn, Configuration $config, EventManager $eventManager = null)
|
||||
{
|
||||
if ( ! $config->getMetadataDriverImpl()) {
|
||||
throw ORMException::missingMappingDriverImpl();
|
||||
}
|
||||
|
||||
$connection = static::createConnection($connection, $config, $eventManager);
|
||||
switch (true) {
|
||||
case (is_array($conn)):
|
||||
$conn = \Doctrine\DBAL\DriverManager::getConnection(
|
||||
$conn, $config, ($eventManager ?: new EventManager())
|
||||
);
|
||||
break;
|
||||
|
||||
return new EntityManager($connection, $config, $connection->getEventManager());
|
||||
}
|
||||
case ($conn instanceof Connection):
|
||||
if ($eventManager !== null && $conn->getEventManager() !== $eventManager) {
|
||||
throw ORMException::mismatchedEventManager();
|
||||
}
|
||||
break;
|
||||
|
||||
/**
|
||||
* Factory method to create Connection instances.
|
||||
*
|
||||
* @param array|Connection $connection An array with the connection parameters or an existing Connection instance.
|
||||
* @param Configuration $config The Configuration instance to use.
|
||||
* @param EventManager $eventManager The EventManager instance to use.
|
||||
*
|
||||
* @return Connection
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws ORMException
|
||||
*/
|
||||
protected static function createConnection($connection, Configuration $config, EventManager $eventManager = null)
|
||||
{
|
||||
if (is_array($connection)) {
|
||||
return DriverManager::getConnection($connection, $config, $eventManager ?: new EventManager());
|
||||
default:
|
||||
throw new \InvalidArgumentException("Invalid argument: " . $conn);
|
||||
}
|
||||
|
||||
if ( ! $connection instanceof Connection) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf(
|
||||
'Invalid $connection argument of type %s given%s.',
|
||||
is_object($connection) ? get_class($connection) : gettype($connection),
|
||||
is_object($connection) ? '' : ': "' . $connection . '"'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ($eventManager !== null && $connection->getEventManager() !== $eventManager) {
|
||||
throw ORMException::mismatchedEventManager();
|
||||
}
|
||||
|
||||
return $connection;
|
||||
return new EntityManager($conn, $config, $conn->getEventManager());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -912,26 +877,4 @@ use Throwable;
|
||||
{
|
||||
return null !== $this->filterCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $lockMode
|
||||
* @param ClassMetadata $class
|
||||
* @throws OptimisticLockException
|
||||
* @throws TransactionRequiredException
|
||||
*/
|
||||
private function checkLockRequirements(int $lockMode, ClassMetadata $class): void
|
||||
{
|
||||
switch ($lockMode) {
|
||||
case LockMode::OPTIMISTIC:
|
||||
if (!$class->isVersioned) {
|
||||
throw OptimisticLockException::notVersioned($class->name);
|
||||
}
|
||||
break;
|
||||
case LockMode::PESSIMISTIC_READ:
|
||||
case LockMode::PESSIMISTIC_WRITE:
|
||||
if (!$this->getConnection()->isTransactionActive()) {
|
||||
throw TransactionRequiredException::transactionRequired();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION); HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE); ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
@@ -150,7 +150,7 @@ interface EntityManagerInterface extends ObjectManager
|
||||
* @param string $entityName The name of the entity type.
|
||||
* @param mixed $id The entity identifier.
|
||||
*
|
||||
* @return object|null The entity reference.
|
||||
* @return object The entity reference.
|
||||
*
|
||||
* @throws ORMException
|
||||
*/
|
||||
@@ -174,7 +174,7 @@ interface EntityManagerInterface extends ObjectManager
|
||||
* @param string $entityName The name of the entity type.
|
||||
* @param mixed $identifier The entity identifier.
|
||||
*
|
||||
* @return object|null The (partial) entity reference.
|
||||
* @return object The (partial) entity reference.
|
||||
*/
|
||||
public function getPartialReference($entityName, $identifier);
|
||||
|
||||
@@ -249,7 +249,7 @@ interface EntityManagerInterface extends ObjectManager
|
||||
*
|
||||
* @deprecated
|
||||
*
|
||||
* @param string|int $hydrationMode
|
||||
* @param int $hydrationMode
|
||||
*
|
||||
* @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator
|
||||
*/
|
||||
@@ -258,7 +258,7 @@ interface EntityManagerInterface extends ObjectManager
|
||||
/**
|
||||
* Create a new instance for the given hydration mode.
|
||||
*
|
||||
* @param string|int $hydrationMode
|
||||
* @param int $hydrationMode
|
||||
*
|
||||
* @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator
|
||||
*
|
||||
|
||||
@@ -37,7 +37,7 @@ class EntityNotFoundException extends ORMException
|
||||
*/
|
||||
public static function fromClassNameAndIdentifier($className, array $id)
|
||||
{
|
||||
$ids = [];
|
||||
$ids = array();
|
||||
|
||||
foreach ($id as $key => $value) {
|
||||
$ids[] = $key . '(' . $value . ')';
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use Doctrine\Common\Inflector\Inflector;
|
||||
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
||||
use Doctrine\Common\Persistence\ObjectRepository;
|
||||
use Doctrine\Common\Collections\Selectable;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
/**
|
||||
* An EntityRepository serves as a repository for entities with generic as well as
|
||||
@@ -61,7 +61,7 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
* @param EntityManager $em The EntityManager to use.
|
||||
* @param Mapping\ClassMetadata $class The class descriptor.
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $em, Mapping\ClassMetadata $class)
|
||||
public function __construct($em, Mapping\ClassMetadata $class)
|
||||
{
|
||||
$this->_entityName = $class->name;
|
||||
$this->_em = $em;
|
||||
@@ -161,7 +161,7 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
*/
|
||||
public function findAll()
|
||||
{
|
||||
return $this->findBy([]);
|
||||
return $this->findBy(array());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,7 +184,7 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
/**
|
||||
* Finds a single entity by a set of criteria.
|
||||
*
|
||||
* @param array $criteria
|
||||
* @param array $criteria
|
||||
* @param array|null $orderBy
|
||||
*
|
||||
* @return object|null The entity instance or NULL if the entity can not be found.
|
||||
@@ -193,52 +193,68 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
{
|
||||
$persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
|
||||
|
||||
return $persister->load($criteria, null, null, [], null, 1, $orderBy);
|
||||
return $persister->load($criteria, null, null, array(), null, 1, $orderBy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts entities by a set of criteria.
|
||||
*
|
||||
* @todo Add this method to `ObjectRepository` interface in the next major release
|
||||
*
|
||||
* @param array $criteria
|
||||
*
|
||||
* @return int The cardinality of the objects that match the given criteria.
|
||||
*/
|
||||
public function count(array $criteria)
|
||||
{
|
||||
return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->count($criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds support for magic method calls.
|
||||
* Adds support for magic finders.
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $arguments
|
||||
*
|
||||
* @return mixed The returned value from the resolved method.
|
||||
* @return array|object The found entity/entities.
|
||||
*
|
||||
* @throws ORMException
|
||||
* @throws \BadMethodCallException If the method called is invalid
|
||||
* @throws \BadMethodCallException If the method called is an invalid find* method
|
||||
* or no find* method at all and therefore an invalid
|
||||
* method call.
|
||||
*/
|
||||
public function __call($method, $arguments)
|
||||
{
|
||||
if (0 === strpos($method, 'findBy')) {
|
||||
return $this->resolveMagicCall('findBy', substr($method, 6), $arguments);
|
||||
switch (true) {
|
||||
case (0 === strpos($method, 'findBy')):
|
||||
$by = substr($method, 6);
|
||||
$method = 'findBy';
|
||||
break;
|
||||
|
||||
case (0 === strpos($method, 'findOneBy')):
|
||||
$by = substr($method, 9);
|
||||
$method = 'findOneBy';
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \BadMethodCallException(
|
||||
"Undefined method '$method'. The method name must start with ".
|
||||
"either findBy or findOneBy!"
|
||||
);
|
||||
}
|
||||
|
||||
if (0 === strpos($method, 'findOneBy')) {
|
||||
return $this->resolveMagicCall('findOneBy', substr($method, 9), $arguments);
|
||||
if (empty($arguments)) {
|
||||
throw ORMException::findByRequiresParameter($method . $by);
|
||||
}
|
||||
|
||||
if (0 === strpos($method, 'countBy')) {
|
||||
return $this->resolveMagicCall('count', substr($method, 7), $arguments);
|
||||
$fieldName = lcfirst(\Doctrine\Common\Util\Inflector::classify($by));
|
||||
|
||||
if ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName)) {
|
||||
switch (count($arguments)) {
|
||||
case 1:
|
||||
return $this->$method(array($fieldName => $arguments[0]));
|
||||
|
||||
case 2:
|
||||
return $this->$method(array($fieldName => $arguments[0]), $arguments[1]);
|
||||
|
||||
case 3:
|
||||
return $this->$method(array($fieldName => $arguments[0]), $arguments[1], $arguments[2]);
|
||||
|
||||
case 4:
|
||||
return $this->$method(array($fieldName => $arguments[0]), $arguments[1], $arguments[2], $arguments[3]);
|
||||
|
||||
default:
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
throw new \BadMethodCallException(
|
||||
"Undefined method '$method'. The method name must start with ".
|
||||
"either findBy, findOneBy or countBy!"
|
||||
);
|
||||
throw ORMException::invalidFindByCall($this->_entityName, $fieldName, $method.$by);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -287,30 +303,4 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
|
||||
return new LazyCriteriaCollection($persister, $criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a magic method call to the proper existent method at `EntityRepository`.
|
||||
*
|
||||
* @param string $method The method to call
|
||||
* @param string $by The property name used as condition
|
||||
* @param array $arguments The arguments to pass at method call
|
||||
*
|
||||
* @throws ORMException If the method called is invalid or the requested field/association does not exist
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function resolveMagicCall($method, $by, array $arguments)
|
||||
{
|
||||
if (! $arguments) {
|
||||
throw ORMException::findByRequiresParameter($method . $by);
|
||||
}
|
||||
|
||||
$fieldName = lcfirst(Inflector::classify($by));
|
||||
|
||||
if (! ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName))) {
|
||||
throw ORMException::invalidMagicCall($this->_entityName, $fieldName, $method . $by);
|
||||
}
|
||||
|
||||
return $this->$method([$fieldName => $arguments[0]], ...array_slice($arguments, 1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Event;
|
||||
|
||||
|
||||
@@ -62,8 +62,8 @@ class ListenersInvoker
|
||||
/**
|
||||
* Get the subscribed event systems
|
||||
*
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
|
||||
* @param string $eventName The entity lifecycle event.
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
|
||||
* @param string $eventName The entity lifecycle event.
|
||||
*
|
||||
* @return integer Bitmask of subscribed event systems.
|
||||
*/
|
||||
@@ -89,21 +89,21 @@ class ListenersInvoker
|
||||
/**
|
||||
* Dispatches the lifecycle event of the given entity.
|
||||
*
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
|
||||
* @param string $eventName The entity lifecycle event.
|
||||
* @param object $entity The Entity on which the event occurred.
|
||||
* @param \Doctrine\Common\EventArgs $event The Event args.
|
||||
* @param integer $invoke Bitmask to invoke listeners.
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
|
||||
* @param string $eventName The entity lifecycle event.
|
||||
* @param object $entity The Entity on which the event occurred.
|
||||
* @param \Doctrine\Common\EventArgs $event The Event args.
|
||||
* @param integer $invoke Bitmask to invoke listeners.
|
||||
*/
|
||||
public function invoke(ClassMetadata $metadata, $eventName, $entity, EventArgs $event, $invoke)
|
||||
{
|
||||
if ($invoke & self::INVOKE_CALLBACKS) {
|
||||
if($invoke & self::INVOKE_CALLBACKS) {
|
||||
foreach ($metadata->lifecycleCallbacks[$eventName] as $callback) {
|
||||
$entity->$callback($event);
|
||||
}
|
||||
}
|
||||
|
||||
if ($invoke & self::INVOKE_LISTENERS) {
|
||||
if($invoke & self::INVOKE_LISTENERS) {
|
||||
foreach ($metadata->entityListeners[$eventName] as $listener) {
|
||||
$class = $listener['class'];
|
||||
$method = $listener['method'];
|
||||
@@ -113,8 +113,8 @@ class ListenersInvoker
|
||||
}
|
||||
}
|
||||
|
||||
if ($invoke & self::INVOKE_MANAGER) {
|
||||
if($invoke & self::INVOKE_MANAGER) {
|
||||
$this->eventManager->dispatchEvent($eventName, $event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ use Doctrine\Common\Persistence\Event\LoadClassMetadataEventArgs as BaseLoadClas
|
||||
* Note: method annotations are used instead of method overrides (due to BC policy)
|
||||
*
|
||||
* @method __construct(\Doctrine\ORM\Mapping\ClassMetadata $classMetadata, \Doctrine\ORM\EntityManager $objectManager)
|
||||
* @method \Doctrine\ORM\Mapping\ClassMetadata getClassMetadata()
|
||||
* @method \Doctrine\ORM\EntityManager getClassMetadata()
|
||||
*/
|
||||
class LoadClassMetadataEventArgs extends BaseLoadClassMetadataEventArgs
|
||||
{
|
||||
|
||||
@@ -26,8 +26,8 @@ abstract class AbstractIdGenerator
|
||||
/**
|
||||
* Generates an identifier for an entity.
|
||||
*
|
||||
* @param EntityManager $em
|
||||
* @param object|null $entity
|
||||
* @param EntityManager|EntityManager $em
|
||||
* @param \Doctrine\ORM\Mapping\Entity $entity
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function generate(EntityManager $em, $entity);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user