Compare commits

..

85 Commits

Author SHA1 Message Date
Marco Pivetta
d9fc5388f1 2.5.3 release 2015-12-25 16:50:05 +01:00
Marco Pivetta
d2e51eacff Reverting incorrect DBAL 2.6 bump 2015-12-25 16:49:48 +01:00
Marco Pivetta
5a6ae4686f Allowing doctrine/common 2.6 2015-12-25 15:58:40 +01:00
Marco Pivetta
8b8a1cbe81 Merge branch 'hotfix/common-2.6-upgrade-compat-2.5' into 2.5 2015-12-25 15:26:15 +01:00
Marco Pivetta
27a5284899 doctrine/common 2.6.0 compat
Less strict assertion - no need to check the exact file name
2015-12-25 15:24:37 +01:00
Marco Pivetta
0086d17afe Common 2.6 compatibility
Internal structure of the ArrayCache has changed, therefore we should fix the tests depending on it instead
2015-12-25 15:24:32 +01:00
Marco Pivetta
ab62167c8a Merge branch 'hotfix/#4884-support-proxy-php7-hints-generation-2.5' into 2.5
Close #4884
2015-12-25 14:47:12 +01:00
Marco Pivetta
6d43195669 #4884 - allow installation of doctrine/common 2.6.x, which allows generating type-hints on proxies 2015-12-25 14:46:59 +01:00
Marco Pivetta
7065ff0ac9 Merge branch 'hotfix/#1572-target-entity-resolver-dql-with-interfaces-support' into 2.5
Close #1572
2015-12-11 21:35:18 +01:00
Marco Pivetta
aa61328e90 #1572 - test coverage - interfaces should also resolve to target entities when in DQL 2015-12-11 21:30:19 +01:00
Marco Pivetta
62719f2a97 Merge branch 'hotfix/#1573-merge-associated-versioned-entity-2.5.x' into 2.5
Close #1573
2015-12-11 20:19:10 +01:00
Marco Pivetta
66770c5bfe #1573 - correcting test asset namespace, removing unused properties and bi-directional association 2015-12-11 20:18:53 +01:00
Marco Pivetta
42691c21b4 Removing empty newline 2015-12-11 20:18:48 +01:00
Marco Pivetta
596e895763 #1573 - correcting docblock arguments/description 2015-12-11 20:18:42 +01:00
Marco Pivetta
d5c82094df #1573 removing unused API 2015-12-11 20:18:37 +01:00
bilouwan
4148220f9c Refactor testing Proxy not initilized 2015-12-11 20:18:31 +01:00
bilouwan
e173c930ec Fix superflous whitespaces & empty lines 2015-12-11 20:18:25 +01:00
bilouwan
7071984559 Fix compatibility with php5.4 2015-12-11 20:18:19 +01:00
bilouwan
216c466233 Unit test & fix for merge versionned entity 2015-12-11 20:18:12 +01:00
Marco Pivetta
65f5777e60 Merge branch 'hotfix/php7-xdebug-incompatibility-fixes-2.5.x' into 2.5
Close #5547
2015-12-11 19:35:42 +01:00
Marco Pivetta
6e3ce26429 Correcting minor test case incompatibility with XDebug 2.4.x
In PHP 5.x + XDebug < 2.4, the output would be "string:..."
In PHP 7.x + XDebug >= 2.4, the output would be "the/file/name.php:11:string:..."

This is an improvement in XDebug that is quite annoying for our purposes, but is actually welcome to most users anyway.

This commit simply fixes that incompatibility
2015-12-11 19:35:28 +01:00
oprokidnev
752d4f9eac Target entity resolver for DQL
Since we have target entity resolver in doctrine this class check is not enought.
To gain interface resolution it is better to add interface check in addition to class_check here.
2015-11-27 16:00:56 +05:00
Marco Pivetta
2983081a60 Bumping current dev version to 2.5.3-DEV 2015-11-23 13:44:56 +01:00
Marco Pivetta
464b5fdbfb Release 2.5.2 2015-11-23 13:44:25 +01:00
Guilherme Blanco
04254a8e34 Merge pull request #1512 from neoglez/2.5
Backport of "LimitSubqueryOutputWalker: fix aliasing of property in OrderBy from MappedSuperclass"
2015-11-15 22:08:59 -05:00
Guilherme Blanco
1d213e6733 Merge pull request #1543 from nicolas-grekas/dep-30
Allow symfony 3.0 components on 2.5
2015-11-09 14:54:44 -05:00
Klein Thomas
bc82e94afc Move to 2.5 section 2015-11-09 03:56:16 +00:00
Klein Thomas
b9af1c8fa5 Update Upgrade.md after minor bc break in 2.5.1
The introduction of the second parameter in EntityRepository#createQueryBuilder generates a runtime notice if you have a sub-class of EntityRepository that has a second parameter in the createQueryBuilder method
2015-11-09 03:56:07 +00:00
Pantel
d606efd4eb [DDC-3711] add Test that check if the association key are composite 2015-11-09 03:46:06 +00:00
Pantel
581e1638a2 [DDC-3711] add Tests that check if the association key are composite 2015-11-09 03:45:56 +00:00
Pantel
17ae8d1b2d [DDC-3711] Correct Error on manyToMany with composite primary key 2015-11-09 03:45:46 +00:00
Guilherme Blanco
3a058f8522 Merge branch 'hotfix/#1375-prevent-duplicate-unique-index' into 2.5
Backported #1375 to 2.5
2015-11-07 16:47:26 +00:00
Michał Bundyra
567220ef71 prevent duplicate unique index 2015-11-07 16:46:22 +00:00
Marco Pivetta
39098ce415 Merge branch 'hotfix/#1507-fixed-wrong-property-name-in-resultset-mapping-builder' into 2.5
Backport merge #1507 into 2.5.x
Close #1507
2015-11-07 10:35:30 -05:00
François-Xavier de Guillebon
1eb9c8a7f6 Added test 2015-11-07 10:35:08 -05:00
François-Xavier de Guillebon
f2f53ba9dc Fixed wrong variable used as array key 2015-11-07 10:35:02 -05:00
François-Xavier de Guillebon
ebbc443ec3 Fixed wrong property name 2015-11-07 10:34:55 -05:00
Nicolas Grekas
16802d2614 Allow symfony 3.0 components
Tests should tell if any deprecated interfaces of Symfony are used. If not, then the bundle is defacto compatible with 3.0
2015-11-05 11:43:20 +01:00
neoglez
ef73249bc7 Entity to test a mapped superclass
Backport of https://github.com/doctrine/doctrine2/pull/1377/files to branch 2.5
2015-09-21 09:32:54 +02:00
neoglez
ed637e51b9 Backport of "fix aliasing of property in OrderBy from MappedSuperclass"
Backport of "LimitSubqueryOutputWalker: fix aliasing of property in OrderBy from MappedSuperclass" ( e501137d1a )
See my comment on a3ece3b419
2015-09-21 08:58:30 +02:00
neoglez
a3ece3b419 UnitTest backport of "Failing test case for broken paginator case"
UnitTest backport of "Failing test case for broken paginator case" ( 192da14842 ).
The branch 2.x is very important because it's related to ZF2 doctrine module (see https://github.com/doctrine/DoctrineORMModule/blob/master/composer.json ) and specially this issue affects the use case of extending the ZF2 user entity defined in ZfcUser ( https://github.com/ZF-Commons/ZfcUser ).
This test is meant to show the need of the backport of e501137d1a
2015-09-21 08:52:46 +02:00
Marco Pivetta
2d1bc78749 Merge branch 'hotfix/#1510-DDC-3908-fix-cache-related-tests-in-2.5' into 2.5
Close #1510
2015-09-19 10:45:24 +02:00
Matthias Pigulla
eaf8b1c7ca Fix tests related to caches, as per doctrine/cache 1.5.0 changes
Backports #1510
Fixes DDC-3908

dd47003641 removes the 'DoctrineNamespaceCacheKey[]' entry from the cache. Thus, all tests counting cache entries were off by one.
2015-09-19 10:44:47 +02:00
Benjamin Eberlei
8070b50150 Bump version to 2.5.2 2015-08-31 14:59:39 +02:00
Benjamin Eberlei
e6a83bedbe Release 2.5.1 2015-08-31 14:59:39 +02:00
Benjamin Eberlei
b6e5464b98 Fix version 2015-08-31 14:59:36 +02:00
Benjamin Eberlei
6366d190d7 [DCOM-293] Fix security misconfiguration vulnerability allowing local remote arbitrary code execution. 2015-08-31 14:58:12 +02:00
Bill Schaller
89eed31e79 Merge pull request #1463 from ehimen/paginate-order-by-subselect
Fixed issue when paginator orders by a subselect expression
Conflicts:
	tests/Doctrine/Tests/ORM/Tools/Pagination/LimitSubqueryOutputWalkerTest.php
2015-08-04 14:31:08 -04:00
Marco Pivetta
4ca00f7a9d Merge branch 'hotfix/#1387-DDC-3699-do-not-merge-managed-uninitialized-entities-2.5' into 2.5
Close #1387
2015-07-15 21:52:41 +01:00
Marco Pivetta
6bc405455e DDC-3699 - #1387 - leveraging the OrmFunctionalTestCase API 2015-07-15 21:52:25 +01:00
Marco Pivetta
173729e560 DDC-3699 - #1387 - catching specific exceptions 2015-07-15 21:52:20 +01:00
Marco Pivetta
86abbb0e78 DDC-3699 - #1387 - simpifying tests, clarifying on test method names 2015-07-15 21:52:16 +01:00
Lenard Palko
69ef75ff2d Added test cases for both one-to-one and one-to-many cases. 2015-07-15 21:52:08 +01:00
Lenard Palko
c68edec0c2 Fix skipping properties if they are listed after a not loaded relation. 2015-07-15 21:51:55 +01:00
Marco Pivetta
f9bbd953a7 Merge branch 'hotfix/#1381-wakeup-reflection-with-embeddable-and-staticreflection-serialization-fix-2.5' into 2.5
Close #1381
2015-07-15 20:50:28 +01:00
Nico Vogelaar
9097014c3d Fixes ClassMetadata wakeupReflection with embeddable and StaticReflectionService 2015-07-15 20:50:05 +01:00
Marco Pivetta
12d178777a Merge branch 'hotfix/#1380-non-cache-persister-bug-2.5' into 2.5
Close #1380
2015-07-15 20:45:38 +01:00
Marco Pivetta
ed1c4de2b6 DDC-3683 - #1380 - reverting BC break, annotating correct types, cs fixes 2015-07-15 20:45:02 +01:00
Darien Hager
fff56c7f3f Remove runtime assertion 2015-07-15 20:44:55 +01:00
Darien Hager
97e90ddefc Clarify state-changes, replace array_key_exists() with isset() for speed 2015-07-15 20:44:50 +01:00
Darien Hager
d5adda954d Whitespace formatting tweaks 2015-07-15 20:44:39 +01:00
Darien Hager
c507b52f20 Remove now-superfluous EntityManager check 2015-07-15 20:44:33 +01:00
Darien Hager
08be905fc3 Refactor LoadClassMetadataEventArgs to ensure it contains an EntityManager 2015-07-15 20:44:24 +01:00
Darien Hager
d29cc3660f Change the test listener than layers on second-level-caching so that it is more conservative, only turning on caching-associations when it knows the target entity is cache-able. 2015-07-15 20:44:10 +01:00
Darien Hager
768c291cd1 Stumbled across a bug where signatures didn't match, but also the current persister-type didn't support getCacheRegion(). Unsure of exact mechanism, but clearly the constructor doesn't take the second argument anyway, may be old code. 2015-07-15 20:43:51 +01:00
Bill Schaller
10ed690d99 Backport Merge pull request #1430 from michael-lavaveshkul/master
"INSTANCE OF" example doesn't match description.
2015-06-18 10:41:56 -04:00
Benjamin Eberlei
e4e59d8064 Merge branch 'DDC-3756' into 2.5 2015-06-16 21:44:14 +02:00
Restless-ET
0e208f7538 [2.5][Bug] Fix ConvertDoctrine1Schema->getMetadata
This bug was introduced at #1205 while resolving #1200.
2015-06-16 21:43:54 +02:00
Guilherme Blanco
3e6c6af845 Merge pull request #1382 from holtkamp/patch-second-level-cache-association-hydration
Patch second level cache association hydration
2015-04-14 11:57:54 -04:00
Marco Pivetta
584345397b Merge branch 'hotfix/#1374-fix-ddc-767-test-php7-pg94-2.5' into 2.5 2015-04-06 04:23:17 +01:00
Matteo Beccati
786791b8fe Fix DDC767Test failing on php7 + pg94
The failure happens when running the full suite or even just:

phpunit tests/Doctrine/Tests/ORM/Functional/Ticket
2015-04-06 04:23:03 +01:00
Marco Pivetta
724dfa0de3 Merge branch 'hotfix/#1361-persistent-collection-collection-hint-fix' into 2.5 2015-04-05 01:30:32 +01:00
Marco Pivetta
39592ba59c Correcting ObjectHydrator logic: if an array is a default value for a collection-valued property, it should be cast to a Collection 2015-04-05 01:29:59 +01:00
Marco Pivetta
58a6013d15 Correcting static introspection issue in cache specific tests (null was being passed to a PersistentCollection) 2015-04-05 01:29:52 +01:00
Marco Pivetta
4580429616 Removing irrelevant tests (as per discussion with @guilhermeblanco and @stof 2015-04-05 01:29:42 +01:00
Marco Pivetta
6e563a313e a PersistentCollection should only allow another collection as a wrapped collection 2015-04-05 01:29:35 +01:00
Marco Pivetta
e8c9cb2f23 Reverting BC break: PersistentConnection#__construct() now accepts null|array|Collection again 2015-04-05 01:29:28 +01:00
Marco Pivetta
4792b4f974 FQCN reference (class was not imported correctly) 2015-04-05 01:29:19 +01:00
Marco Pivetta
c2f6b09ee0 PersistentCollection should still accept null and array as constructor argument, as it did before 2015-04-05 01:29:08 +01:00
Marco Pivetta
cb3179865b Minor docblock correction (discovered during testing) 2015-04-05 01:29:00 +01:00
Marco Pivetta
a1602bd91f Hydration of fetch-joined results fails when an entity has a default value of array for the collection property 2015-04-05 01:28:53 +01:00
Marco Pivetta
34696126e0 Merge branch 'hotfix/#1365-query-dql-function-test-determinism-2.5' into 2.5 2015-04-05 00:10:25 +01:00
Bill Schaller
aa4b2e59ce fix rare query test failures due to nondeterminism without order by clause 2015-04-05 00:10:11 +01:00
Marco Pivetta
2cd86deeb4 Merge branch 'hotfix/#1360-docs-fix-misleading-embeddable-documentation-prefix' into 2.5 2015-04-02 23:25:11 +01:00
Marco Pivetta
52288abf48 Merge branch 'docs/#1359-correcting-mapping-in-working-with-objects-reference' into 2.5 2015-04-02 21:56:03 +01:00
1045 changed files with 18356 additions and 37404 deletions

4
.coveralls.yml Normal file
View File

@@ -0,0 +1,4 @@
# for php-coveralls
service_name: travis-ci
src_dir: lib
coverage_clover: build/logs/clover*.xml

1
.gitattributes vendored
View File

@@ -10,4 +10,3 @@ build.xml export-ignore
CONTRIBUTING.md export-ignore
phpunit.xml.dist export-ignore
run-all.sh export-ignore
phpcs.xml.dist export-ignore

4
.gitignore vendored
View File

@@ -10,7 +10,5 @@ lib/Doctrine/DBAL
.buildpath
.project
.idea
*.iml
vendor/
/tests/Doctrine/Performance/history.db
/.phpcs-cache
composer.lock

View File

@@ -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

View File

@@ -1,132 +1,41 @@
dist: trusty
sudo: false
language: php
php:
- 7.1
- 7.2
- 7.3
- 7.4snapshot
- nightly
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
- hhvm-nightly
env:
- DB=sqlite
- DB=mysql
- DB=pgsql
- DB=sqlite
before_install:
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{,.disabled} || echo "xdebug not available"
before_script:
- if [[ $TRAVIS_PHP_VERSION = '5.6' && $DB = 'sqlite' ]]; then PHPUNIT_FLAGS="--coverage-clover ./build/logs/clover.xml"; else PHPUNIT_FLAGS=""; fi
- if [[ $TRAVIS_PHP_VERSION != '5.6' && $TRAVIS_PHP_VERSION != 'hhvm' && $TRAVIS_PHP_VERSION != 'hhvm-nightly' && $TRAVIS_PHP_VERSION != '7.0' ]]; then phpenv config-rm xdebug.ini; fi
- composer self-update
install: travis_retry composer validate --strict && composer update --prefer-dist
- composer install --prefer-source --dev
script:
- if [[ "$DB" == "mysql" || "$DB" == "mariadb" ]]; then mysql -e "CREATE SCHEMA doctrine_tests; GRANT ALL PRIVILEGES ON doctrine_tests.* to travis@'%'"; fi
- ENABLE_SECOND_LEVEL_CACHE=0 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml
- ENABLE_SECOND_LEVEL_CACHE=0 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml $PHPUNIT_FLAGS
- ENABLE_SECOND_LEVEL_CACHE=1 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml --exclude-group performance,non-cacheable,locking_functional
jobs:
include:
- stage: Test
env: DB=mariadb
addons:
mariadb: 10.1
- stage: Test
dist: xenial
env: DB=mysql MYSQL_VERSION=5.7
php: 7.1
services:
- mysql
before_script:
- ./tests/travis/install-mysql-$MYSQL_VERSION.sh
sudo: required
- stage: Test
dist: xenial
env: DB=mysql MYSQL_VERSION=5.7
php: 7.2
services:
- mysql
before_script:
- ./tests/travis/install-mysql-$MYSQL_VERSION.sh
sudo: required
- stage: Test
dist: xenial
env: DB=mysql MYSQL_VERSION=5.7
php: 7.4snapshot
services:
- mysql
before_script:
- ./tests/travis/install-mysql-$MYSQL_VERSION.sh
sudo: required
- stage: Test
env: DB=sqlite DEPENDENCIES=low
install: travis_retry composer update --prefer-dist --prefer-lowest
- stage: Test
if: type = cron
php: 7.3
env: DB=sqlite DEV_DEPENDENCIES
install:
- composer config minimum-stability dev
- travis_retry composer update --prefer-dist
- stage: Test
env: DB=sqlite COVERAGE
before_script:
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{.disabled,}
- if [[ ! $(php -m | grep -si xdebug) ]]; then echo "xdebug required for coverage"; exit 1; fi
script:
- ENABLE_SECOND_LEVEL_CACHE=0 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml --coverage-clover ./build/logs/clover.xml
after_script:
- wget https://scrutinizer-ci.com/ocular.phar
- php ocular.phar code-coverage:upload --format=php-clover build/logs/clover.xml
- stage: Code Quality
env: DB=none STATIC_ANALYSIS
install: travis_retry composer install --prefer-dist
before_script:
- echo "extension=memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
- echo "extension=redis.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
- travis_retry composer require --dev --prefer-dist --prefer-stable phpstan/phpstan:^0.9
script: vendor/bin/phpstan analyse -l 1 -c phpstan.neon lib
- stage: Code Quality
env: DB=none BENCHMARK
before_script: wget https://phpbench.github.io/phpbench/phpbench.phar https://phpbench.github.io/phpbench/phpbench.phar.pubkey
script: php phpbench.phar run -l dots --report=default
- stage: Code Quality
if: NOT type = pull_request
env: DB=none CODING_STANDARDS
php: 7.4snapshot
install: travis_retry composer install --prefer-dist
script:
- ./vendor/bin/phpcs
- stage: Code Quality
if: type = pull_request
env: DB=none PULL_REQUEST_CODING_STANDARDS
php: 7.1
install: travis_retry composer install --prefer-dist
script:
- |
if [ $TRAVIS_BRANCH != "master" ]; then
git remote set-branches --add origin $TRAVIS_BRANCH;
git fetch origin $TRAVIS_BRANCH;
fi
- git merge-base origin/$TRAVIS_BRANCH $TRAVIS_PULL_REQUEST_SHA || git fetch origin +refs/pull/$TRAVIS_PULL_REQUEST/merge --unshallow
- wget https://github.com/diff-sniffer/git/releases/download/0.2.0/git-phpcs.phar
- php git-phpcs.phar origin/$TRAVIS_BRANCH...$TRAVIS_PULL_REQUEST_SHA
after_script:
- php vendor/bin/coveralls -v
matrix:
exclude:
- php: hhvm
env: DB=pgsql # driver currently unsupported by HHVM
- php: hhvm
env: DB=mysqli # driver currently unsupported by HHVM
- php: hhvm-nightly
env: DB=pgsql # driver currently unsupported by HHVM
- php: hhvm-nightly
env: DB=mysqli # driver currently unsupported by HHVM
allow_failures:
- php: nightly
- stage: Code Quality
env: DB=none CODING_STANDARDS
cache:
directories:
- $HOME/.composer/cache
- php: 7.0
- php: hhvm-nightly # hhvm-nightly currently chokes on composer installation

View File

@@ -41,32 +41,19 @@ Please try to add a test for your pull-request.
* If you want to contribute new functionality add unit- or functional tests
depending on the scope of the feature.
You can run the unit-tests by calling ``vendor/bin/phpunit`` from the root of the project.
You can run the unit-tests by calling ``phpunit`` from the root of the project.
It will run all the tests with an in memory SQLite database.
In order to do that, you will need a fresh copy of the ORM, and you
will have to run a composer installation in the project:
```sh
git clone git@github.com:doctrine/orm.git
cd orm
curl -sS https://getcomposer.org/installer | php --
./composer.phar install
```
To run the testsuite against another database, copy the ``phpunit.xml.dist``
to for example ``mysql.phpunit.xml`` and edit the parameters. You can
take a look at the ``tests/travis`` folder for some examples. Then run:
vendor/bin/phpunit -c mysql.phpunit.xml
If you do not provide these parameters, the test suite will use an in-memory
sqlite database.
phpunit -c mysql.phpunit.xml
Tips for creating unit tests:
Tips for creating unittests:
1. If you put a test into the `Ticket` namespace as described above, put the testcase and all entities into the same class.
See `https://github.com/doctrine/orm/tree/master/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2306Test.php` for an
See `https://github.com/doctrine/doctrine2/tree/master/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2306Test.php` for an
example.
## Travis
@@ -75,6 +62,13 @@ We automatically run your pull request through [Travis CI](http://www.travis-ci.
against SQLite, MySQL and PostgreSQL. If you break the tests, we cannot merge your code,
so please make sure that your code is working before opening up a Pull-Request.
## DoctrineBot, Tickets and Jira
DoctrineBot will synchronize your Pull-Request into our [Jira](http://www.doctrine-project.org).
Make sure to add any existing Jira ticket into the Pull-Request Title, for example:
"[DDC-123] My Pull Request"
## Getting merged
Please allow us time to review your pull requests. We will give our best to review

View File

@@ -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
View 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

View File

@@ -1,26 +0,0 @@
| [Master][Master] | [2.5][2.5] |
|:----------------:|:----------:|
| [![Build status][Master image]][Master] | [![Build status][2.5 image]][2.5] |
| [![Coverage Status][Master coverage image]][Master coverage] | [![Coverage Status][2.5 coverage image]][2.5 coverage] |
Doctrine 2 is an object-relational mapper (ORM) for PHP 7.1+ that provides transparent persistence
for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features
is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL),
inspired by Hibernate's HQL. This provides developers with a powerful alternative to SQL that maintains flexibility
without requiring unnecessary code duplication.
## More resources:
* [Website](http://www.doctrine-project.org)
* [Documentation](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/index.html)
[Master image]: https://img.shields.io/travis/doctrine/orm/master.svg?style=flat-square
[Master]: https://travis-ci.org/doctrine/orm
[Master coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/orm/master.svg?style=flat-square
[Master coverage]: https://scrutinizer-ci.com/g/doctrine/orm/?branch=master
[2.5 image]: https://img.shields.io/travis/doctrine/orm/2.5.svg?style=flat-square
[2.5]: https://github.com/doctrine/orm/tree/2.5
[2.5 coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/orm/2.5.svg?style=flat-square
[2.5 coverage]: https://scrutinizer-ci.com/g/doctrine/orm/?branch=2.5

View File

@@ -11,7 +11,7 @@ Please read the documentation chapter on Security in Doctrine DBAL and ORM to
understand the assumptions we make.
- [DBAL Security Page](https://github.com/doctrine/dbal/blob/master/docs/en/reference/security.rst)
- [ORM Security Page](https://github.com/doctrine/orm/blob/master/docs/en/reference/security.rst)
- [ORM Security Page](https://github.com/doctrine/doctrine2/blob/master/docs/en/reference/security.rst)
If you find a Security bug in Doctrine, please report it on Jira and change the
Security Level to "Security Issues". It will be visible to Doctrine Core

View File

@@ -1,195 +1,5 @@
# Upgrade to 2.7
## Added `Doctrine\ORM\AbstractQuery#enableResultCache()` and `Doctrine\ORM\AbstractQuery#disableResultCache()` methods
Method `Doctrine\ORM\AbstractQuery#useResultCache()` which could be used for both enabling and disabling the cache
(depending on passed flag) was split into two.
## Minor BC BREAK: paginator output walkers aren't be called anymore on sub-queries for queries without max results
To optimize DB interaction, `Doctrine\ORM\Tools\Pagination\Paginator` no longer fetches identifiers to be able to
perform the pagination with join collections when max results isn't set in the query.
## Deprecated number unaware `Doctrine\ORM\Mapping\UnderscoreNamingStrategy`
In the last patch of the `v2.6.x` series, we fixed a bug that was not converting names properly when they had numbers
(e.g.: `base64Encoded` was wrongly converted to `base64encoded` instead of `base64_encoded`).
In order to not break BC we've introduced a way to enable the fixed behavior using a boolean constructor argument. This
argument will be removed in 3.0 and the default behavior will be the fixed one.
## Deprecated: `Doctrine\ORM\AbstractQuery#useResultCache()`
Method `Doctrine\ORM\AbstractQuery#useResultCache()` is deprecated because it is split into `enableResultCache()`
and `disableResultCache()`. It will be removed in 3.0.
## Deprecated code generators and related console commands
These console commands have been deprecated:
* `orm:convert-mapping`
* `orm:generate:entities`
* `orm:generate-repositories`
These classes have been deprecated:
* `Doctrine\ORM\Tools\EntityGenerator`
* `Doctrine\ORM\Tools\EntityRepositoryGenerator`
Whole Doctrine\ORM\Tools\Export namespace with all its members have been deprecated as well.
## Deprecated `Doctrine\ORM\Proxy\Proxy` marker interface
Proxy objects in Doctrine ORM 3.0 will no longer implement `Doctrine\ORM\Proxy\Proxy` nor
`Doctrine\Common\Persistence\Proxy`: instead, they implement
`ProxyManager\Proxy\GhostObjectInterface`.
These related classes have been deprecated:
* `Doctrine\ORM\Proxy\ProxyFactory`
* `Doctrine\ORM\Proxy\Autoloader` - we suggest using the composer autoloader instead
These methods have been deprecated:
* `Doctrine\ORM\Configuration#getAutoGenerateProxyClasses()`
* `Doctrine\ORM\Configuration#getProxyDir()`
* `Doctrine\ORM\Configuration#getProxyNamespace()`
## Deprecated `Doctrine\ORM\Version`
The `Doctrine\ORM\Version` class is now deprecated and will be removed in Doctrine ORM 3.0:
please refrain from checking the ORM version at runtime or use
[ocramius/package-versions](https://github.com/Ocramius/PackageVersions/).
## Deprecated `EntityManager#merge()` and `EntityManager#detach()` methods
Merge and detach semantics were a poor fit for the PHP "share-nothing" architecture.
In addition to that, merging/detaching caused multiple issues with data integrity
in the managed entity graph, which was constantly spawning more edge-case bugs/scenarios.
The following API methods were therefore deprecated:
* `EntityManager#merge()`
* `EntityManager#detach()`
* `UnitOfWork#merge()`
* `UnitOfWork#detach()`
Users are encouraged to migrate `EntityManager#detach()` calls to `EntityManager#clear()`.
In order to maintain performance on batch processing jobs, it is endorsed to enable
the second level cache (http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/second-level-cache.html)
on entities that are frequently reused across multiple `EntityManager#clear()` calls.
An alternative to `EntityManager#merge()` will not be provided by ORM 3.0, since the merging
semantics should be part of the business domain rather than the persistence domain of an
application. If your application relies heavily on CRUD-alike interactions and/or `PATCH`
restful operations, you should look at alternatives such as [JMSSerializer](https://github.com/schmittjoh/serializer).
## Extending `EntityManager` is deprecated
Final keyword will be added to the `EntityManager::class` in Doctrine ORM 3.0 in order to ensure that EntityManager
is not used as valid extension point. Valid extension point should be EntityManagerInterface.
## Deprecated `EntityManager#clear($entityName)`
If your code relies on clearing a single entity type via `EntityManager#clear($entityName)`,
the signature has been changed to `EntityManager#clear()`.
The main reason is that partial clears caused multiple issues with data integrity
in the managed entity graph, which was constantly spawning more edge-case bugs/scenarios.
## Deprecated `EntityManager#flush($entity)` and `EntityManager#flush($entities)`
If your code relies on single entity flushing optimisations via
`EntityManager#flush($entity)`, the signature has been changed to
`EntityManager#flush()`.
Said API was affected by multiple data integrity bugs due to the fact
that change tracking was being restricted upon a subset of the managed
entities. The ORM cannot support committing subsets of the managed
entities while also guaranteeing data integrity, therefore this
utility was removed.
The `flush()` semantics will remain the same, but the change tracking will be performed
on all entities managed by the unit of work, and not just on the provided
`$entity` or `$entities`, as the parameter is now completely ignored.
The same applies to `UnitOfWork#commit($entity)`, which will simply be
`UnitOfWork#commit()`.
If you would still like to perform batching operations over small `UnitOfWork`
instances, it is suggested to follow these paths instead:
* eagerly use `EntityManager#clear()` in conjunction with a specific second level
cache configuration (see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/second-level-cache.html)
* use an explicit change tracking policy (see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/change-tracking-policies.html)
## Deprecated `YAML` mapping drivers.
If your code relies on `YamlDriver` or `SimpleYamlDriver`, you **MUST** change to
annotation or XML drivers instead.
## Deprecated: `Doctrine\ORM\EntityManagerInterface#copy()`
Method `Doctrine\ORM\EntityManagerInterface#copy()` never got its implementation and is deprecated.
It will be removed in 3.0.
# Upgrade to 2.6
## Added `Doctrine\ORM\EntityRepository::count()` method
`Doctrine\ORM\EntityRepository::count()` has been added. This new method has different
signature than `Countable::count()` (required parameter) and therefore are not compatible.
If your repository implemented the `Countable` interface, you will have to use
`$repository->count([])` instead and not implement `Countable` interface anymore.
## Minor BC BREAK: `Doctrine\ORM\Tools\Console\ConsoleRunner` is now final
Since it's just an utilitarian class and should not be inherited.
## Minor BC BREAK: removed `Doctrine\ORM\Query\QueryException::associationPathInverseSideNotSupported()`
Method `Doctrine\ORM\Query\QueryException::associationPathInverseSideNotSupported()`
now has a required parameter `$pathExpr`.
## Minor BC BREAK: removed `Doctrine\ORM\Query\Parser#isInternalFunction()`
Method `Doctrine\ORM\Query\Parser#isInternalFunction()` was removed because
the distinction between internal function and user defined DQL was removed.
[#6500](https://github.com/doctrine/orm/pull/6500)
## Minor BC BREAK: removed `Doctrine\ORM\ORMException#overwriteInternalDQLFunctionNotAllowed()`
Method `Doctrine\ORM\Query\Parser#overwriteInternalDQLFunctionNotAllowed()` was
removed because of the choice to allow users to overwrite internal functions, ie
`AVG`, `SUM`, `COUNT`, `MIN` and `MAX`. [#6500](https://github.com/doctrine/orm/pull/6500)
## PHP 7.1 is now required
Doctrine 2.6 now requires PHP 7.1 or newer.
As a consequence, automatic cache setup in Doctrine\ORM\Tools\Setup::create*Configuration() was changed:
- APCu extension (ext-apcu) will now be used instead of abandoned APC (ext-apc).
- Memcached extension (ext-memcached) will be used instead of obsolete Memcache (ext-memcache).
- XCache support was dropped as it doesn't work with PHP 7.
# Upgrade to 2.5
## Minor BC BREAK: removed `Doctrine\ORM\Query\SqlWalker#walkCaseExpression()`
Method `Doctrine\ORM\Query\SqlWalker#walkCaseExpression()` was unused and part
of the internal API of the ORM, so it was removed. [#5600](https://github.com/doctrine/orm/pull/5600).
## Minor BC BREAK: removed $className parameter on `AbstractEntityInheritancePersister#getSelectJoinColumnSQL()`
As `$className` parameter was not used in the method, it was safely removed.
## Minor BC BREAK: query cache key time is now a float
As of 2.5.5, the `QueryCacheEntry#time` property will contain a float value
instead of an integer in order to have more precision and also to be consistent
with the `TimestampCacheEntry#time`.
## Minor BC BREAK: discriminator map must now include all non-transient classes
It is now required that you declare the root of an inheritance in the

View File

@@ -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
View 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);

View File

@@ -3,55 +3,46 @@
"type": "library",
"description": "Object-Relational-Mapper for PHP",
"keywords": ["orm", "database"],
"homepage": "https://www.doctrine-project.org/projects/orm.html",
"homepage": "http://www.doctrine-project.org",
"license": "MIT",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"},
{"name": "Marco Pivetta", "email": "ocramius@gmail.com"}
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"}
],
"config": {
"sort-packages": true
},
"minimum-stability": "dev",
"require": {
"php": "^7.1",
"php": ">=5.4",
"ext-pdo": "*",
"doctrine/annotations": "^1.8",
"doctrine/cache": "^1.9.1",
"doctrine/collections": "^1.5",
"doctrine/common": "^2.11",
"doctrine/dbal": "^2.9.3",
"doctrine/event-manager": "^1.1",
"doctrine/instantiator": "^1.3",
"doctrine/persistence": "^1.2",
"symfony/console": "^3.0|^4.0|^5.0"
"doctrine/collections": "~1.2",
"doctrine/dbal": ">=2.5-dev,<2.6-dev",
"doctrine/instantiator": "~1.0.1",
"doctrine/common": ">=2.5-dev,<2.7-dev",
"doctrine/cache": "~1.4",
"symfony/console": "~2.5|~3.0"
},
"require-dev": {
"doctrine/coding-standard": "^5.0",
"phpunit/phpunit": "^7.5",
"symfony/yaml": "^3.4|^4.0|^5.0"
"symfony/yaml": "~2.3|~3.0",
"phpunit/phpunit": "~4.0",
"satooshi/php-coveralls": "dev-master"
},
"suggest": {
"symfony/yaml": "If you want to use YAML Metadata Mapping Driver"
},
"autoload": {
"psr-4": { "Doctrine\\ORM\\": "lib/Doctrine/ORM" }
"psr-0": { "Doctrine\\ORM\\": "lib/" }
},
"autoload-dev": {
"psr-4": {
"Doctrine\\Tests\\": "tests/Doctrine/Tests",
"Doctrine\\Performance\\": "tests/Doctrine/Performance"
}
"psr-0": { "Doctrine\\Tests\\": "tests/" }
},
"bin": ["bin/doctrine"],
"bin": ["bin/doctrine", "bin/doctrine.php"],
"extra": {
"branch-alias": {
"dev-master": "2.7.x-dev"
"dev-master": "2.6.x-dev"
}
},
"archive": {
"exclude": ["!vendor", "tests", "*phpunit.xml", ".travis.yml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp"]
"exclude": ["!vendor", "tests", "*phpunit.xml", ".travis.yml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp", "*coveralls.yml"]
}
}

2744
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -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.

View File

@@ -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

View File

@@ -0,0 +1,710 @@
What is new in Doctrine ORM 2.5?
================================
This document describes changes between Doctrine ORM 2.4 and 2.5 (currently in
Beta). It contains a description of all the new features and sections
about behavioral changes and potential backwards compatibility breaks.
Please review this document carefully when updating to Doctrine 2.5.
First note, that with the ORM 2.5 release we are dropping support
for PHP 5.3. We are enforcing this with Composer, servers without
at least PHP 5.4 will not allow installing Doctrine 2.5.
New Features and Improvements
-----------------------------
Events: PostLoad now triggered after associations are loaded
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Before Doctrine 2.5 if you had an entity with a ``@PostLoad`` event
defined then Doctrine would trigger listeners after the fields were
loaded, but before assocations are available.
- `DDC-54 <http://doctrine-project.org/jira/browse/DDC-54>`_
- `Commit <https://github.com/doctrine/doctrine2/commit/a906295c65f1516737458fbee2f6fa96254f27a5>`_
Events: Add API to programatically add event listeners to Entity
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When developing third party libraries or decoupled applications
it can be interesting to develop an entity listener without knowing
the entities that require this listener.
You can now attach entity listeners to entities using the
``AttachEntityListenersListener`` class, which is listening to the
``loadMetadata`` event that is fired once for every entity during
metadata generation:
.. code-block:: php
<?php
use Doctrine\ORM\Tools\AttachEntityListenersListener;
use Doctrine\ORM\Events;
$listener = new AttachEntityListenersListener();
$listener->addEntityListener(
'MyProject\Entity\User', 'MyProject\Listener\TimestampableListener',
Events::prePersist, 'onPrePersist'
);
$evm->addEventListener(Events::loadClassMetadata, $listener);
class TimestampableListener
{
public function onPrePersist($event)
{
$entity = $event->getEntity();
$entity->setCreated(new \DateTime('now'));
}
}
Embeddedable Objects
~~~~~~~~~~~~~~~~~~~~
Doctrine now supports creating multiple PHP objects from one database table
implementing a feature called "Embeddedable Objects". Next to an ``@Entity``
class you can now define a class that is embeddable into a database table of an
entity using the ``@Embeddable`` annotation. Embeddable objects can never be
saved, updated or deleted on their own, only as part of an entity (called
"root-entity" or "aggregate"). Consequently embeddables don't have a primary
key, they are identified only by their values.
Example of defining and using embeddables classes:
.. code-block:: php
<?php
/** @Entity */
class Product
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @Embedded(class = "Money") */
private $price;
}
/** @Embeddable */
class Money
{
/** @Column(type = "decimal") */
private $value;
/** @Column(type = "string") */
private $currency = 'EUR';
}
You can read more on the features of Embeddables objects `in the documentation
<http://docs.doctrine-project.org/en/latest/tutorials/embeddables.html>`_.
This feature was developed by external contributor `Johannes Schmitt
<https://twitter.com/schmittjoh>`_
- `DDC-93 <http://doctrine-project.org/jira/browse/DDC-93>`_
- `Pull Request <https://github.com/doctrine/doctrine2/pull/835>`_
Second-Level-Cache
~~~~~~~~~~~~~~~~~~
Since version 2.0 of Doctrine, fetching the same object twice by primary key
would result in just one query. This was achieved by the identity map pattern
(first-level-cache) that kept entities in memory.
The newly introduced second-level-cache works a bit differently. Instead
of saving objects in memory, it saves them in a fast in-memory cache such
as Memcache, Redis, Riak or MongoDB. Additionally it allows saving the result
of more complex queries than by primary key. Summarized this feature works
like the existing Query result cache, but it is much more powerful.
As an example lets cache an entity Country that is a relation to the User
entity. We always want to display the country, but avoid the additional
query to this table.
.. code-block:: php
<?php
/**
* @Entity
* @Cache(usage="READ_ONLY", region="country_region")
*/
class Country
{
/**
* @Id
* @GeneratedValue
* @Column(type="integer")
*/
protected $id;
/**
* @Column(unique=true)
*/
protected $name;
}
In this example we have specified a caching region name called
``country_region``, which we have to configure now on the EntityManager:
.. code-block:: php
$config = new \Doctrine\ORM\Configuration();
$config->setSecondLevelCacheEnabled();
$cacheConfig = $config->getSecondLevelCacheConfiguration();
$regionConfig = $cacheConfig->getRegionsConfiguration();
$regionConfig->setLifetime('country_region', 3600);
Now Doctrine will first check for the data of any country in the cache
instead of the database.
- `Documentation
<http://docs.doctrine-project.org/en/latest/reference/second-level-cache.html>`_
- `Pull Request <https://github.com/doctrine/doctrine2/pull/808>`_
Criteria API: Support for ManyToMany assocations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We introduced support for querying collections using the `Criteria API
<http://docs.doctrine-project.org/en/latest/reference/working-with-associations.html#filtering-collections>`_
in 2.4. This only worked efficently for One-To-Many assocations, not for
Many-To-Many. With the start of 2.5 also Many-To-Many associations get queried
instead of loading them into memory.
Criteria API: Add new contains() expression
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is now possible to use the Criteria API to check for string contains needle
using ``contains()``. This translates to using a ``column LIKE '%needle%'`` SQL
condition.
.. code-block:: php
<?php
use \Doctrine\Common\Collections\Criteria;
$criteria = Criteria::create()
->where(Criteria::expr()->contains('name', 'Benjamin'));
$users = $repository->matching($criteria);
Criteria API: Support for EXTRA_LAZY
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A collection that is marked as ``fetch="EXTRA_LAZY"`` will now return another
lazy collection when using ``Collection::matching($criteria)``:
.. code-block:: php
<?php
class Post
{
/** @OneToMany(targetEntity="Comment", fetch="EXTRA_LAZY") */
private $comments;
}
$criteria = Criteria::create()
->where(Criteria->expr()->eq("published", 1));
$publishedComments = $post->getComments()->matching($criteria);
echo count($publishedComments);
The lazy criteria currently supports the ``count()`` and ``contains()``
functionality lazily. All other operations of the ``Collection`` interface
trigger a full load of the collection.
This feature was contributed by `Michaël Gallego <https://github.com/bakura10>`_.
- `Pull Request #1 <https://github.com/doctrine/doctrine2/pull/882>`_
- `Pull Request #2 <https://github.com/doctrine/doctrine2/pull/1032>`_
Mapping: Allow configuring Index flags
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is now possible to control the index flags in the DBAL
schema abstraction from the ORM using metadata. This was possible
only with a schema event listener before.
.. code-block:: php
<?php
/**
* @Table(name="product", indexes={@Index(columns={"description"},flags={"fulltext"})})
*/
class Product
{
private $description;
}
This feature was contributed by `Adrian Olek <https://github.com/adrianolek>`_.
- `Pull Request <https://github.com/doctrine/doctrine2/pull/973>`_
SQLFilter API: Check if a parameter is set
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can now check in your SQLFilter if a parameter was set. This allows
to more easily control which features of a filter to enable or disable.
Extending on the locale example of the documentation:
.. code-block:: php
<?php
class MyLocaleFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
{
if (!$targetEntity->reflClass->implementsInterface('LocaleAware')) {
return "";
}
if (!$this->hasParameter('locale')) {
return "";
}
return $targetTableAlias.'.locale = ' . $this->getParameter('locale');
}
}
This feature was contributed by `Miroslav Demovic <https://github.com/mdemo>`_
- `Pull Request <https://github.com/doctrine/doctrine2/pull/963>`_
EXTRA_LAZY Improvements
~~~~~~~~~~~~~~~~~~~~~~~
1. Efficient query when using EXTRA_LAZY and containsKey
When calling ``Collection::containsKey($key)`` on one-to-many and many-to-many
collections using ``indexBy`` and ``EXTRA_LAZY`` a query is now executed to check
for the existance for the item. Prevoiusly this operation was performed in memory
by loading all entities of the collection.
.. code-block:: php
<?php
class User
{
/** @OneToMany(targetEntity="Group", indexBy="id") */
private $groups;
}
if ($user->getGroups()->containsKey($groupId)) {
echo "User is in group $groupId\n";
}
This feature was contributed by `Asmir Mustafic <https://github.com/goetas>`_
- `Pull Request <https://github.com/doctrine/doctrine2/pull/937>`_
2. Add EXTRA_LAZY Support for get() for owning and inverse many-to-many
This was contributed by `Sander Marechal <https://github.com/sandermarechal>`_.
Improve efficiency of One-To-Many EAGER
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When marking a one-to-many association with ``fetch="EAGER"`` it will now
execute one query less than before and work correctly in combination with
``indexBy``.
Better support for EntityManagerInterface
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Many of the locations where previously only the ``Doctrine\ORM\EntityManager``
was allowed are now changed to accept the ``EntityManagerInterface`` that was
introduced in 2.4. This allows you to more easily use the decorator pattern
to extend the EntityManager if you need. It's still not replaced everywhere,
so you still have to be careful.
DQL Improvements
~~~~~~~~~~~~~~~~
1. It is now possible to add functions to the ``ORDER BY`` clause in DQL statements:
.. code-block:: php
<?php
$dql = "SELECT u FROM User u ORDER BY CONCAT(u.username, u.name)";
2. Support for functions in ``IS NULL`` expressions:
.. code-block:: php
<?php
$dql = "SELECT u.name FROM User u WHERE MAX(u.name) IS NULL";
3. A ``LIKE`` expression is now suported in ``HAVING`` clause.
4. Subselects are now supported inside a ``NEW()`` expression:
.. code-block:: php
<?php
$dql = "SELECT new UserDTO(u.name, SELECT count(g.id) FROM Group g WHERE g.id = u.id) FROM User u";
5. ``MEMBER OF`` expression now allows to filter for more than one result:
.. code-block:: php
<?php
$dql = "SELECT u FROM User u WHERE :groups MEMBER OF u.groups";
$query = $entityManager->createQuery($dql);
$query->setParameter('groups', array(1, 2, 3));
$users = $query->getResult();
6. Expressions inside ``COUNT()`` now allowed
.. code-block:: php
<?php
$dql = "SELECT COUNT(DISTINCT CONCAT(u.name, u.lastname)) FROM User u";
7. Add support for ``HOUR`` in ``DATE_ADD()``/``DATE_SUB()`` functions
Custom DQL Functions: Add support for factories
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Previously custom DQL functions could only be provided with their
full-qualified class-name, preventing runtime configuration through
dependency injection.
A simplistic approach has been contributed by `Matthieu Napoli
<https://github.com/mnapoli>`_ to pass a callback instead that resolves
the function:
.. code-block:: php
<?php
$config = new \Doctrine\ORM\Configuration();
$config->addCustomNumericFunction(
'IS_PUBLISHED', function($funcName) use ($currentSiteId) {
return new IsPublishedFunction($currentSiteId);
}
);
Query API: WHERE IN Query using a Collection as parameter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When performing a ``WHERE IN`` query for a collection of entities you can
now pass the array collection of entities as a parameter value to the query
object:
.. code-block:: php
<?php
$categories = $rootCategory->getChildren();
$queryBuilder
->select('p')
->from('Product', 'p')
->where('p.category IN (:categories)')
->setParameter('categories', $categories)
;
This feature was contributed by `Michael Perrin
<https://github.com/michaelperrin>`_.
- `Pull Request <https://github.com/doctrine/doctrine2/pull/590>`_
- `DDC-2319 <http://doctrine-project.org/jira/browse/DDC-2319>`_
Query API: Add suport for default Query Hints
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To configure multiple different features such as custom AST Walker, fetch modes,
locking and other features affecting DQL generation we have had a feature
called "query hints" since version 2.0.
It is now possible to add query hints that are always enabled for every Query:
.. code-block:: php
<?php
$config = new \Doctrine\ORM\Configuration();
$config->setDefaultQueryHints(
'doctrine.customOutputWalker' => 'MyProject\CustomOutputWalker'
);
This feature was contributed by `Artur Eshenbrener
<https://github.com/Strate>`_.
- `Pull Request <https://github.com/doctrine/doctrine2/pull/863>`_
ResultSetMappingBuilder: Add support for Single-Table Inheritance
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Before 2.5 the ResultSetMappingBuilder did not work with entities
that are using Single-Table-Inheritance. This restriction was lifted
by adding the missing support.
YAML Mapping: Many-To-Many doesnt require join column definition
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In Annotations and XML it was not necessary using conventions for naming
the many-to-many join column names, in YAML it was not possible however.
A many-to-many definition in YAML is now possible using this minimal
definition:
.. code-block:: yaml
manyToMany:
groups:
targetEntity: Group
joinTable:
name: users_groups
Schema Validator Command: Allow to skip sub-checks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The Schema Validator command executes two independent checks
for validity of the mappings and if the schema is synchronized
correctly. It is now possible to skip any of the two steps
when executing the command:
::
$ php vendor/bin/doctrine orm:validate-schema --skip-mapping
$ php vendor/bin/doctrine orm:validate-schema --skip-sync
This allows you to write more specialized continuous integration and automation
checks. When no changes are found the command returns the exit code 0
and 1, 2 or 3 when failing because of mapping, sync or both.
EntityGenerator Command: Avoid backups
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When calling the EntityGenerator for an existing entity, Doctrine would
create a backup file every time to avoid loosing changes to the code.
You can now skip generating the backup file by passing the ``--no-backup``
flag:
::
$ php vendor/bin/doctrine orm:generate-entities src/ --no-backup
Support for Objects as Identifiers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is now possible to use Objects as identifiers for Entities
as long as they implement the magic method ``__toString()``.
.. code-block:: php
<?php
class UserId
{
private $value;
public function __construct($value)
{
$this->value = $value;
}
public function __toString()
{
return (string)$this->value;
}
}
class User
{
/** @Id @Column(type="userid") */
private $id;
public function __construct(UserId $id)
{
$this->id = $id;
}
}
class UserIdType extends \Doctrine\DBAL\Types\Type
{
// ...
}
Doctrine\DBAL\Types\Type::addType('userid', 'MyProject\UserIdType');
Behavioral Changes (BC Breaks)
------------------------------
NamingStrategy interface changed
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``Doctrine\ORM\Mapping\NamingStrategyInterface`` changed slightly
to pass the Class Name of the entity into the join column name generation:
::
- function joinColumnName($propertyName);
+ function joinColumnName($propertyName, $className = null);
It also received a new method for supporting embeddables:
::
public function embeddedFieldToColumnName($propertyName, $embeddedColumnName);
Minor BC BREAK: EntityManagerInterface instead of EntityManager in type-hints
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As of 2.5, classes requiring the ``EntityManager`` in any method signature will now require
an ``EntityManagerInterface`` instead.
If you are extending any of the following classes, then you need to check following
signatures:
- ``Doctrine\ORM\Tools\DebugUnitOfWorkListener#dumpIdentityMap(EntityManagerInterface $em)``
- ``Doctrine\ORM\Mapping\ClassMetadataFactory#setEntityManager(EntityManagerInterface $em)``
Minor BC BREAK: Custom Hydrators API change
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As of 2.5, ``AbstractHydrator`` does not enforce the usage of cache as part of
API, and now provides you a clean API for column information through the method
``hydrateColumnInfo($column)``.
Cache variable being passed around by reference is no longer needed since
Hydrators are per query instantiated since Doctrine 2.4.
- `DDC-3060 <http://doctrine-project.org/jira/browse/DDC-3060>`_
Minor BC BREAK: All non-transient classes in an inheritance must be part of the inheritance map
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As of 2.5, classes, if you define an inheritance map for an inheritance tree, you are required
to map all non-transient classes in that inheritance, including the root of the inheritance.
So far, the root of the inheritance was allowed to be skipped in the inheritance map: this is
not possible anymore, and if you don't plan to persist instances of that class, then you should
either:
- make that class as ``abstract``
- add that class to your inheritance map
If you fail to do so, then a ``Doctrine\ORM\Mapping\MappingException`` will be thrown.
- `DDC-3300 <http://doctrine-project.org/jira/browse/DDC-3300>`_
- `DDC-3503 <http://doctrine-project.org/jira/browse/DDC-3503>`_
Minor BC BREAK: Entity based EntityManager#clear() calls follow cascade detach
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Whenever ``EntityManager#clear()`` method gets called with a given entity class
name, until 2.4, it was only detaching the specific requested entity.
As of 2.5, ``EntityManager`` will follow configured cascades, providing a better
memory management since associations will be garbage collected, optimizing
resources consumption on long running jobs.
Updates on entities scheduled for deletion are no longer processed
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In Doctrine 2.4, if you modified properties of an entity scheduled for deletion, UnitOfWork would
produce an ``UPDATE`` statement to be executed right before the ``DELETE`` statement. The entity in question
was therefore present in ``UnitOfWork#entityUpdates``, which means that ``preUpdate`` and ``postUpdate``
listeners were (quite pointlessly) called. In ``preFlush`` listeners, it used to be possible to undo
the scheduled deletion for updated entities (by calling ``persist()`` if the entity was found in both
``entityUpdates`` and ``entityDeletions``). This does not work any longer, because the entire changeset
calculation logic is optimized away.
Minor BC BREAK: Default lock mode changed from LockMode::NONE to null in method signatures
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A misconception concerning default lock mode values in method signatures lead to unexpected behaviour
in SQL statements on SQL Server. With a default lock mode of ``LockMode::NONE`` throughout the
method signatures in ORM, the table lock hint ``WITH (NOLOCK)`` was appended to all locking related
queries by default. This could result in unpredictable results because an explicit ``WITH (NOLOCK)``
table hint tells SQL Server to run a specific query in transaction isolation level READ UNCOMMITTED
instead of the default READ COMMITTED transaction isolation level.
Therefore there now is a distinction between ``LockMode::NONE`` and ``null`` to be able to tell
Doctrine whether to add table lock hints to queries by intention or not. To achieve this, the following
method signatures have been changed to declare ``$lockMode = null`` instead of ``$lockMode = LockMode::NONE``:
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#getSelectSQL()``
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#load()``
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#refresh()``
- ``Doctrine\ORM\Decorator\EntityManagerDecorator#find()``
- ``Doctrine\ORM\EntityManager#find()``
- ``Doctrine\ORM\EntityRepository#find()``
- ``Doctrine\ORM\Persisters\BasicEntityPersister#getSelectSQL()``
- ``Doctrine\ORM\Persisters\BasicEntityPersister#load()``
- ``Doctrine\ORM\Persisters\BasicEntityPersister#refresh()``
- ``Doctrine\ORM\Persisters\EntityPersister#getSelectSQL()``
- ``Doctrine\ORM\Persisters\EntityPersister#load()``
- ``Doctrine\ORM\Persisters\EntityPersister#refresh()``
- ``Doctrine\ORM\Persisters\JoinedSubclassPersister#getSelectSQL()``
You should update signatures for these methods if you have subclassed one of the above classes.
Please also check the calling code of these methods in your application and update if necessary.
.. note::
This in fact is really a minor BC BREAK and should not have any affect on database vendors
other than SQL Server because it is the only one that supports and therefore cares about
``LockMode::NONE``. It's really just a FIX for SQL Server environments using ORM.
Minor BC BREAK: __clone method not called anymore when entities are instantiated via metadata API
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As of PHP 5.6, instantiation of new entities is deferred to the
`doctrine/instantiator <https://github.com/doctrine/instantiator>`_ library, which will avoid calling ``__clone``
or any public API on instantiated objects.
BC BREAK: DefaultRepositoryFactory is now final
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Please implement the ``Doctrine\ORM\Repository\RepositoryFactory`` interface instead of extending
the ``Doctrine\ORM\Repository\DefaultRepositoryFactory``.
BC BREAK: New object expression DQL queries now respects user provided aliasing and not return consumed fields
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When executing DQL queries with new object expressions, instead of returning
DTOs numerically indexes, it will now respect user provided aliases. Consider
the following query:
::
SELECT new UserDTO(u.id,u.name) as user,new AddressDTO(a.street,a.postalCode) as address, a.id as addressId
FROM User u INNER JOIN u.addresses a WITH a.isPrimary = true
Previously, your result would be similar to this:
::
array(
0=>array(
0=>{UserDTO object},
1=>{AddressDTO object},
2=>{u.id scalar},
3=>{u.name scalar},
4=>{a.street scalar},
5=>{a.postalCode scalar},
'addressId'=>{a.id scalar},
),
...
)
From now on, the resultset will look like this:
::
array(
0=>array(
'user'=>{UserDTO object},
'address'=>{AddressDTO object},
'addressId'=>{a.id scalar}
),
...
)

View File

@@ -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

View File

@@ -11,9 +11,9 @@ There are several ways to achieve this: converting the value inside the Type
class, converting the value on the database-level or a combination of both.
This article describes the third way by implementing the MySQL specific column
type `Point <https://dev.mysql.com/doc/refman/8.0/en/gis-class-point.html>`_.
type `Point <http://dev.mysql.com/doc/refman/5.5/en/gis-class-point.html>`_.
The ``Point`` type is part of the `Spatial extension <https://dev.mysql.com/doc/refman/8.0/en/spatial-extensions.html>`_
The ``Point`` type is part of the `Spatial extension <http://dev.mysql.com/doc/refman/5.5/en/spatial-extensions.html>`_
of MySQL and enables you to store a single location in a coordinate space by
using x and y coordinates. You can use the Point type to store a
longitude/latitude pair to represent a geographic location.
@@ -192,9 +192,9 @@ object into a string representation before saving to the database (in the
``convertToDatabaseValue`` method) and back into an object after fetching the
value from the database (in the ``convertToPHPValue`` method).
The format of the string representation format is called
`Well-known text (WKT) <http://en.wikipedia.org/wiki/Well-known_text>`_.
The advantage of this format is, that it is both human readable and parsable by MySQL.
The format of the string representation format is called `Well-known text (WKT)
<http://en.wikipedia.org/wiki/Well-known_text>`_. The advantage of this format
is, that it is both human readable and parsable by MySQL.
Internally, MySQL stores geometry values in a binary format that is not
identical to the WKT format. So, we need to let MySQL transform the WKT
@@ -204,8 +204,8 @@ This is where the ``convertToPHPValueSQL`` and ``convertToDatabaseValueSQL``
methods come into play.
This methods wrap a sql expression (the WKT representation of the Point) into
MySQL functions `ST_PointFromText <https://dev.mysql.com/doc/refman/8.0/en/gis-wkt-functions.html#function_st-pointfromtext>`_
and `ST_AsText <https://dev.mysql.com/doc/refman/8.0/en/gis-format-conversion-functions.html#function_st-astext>`_
MySQL functions `PointFromText <http://dev.mysql.com/doc/refman/5.5/en/creating-spatial-values.html#function_pointfromtext>`_
and `AsText <http://dev.mysql.com/doc/refman/5.5/en/functions-to-convert-geometries-between-formats.html#function_astext>`_
which convert WKT strings to and from the internal format of MySQL.
.. note::
@@ -249,7 +249,7 @@ Example usage
$em->clear();
// Fetch the Location object
$query = $em->createQuery("SELECT l FROM Geo\Entity\Location l WHERE l.address = '1600 Amphitheatre Parkway, Mountain View, CA'");
$query = $em->createQuery("SELECT l FROM Geo\Entity\Location WHERE l.address = '1600 Amphitheatre Parkway, Mountain View, CA'");
$location = $query->getSingleResult();
/* @var Geo\ValueObject\Point */

View File

@@ -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.

View File

@@ -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

View File

@@ -21,7 +21,7 @@ the :doc:`Native Query <../reference/native-sql>` chapter.
The DQL Parser has hooks to register functions that can then be
used in your DQL queries and transformed into SQL, allowing to
extend Doctrines Query capabilities to the vendors strength. This
post explains the User-Defined Functions API (UDF) of the Dql
post explains the Used-Defined Functions API (UDF) of the Dql
Parser and shows some examples to give you some hints how you would
extend DQL.
@@ -70,7 +70,7 @@ methods, which are quite handy in my opinion:
Date Diff
---------
`Mysql's DateDiff function <https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_datediff>`_
`Mysql's DateDiff function <http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_datediff>`_
takes two dates as argument and calculates the difference in days
with ``date1-date2``.
@@ -132,7 +132,7 @@ dql statement.
The ``ArithmeticPrimary`` method call is the most common
denominator of valid EBNF tokens taken from the
`DQL EBNF grammar <http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html#ebnf>`_
`DQL EBNF grammar <http://www.doctrine-project.org/documentation/manual/2_0/en/dql-doctrine-query-language#ebnf>`_
that matches our requirements for valid input into the DateDiff Dql
function. Picking the right tokens for your methods is a tricky
business, but the EBNF grammar is pretty helpful finding it, as is
@@ -164,7 +164,7 @@ Date Add
Often useful it the ability to do some simple date calculations in
your DQL query using
`MySql's DATE_ADD function <https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_date-add>`_.
`MySql's DATE\_ADD function <http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date-add>`_.
I'll skip the blah and show the code for this function:
@@ -246,6 +246,6 @@ vendor sql functions and extend the DQL languages scope.
Code for this Extension to DQL and other Doctrine Extensions can be
found
`in the GitHub DoctrineExtensions repository <http://github.com/beberlei/DoctrineExtensions>`_.
`in my Github DoctrineExtensions repository <http://github.com/beberlei/DoctrineExtensions>`_.

View File

@@ -4,15 +4,15 @@ Implementing Wakeup or Clone
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
As explained in the
`restrictions for entity classes in the manual <http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/architecture.html#entities>`_,
`restrictions for entity classes in the manual <http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities>`_,
it is usually not allowed for an entity to implement ``__wakeup``
or ``__clone``, because Doctrine makes special use of them.
However, it is quite easy to make use of these methods in a safe
way by guarding the custom wakeup or clone code with an entity
identity check, as demonstrated in the following sections.
Safely implementing __wakeup
----------------------------
Safely implementing \_\_wakeup
------------------------------
To safely implement ``__wakeup``, simply enclose your
implementation code in an identity check as follows:
@@ -37,8 +37,8 @@ implementation code in an identity check as follows:
//...
}
Safely implementing __clone
---------------------------
Safely implementing \_\_clone
-----------------------------
Safely implementing ``__clone`` is pretty much the same:

View File

@@ -0,0 +1,140 @@
Integrating with CodeIgniter
============================
This is recipe for using Doctrine 2 in your
`CodeIgniter <http://www.codeigniter.com>`_ framework.
.. note::
This might not work for all CodeIgniter versions and may require
slight adjustments.
Here is how to set it up:
Make a CodeIgniter library that is both a wrapper and a bootstrap
for Doctrine 2.
Setting up the file structure
-----------------------------
Here are the steps:
- Add a php file to your system/application/libraries folder
called Doctrine.php. This is going to be your wrapper/bootstrap for
the D2 entity manager.
- Put the Doctrine folder (the one that contains Common, DBAL, and
ORM) inside that same libraries folder.
- Your system/application/libraries folder now looks like this:
system/applications/libraries -Doctrine -Doctrine.php -index.html
- If you want, open your config/autoload.php file and autoload
your Doctrine library.
<?php $autoload['libraries'] = array('doctrine');
Creating your Doctrine CodeIgniter library
------------------------------------------
Now, here is what your Doctrine.php file should look like.
Customize it to your needs.
.. code-block:: php
<?php
use Doctrine\Common\ClassLoader,
Doctrine\ORM\Configuration,
Doctrine\ORM\EntityManager,
Doctrine\Common\Cache\ArrayCache,
Doctrine\DBAL\Logging\EchoSQLLogger;
class Doctrine {
public $em = null;
public function __construct()
{
// load database configuration from CodeIgniter
require_once APPPATH.'config/database.php';
// Set up class loading. You could use different autoloaders, provided by your favorite framework,
// if you want to.
require_once APPPATH.'libraries/Doctrine/Common/ClassLoader.php';
$doctrineClassLoader = new ClassLoader('Doctrine', APPPATH.'libraries');
$doctrineClassLoader->register();
$entitiesClassLoader = new ClassLoader('models', rtrim(APPPATH, "/" ));
$entitiesClassLoader->register();
$proxiesClassLoader = new ClassLoader('Proxies', APPPATH.'models/proxies');
$proxiesClassLoader->register();
// Set up caches
$config = new Configuration;
$cache = new ArrayCache;
$config->setMetadataCacheImpl($cache);
$driverImpl = $config->newDefaultAnnotationDriver(array(APPPATH.'models/Entities'));
$config->setMetadataDriverImpl($driverImpl);
$config->setQueryCacheImpl($cache);
$config->setQueryCacheImpl($cache);
// Proxy configuration
$config->setProxyDir(APPPATH.'/models/proxies');
$config->setProxyNamespace('Proxies');
// Set up logger
$logger = new EchoSQLLogger;
$config->setSQLLogger($logger);
$config->setAutoGenerateProxyClasses( TRUE );
// Database connection information
$connectionOptions = array(
'driver' => 'pdo_mysql',
'user' => $db['default']['username'],
'password' => $db['default']['password'],
'host' => $db['default']['hostname'],
'dbname' => $db['default']['database']
);
// Create EntityManager
$this->em = EntityManager::create($connectionOptions, $config);
}
}
Please note that this is a development configuration; for a
production system you'll want to use a real caching system like
APC, get rid of EchoSqlLogger, and turn off
autoGenerateProxyClasses.
For more details, consult the
`Doctrine 2 Configuration documentation <http://www.doctrine-project.org/documentation/manual/2_0/en/configuration#configuration-options>`_.
Now to use it
-------------
Whenever you need a reference to the entity manager inside one of
your controllers, views, or models you can do this:
.. code-block:: php
<?php
$em = $this->doctrine->em;
That's all there is to it. Once you get the reference to your
EntityManager do your Doctrine 2.0 voodoo as normal.
Note: If you do not choose to autoload the Doctrine library, you
will need to put this line before you get a reference to it:
.. code-block:: php
<?php
$this->load->library('doctrine');
Good luck!

View File

@@ -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:

View File

@@ -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) {

View File

@@ -3,7 +3,7 @@ Strategy-Pattern
This recipe will give you a short introduction on how to design
similar entities without using expensive (i.e. slow) inheritance
but with not more than *the well-known strategy pattern* event
but with not more than \* the well-known strategy pattern \* event
listeners
Scenario / Problem

View File

@@ -89,7 +89,7 @@ events for one method, this will happen before Beta 1 though.
Now validation is performed whenever you call
``EntityManager#persist($order)`` or when you call
``EntityManager#flush()`` and an order is about to be updated. Any
Exception that happens in the lifecycle callbacks will be caught by
Exception that happens in the lifecycle callbacks will be cached by
the EntityManager and the current transaction is rolled back.
Of course you can do any type of primitive checks, not null,
@@ -134,4 +134,4 @@ instances. This was already discussed in the previous blog post on
the Versionable extension, which requires another type of event
called "onFlush".
Further readings: :ref:`reference-events-lifecycle-events`
Further readings: :doc:`Lifecycle Events <../reference/events>`

View File

@@ -1,7 +1,7 @@
Working with DateTime Instances
===============================
There are many nitty gritty details when working with PHPs DateTime instances. You have to know their inner
There are many nitty gritty details when working with PHPs DateTime instances. You have know their inner
workings pretty well not to make mistakes with date handling. This cookbook entry holds several
interesting pieces of information on how to work with PHP DateTime instances in Doctrine 2.
@@ -49,15 +49,14 @@ By default Doctrine assumes that you are working with a default timezone. Each D
is created by Doctrine will be assigned the timezone that is currently the default, either through
the ``date.timezone`` ini setting or by calling ``date_default_timezone_set()``.
This is very important to handle correctly if your application runs on different servers or is moved from one to another server
This is very important to handle correctly if your application runs on different serves or is moved from one to another server
(with different timezone settings). You have to make sure that the timezone is the correct one
on all this systems.
Handling different Timezones with the DateTime Type
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you first come across the requirement to save different timezones you may be still optimistic about how
to manage this mess,
If you first come across the requirement to save different you are still optimistic to manage this mess,
however let me crush your expectations fast. There is not a single database out there (supported by Doctrine 2)
that supports timezones correctly. Correctly here means that you can cover all the use-cases that
can come up with timezones. If you don't believe me you should read up on `Storing DateTime
@@ -86,71 +85,43 @@ the UTC time at the time of the booking and the timezone the event happened in.
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\ConversionException;
use Doctrine\DBAL\Types\DateTimeType;
class UTCDateTimeType extends DateTimeType
{
/**
* @var \DateTimeZone
*/
private static $utc;
static private $utc = null;
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
if ($value instanceof \DateTime) {
$value->setTimezone(self::getUtc());
if ($value === null) {
return null;
}
return parent::convertToDatabaseValue($value, $platform);
return $value->format($platform->getDateTimeFormatString(),
(self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone('UTC'))
);
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
if (null === $value || $value instanceof \DateTime) {
return $value;
if ($value === null) {
return null;
}
$converted = \DateTime::createFromFormat(
$val = \DateTime::createFromFormat(
$platform->getDateTimeFormatString(),
$value,
self::getUtc()
(self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone('UTC'))
);
if (! $converted) {
throw ConversionException::conversionFailedFormat(
$value,
$this->getName(),
$platform->getDateTimeFormatString()
);
if (!$val) {
throw ConversionException::conversionFailed($value, $this->getName());
}
return $converted;
}
private static function getUtc(): \DateTimeZone
{
return self::$utc ?: self::$utc = new \DateTimeZone('UTC');
return $val;
}
}
This database type makes sure that every DateTime instance is always saved in UTC, relative
to the current timezone that the passed DateTime instance has.
To actually use this new type instead of the default ``datetime`` type, you need to run following
code before bootstrapping the ORM:
.. code-block:: php
<?php
use Doctrine\DBAL\Types\Type;
use DoctrineExtensions\DBAL\Types\UTCDateTimeType;
Type::overrideType('datetime', UTCDateTimeType::class);
Type::overrideType('datetimetz', UTCDateTimeType::class);
To be able to transform these values
to the current timezone that the passed DateTime instance has. To be able to transform these values
back into their real timezone you have to save the timezone in a separate field of the entity
requiring timezoned datetimes:

View File

@@ -13,11 +13,11 @@ If this documentation is not helping to answer questions you have about
Doctrine ORM don't panic. You can get help from different sources:
- There is a :doc:`FAQ <reference/faq>` with answers to frequent questions.
- The `Doctrine Mailing List <https://groups.google.com/group/doctrine-user>`_
- Slack chat room `#orm <https://www.doctrine-project.org/slack>`_
- Report a bug on `GitHub <https://github.com/doctrine/orm/issues>`_.
- The `Doctrine Mailing List <http://groups.google.com/group/doctrine-user>`_
- Internet Relay Chat (IRC) in #doctrine on Freenode
- Report a bug on `JIRA <http://www.doctrine-project.org/jira>`_.
- On `Twitter <https://twitter.com/search/%23doctrine2>`_ with ``#doctrine2``
- On `StackOverflow <https://stackoverflow.com/questions/tagged/doctrine-orm>`_
- On `StackOverflow <http://stackoverflow.com/questions/tagged/doctrine2>`_
If you need more structure over the different topics you can browse the :doc:`table
of contents <toc>`.
@@ -72,9 +72,9 @@ Advanced Topics
* :doc:`Transactions and Concurrency <reference/transactions-and-concurrency>`
* :doc:`Filters <reference/filters>`
* :doc:`NamingStrategy <reference/namingstrategy>`
* :doc:`Improving Performance <reference/improving-performance>`
* :doc:`Caching <reference/caching>`
* :doc:`Partial Objects <reference/partial-objects>`
* :doc:`Improving Performance <reference/improving-performance>`
* :doc:`Caching <reference/caching>`
* :doc:`Partial Objects <reference/partial-objects>`
* :doc:`Change Tracking Policies <reference/change-tracking-policies>`
* :doc:`Best Practices <reference/best-practices>`
* :doc:`Metadata Drivers <reference/metadata-drivers>`
@@ -95,7 +95,7 @@ Tutorials
Changelogs
----------
* `Upgrade <https://github.com/doctrine/doctrine2/blob/master/UPGRADE.md>`_
* :doc:`Migration to 2.5 <changelog/migration_2_5>`
Cookbook
--------
@@ -103,7 +103,7 @@ Cookbook
* **Patterns**:
:doc:`Aggregate Fields <cookbook/aggregate-fields>` |
:doc:`Decorator Pattern <cookbook/decorator-pattern>` |
:doc:`Strategy Pattern <cookbook/strategy-cookbook-introduction>`
:doc:`Strategy Pattern <cookbook/strategy-cookbook-introduction>`
* **DQL Extension Points**:
:doc:`DQL Custom Walkers <cookbook/dql-custom-walkers>` |
@@ -118,6 +118,9 @@ Cookbook
:doc:`Entities in the Session <cookbook/entities-in-session>` |
:doc:`Keeping your Modules independent <cookbook/resolve-target-entity-listener>`
* **Integration into Frameworks/Libraries**
:doc:`CodeIgniter <cookbook/integrating-with-codeigniter>`
* **Hidden Gems**
:doc:`Prefixing Table Name <cookbook/sql-table-prefixes>`

View File

@@ -152,7 +152,6 @@ The recommended implementations for production are:
- ``Doctrine\Common\Cache\ApcCache``
- ``Doctrine\Common\Cache\ApcuCache``
- ``Doctrine\Common\Cache\MemcacheCache``
- ``Doctrine\Common\Cache\XcacheCache``
- ``Doctrine\Common\Cache\RedisCache``
@@ -184,7 +183,6 @@ The recommended implementations for production are:
- ``Doctrine\Common\Cache\ApcCache``
- ``Doctrine\Common\Cache\ApcuCache``
- ``Doctrine\Common\Cache\MemcacheCache``
- ``Doctrine\Common\Cache\XcacheCache``
- ``Doctrine\Common\Cache\RedisCache``
@@ -292,7 +290,7 @@ instance of ``Doctrine\DBAL\Connection``. If an array is passed it
is directly passed along to the DBAL Factory
``Doctrine\DBAL\DriverManager::getConnection()``. The DBAL
configuration is explained in the
`DBAL section <https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/configuration.html>`_.
`DBAL section <./../../../../../projects/doctrine-dbal/en/latest/reference/configuration.html>`_.
Proxy Objects
-------------
@@ -396,7 +394,7 @@ means that you have to register a special autoloader for these classes:
.. code-block:: php
<?php
use Doctrine\Common\Proxy\Autoloader;
use Doctrine\ORM\Proxy\Autoloader;
$proxyDir = "/path/to/proxies";
$proxyNamespace = "MyProxies";

View File

@@ -37,11 +37,8 @@ Index
- :ref:`@ColumnResult <annref_column_result>`
- :ref:`@Cache <annref_cache>`
- :ref:`@ChangeTrackingPolicy <annref_changetrackingpolicy>`
- :ref:`@CustomIdGenerator <annref_customidgenerator>`
- :ref:`@DiscriminatorColumn <annref_discriminatorcolumn>`
- :ref:`@DiscriminatorMap <annref_discriminatormap>`
- :ref:`@Embeddable <annref_embeddable>`
- :ref:`@Embedded <annref_embedded>`
- :ref:`@Entity <annref_entity>`
- :ref:`@EntityResult <annref_entity_result>`
- :ref:`@FieldResult <annref_field_result>`
@@ -113,7 +110,7 @@ Optional attributes:
- **unique**: Boolean value to determine if the value of the column
should be unique across all rows of the underlying entities table.
- **nullable**: Determines if NULL values allowed for this column. If not specified, default value is false.
- **nullable**: Determines if NULL values allowed for this column.
- **options**: Array of additional options:
@@ -134,9 +131,6 @@ Optional attributes:
- ``collation``: The collation of the column (only supported by Drizzle, Mysql, PostgreSQL>=9.1, Sqlite and SQLServer).
- ``check``: Adds a check constraint type to the column (might not
be supported by all vendors).
- **columnDefinition**: DDL SQL snippet that starts after the column
name and specifies the complete (non-portable!) column definition.
This attribute allows to make use of advanced RMDBS features.
@@ -181,7 +175,7 @@ Examples:
protected $initials;
/**
* @Column(type="integer", name="login_count", nullable=false, options={"unsigned":true, "default":0})
* @Column(type="integer", name="login_count" nullable=false, options={"unsigned":true, "default":0})
*/
protected $loginCount;
@@ -237,43 +231,16 @@ Example:
*/
class User {}
.. _annref_customidgenerator:
@CustomIdGenerator
~~~~~~~~~~~~~~~~~~~~~
This annotations allows you to specify a user-provided class to generate identifiers. This annotation only works when both :ref:`@Id <annref_id>` and :ref:`@GeneratedValue(strategy="CUSTOM") <annref_generatedvalue>` are specified.
Required attributes:
- **class**: name of the class which should extend Doctrine\ORM\Id\AbstractIdGenerator
Example:
.. code-block:: php
<?php
/**
* @Id
* @Column(type="integer")
* @GeneratedValue(strategy="CUSTOM")
* @CustomIdGenerator(class="My\Namespace\MyIdGenerator")
*/
public $id;
.. _annref_discriminatorcolumn:
@DiscriminatorColumn
~~~~~~~~~~~~~~~~~~~~~
This annotation is an optional annotation for the topmost/super
This annotation is a required annotation for the topmost/super
class of an inheritance hierarchy. It specifies the details of the
column which saves the name of the class, which the entity is
actually instantiated as.
If this annotation is not specified, the discriminator column defaults
to a string column of length 255 called ``dtype``.
Required attributes:
@@ -312,67 +279,6 @@ depending on whether the classes are in the namespace or not.
// ...
}
.. _annref_embeddable:
@Embeddable
~~~~~~~~~~~~~~~~~~~~~
The embeddable annotation is required on a class, in order to make it
embeddable inside an entity. It works together with the :ref:`@Embedded <annref_embedded>`
annotation to establish the relationship between the two classes.
.. code-block:: php
<?php
/**
* @Embeddable
*/
class Address
{
// ...
class User
{
/**
* @Embedded(class = "Address")
*/
private $address;
.. _annref_embedded:
@Embedded
~~~~~~~~~~~~~~~~~~~~~
The embedded annotation is required on an entity's member variable,
in order to specify that it is an embedded class.
Required attributes:
- **class**: The embeddable class
.. code-block:: php
<?php
// ...
class User
{
/**
* @Embedded(class = "Address")
*/
private $address;
/**
* @Embeddable
*/
class Address
{
// ...
.. _annref_entity:
@Entity
@@ -451,12 +357,11 @@ conjunction with @Id.
If this annotation is not specified with @Id the NONE strategy is
used as default.
Optional attributes:
Required attributes:
- **strategy**: Set the name of the identifier generation strategy.
Valid values are AUTO, SEQUENCE, TABLE, IDENTITY, UUID, CUSTOM and NONE.
If not specified, default value is AUTO.
Example:
@@ -1306,13 +1211,12 @@ Example with partial indexes:
.. _annref_version:
@Version
~~~~~~~~
~~~~~~~~~~~~~~
Marker annotation that defines a specified column as version attribute used in
an :ref:`optimistic locking <transactions-and-concurrency_optimistic-locking>`
scenario. It only works on :ref:`@Column <annref_column>` annotations that have
the type ``integer`` or ``datetime``. Combining ``@Version`` with
:ref:`@Id <annref_id>` is not supported.
Marker annotation that defines a specified column as version
attribute used in an optimistic locking scenario. It only works on
:ref:`@Column <annref_column>` annotations that have the type integer or
datetime. Combining @Version with :ref:`@Id <annref_id>` is not supported.
Example:

View File

@@ -18,7 +18,7 @@ well.
Requirements
------------
Doctrine 2 requires a minimum of PHP 7.1. For greatly improved
Doctrine 2 requires a minimum of PHP 5.4. For greatly improved
performance it is also recommended that you use APC with PHP.
Doctrine 2 Packages

View File

@@ -16,31 +16,20 @@ This chapter is split into three different sections.
- :ref:`association_mapping_defaults` are explained that simplify the use-case examples.
- :ref:`collections` are introduced that contain entities in associations.
One tip for working with relations is to read the relation from left to right, where the left word refers to the current Entity. For example:
- OneToMany - One instance of the current Entity has Many instances (references) to the refered Entity.
- ManyToOne - Many instances of the current Entity refer to One instance of the refered Entity.
- OneToOne - One instance of the current Entity refers to One instance of the refered Entity.
See below for all the possible relations.
An association is considered to be unidirectional if only one side of the association has
a property referring to the other side.
To gain a full understanding of associations you should also read about :doc:`owning and
inverse sides of associations <unitofwork-associations>`
Many-To-One, Unidirectional
---------------------------
A many-to-one association is the most common association between objects. Example: Many Users have One Address:
A many-to-one association is the most common association between objects.
.. configuration-block::
.. code-block:: php
<?php
/** @Entity */
/** @Entity **/
class User
{
// ...
@@ -48,11 +37,11 @@ A many-to-one association is the most common association between objects. Exampl
/**
* @ManyToOne(targetEntity="Address")
* @JoinColumn(name="address_id", referencedColumnName="id")
*/
**/
private $address;
}
/** @Entity */
/** @Entity **/
class Address
{
// ...
@@ -107,30 +96,31 @@ One-To-One, Unidirectional
--------------------------
Here is an example of a one-to-one association with a ``Product`` entity that
references one ``Shipment`` entity.
references one ``Shipping`` entity. The ``Shipping`` does not reference back to
the ``Product`` so that the reference is said to be unidirectional, in one
direction only.
.. configuration-block::
.. code-block:: php
<?php
/** @Entity */
/** @Entity **/
class Product
{
// ...
/**
* One Product has One Shipment.
* @OneToOne(targetEntity="Shipment")
* @JoinColumn(name="shipment_id", referencedColumnName="id")
*/
private $shipment;
* @OneToOne(targetEntity="Shipping")
* @JoinColumn(name="shipping_id", referencedColumnName="id")
**/
private $shipping;
// ...
}
/** @Entity */
class Shipment
/** @Entity **/
class Shipping
{
// ...
}
@@ -139,8 +129,8 @@ references one ``Shipment`` entity.
<doctrine-mapping>
<entity class="Product">
<one-to-one field="shipment" target-entity="Shipment">
<join-column name="shipment_id" referenced-column-name="id" />
<one-to-one field="shipping" target-entity="Shipping">
<join-column name="shipping_id" referenced-column-name="id" />
</one-to-one>
</entity>
</doctrine-mapping>
@@ -150,10 +140,10 @@ references one ``Shipment`` entity.
Product:
type: entity
oneToOne:
shipment:
targetEntity: Shipment
shipping:
targetEntity: Shipping
joinColumn:
name: shipment_id
name: shipping_id
referencedColumnName: id
Note that the @JoinColumn is not really necessary in this example,
@@ -165,15 +155,15 @@ Generated MySQL Schema:
CREATE TABLE Product (
id INT AUTO_INCREMENT NOT NULL,
shipment_id INT DEFAULT NULL,
UNIQUE INDEX UNIQ_6FBC94267FE4B2B (shipment_id),
shipping_id INT DEFAULT NULL,
UNIQUE INDEX UNIQ_6FBC94267FE4B2B (shipping_id),
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE Shipment (
CREATE TABLE Shipping (
id INT AUTO_INCREMENT NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE Product ADD FOREIGN KEY (shipment_id) REFERENCES Shipment(id);
ALTER TABLE Product ADD FOREIGN KEY (shipping_id) REFERENCES Shipping(id);
One-To-One, Bidirectional
-------------------------
@@ -182,39 +172,33 @@ Here is a one-to-one relationship between a ``Customer`` and a
``Cart``. The ``Cart`` has a reference back to the ``Customer`` so
it is bidirectional.
Here we see the ``mappedBy`` and ``inversedBy`` annotations for the first time.
They are used to tell Doctrine which property on the other side refers to the
object.
.. configuration-block::
.. code-block:: php
<?php
/** @Entity */
/** @Entity **/
class Customer
{
// ...
/**
* One Customer has One Cart.
* @OneToOne(targetEntity="Cart", mappedBy="customer")
*/
**/
private $cart;
// ...
}
/** @Entity */
/** @Entity **/
class Cart
{
// ...
/**
* One Cart has One Customer.
* @OneToOne(targetEntity="Customer", inversedBy="cart")
* @JoinColumn(name="customer_id", referencedColumnName="id")
*/
**/
private $customer;
// ...
@@ -267,9 +251,8 @@ Generated MySQL Schema:
) ENGINE = InnoDB;
ALTER TABLE Cart ADD FOREIGN KEY (customer_id) REFERENCES Customer(id);
We had a choice of sides on which to place the ``inversedBy`` attribute. Because it
is on the ``Cart``, that is the owning side of the relation, and thus holds the
foreign key.
See how the foreign key is defined on the owning side of the
relation, the table ``Cart``.
One-To-One, Self-referencing
----------------------------
@@ -280,16 +263,15 @@ below.
.. code-block:: php
<?php
/** @Entity */
/** @Entity **/
class Student
{
// ...
/**
* One Student has One Mentor.
* @OneToOne(targetEntity="Student")
* @JoinColumn(name="mentor_id", referencedColumnName="id")
*/
**/
private $mentor;
// ...
@@ -312,16 +294,15 @@ With the generated MySQL Schema:
One-To-Many, Bidirectional
--------------------------
A one-to-many association has to be bidirectional, unless you are using a
join table. This is because the "many" side in a one-to-many association holds
the foreign key, making it the owning side. Doctrine needs the "many" side
defined in order to understand the association.
A one-to-many association has to be bidirectional, unless you are using an
additional join-table. This is necessary, because of the foreign key
in a one-to-many association being defined on the "many" side. Doctrine
needs a many-to-one association that defines the mapping of this
foreign key.
This bidirectional mapping requires the ``mappedBy`` attribute on the
"one" side and the ``inversedBy`` attribute on the "many" side.
This means there is no difference between a bidirectional one-to-many and a
bidirectional many-to-one.
``OneToMany`` association and the ``inversedBy`` attribute on the ``ManyToOne``
association.
.. configuration-block::
@@ -330,14 +311,13 @@ bidirectional many-to-one.
<?php
use Doctrine\Common\Collections\ArrayCollection;
/** @Entity */
/** @Entity **/
class Product
{
// ...
/**
* One product has many features. This is the inverse side.
* @OneToMany(targetEntity="Feature", mappedBy="product")
*/
**/
private $features;
// ...
@@ -346,15 +326,14 @@ bidirectional many-to-one.
}
}
/** @Entity */
/** @Entity **/
class Feature
{
// ...
/**
* Many features have one product. This is the owning side.
* @ManyToOne(targetEntity="Product", inversedBy="features")
* @JoinColumn(name="product_id", referencedColumnName="id")
*/
**/
private $product;
// ...
}
@@ -423,19 +402,18 @@ The following example sets up such a unidirectional one-to-many association:
.. code-block:: php
<?php
/** @Entity */
/** @Entity **/
class User
{
// ...
/**
* Many User have Many Phonenumbers.
* @ManyToMany(targetEntity="Phonenumber")
* @JoinTable(name="users_phonenumbers",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)}
* )
*/
**/
private $phonenumbers;
public function __construct()
@@ -446,7 +424,7 @@ The following example sets up such a unidirectional one-to-many association:
// ...
}
/** @Entity */
/** @Entity **/
class Phonenumber
{
// ...
@@ -525,21 +503,19 @@ database perspective is known as an adjacency list approach.
.. code-block:: php
<?php
/** @Entity */
/** @Entity **/
class Category
{
// ...
/**
* One Category has Many Categories.
* @OneToMany(targetEntity="Category", mappedBy="parent")
*/
**/
private $children;
/**
* Many Categories have One Category.
* @ManyToOne(targetEntity="Category", inversedBy="children")
* @JoinColumn(name="parent_id", referencedColumnName="id")
*/
**/
private $parent;
// ...
@@ -596,19 +572,18 @@ entities:
.. code-block:: php
<?php
/** @Entity */
/** @Entity **/
class User
{
// ...
/**
* Many Users have Many Groups.
* @ManyToMany(targetEntity="Group")
* @JoinTable(name="users_groups",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
* )
*/
**/
private $groups;
// ...
@@ -618,7 +593,7 @@ entities:
}
}
/** @Entity */
/** @Entity **/
class Group
{
// ...
@@ -697,16 +672,15 @@ one is bidirectional.
.. code-block:: php
<?php
/** @Entity */
/** @Entity **/
class User
{
// ...
/**
* Many Users have Many Groups.
* @ManyToMany(targetEntity="Group", inversedBy="users")
* @JoinTable(name="users_groups")
*/
**/
private $groups;
public function __construct() {
@@ -716,14 +690,13 @@ one is bidirectional.
// ...
}
/** @Entity */
/** @Entity **/
class Group
{
// ...
/**
* Many Groups have Many Users.
* @ManyToMany(targetEntity="User", mappedBy="groups")
*/
**/
private $users;
public function __construct() {
@@ -781,22 +754,22 @@ one is bidirectional.
The MySQL schema is exactly the same as for the Many-To-Many
uni-directional case above.
Owning and Inverse Side on a ManyToMany Association
Owning and Inverse Side on a ManyToMany association
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For Many-To-Many associations you can chose which entity is the
owning and which the inverse side. There is a very simple semantic
rule to decide which side is more suitable to be the owning side
from a developers perspective. You only have to ask yourself which
entity is responsible for the connection management, and pick that
from a developers perspective. You only have to ask yourself, which
entity is responsible for the connection management and pick that
as the owning side.
Take an example of two entities ``Article`` and ``Tag``. Whenever
you want to connect an Article to a Tag and vice-versa, it is
mostly the Article that is responsible for this relation. Whenever
you add a new article, you want to connect it with existing or new
tags. Your "Create Article" form will probably support this notion
and allow specifying the tags directly. This is why you should pick
tags. Your create Article form will probably support this notion
and allow to specify the tags directly. This is why you should pick
the Article as owning side, as it makes the code more
understandable:
@@ -846,25 +819,23 @@ field named ``$friendsWithMe`` and ``$myFriends``.
.. code-block:: php
<?php
/** @Entity */
/** @Entity **/
class User
{
// ...
/**
* Many Users have Many Users.
* @ManyToMany(targetEntity="User", mappedBy="myFriends")
*/
**/
private $friendsWithMe;
/**
* Many Users have many Users.
* @ManyToMany(targetEntity="User", inversedBy="friendsWithMe")
* @JoinTable(name="friends",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="friend_user_id", referencedColumnName="id")}
* )
*/
**/
private $myFriends;
public function __construct() {
@@ -912,14 +883,14 @@ As an example, consider this mapping:
.. code-block:: php
<?php
/** @OneToOne(targetEntity="Shipment") */
private $shipment;
/** @OneToOne(targetEntity="Shipping") **/
private $shipping;
.. code-block:: xml
<doctrine-mapping>
<entity class="Product">
<one-to-one field="shipment" target-entity="Shipment" />
<one-to-one field="shipping" target-entity="Shipping" />
</entity>
</doctrine-mapping>
@@ -928,8 +899,8 @@ As an example, consider this mapping:
Product:
type: entity
oneToOne:
shipment:
targetEntity: Shipment
shipping:
targetEntity: Shipping
This is essentially the same as the following, more verbose,
mapping:
@@ -940,18 +911,17 @@ mapping:
<?php
/**
* One Product has One Shipment.
* @OneToOne(targetEntity="Shipment")
* @JoinColumn(name="shipment_id", referencedColumnName="id")
*/
private $shipment;
* @OneToOne(targetEntity="Shipping")
* @JoinColumn(name="shipping_id", referencedColumnName="id")
**/
private $shipping;
.. code-block:: xml
<doctrine-mapping>
<entity class="Product">
<one-to-one field="shipment" target-entity="Shipment">
<join-column name="shipment_id" referenced-column-name="id" />
<one-to-one field="shipping" target-entity="Shipping">
<join-column name="shipping_id" referenced-column-name="id" />
</one-to-one>
</entity>
</doctrine-mapping>
@@ -961,10 +931,10 @@ mapping:
Product:
type: entity
oneToOne:
shipment:
targetEntity: Shipment
shipping:
targetEntity: Shipping
joinColumn:
name: shipment_id
name: shipping_id
referencedColumnName: id
The @JoinTable definition used for many-to-many mappings has
@@ -978,7 +948,7 @@ similar defaults. As an example, consider this mapping:
class User
{
//...
/** @ManyToMany(targetEntity="Group") */
/** @ManyToMany(targetEntity="Group") **/
private $groups;
//...
}
@@ -1010,13 +980,12 @@ This is essentially the same as the following, more verbose, mapping:
{
//...
/**
* Many Users have Many Groups.
* @ManyToMany(targetEntity="Group")
* @JoinTable(name="User_Group",
* joinColumns={@JoinColumn(name="User_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="Group_id", referencedColumnName="id")}
* )
*/
**/
private $groups;
//...
}
@@ -1095,17 +1064,12 @@ and ``@ManyToMany`` associations in the constructor of your entities:
.. code-block:: php
<?php
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
/** @Entity */
/** @Entity **/
class User
{
/**
* Many Users have Many Groups.
* @var Collection
* @ManyToMany(targetEntity="Group")
*/
/** @ManyToMany(targetEntity="Group") **/
private $groups;
public function __construct()

View File

@@ -270,7 +270,7 @@ A cookbook article shows how to define :doc:`your own custom mapping types
.. warning::
All Date types assume that you are exclusively using the default timezone
set by `date_default_timezone_set() <http://php.net/manual/en/function.date-default-timezone-set.php>`_
set by `date_default_timezone_set() <http://docs.php.net/manual/en/function.date-default-timezone-set.php>`_
or by the php.ini configuration ``date.timezone``. Working with
different timezones will cause troubles and unexpected behavior.
@@ -295,8 +295,7 @@ annotation.
class Message
{
/**
* @Id
* @Column(type="integer")
* @Id @Column(type="integer")
* @GeneratedValue
*/
private $id;
@@ -355,8 +354,6 @@ Here is the list of possible generation strategies:
strategy does currently not provide full portability and is
supported by the following platforms: MySQL/SQLite/SQL Anywhere
(AUTO\_INCREMENT), MSSQL (IDENTITY) and PostgreSQL (SERIAL).
- ``UUID``: Tells Doctrine to use the built-in Universally Unique Identifier
generator. This strategy provides full portability.
- ``TABLE``: Tells Doctrine to use a separate table for ID
generation. This strategy provides full portability.
***This strategy is not yet implemented!***
@@ -364,8 +361,6 @@ Here is the list of possible generation strategies:
thus generated) by your code. The assignment must take place before
a new entity is passed to ``EntityManager#persist``. NONE is the
same as leaving off the @GeneratedValue entirely.
- ``CUSTOM``: With this option, you can use the ``@CustomIdGenerator`` annotation.
It will allow you to pass a :doc:`class of your own to generate the identifiers.<_annref_customidgenerator>`
Sequence Generator
^^^^^^^^^^^^^^^^^^
@@ -451,7 +446,7 @@ need to access the sequence once to generate the identifiers for
Composite Keys
~~~~~~~~~~~~~~
With Doctrine 2 you can use composite primary keys, using ``@Id`` on more then
with Doctrine 2 you can use composite primary keys, using ``@Id`` on more then
one column. Some restrictions exist opposed to using a single identifier in
this case: The use of the ``@GeneratedValue`` annotation is not supported,
which means you can only use composite keys if you generate the primary key

View File

@@ -16,15 +16,6 @@ especially what the strategies presented here provide help with.
operations.
.. note::
Having an SQL logger enabled when processing batches can have a serious impact on performance and resource usage.
To avoid that you should disable it in the DBAL configuration:
.. code-block:: php
<?php
$em->getConnection()->getConfiguration()->setSQLLogger(null);
Bulk Inserts
------------
@@ -84,7 +75,7 @@ with the batching strategy that was already used for bulk inserts:
<?php
$batchSize = 20;
$i = 1;
$i = 0;
$q = $em->createQuery('select u from MyProject\Model\User u');
$iterableResult = $q->iterate();
foreach ($iterableResult as $row) {
@@ -109,7 +100,7 @@ with the batching strategy that was already used for bulk inserts:
Results may be fully buffered by the database client/ connection allocating
additional memory not visible to the PHP process. For large sets this
may easily kill the process for no apparent reason.
may easily kill the process for no apparant reason.
Bulk Deletes
@@ -145,7 +136,7 @@ The following example shows how to do this:
<?php
$batchSize = 20;
$i = 1;
$i = 0;
$q = $em->createQuery('select u from MyProject\Model\User u');
$iterableResult = $q->iterate();
while (($row = $iterableResult->next()) !== false) {

View File

@@ -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
----------------------------

View File

@@ -13,7 +13,7 @@ Cache Drivers
The cache drivers follow a simple interface that is defined in
``Doctrine\Common\Cache\Cache``. All the cache drivers extend a
base class ``Doctrine\Common\Cache\CacheProvider`` which implements
base class ``Doctrine\Common\Cache\AbstractCache`` which implements
this interface.
The interface defines the following public methods for you to implement:
@@ -21,30 +21,26 @@ The interface defines the following public methods for you to implement:
- fetch($id) - Fetches an entry from the cache
- contains($id) - Test if an entry exists in the cache
- save($id, $data, $lifeTime = false) - Puts data into the cache for x seconds. 0 = infinite time
- save($id, $data, $lifeTime = false) - Puts data into the cache
- delete($id) - Deletes a cache entry
Each driver extends the ``CacheProvider`` class which defines a few
Each driver extends the ``AbstractCache`` class which defines a few
abstract protected methods that each of the drivers must
implement:
- doFetch($id)
- doContains($id)
- doSave($id, $data, $lifeTime = false)
- doDelete($id)
- \_doFetch($id)
- \_doContains($id)
- \_doSave($id, $data, $lifeTime = false)
- \_doDelete($id)
The public methods ``fetch()``, ``contains()`` etc. use the
above protected methods which are implemented by the drivers. The
code is organized this way so that the protected methods in the
drivers do the raw interaction with the cache implementation and
the ``CacheProvider`` can build custom functionality on top of
the ``AbstractCache`` can build custom functionality on top of
these methods.
This documentation does not cover every single cache driver included
with Doctrine. For an up-to-date-list, see the
`cache directory on GitHub <https://github.com/doctrine/cache/tree/master/lib/Doctrine/Common/Cache>`_.
APC
~~~
@@ -63,24 +59,6 @@ by itself.
$cacheDriver = new \Doctrine\Common\Cache\ApcCache();
$cacheDriver->save('cache_id', 'my_data');
APCu
~~~~
In order to use the APCu cache driver you must have it compiled and
enabled in your php.ini. You can read about APCu
`in the PHP Documentation <http://us2.php.net/apcu>`_. It will give
you a little background information about what it is and how you
can use it as well as how to install it.
Below is a simple example of how you could use the APCu cache driver
by itself.
.. code-block:: php
<?php
$cacheDriver = new \Doctrine\Common\Cache\ApcuCache();
$cacheDriver->save('cache_id', 'my_data');
Memcache
~~~~~~~~
@@ -104,7 +82,7 @@ driver by itself.
$cacheDriver->save('cache_id', 'my_data');
Memcached
~~~~~~~~~
~~~~~~~~
Memcached is a more recent and complete alternative extension to
Memcache.
@@ -282,8 +260,6 @@ You can set the namespace a cache driver should use by using the
<?php
$cacheDriver->setNamespace('my_namespace_');
.. _integrating-with-the-orm:
Integrating with the ORM
------------------------
@@ -307,7 +283,7 @@ use on your ORM configuration.
<?php
$config = new \Doctrine\ORM\Configuration();
$config->setQueryCacheImpl(new \Doctrine\Common\Cache\ApcuCache());
$config->setQueryCacheImpl(new \Doctrine\Common\Cache\ApcCache());
Result Cache
~~~~~~~~~~~~
@@ -320,7 +296,7 @@ cache implementation.
.. code-block:: php
<?php
$config->setResultCacheImpl(new \Doctrine\Common\Cache\ApcuCache());
$config->setResultCacheImpl(new \Doctrine\Common\Cache\ApcCache());
Now when you're executing DQL queries you can configure them to use
the result cache.
@@ -337,7 +313,7 @@ result cache driver.
.. code-block:: php
<?php
$query->setResultCacheDriver(new \Doctrine\Common\Cache\ApcuCache());
$query->setResultCacheDriver(new \Doctrine\Common\Cache\ApcCache());
.. note::
@@ -389,7 +365,7 @@ first.
.. code-block:: php
<?php
$config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcuCache());
$config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcCache());
Now the metadata information will only be parsed once and stored in
the cache driver.
@@ -425,39 +401,6 @@ To clear the result cache use the ``orm:clear-cache:result`` task.
All these tasks accept a ``--flush`` option to flush the entire
contents of the cache instead of invalidating the entries.
Cache Chaining
--------------
A common pattern is to use a static cache to store data that is
requested many times in a single PHP request. Even though this data
may be stored in a fast memory cache, often that cache is over a
network link leading to sizable network traffic.
The ChainCache class allows multiple caches to be registered at once.
For example, a per-request ArrayCache can be used first, followed by
a (relatively) slower MemcacheCache if the ArrayCache misses.
ChainCache automatically handles pushing data up to faster caches in
the chain and clearing data in the entire stack when it is deleted.
A ChainCache takes a simple array of CacheProviders in the order that
they should be used.
.. code-block:: php
$arrayCache = new \Doctrine\Common\Cache\ArrayCache();
$memcache = new Memcache();
$memcache->connect('memcache_host', 11211);
$chainCache = new \Doctrine\Common\Cache\ChainCache([
$arrayCache,
$memcache,
]);
ChainCache itself extends the CacheProvider interface, so it is
possible to create chains of chains. While this may seem like an easy
way to build a simple high-availability cache, ChainCache does not
implement any exception handling so using it as a high-availability
mechanism is not recommended.
Cache Slams
-----------

View File

@@ -1,7 +1,9 @@
Installation and Configuration
==============================
Doctrine can be installed with `Composer <https://getcomposer.org>`_.
Doctrine can be installed with `Composer <http://www.getcomposer.org>`_. For
older versions we still have `PEAR packages
<http://pear.doctrine-project.org>`_.
Define the following requirement in your ``composer.json`` file:
@@ -14,7 +16,8 @@ Define the following requirement in your ``composer.json`` file:
}
Then call ``composer install`` from your command line. If you don't know
how Composer works, check out their `Getting Started <https://getcomposer.org/doc/00-intro.md>`_ to set up.
how Composer works, check out their `Getting Started
<http://getcomposer.org/doc/00-intro.md>`_ to set up.
Class loading
-------------
@@ -76,13 +79,6 @@ Or if you prefer YAML:
$config = Setup::createYAMLMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);
.. note::
If you want to use yml mapping you should add yaml dependency to your `composer.json`:
::
"symfony/yaml": "*"
Inside the ``Setup`` methods several assumptions are made:
- If `$isDevMode` is true caching is done in memory with the ``ArrayCache``. Proxy objects are recreated on every request.
@@ -90,7 +86,8 @@ Inside the ``Setup`` methods several assumptions are made:
- If `$isDevMode` is false, set then proxy classes have to be explicitly created through the command line.
- If third argument `$proxyDir` is not set, use the systems temporary directory.
If you want to configure Doctrine in more detail, take a look at the :doc:`Advanced Configuration <reference/advanced-configuration>` section.
If you want to configure Doctrine in more detail, take a look at the :doc:`Advanced
Configuration <reference/advanced-configuration>` section.
.. note::

View File

@@ -2,7 +2,7 @@ Doctrine Query Language
===========================
DQL stands for Doctrine Query Language and is an Object
Query Language derivative that is very similar to the Hibernate
Query Language derivate that is very similar to the Hibernate
Query Language (HQL) or the Java Persistence Query Language (JPQL).
In essence, DQL provides powerful querying capabilities over your
@@ -34,9 +34,9 @@ object model.
DQL SELECT statements are a very powerful way of retrieving parts
of your domain model that are not accessible via associations.
Additionally they allow you to retrieve entities and their associations
Additionally they allow to retrieve entities and their associations
in one single SQL select statement which can make a huge difference
in performance compared to using several queries.
in performance in contrast to using several queries.
DQL UPDATE and DELETE statements offer a way to execute bulk
changes on the entities of your domain model. This is often
@@ -49,6 +49,10 @@ SELECT queries
DQL SELECT clause
~~~~~~~~~~~~~~~~~
The select clause of a DQL query specifies what appears in the
query result. The composition of all the expressions in the select
clause also influences the nature of the query result.
Here is an example that selects all users with an age > 20:
.. code-block:: php
@@ -79,58 +83,14 @@ Lets examine the query:
The result of this query would be a list of User objects where all
users are older than 20.
Result format
~~~~~~~~~~~~~
The composition of the expressions in the SELECT clause also
influences the nature of the query result. There are three
cases:
**All objects**
.. code-block:: sql
SELECT u, p, n FROM Users u...
In this case, the result will be an array of User objects because of
the FROM clause, with children ``p`` and ``n`` hydrated because of
their inclusion in the SELECT clause.
**All scalars**
.. code-block:: sql
SELECT u.name, u.address FROM Users u...
In this case, the result will be an array of arrays. In the example
above, each element of the result array would be an array of the
scalar name and address values.
You can select scalars from any entity in the query.
**Mixed**
.. code-block:: sql
SELECT u, p.quantity FROM Users u...
Here, the result will again be an array of arrays, with each element
being an array made up of a User object and the scalar value
``p.quantity``.
Multiple FROM clauses are allowed, which would cause the result
array elements to cycle through the classes included in the
multiple FROM clauses.
.. note::
You cannot select other entities unless you also select the
root of the selection (which is the first entity in FROM).
For example, ``SELECT p,n FROM Users u...`` would be wrong because
``u`` is not part of the SELECT
Doctrine throws an exception if you violate this constraint.
The SELECT clause allows to specify both class identification
variables that signal the hydration of a complete entity class or
just fields of the entity using the syntax ``u.name``. Combinations
of both are also allowed and it is possible to wrap both fields and
identification values into aggregation and DQL functions. Numerical
fields can be part of computations using mathematical operations.
See the sub-section on `Functions, Operators, Aggregates`_ for
more information.
Joins
~~~~~
@@ -359,8 +319,7 @@ article-ids:
$query = $em->createQuery('SELECT u.id, a.id as article_id FROM CmsUser u LEFT JOIN u.articles a');
$results = $query->getResult(); // array of user ids and every article_id for each user
Restricting a JOIN clause by additional conditions specified by
WITH:
Restricting a JOIN clause by additional conditions:
.. code-block:: php
@@ -493,18 +452,6 @@ Joins between entities without associations were not possible until version
<?php
$query = $em->createQuery('SELECT u FROM User u JOIN Blacklist b WITH u.email = b.email');
.. note::
The differences between WHERE, WITH and HAVING clauses may be
confusing.
- WHERE is applied to the results of an entire query
- WITH is applied to a join as an additional condition. For
arbitrary joins (SELECT f, b FROM Foo f, Bar b WITH f.id = b.id)
the WITH is required, even if it is 1 = 1
- HAVING is applied to the results of a query after
aggregation (GROUP BY)
Partial Object Syntax
^^^^^^^^^^^^^^^^^^^^^
@@ -539,9 +486,9 @@ You use the partial syntax when joining as well:
Using the ``NEW`` operator you can construct Data Transfer Objects (DTOs) directly from DQL queries.
- When using ``SELECT NEW`` you don't need to specify a mapped entity.
- You can specify any PHP class, it only requires that the constructor of this class matches the ``NEW`` statement.
- You can specify any PHP class, it's only require that the constructor of this class matches the ``NEW`` statement.
- This approach involves determining exactly which columns you really need,
and instantiating a data-transfer object that contains a constructor with those arguments.
and instantiating data-transfer object that containing a constructor with those arguments.
If you want to select data-transfer objects you should create a class:
@@ -654,15 +601,12 @@ The same restrictions apply for the reference of related entities.
DQL DELETE statements are ported directly into a
Database DELETE statement and therefore bypass any events and checks for the
version column if they are not explicitly added to the WHERE clause
of the query. Additionally Deletes of specified entities are *NOT*
of the query. Additionally Deletes of specifies entities are *NOT*
cascaded to related entities even if specified in the metadata.
Functions, Operators, Aggregates
--------------------------------
It is possible to wrap both fields and identification values into
aggregation and DQL functions. Numerical fields can be part of
computations using mathematical operations.
DQL Functions
~~~~~~~~~~~~~
@@ -691,8 +635,8 @@ clauses:
- TRIM([LEADING \| TRAILING \| BOTH] ['trchar' FROM] str) - Trim
the string by the given trim char, defaults to whitespaces.
- UPPER(str) - Return the upper-case of the given string.
- DATE_ADD(date, value, unit) - Add the given time to a given date. (Supported units are SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR)
- DATE_SUB(date, value, unit) - Subtract the given time from a given date. (Supported units are SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR)
- DATE_ADD(date, days, unit) - Add the number of days to a given date. (Supported units are DAY, MONTH)
- DATE_SUB(date, days, unit) - Substract the number of days from a given date. (Supported units are DAY, MONTH)
- DATE_DIFF(date1, date2) - Calculate the difference in days between date1-date2.
Arithmetic operators
@@ -775,6 +719,8 @@ classes have to implement the base class :
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$lexer = $parser->getLexer();
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
@@ -965,7 +911,7 @@ An instance of the ``Doctrine\ORM\Query`` class represents a DQL
query. You create a Query instance be calling
``EntityManager#createQuery($dql)``, passing the DQL query string.
Alternatively you can create an empty ``Query`` instance and invoke
``Query#setDQL($dql)`` afterwards. Here are some examples:
``Query#setDql($dql)`` afterwards. Here are some examples:
.. code-block:: php
@@ -975,9 +921,9 @@ Alternatively you can create an empty ``Query`` instance and invoke
// example1: passing a DQL string
$q = $em->createQuery('select u from MyProject\Model\User u');
// example2: using setDQL
// example2: using setDql
$q = $em->createQuery();
$q->setDQL('select u from MyProject\Model\User u');
$q->setDql('select u from MyProject\Model\User u');
Query Result Formats
~~~~~~~~~~~~~~~~~~~~
@@ -996,9 +942,8 @@ the Query class. Here they are:
result contains more than one object, an ``NonUniqueResultException``
is thrown. If the result contains no objects, an ``NoResultException``
is thrown. The pure/mixed distinction does not apply.
- ``Query#getOneOrNullResult()``: Retrieve a single object. If the
result contains more than one object, a ``NonUniqueResultException``
is thrown. If no object is found null will be returned.
- ``Query#getOneOrNullResult()``: Retrieve a single object. If no
object is found null will be returned.
- ``Query#getArrayResult()``: Retrieves an array graph (a nested
array) that is largely interchangeable with the object graph
generated by ``Query#getResult()`` for read-only purposes.
@@ -1061,7 +1006,7 @@ structure:
.. code-block:: php
$dql = "SELECT u, 'some scalar string', count(g.id) AS num FROM User u JOIN u.groups g GROUP BY u.id";
$dql = "SELECT u, 'some scalar string', count(u.groups) AS num FROM User u JOIN u.groups g GROUP BY u.id";
array
[0]
@@ -1161,22 +1106,6 @@ Object hydration hydrates the result set into the object graph:
$query = $em->createQuery('SELECT u FROM CmsUser u');
$users = $query->getResult(Query::HYDRATE_OBJECT);
Sometimes the behavior in the object hydrator can be confusing, which is
why we are listing as many of the assumptions here for reference:
- Objects fetched in a FROM clause are returned as a Set, that means every
object is only ever included in the resulting array once. This is the case
even when using JOIN or GROUP BY in ways that return the same row for an
object multiple times. If the hydrator sees the same object multiple times,
then it makes sure it is only returned once.
- If an object is already in memory from a previous query of any kind, then
then the previous object is used, even if the database may contain more
recent data. Data from the database is discarded. This even happens if the
previous object is still an unloaded proxy.
This list might be incomplete.
Array Hydration
^^^^^^^^^^^^^^^
@@ -1453,13 +1382,7 @@ Given that there are 10 users and corresponding addresses in the database the ex
SELECT * FROM address WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
.. note::
Changing the fetch mode during a query mostly makes sense for one-to-one and many-to-one relations. In that case,
all the necessary IDs are available after the root entity (``user`` in the above example) has been loaded. So, one
query per association can be executed to fetch all the referred-to entities (``address``).
For one-to-many relations, changing the fetch mode to eager will cause to execute one query **for every root entity
loaded**. This gives no improvement over the ``lazy`` fetch mode which will also initialize the associations on
a one-by-one basis once they are accessed.
Changing the fetch mode during a query is only possible for one-to-one and many-to-one relations.
EBNF
@@ -1488,9 +1411,7 @@ Terminals
~~~~~~~~~
- identifier (name, email, ...) must match ``[a-z_][a-z0-9_]*``
- fully_qualified_name (Doctrine\Tests\Models\CMS\CmsUser) matches PHP's fully qualified class names
- aliased_name (CMS:CmsUser) uses two identifiers, one for the namespace alias and one for the class inside it
- identifier (name, email, ...)
- string ('foo', 'bar''s house', '%ninja%', ...)
- char ('/', '\\', ' ', ...)
- integer (-1, 0, 1, 34, ...)
@@ -1524,8 +1445,8 @@ Identifiers
/* Alias Identification declaration (the "u" of "FROM User u") */
AliasIdentificationVariable :: = identifier
/* identifier that must be a class name (the "User" of "FROM User u"), possibly as a fully qualified class name or namespace-aliased */
AbstractSchemaName ::= fully_qualified_name | aliased_name | identifier
/* identifier that must be a class name (the "User" of "FROM User u") */
AbstractSchemaName ::= identifier
/* Alias ResultVariable declaration (the "total" of "COUNT(*) AS total") */
AliasResultVariable = identifier
@@ -1624,7 +1545,7 @@ Select Expressions
SimpleSelectExpression ::= (StateFieldPathExpression | IdentificationVariable | FunctionDeclaration | AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable]
PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")"
NewObjectExpression ::= "NEW" IdentificationVariable "(" NewObjectArg {"," NewObjectArg}* ")"
NewObjectArg ::= ScalarExpression | "(" Subselect ")"
Conditional Expressions

View File

@@ -164,8 +164,7 @@ the life-time of their registered entities.
database insert operations. Generated primary key values are
available in the postPersist event.
- preUpdate - The preUpdate event occurs before the database
update operations to entity data. It is not called for a DQL UPDATE statement
nor when the computed changeset is empty.
update operations to entity data. It is not called for a DQL UPDATE statement.
- postUpdate - The postUpdate event occurs after the database
update operations to entity data. It is not called for a DQL UPDATE statement.
- postLoad - The postLoad event occurs for an entity after the
@@ -179,7 +178,7 @@ the life-time of their registered entities.
allows providing fallback metadata even when no actual metadata exists
or could be found. This event is not a lifecycle callback.
- preFlush - The preFlush event occurs at the very beginning of a flush
operation.
operation. This event is not a lifecycle callback.
- onFlush - The onFlush event occurs after the change-sets of all
managed entities are computed. This event is not a lifecycle
callback.
@@ -323,7 +322,7 @@ XML would look something like this:
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
/Users/robo/dev/php/Doctrine/doctrine-mapping.xsd">
<entity name="User">
@@ -406,8 +405,8 @@ behaviors across different entity classes.
Note that they require much more detailed knowledge about the inner
workings of the EntityManager and UnitOfWork. Please read the
:ref:`reference-events-implementing-listeners` section carefully if you
are trying to write your own listener.
*Implementing Event Listeners* section carefully if you are trying
to write your own listener.
For event subscribers, there are no surprises. They declare the
lifecycle events in their ``getSubscribedEvents`` method and provide
@@ -434,7 +433,7 @@ A lifecycle event listener looks like the following:
}
}
A lifecycle event subscriber may look like this:
A lifecycle event subscriber may looks like this:
.. code-block:: php
@@ -548,9 +547,8 @@ preFlush
~~~~~~~~
``preFlush`` is called at ``EntityManager#flush()`` before
anything else. ``EntityManager#flush()`` should not be called inside
its listeners, since `preFlush` event is dispatched in it, which would
result in infinite loop.
anything else. ``EntityManager#flush()`` can be called safely
inside its listeners.
.. code-block:: php
@@ -654,8 +652,7 @@ preUpdate
PreUpdate is the most restrictive to use event, since it is called
right before an update statement is called for an entity inside the
``EntityManager#flush()`` method. Note that this event is not
triggered when the computed changeset is empty.
``EntityManager#flush()`` method.
Changes to associations of the updated entity are never allowed in
this event, since Doctrine cannot guarantee to correctly handle
@@ -741,7 +738,7 @@ The three post events are called inside ``EntityManager#flush()``.
Changes in here are not relevant to the persistence in the
database, but you can use these events to alter non-persistable items,
like non-mapped fields, logging or even associated classes that are
not directly mapped by Doctrine.
directly mapped by Doctrine.
postLoad
~~~~~~~~
@@ -889,9 +886,6 @@ you need to map the listener method using the event type mapping:
preRemove: [preRemoveHandler]
# ....
.. note::
The order of execution of multiple methods for the same event (e.g. multiple @PrePersist) is not guaranteed.
Entity listeners resolver

View File

@@ -21,6 +21,12 @@ created database tables and columns.
Entity Classes
--------------
I access a variable and its null, what is wrong?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If this variable is a public variable then you are violating one of the criteria for entities.
All properties have to be protected or private for the proxy object pattern to work.
How can I add default values to a column?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -198,21 +204,6 @@ No, it is not supported to sort by function in DQL. If you need this functionali
use a native-query or come up with another solution. As a side note: Sorting with ORDER BY RAND() is painfully slow
starting with 1000 rows.
Is it better to write DQL or to generate it with the query builder?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The purpose of the ``QueryBuilder`` is to generate DQL dynamically,
which is useful when you have optional filters, conditional joins, etc.
But the ``QueryBuilder`` is not an alternative to DQL, it actually generates DQL
queries at runtime, which are then interpreted by Doctrine. This means that
using the ``QueryBuilder`` to build and run a query is actually always slower
than only running the corresponding DQL query.
So if you only need to generate a query and bind parameters to it,
you should use plain DQL, as this is a simpler and much more readable solution.
You should only use the ``QueryBuilder`` when you can't achieve what you want to do with a DQL query.
A Query fails, how can I debug it?
----------------------------------

View File

@@ -39,7 +39,7 @@ proper quoting of parameters.
<?php
namespace Example;
use Doctrine\ORM\Mapping\ClassMetadata,
use Doctrine\ORM\Mapping\ClassMetaData,
Doctrine\ORM\Query\Filter\SQLFilter;
class MyLocaleFilter extends SQLFilter

View File

@@ -4,7 +4,7 @@ Improving Performance
Bytecode Cache
--------------
It is highly recommended to make use of a bytecode cache like OPcache.
It is highly recommended to make use of a bytecode cache like APC.
A bytecode cache removes the need for parsing PHP code on every
request and can greatly improve performance.
@@ -26,8 +26,6 @@ Doctrine will need to load your mapping information on every single
request and has to parse each DQL query on every single request.
This is a waste of resources.
See :ref:`integrating-with-the-orm`
Alternative Query Result Formats
--------------------------------
@@ -44,8 +42,6 @@ for updates, which means when you call flush on the EntityManager these entities
even if properties changed. Read-Only allows to persist new entities of a kind and remove existing
ones, they are just not considered for updates.
See :ref:`annref_entity`
Extra-Lazy Collections
----------------------
@@ -56,7 +52,7 @@ for more information on how this fetch mode works.
Temporarily change fetch mode in DQL
------------------------------------
See :ref:`dql-temporarily-change-fetch-mode`
See :ref:`Doctrine Query Language chapter <dql-temporarily-change-fetch-mode>`
Apply Best Practices
@@ -65,7 +61,6 @@ Apply Best Practices
A lot of the points mentioned in the Best Practices chapter will
also positively affect the performance of Doctrine.
See :doc:`Best Practices <reference/best-practices>`
Change Tracking policies
------------------------

View File

@@ -125,8 +125,9 @@ Example:
Things to note:
- The @InheritanceType and @DiscriminatorColumn must be specified
on the topmost class that is part of the mapped entity hierarchy.
- The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap
must be specified on the topmost class that is part of the mapped
entity hierarchy.
- The @DiscriminatorMap specifies which values of the
discriminator column identify a row as being of a certain type. In
the case above a value of "person" identifies a row as being of
@@ -159,7 +160,7 @@ This strategy is very efficient for querying across all types in
the hierarchy or for specific types. No table joins are required,
only a WHERE clause listing the type identifiers. In particular,
relationships involving types that employ this mapping strategy are
very performing.
very performant.
There is a general performance consideration with Single Table
Inheritance: If the target-entity of a many-to-one or one-to-one
@@ -174,7 +175,7 @@ SQL Schema considerations
For Single-Table-Inheritance to work in scenarios where you are
using either a legacy database schema or a self-written database
schema you have to make sure that all columns that are not in the
root entity but in any of the different sub-entities has to allow
root entity but in any of the different sub-entities has to allows
null values. Columns that have NOT NULL constraints have to be on
the root entity of the single-table inheritance hierarchy.
@@ -454,8 +455,6 @@ Things to note:
- This feature is available for all kind of associations. (OneToOne, OneToMany, ManyToOne, ManyToMany)
- The association type *CANNOT* be changed.
- The override could redefine the joinTables or joinColumns depending on the association type.
- The override could redefine inversedBy to reference more than one extended entity.
- The override could redefine fetch to modify the fetch strategy of the extended entity.
Attribute Override
~~~~~~~~~~~~~~~~~~~~
@@ -493,7 +492,7 @@ Could be used by an entity that extends a mapped superclass to override a field
* column=@Column(
* name = "guest_id",
* type = "integer",
* length = 140
length = 140
* )
* ),
* @AttributeOverride(name="name",
@@ -501,7 +500,7 @@ Could be used by an entity that extends a mapped superclass to override a field
* name = "guest_name",
* nullable = false,
* unique = true,
* length = 240
length = 240
* )
* )
* })

View File

@@ -1,4 +1,5 @@
Installation
============
The installation chapter has moved to :doc:`Installation and Configuration <reference/configuration>`_.
The installation chapter has moved to `Installation and Configuration
<reference/configuration>`_.

View File

@@ -63,7 +63,7 @@ Where the ``attribute_name`` column contains the key and
``$attributes``.
The feature request for persistence of primitive value arrays
`is described in the DDC-298 ticket <https://github.com/doctrine/orm/issues/3743>`_.
`is described in the DDC-298 ticket <http://www.doctrine-project.org/jira/browse/DDC-298>`_.
Cascade Merge with Bi-directional Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -71,8 +71,8 @@ Cascade Merge with Bi-directional Associations
There are two bugs now that concern the use of cascade merge in combination with bi-directional associations.
Make sure to study the behavior of cascade merge if you are using it:
- `DDC-875 <https://github.com/doctrine/orm/issues/5398>`_ Merge can sometimes add the same entity twice into a collection
- `DDC-763 <https://github.com/doctrine/orm/issues/5277>`_ Cascade merge on associated entities can insert too many rows through "Persistence by Reachability"
- `DDC-875 <http://www.doctrine-project.org/jira/browse/DDC-875>`_ Merge can sometimes add the same entity twice into a collection
- `DDC-763 <http://www.doctrine-project.org/jira/browse/DDC-763>`_ Cascade merge on associated entities can insert too many rows through "Persistence by Reachability"
Custom Persisters
~~~~~~~~~~~~~~~~~
@@ -83,8 +83,10 @@ Currently there is no way to overwrite the persister implementation
for a given entity, however there are several use-cases that can
benefit from custom persister implementations:
- `Add Upsert Support <https://github.com/doctrine/orm/issues/5178>`_
- `Evaluate possible ways in which stored-procedures can be used <https://github.com/doctrine/orm/issues/4946>`_
- `Add Upsert Support <http://www.doctrine-project.org/jira/browse/DDC-668>`_
- `Evaluate possible ways in which stored-procedures can be used <http://www.doctrine-project.org/jira/browse/DDC-445>`_
- The previous Filter Rules Feature Request
Persist Keys of Collections
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -94,7 +96,7 @@ PHP Arrays are ordered hash-maps and so should be the
evaluate a feature that optionally persists and hydrates the keys
of a Collection instance.
`Ticket DDC-213 <https://github.com/doctrine/orm/issues/2817>`_
`Ticket DDC-213 <http://www.doctrine-project.org/jira/browse/DDC-213>`_
Mapping many tables to one entity
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -112,10 +114,11 @@ in the core library. We don't think behaviors add more value than
they cost pain and debugging hell. Please see the many different
blog posts we have written on this topics:
- `Doctrine2 "Behaviors" in a Nutshell <http://www.doctrine-project.org/2010/02/17/doctrine2-behaviours-nutshell.html>`_
- `A re-usable Versionable behavior for Doctrine2 <http://www.doctrine-project.org/2010/02/24/doctrine2-versionable.html>`_
- `Write your own ORM on top of Doctrine2 <http://www.doctrine-project.org/2010/07/19/your-own-orm-doctrine2.html>`_
- `Doctrine 2 Behavioral Extensions <http://www.doctrine-project.org/2010/11/18/doctrine2-behavioral-extensions.html>`_
- `Doctrine2 "Behaviors" in a Nutshell <http://www.doctrine-project.org/blog/doctrine2-behaviours-nutshell>`_
- `A re-usable Versionable behavior for Doctrine2 <http://www.doctrine-project.org/blog/doctrine2-versionable>`_
- `Write your own ORM on top of Doctrine2 <http://www.doctrine-project.org/blog/your-own-orm-doctrine2>`_
- `Doctrine 2 Behavioral Extensions <http://www.doctrine-project.org/blog/doctrine2-behavioral-extensions>`_
- `Doctrator <https://github.com/pablodip/doctrator`>_
Doctrine 2 has enough hooks and extension points so that **you** can
add whatever you want on top of it. None of this will ever become
@@ -143,7 +146,9 @@ backwards compatibility issues or where no simple fix exists (yet).
We don't plan to add every bug in the tracker there, just those
issues that can potentially cause nightmares or pain of any sort.
See bugs, improvement and feature requests on `Github issues <https://github.com/doctrine/orm/issues>`_.
See the Open Bugs on Jira for more details on `bugs, improvement and feature
requests
<http://www.doctrine-project.org/jira/secure/IssueNavigator.jspa?reset=true&mode=hide&pid=10032&resolution=-1&sorter/field=updated&sorter/order=DESC>`_.
Identifier Quoting and Legacy Databases
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -177,27 +182,3 @@ MySQL with MyISAM tables
Doctrine cannot provide atomic operations when calling ``EntityManager#flush()`` if one
of the tables involved uses the storage engine MyISAM. You must use InnoDB or
other storage engines that support transactions if you need integrity.
Entities, Proxies and Reflection
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Using methods for Reflection on entities can be prone to error, when the entity
is actually a proxy the following methods will not work correctly:
- ``new ReflectionClass``
- ``new ReflectionObject``
- ``get_class()``
- ``get_parent_class()``
This is why ``Doctrine\Common\Util\ClassUtils`` class exists that has similar
methods, which resolve the proxy problem beforehand.
.. code-block:: php
<?php
use Doctrine\Common\Util\ClassUtils;
$bookProxy = $entityManager->getReference('Acme\Book');
$reflection = ClassUtils::newReflectionClass($bookProxy);
$class = ClassUtils::getClass($bookProxy)¸

View File

@@ -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

View File

@@ -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);

View File

@@ -63,7 +63,7 @@ This has several benefits:
- The API is much simpler than the usual ``ResultSetMapping`` API.
One downside is that the builder API does not yet support entities
with inheritance hierarchies.
with inheritance hierachies.
.. code-block:: php
@@ -92,7 +92,7 @@ a mapping from DQL alias (key) to SQL alias (value)
<?php
$selectClause = $rsm->generateSelectClause(array(
$selectClause = $builder->generateSelectClause(array(
'u' => 't1',
'g' => 't2'
));

View File

@@ -7,20 +7,14 @@ conditionally constructing a DQL query in several steps.
It provides a set of classes and methods that is able to
programmatically build queries, and also provides a fluent API.
This means that you can change between one methodology to the other
as you want, or just pick a preferred one.
.. note::
The ``QueryBuilder`` is not an abstraction of DQL, but merely a tool to dynamically build it.
You should still use plain DQL when you can, as it is simpler and more readable.
More about this in the :doc:`FAQ <faq>`_.
as you want, and also pick one if you prefer.
Constructing a new QueryBuilder object
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The same way you build a normal Query, you build a ``QueryBuilder``
object. Here is an example of how to build a ``QueryBuilder``
object:
object, just providing the correct method name. Here is an example
how to build a ``QueryBuilder`` object:
.. code-block:: php
@@ -30,9 +24,9 @@ object:
// example1: creating a QueryBuilder instance
$qb = $em->createQueryBuilder();
An instance of QueryBuilder has several informative methods. One
good example is to inspect what type of object the
``QueryBuilder`` is.
Once you have created an instance of QueryBuilder, it provides a
set of useful informative functions that you can use. One good
example is to inspect what type of object the ``QueryBuilder`` is.
.. code-block:: php
@@ -86,11 +80,11 @@ Working with QueryBuilder
High level API methods
^^^^^^^^^^^^^^^^^^^^^^
The most straightforward way to build a dynamic query with the ``QueryBuilder`` is by taking
advantage of Helper methods. For all base code, there is a set of
useful methods to simplify a programmer's life. To illustrate how
to work with them, here is the same example 6 re-written using
``QueryBuilder`` helper methods:
To simplify even more the way you build a query in Doctrine, we can take
advantage of what we call Helper methods. For all base code, there
is a set of useful methods to simplify a programmer's life. To
illustrate how to work with them, here is the same example 6
re-written using ``QueryBuilder`` helper methods:
.. code-block:: php
@@ -103,9 +97,10 @@ to work with them, here is the same example 6 re-written using
->orderBy('u.name', 'ASC');
``QueryBuilder`` helper methods are considered the standard way to
use the ``QueryBuilder``. The ``$qb->expr()->*`` methods can help you
build conditional expressions dynamically. Here is a converted example 8 to
suggested way to build queries with dynamic conditions:
build DQL queries. Although it is supported, it should be avoided
to use string based queries and greatly encouraged to use
``$qb->expr()->*`` methods. Here is a converted example 8 to
suggested standard way to build queries:
.. code-block:: php
@@ -118,7 +113,7 @@ suggested way to build queries with dynamic conditions:
$qb->expr()->eq('u.id', '?1'),
$qb->expr()->like('u.nickname', '?2')
))
->orderBy('u.surname', 'ASC');
->orderBy('u.surname', 'ASC'));
Here is a complete list of helper methods available in ``QueryBuilder``:
@@ -131,7 +126,7 @@ Here is a complete list of helper methods available in ``QueryBuilder``:
// Example - $qb->select(array('u', 'p'))
// Example - $qb->select($qb->expr()->select('u', 'p'))
public function select($select = null);
// addSelect does not override previous calls to select
//
// Example - $qb->select('u');
@@ -322,7 +317,7 @@ the Query object which can be retrieved from ``EntityManager#createQuery()``.
Executing a Query
^^^^^^^^^^^^^^^^^
The QueryBuilder is a builder object only - it has no means of actually
The QueryBuilder is a builder object only, it has no means of actually
executing the Query. Additionally a set of parameters such as query hints
cannot be set on the QueryBuilder itself. This is why you always have to convert
a querybuilder instance into a Query object:
@@ -504,32 +499,14 @@ complete list of supported helper methods available:
public function countDistinct($x); // Returns Expr\Func
}
Adding a Criteria to a Query
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can also add a :ref:`filtering-collections` to a QueryBuilder by
using ``addCriteria``:
.. code-block:: php
<?php
use Doctrine\Common\Collections\Criteria;
// ...
$criteria = Criteria::create()
->orderBy(['firstName', 'ASC']);
// $qb instanceof QueryBuilder
$qb->addCriteria($criteria);
// then execute your query like normal
Low Level API
^^^^^^^^^^^^^
Now we will describe the low level method of creating queries.
It may be useful to work at this level for optimization purposes,
but most of the time it is preferred to work at a higher level of
abstraction.
Now we have describe the low level (thought of as the
hardcore method) of creating queries. It may be useful to work at
this level for optimization purposes, but most of the time it is
preferred to work at a higher level of abstraction.
All helper methods in ``QueryBuilder`` actually rely on a single
one: ``add()``. This method is responsible of building every piece
@@ -582,3 +559,7 @@ same query of example 6 written using
->add('where', new Expr\Comparison('u.id', '=', '?1'))
->add('orderBy', new Expr\OrderBy('u.name', 'ASC'));
Of course this is the hardest way to build a DQL query in Doctrine.
To simplify some of these efforts, we introduce what we call as
``Expr`` helper class.

View File

@@ -11,7 +11,7 @@ The Second Level Cache is designed to reduce the amount of necessary database ac
It sits between your application and the database to avoid the number of database hits as much as possible.
When turned on, entities will be first searched in cache and if they are not found,
a database query will be fired and then the entity result will be stored in a cache provider.
a database query will be fired an then the entity result will be stored in a cache provider.
There are some flavors of caching available, but is better to cache read-only data.
@@ -97,7 +97,7 @@ Defines a contract for accessing a particular region.
Defines a contract for accessing a particular cache region.
`See API Doc <https://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/Region.html>`_.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.Region.html/>`_.
Concurrent cache region
~~~~~~~~~~~~~~~~~~~~~~~
@@ -111,7 +111,7 @@ If you want to use an ``READ_WRITE`` cache, you should consider providing your o
Defines contract for concurrently managed data region.
`See API Doc <https://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/ConcurrentRegion.html>`_.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.ConcurrentRegion.html/>`_.
Timestamp region
~~~~~~~~~~~~~~~~
@@ -120,7 +120,7 @@ Timestamp region
Tracks the timestamps of the most recent updates to particular entity.
`See API Doc <http://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/TimestampRegion.html>`_.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.TimestampRegion.html/>`_.
.. _reference-second-level-cache-mode:
@@ -149,25 +149,25 @@ Caching mode
Built-in cached persisters
~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Cached persisters are responsible to access cache regions.
+-----------------------+-------------------------------------------------------------------------------------------+
| Cache Usage | Persister |
+=======================+===========================================================================================+
| READ_ONLY | Doctrine\\ORM\\Cache\\Persister\\Entity\\ReadOnlyCachedEntityPersister |
+-----------------------+-------------------------------------------------------------------------------------------+
| READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\Entity\\ReadWriteCachedEntityPersister |
+-----------------------+-------------------------------------------------------------------------------------------+
| NONSTRICT_READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\Entity\\NonStrictReadWriteCachedEntityPersister |
+-----------------------+-------------------------------------------------------------------------------------------+
| READ_ONLY | Doctrine\\ORM\\Cache\\Persister\\Collection\\ReadOnlyCachedCollectionPersister |
+-----------------------+-------------------------------------------------------------------------------------------+
| READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\Collection\\ReadWriteCachedCollectionPersister |
+-----------------------+-------------------------------------------------------------------------------------------+
| NONSTRICT_READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\Collection\\NonStrictReadWriteCachedCollectionPersister |
+-----------------------+-------------------------------------------------------------------------------------------+
+-----------------------+-------------------------------------------------------------------------------+
| Cache Usage | Persister |
+=======================+===============================================================================+
| READ_ONLY | Doctrine\\ORM\\Cache\\Persister\\ReadOnlyCachedEntityPersister |
+-----------------------+-------------------------------------------------------------------------------+
| READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\ReadWriteCachedEntityPersister |
+-----------------------+-------------------------------------------------------------------------------+
| NONSTRICT_READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\NonStrictReadWriteCachedEntityPersister |
+-----------------------+-------------------------------------------------------------------------------+
| READ_ONLY | Doctrine\\ORM\\Cache\\Persister\\ReadOnlyCachedCollectionPersister |
+-----------------------+-------------------------------------------------------------------------------+
| READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\ReadWriteCachedCollectionPersister |
+-----------------------+-------------------------------------------------------------------------------+
| NONSTRICT_READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\NonStrictReadWriteCacheCollectionPersister |
+-----------------------+-------------------------------------------------------------------------------+
Configuration
-------------
@@ -209,7 +209,7 @@ It allows you to provide a specific implementation of the following components :
* ``EntityHydrator`` Transform an entity into a cache entry and cache entry into entities
* ``CollectionHydrator`` Transform a collection into a cache entry and cache entry into collection
`See API Doc <http://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/DefaultCacheFactory.html>`_.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.DefaultCacheFactory.html/>`_.
Region Lifetime
~~~~~~~~~~~~~~~
@@ -220,7 +220,7 @@ To specify a default lifetime for all regions or specify a different lifetime fo
<?php
/* @var $config \Doctrine\ORM\Configuration */
/* @var $cacheConfig \Doctrine\ORM\Cache\CacheConfiguration */
/* @var $cacheConfig \Doctrine\ORM\Configuration */
$cacheConfig = $config->getSecondLevelCacheConfiguration();
$regionConfig = $cacheConfig->getRegionsConfiguration();
@@ -239,7 +239,7 @@ By providing a cache logger you should be able to get information about all cach
<?php
/* @var $config \Doctrine\ORM\Configuration */
$logger = new \Doctrine\ORM\Cache\Logging\StatisticsCacheLogger();
$logger = \Doctrine\ORM\Cache\Logging\StatisticsCacheLogger();
// Cache logger
$config->setSecondLevelCacheEnabled(true);
@@ -270,7 +270,7 @@ By providing a cache logger you should be able to get information about all cach
If you want to get more information you should implement ``\Doctrine\ORM\Cache\Logging\CacheLogger``.
and collect all information you want.
`See API Doc <http://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/Logging/CacheLogger.html>`_.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.CacheLogger.html/>`_.
Entity cache definition
@@ -310,7 +310,7 @@ Entity cache definition
.. code-block:: xml
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Country">
<cache usage="READ_ONLY" region="my_entity_region" />
<id name="id" type="integer" column="id">
@@ -386,7 +386,7 @@ It caches the primary keys of association and cache each element will be cached
.. code-block:: xml
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="State">
<cache usage="NONSTRICT_READ_WRITE" />
@@ -619,7 +619,7 @@ Execute the ``UPDATE`` and invalidate ``a specific cache entry`` using the cache
$em->getCache()->evictEntity('Entity\Country', 1);
Using the repository query cache
--------------------------------
---------------------
As well as ``Query Cache`` all persister queries store only identifier values for an individual query.
All persister use a single timestamps cache region keeps track of the last update for each persister,

View File

@@ -10,7 +10,7 @@ we cannot protect you from SQL injection.
Please also read the documentation chapter on Security in Doctrine DBAL. This
page only handles Security issues in the ORM.
- `DBAL Security Page <http://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/security.html>`
- [DBAL Security Page](https://github.com/doctrine/dbal/blob/master/docs/en/reference/security.rst)
If you find a Security bug in Doctrine, please report it on Jira and change the
Security Level to "Security Issues". It will be visible to Doctrine Core
@@ -32,7 +32,7 @@ You can consider the following APIs to be safe from SQL injection:
- Queries through the Criteria API on ``Doctrine\ORM\PersistentCollection`` and
``Doctrine\ORM\EntityRepository``.
You are **NOT** safe from SQL injection when using user input with:
You are **NOT** save from SQL injection when using user input with:
- Expression API of ``Doctrine\ORM\QueryBuilder``
- Concatenating user input into DQL SELECT, UPDATE or DELETE statements or
@@ -119,7 +119,7 @@ entity might look like this:
}
Now the possiblity of mass-asignment exists on this entity and can
be exploited by attackers to set the "isAdmin" flag to true on any
be exploitet by attackers to set the "isAdmin" flag to true on any
object when you pass the whole request data to this method like:
.. code-block:: php

View File

@@ -205,7 +205,7 @@ tables of the current model to clean up with orphaned tables.
You can also use database introspection to update your schema
easily with the ``updateSchema()`` method. It will compare your
existing database schema to the passed array of
``ClassMetadataInfo`` instances.
``ClassMetdataInfo`` instances.
.. code-block:: php
@@ -252,6 +252,15 @@ will output the SQL for the ran operation.
Before using the orm:schema-tool commands, remember to configure
your cli-config.php properly.
.. note::
When using the Annotation Mapping Driver you have to either setup
your autoloader in the cli-config.php correctly to find all the
entities, or you can use the second argument of the
``EntityManagerHelper`` to specify all the paths of your entities
(or mapping files), i.e.
``new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em, $mappingPaths);``
Entity Generation
-----------------
@@ -376,7 +385,7 @@ First you need to retrieve the metadata instances with the
)
);
$cmf = new \Doctrine\ORM\Tools\DisconnectedClassMetadataFactory();
$cmf = new DisconnectedClassMetadataFactory();
$cmf->setEntityManager($em);
$metadata = $cmf->getAllMetadata();
@@ -386,7 +395,6 @@ to yml:
.. code-block:: php
<?php
$cme = new \Doctrine\ORM\Tools\Export\ClassMetadataExporter();
$exporter = $cme->getExporter('yml', '/path/to/export/yml');
$exporter->setMetadata($metadata);
$exporter->export();
@@ -468,7 +476,7 @@ To include a new command on Doctrine Console, you need to do modify the
<?php
// doctrine.php
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Helper\Application;
// as before ...

View File

@@ -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
~~~~~~~~~~~~~~~~~~~

View File

@@ -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.

View File

@@ -156,7 +156,7 @@ wishes to be hydrated. Default result-types include:
- SQL to simple scalar result arrays
- SQL to a single result variable
Hydration to entities and arrays is one of the most complex parts of Doctrine
Hydration to entities and arrays is one of most complex parts of Doctrine
algorithm-wise. It can build results with for example:
- Single table selects

View File

@@ -15,7 +15,7 @@ with associations in Doctrine:
removed, not the entity itself. A collection of entities always
only represents the association to the containing entities, not the
entity itself.
- When a bidirectional association is updated, Doctrine only checks
- When a bidirectional assocation is updated, Doctrine only checks
on one of both sides for these changes. This is called the :doc:`owning side <unitofwork-associations>`
of the association.
- A property with a reference to many entities has to be instances of the
@@ -238,14 +238,14 @@ the database permanently.
Notice how both sides of the bidirectional association are always
updated. Unidirectional associations are consequently simpler to
handle.
Also note that if you use type-hinting in your methods, you will
have to specify a nullable type, i.e. ``setAddress(?Address $address)``,
otherwise ``setAddress(null)`` will fail to remove the association.
Another way to deal with this is to provide a special method, like
``removeAddress()``. This can also provide better encapsulation as
it hides the internal meaning of not having an address.
handle. Also note that if you use type-hinting in your methods, i.e.
``setAddress(Address $address)``, PHP will only allow null
values if ``null`` is set as default value. Otherwise
setAddress(null) will fail for removing the association. If you
insist on type-hinting a typical way to deal with this is to
provide a special method, like ``removeAddress()``. This can also
provide better encapsulation as it hides the internal meaning of
not having an address.
When working with collections, keep in mind that a Collection is
essentially an ordered map (just like a PHP array). That is why the
@@ -396,25 +396,54 @@ There are two approaches to handle this problem in your code:
1. Ignore updating the inverse side of bidirectional collections,
BUT never read from them in requests that changed their state. In
the next request Doctrine hydrates the consistent collection state
the next Request Doctrine hydrates the consistent collection state
again.
2. Always keep the bidirectional collections in sync through
association management methods. Reads of the Collections directly
after changes are consistent then.
.. _transitive-persistence:
Transitive persistence / Cascade Operations
-------------------------------------------
Doctrine 2 provides a mechanism for transitive persistence through cascading of certain operations.
Each association to another entity or a collection of
entities can be configured to automatically cascade the following operations to the associated entities:
``persist``, ``remove``, ``merge``, ``detach``, ``refresh`` or ``all``.
Persisting, removing, detaching, refreshing and merging individual entities can
become pretty cumbersome, especially when a highly interweaved object graph
is involved. Therefore Doctrine 2 provides a
mechanism for transitive persistence through cascading of these
operations. Each association to another entity or a collection of
entities can be configured to automatically cascade certain
operations. By default, no operations are cascaded.
The main use case for ``cascade: persist`` is to avoid "exposing" associated entities to your PHP application.
Continuing with the User-Comment example of this chapter, this is how the creation of a new user and a new
comment might look like in your controller (without ``cascade: persist``):
The following cascade options exist:
- persist : Cascades persist operations to the associated
entities.
- remove : Cascades remove operations to the associated entities.
- merge : Cascades merge operations to the associated entities.
- detach : Cascades detach operations to the associated entities.
- refresh : Cascades refresh operations to the associated entities.
- all : Cascades persist, remove, merge, refresh and detach operations to
associated entities.
.. note::
Cascade operations are performed in memory. That means collections and related entities
are fetched into memory, even if they are still marked as lazy when
the cascade operation is about to be performed. However this approach allows
entity lifecycle events to be performed for each of these operations.
However, pulling objects graph into memory on cascade can cause considerable performance
overhead, especially when cascading collections are large. Makes sure
to weigh the benefits and downsides of each cascade operation that you define.
To rely on the database level cascade operations for the delete operation instead, you can
configure each join column with the **onDelete** option. See the respective
mapping driver chapters for more information.
The following example is an extension to the User-Comment example
of this chapter. Suppose in our application a user is created
whenever he writes his first comment. In this case we would use the
following code:
.. code-block:: php
@@ -424,39 +453,37 @@ comment might look like in your controller (without ``cascade: persist``):
$user->addComment($myFirstComment);
$em->persist($user);
$em->persist($myFirstComment); // required, if `cascade: persist` is not set
$em->persist($myFirstComment);
$em->flush();
Note that the Comment entity is instantiated right here in the controller.
To avoid this, ``cascade: persist`` allows you to "hide" the Comment entity from the controller,
only accessing it through the User entity:
Even if you *persist* a new User that contains our new Comment this
code would fail if you removed the call to
``EntityManager#persist($myFirstComment)``. Doctrine 2 does not
cascade the persist operation to all nested entities that are new
as well.
More complicated is the deletion of all of a user's comments when he is
removed from the system:
.. code-block:: php
<?php
// User entity
class User
{
private $id;
private $comments;
public function __construct()
{
$this->id = User::new();
$this->comments = new ArrayCollection();
}
public function comment(string $text, DateTimeInterface $time) : void
{
$newComment = Comment::create($text, $time);
$newComment->setUser($this);
$this->comments->add($newComment);
}
// ...
$user = $em->find('User', $deleteUserId);
foreach ($user->getAuthoredComments() as $comment) {
$em->remove($comment);
}
$em->remove($user);
$em->flush();
If you then set up the cascading to the ``User#commentsAuthored`` property...
Without the loop over all the authored comments Doctrine would use
an UPDATE statement only to set the foreign key to NULL and only
the User would be deleted from the database during the
flush()-Operation.
To have Doctrine handle both cases automatically we can change the
``User#commentsAuthored`` property to cascade both the "persist"
and the "remove" operation.
.. code-block:: php
@@ -473,51 +500,10 @@ If you then set up the cascading to the ``User#commentsAuthored`` property...
//...
}
...you can now create a user and an associated comment like this:
.. code-block:: php
<?php
$user = new User();
$user->comment('Lorem ipsum', new DateTime());
$em->persist($user);
$em->flush();
.. note::
The idea of ``cascade: persist`` is not to save you any lines of code in the controller.
If you instantiate the comment object in the controller (i.e. don't set up the user entity as shown above),
even with ``cascade: persist`` you still have to call ``$myFirstComment->setUser($user);``.
Thanks to ``cascade: remove``, you can easily delete a user and all linked comments without having to loop through them:
.. code-block:: php
<?php
$user = $em->find('User', $deleteUserId);
$em->remove($user);
$em->flush();
.. note::
Cascade operations are performed in memory. That means collections and related entities
are fetched into memory (even if they are marked as lazy) when
the cascade operation is about to be performed. This approach allows
entity lifecycle events to be performed for each of these operations.
However, pulling object graphs into memory on cascade can cause considerable performance
overhead, especially when the cascaded collections are large. Make sure
to weigh the benefits and downsides of each cascade operation that you define.
To rely on the database level cascade operations for the delete operation instead, you can
configure each join column with :doc:`the onDelete option <working-with-objects>`.
Even though automatic cascading is convenient, it should be used
with care. Do not blindly apply ``cascade=all`` to all associations as
Even though automatic cascading is convenient it should be used
with care. Do not blindly apply cascade=all to all associations as
it will unnecessarily degrade the performance of your application.
For each cascade operation that gets activated, Doctrine also
For each cascade operation that gets activated Doctrine also
applies that operation to the association, be it single or
collection valued.
@@ -525,20 +511,21 @@ Persistence by Reachability: Cascade Persist
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are additional semantics that apply to the Cascade Persist
operation. During each ``flush()`` operation Doctrine detects if there
operation. During each flush() operation Doctrine detects if there
are new entities in any collection and three possible cases can
happen:
1. New entities in a collection marked as ``cascade: persist`` will be
1. New entities in a collection marked as cascade persist will be
directly persisted by Doctrine.
2. New entities in a collection not marked as ``cascade: persist`` will
produce an Exception and rollback the ``flush()`` operation.
2. New entities in a collection not marked as cascade persist will
produce an Exception and rollback the flush() operation.
3. Collections without new entities are skipped.
This concept is called Persistence by Reachability: New entities
that are found on already managed entities are automatically
persisted as long as the association is defined as ``cascade: persist``.
persisted as long as the association is defined as cascade
persist.
Orphan Removal
--------------
@@ -616,11 +603,11 @@ address reference. When flush is called not only are the references removed
but both the old standing data and the one address entity are also deleted
from the database.
.. _filtering-collections:
Filtering Collections
---------------------
.. filtering-collections:
Collections have a filtering API that allows to slice parts of data from
a collection. If the collection has not been loaded from the database yet,
the filtering API can work on the SQL level to make optimized access to
@@ -716,9 +703,6 @@ methods:
* ``in($field, array $values)``
* ``notIn($field, array $values)``
* ``contains($field, $value)``
* ``memberOf($value, $field)``
* ``startsWith($field, $value)``
* ``endsWith($field, $value)``
.. note::

View File

@@ -25,13 +25,6 @@ Work that have not yet been persisted are lost.
Not calling ``EntityManager#flush()`` will lead to all changes
during that request being lost.
.. note::
Doctrine does NEVER touch the public API of methods in your entity
classes (like getters and setters) nor the constructor method.
Instead, it uses reflection to get/set data from/to your entity objects.
When Doctrine fetches data from DB and saves it back,
any code put in your get/set methods won't be implicitly taken into account.
Entities and the Identity Map
-----------------------------
@@ -245,7 +238,7 @@ as follows:
persist operation. However, the persist operation is cascaded to
entities referenced by X, if the relationships from X to these
other entities are mapped with cascade=PERSIST or cascade=ALL (see
":ref:`transitive-persistence`").
"Transitive Persistence").
- If X is a removed entity, it becomes managed.
- If X is a detached entity, an exception will be thrown on
flush.
@@ -286,12 +279,12 @@ as follows:
- If X is a new entity, it is ignored by the remove operation.
However, the remove operation is cascaded to entities referenced by
X, if the relationship from X to these other entities is mapped
with cascade=REMOVE or cascade=ALL (see ":ref:`transitive-persistence`").
with cascade=REMOVE or cascade=ALL (see "Transitive Persistence").
- If X is a managed entity, the remove operation causes it to
become removed. The remove operation is cascaded to entities
referenced by X, if the relationships from X to these other
entities is mapped with cascade=REMOVE or cascade=ALL (see
":ref:`transitive-persistence`").
"Transitive Persistence").
- If X is a detached entity, an InvalidArgumentException will be
thrown.
- If X is a removed entity, it is ignored by the remove operation.
@@ -357,14 +350,14 @@ as follows:
become detached. The detach operation is cascaded to entities
referenced by X, if the relationships from X to these other
entities is mapped with cascade=DETACH or cascade=ALL (see
":ref:`transitive-persistence`"). Entities which previously referenced X
"Transitive Persistence"). Entities which previously referenced X
will continue to reference X.
- If X is a new or detached entity, it is ignored by the detach
operation.
- If X is a removed entity, the detach operation is cascaded to
entities referenced by X, if the relationships from X to these
other entities is mapped with cascade=DETACH or cascade=ALL (see
":ref:`transitive-persistence`"). Entities which previously referenced X
"Transitive Persistence"). Entities which previously referenced X
will continue to reference X.
There are several situations in which an entity is detached
@@ -423,7 +416,8 @@ as follows:
- If X is a managed entity, it is ignored by the merge operation,
however, the merge operation is cascaded to entities referenced by
relationships from X if these relationships have been mapped with
the cascade element value MERGE or ALL (see ":ref:`transitive-persistence`").
the cascade element value MERGE or ALL (see "Transitive
Persistence").
- For all entities Y referenced by relationships from X having the
cascade element value MERGE or ALL, Y is merged recursively as Y'.
For all such Y referenced by X, X' is set to reference Y'. (Note
@@ -705,6 +699,8 @@ You can also load by owning side associations through the repository:
$number = $em->find('MyProject\Domain\Phonenumber', 1234);
$user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('phone' => $number->getId()));
Be careful that this only works by passing the ID of the associated entity, not yet by passing the associated entity itself.
The ``EntityRepository#findBy()`` method additionally accepts orderings, limit and offset as second to fourth parameters:
.. code-block:: php
@@ -733,14 +729,6 @@ examples are equivalent:
// A single user by its nickname (__call magic)
$user = $em->getRepository('MyProject\Domain\User')->findOneByNickname('romanb');
Additionally, you can just count the result of the provided conditions when you don't really need the data:
.. code-block:: php
<?php
// Check there is no user with nickname
$availableNickname = 0 === $em->getRepository('MyProject\Domain\User')->count(['nickname' => 'nonexistent']);
By Criteria
~~~~~~~~~~~
@@ -750,7 +738,8 @@ The Repository implement the ``Doctrine\Common\Collections\Selectable``
interface. That means you can build ``Doctrine\Common\Collections\Criteria``
and pass them to the ``matching($criteria)`` method.
See section `Filtering collections` of chapter :doc:`Working with Associations <working-with-associations>`
See the :ref:`Working with Associations: Filtering collections
<filtering-collections>`.
By Eager Loading
~~~~~~~~~~~~~~~~
@@ -800,9 +789,7 @@ DQL and its syntax as well as the Doctrine class can be found in
:doc:`the dedicated chapter <dql-doctrine-query-language>`.
For programmatically building up queries based on conditions that
are only known at runtime, Doctrine provides the special
``Doctrine\ORM\QueryBuilder`` class. While this a powerful tool,
it also brings more complexity to your code compared to plain DQL,
so you should only use it when you need it. More information on
``Doctrine\ORM\QueryBuilder`` class. More information on
constructing queries with a QueryBuilder can be found
:doc:`in Query Builder chapter <query-builder>`.

View File

@@ -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" />

View File

@@ -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.

View File

@@ -1,83 +0,0 @@
.. toc::
.. tocheader:: Tutorials
.. toctree::
:depth: 3
tutorials/getting-started
tutorials/getting-started-database
tutorials/getting-started-models
tutorials/working-with-indexed-associations
tutorials/extra-lazy-associations
tutorials/composite-primary-keys
tutorials/ordered-associations
tutorials/override-field-association-mappings-in-subclasses
tutorials/pagination
tutorials/embeddables
.. toc::
.. tocheader:: Reference
.. toctree::
:depth: 3
reference/architecture
reference/configuration
reference/faq
reference/basic-mapping
reference/association-mapping
reference/inheritance-mapping
reference/working-with-objects
reference/working-with-associations
reference/events
reference/unitofwork
reference/unitofwork-associations
reference/transactions-and-concurrency
reference/batch-processing
reference/dql-doctrine-query-language
reference/query-builder
reference/native-sql
reference/change-tracking-policies
reference/partial-objects
reference/xml-mapping
reference/yaml-mapping
reference/annotations-reference
reference/php-mapping
reference/caching
reference/improving-performance
reference/tools
reference/metadata-drivers
reference/best-practices
reference/limitations-and-known-issues
tutorials/pagination
reference/filters
reference/namingstrategy
reference/advanced-configuration
reference/second-level-cache
reference/security
.. toc::
.. tocheader:: Cookbook
.. toctree::
:depth: 3
cookbook/aggregate-fields
cookbook/custom-mapping-types
cookbook/decorator-pattern
cookbook/dql-custom-walkers
cookbook/dql-user-defined-functions
cookbook/implementing-arrayaccess-for-domain-objects
cookbook/implementing-the-notify-changetracking-policy
cookbook/implementing-wakeup-or-clone
cookbook/resolve-target-entity-listener
cookbook/sql-table-prefixes
cookbook/strategy-cookbook-introduction
cookbook/validation-of-entities
cookbook/working-with-datetime
cookbook/mysql-enums
cookbook/advanced-field-value-conversion-using-custom-mapping-types
cookbook/entities-in-session

View File

@@ -75,6 +75,7 @@ Cookbook
cookbook/implementing-arrayaccess-for-domain-objects
cookbook/implementing-the-notify-changetracking-policy
cookbook/implementing-wakeup-or-clone
cookbook/integrating-with-codeigniter
cookbook/resolve-target-entity-listener
cookbook/sql-table-prefixes
cookbook/strategy-cookbook-introduction

View File

@@ -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" />

View File

@@ -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
----------------

View File

@@ -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">
<!-- ... -->

View File

@@ -7,7 +7,7 @@ Getting Started: Database First
start with developing Objects and then map them onto your database. When
you :doc:`Model First <getting-started-models>`, you are modelling your application using tools (for
example UML) and generate database schema and PHP code from this model.
When you have a Database First, you already have a database schema
When you have a :doc:`Database First <getting-started-database>`, you already have a database schema
and generate the corresponding PHP code from it.
.. note::

View File

@@ -5,7 +5,7 @@ Getting Started: Model First
When you :doc:`Code First <getting-started>`, you
start with developing Objects and then map them onto your database. When
you Model First, you are modelling your application using tools (for
you :doc:`Model First <getting-started-models>`, you are modelling your application using tools (for
example UML) and generate database schema and PHP code from this model.
When you have a :doc:`Database First <getting-started-database>`, then you already have a database schema
and generate the corresponding PHP code from it.

View File

@@ -14,25 +14,26 @@ Guide Assumptions
-----------------
This guide is designed for beginners that haven't worked with Doctrine ORM
before. There are some prerequisites for the tutorial that have to be
before. There are some prerequesites for the tutorial that have to be
installed:
- PHP (latest stable version)
- PHP 5.4 or above
- Composer Package Manager (`Install Composer
<https://getcomposer.org/doc/00-intro.md>`_)
<http://getcomposer.org/doc/00-intro.md>`_)
The code of this tutorial is `available on Github <https://github.com/doctrine/doctrine2-orm-tutorial>`_.
.. note::
This tutorial assumes you work with **Doctrine 2.6** and above.
This tutorial assumes you work with **Doctrine 2.4** and above.
Some of the code will not work with lower versions.
What is Doctrine?
-----------------
Doctrine 2 is an `object-relational mapper (ORM) <https://en.wikipedia.org/wiki/Object-relational_mapping>`_
for PHP 7.1+ that provides transparent persistence for PHP objects. It uses the Data Mapper
Doctrine 2 is an `object-relational mapper (ORM)
<http://en.wikipedia.org/wiki/Object-relational_mapping>`_ for PHP 5.4+ that
provides transparent persistence for PHP objects. It uses the Data Mapper
pattern at the heart, aiming for a complete separation of your domain/business
logic from the persistence in a relational database management system.
@@ -50,7 +51,7 @@ Entities are PHP Objects that can be identified over many requests
by a unique identifier or primary key. These classes don't need to extend any
abstract base class or interface. An entity class must not be final
or contain final methods. Additionally it must not implement
**clone** nor **wakeup**, unless it :doc:`does so safely <../cookbook/implementing-wakeup-or-clone>`.
**clone** nor **wakeup** or :doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
An entity contains persistable properties. A persistable property
is an instance variable of the entity that is saved into and retrieved from the database
@@ -61,7 +62,7 @@ An Example Model: Bug Tracker
For this Getting Started Guide for Doctrine we will implement the
Bug Tracker domain model from the
`Zend_Db_Table <https://framework.zend.com/manual/1.12/en/zend.db.adapter.html>`_
`Zend\_Db\_Table <http://framework.zend.com/manual/en/zend.db.table.html>`_
documentation. Reading their documentation we can extract the
requirements:
@@ -79,14 +80,14 @@ Project Setup
-------------
Create a new empty folder for this tutorial project, for example
``doctrine2-tutorial`` and create a new file ``composer.json`` inside
that directory with the following contents:
``doctrine2-tutorial`` and create a new file ``composer.json`` with
the following contents:
::
{
"require": {
"doctrine/orm": "^2.6.2",
"doctrine/orm": "2.4.*",
"symfony/yaml": "2.*"
},
"autoload": {
@@ -102,26 +103,23 @@ Install Doctrine using the Composer Dependency Management tool, by calling:
$ composer install
This will install the packages Doctrine Common, Doctrine DBAL, Doctrine ORM,
into the ``vendor`` directory.
Symfony YAML and Symfony Console into the `vendor` directory. The Symfony
dependencies are not required by Doctrine but will be used in this tutorial.
Add the following directories:
::
doctrine2-tutorial
|-- config
| `-- xml
| |-- xml
| `-- yaml
`-- src
.. note::
The YAML driver is deprecated and will be removed in version 3.0.
It is strongly recommended to switch to one of the other mappings.
Obtaining the EntityManager
---------------------------
Doctrine's public interface is through the ``EntityManager``. This class
provides access points to the complete lifecycle management for your entities,
Doctrine's public interface is the EntityManager, it provides the
access point to the complete lifecycle management of your entities
and transforms entities from and back to persistence. You have to
configure and create it to use your entities with Doctrine 2. I
will show the configuration steps and then discuss them step by
@@ -138,10 +136,7 @@ step:
// Create a simple "default" Doctrine ORM configuration for Annotations
$isDevMode = true;
$proxyDir = null;
$cache = null;
$useSimpleAnnotationReader = false;
$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/src"), $isDevMode, $proxyDir, $cache, $useSimpleAnnotationReader);
$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/src"), $isDevMode);
// or if you prefer yaml or XML
//$config = Setup::createXMLMetadataConfiguration(array(__DIR__."/config/xml"), $isDevMode);
//$config = Setup::createYAMLMetadataConfiguration(array(__DIR__."/config/yaml"), $isDevMode);
@@ -155,16 +150,8 @@ step:
// obtaining the entity manager
$entityManager = EntityManager::create($conn, $config);
.. note::
The YAML driver is deprecated and will be removed in version 3.0.
It is strongly recommended to switch to one of the other mappings.
.. note::
It is recommended not to use the SimpleAnnotationReader because its
usage will be removed for version 3.0.
The ``require_once`` statement sets up the class autoloading for Doctrine and
its dependencies using Composer's autoloader.
The first require statement sets up the autoloading capabilities of Doctrine
using the Composer autoload.
The second block consists of the instantiation of the ORM
``Configuration`` object using the Setup helper. It assumes a bunch
@@ -172,10 +159,10 @@ of defaults that you don't have to bother about for now. You can
read up on the configuration details in the
:doc:`reference chapter on configuration <../reference/configuration>`.
The third block shows the configuration options required to connect to
a database. In this case, we'll use a file-based SQLite database. All the
The third block shows the configuration options required to connect
to a database, in my case a file-based sqlite database. All the
configuration options for all the shipped drivers are given in the
`DBAL Configuration section of the manual <https://www.doctrine-project.org/projects/doctrine-dbal/en/current/>`_.
`DBAL Configuration section of the manual <http://www.doctrine-project.org/documentation/manual/2_0/en/dbal>`_.
The last block shows how the ``EntityManager`` is obtained from a
factory method.
@@ -183,10 +170,15 @@ factory method.
Generating the Database Schema
------------------------------
Doctrine has a command-line interface that allows you to access the SchemaTool,
a component that can generate a relational database schema based entirely on the
defined entity classes and their metadata. For this tool to work, a
``cli-config.php`` file must exist in the project root directory:
Now that we have defined the Metadata mappings and bootstrapped the
EntityManager we want to generate the relational database schema
from it. Doctrine has a Command-Line Interface that allows you to
access the SchemaTool, a component that generates the required
tables to work with the metadata.
For the command-line tool to work a cli-config.php file has to be
present in the project root directory, where you will execute the
doctrine command. Its a fairly simple file:
.. code-block:: php
@@ -196,37 +188,40 @@ defined entity classes and their metadata. For this tool to work, a
return \Doctrine\ORM\Tools\Console\ConsoleRunner::createHelperSet($entityManager);
Now call the Doctrine command-line tool:
You can then change into your project directory and call the
Doctrine command-line tool:
::
$ cd project/
$ vendor/bin/doctrine orm:schema-tool:create
Since we haven't added any entity metadata in ``src`` yet, you'll see a message
stating "No Metadata Classes to process." In the next section, we'll create a
Product entity along with the corresponding metadata, and run this command again.
At this point no entity metadata exists in `src` so you will see a message like
"No Metadata Classes to process." Don't worry, we'll create a Product entity and
corresponding metadata in the next section.
Note that as you modify your entities' metadata during the development process,
you'll need to update your database schema to stay in sync with the metadata.
You can easily recreate the database using the following commands:
You should be aware that during the development process you'll periodically need
to update your database schema to be in sync with your Entities metadata.
You can easily recreate the database:
::
$ vendor/bin/doctrine orm:schema-tool:drop --force
$ vendor/bin/doctrine orm:schema-tool:create
Or you can use the update functionality:
Or use the update functionality:
::
$ vendor/bin/doctrine orm:schema-tool:update --force
The updating of databases uses a diff algorithm for a given
database schema. This is a cornerstone of the ``Doctrine\DBAL`` package,
The updating of databases uses a Diff Algorithm for a given
Database Schema, a cornerstone of the ``Doctrine\DBAL`` package,
which can even be used without the Doctrine ORM package.
Starting with the Product Entity
--------------------------------
Starting with the Product
-------------------------
We start with the simplest entity, the Product. Create a ``src/Product.php`` file to contain the ``Product``
entity definition:
@@ -262,16 +257,16 @@ entity definition:
}
}
When creating entity classes, all of the fields should be ``protected`` or ``private``
(not ``public``), with getter and setter methods for each one (except ``$id``).
The use of mutators allows Doctrine to hook into calls which
manipulate the entities in ways that it could not if you just
Note that all fields are set to protected (not public) with a
mutator (getter and setter) defined for every field except $id.
The use of mutators allows Doctrine to hook into calls which
manipulate the entities in ways that it could not if you just
directly set the values with ``entity#field = foo;``
The id field has no setter since, generally speaking, your code
should not set this value since it represents a database id value.
(Note that Doctrine itself can still set the value using the
Reflection API instead of a defined setter function.)
The id field has no setter since, generally speaking, your code
should not set this value since it represents a database id value.
(Note that Doctrine itself can still set the value using the
Reflection API instead of a defined setter function)
The next step for persistence with Doctrine is to describe the
structure of the ``Product`` entity to Doctrine using a metadata
@@ -279,10 +274,9 @@ language. The metadata language describes how entities, their
properties and references should be persisted and what constraints
should be applied to them.
Metadata for an Entity can be configured using DocBlock annotations directly
in the Entity class itself, or in an external XML or YAML file. This Getting
Started guide will demonstrate metadata mappings using all three methods,
but you only need to choose one.
Metadata for entities are configured using a XML, YAML or Docblock Annotations.
This Getting Started Guide will show the mappings for all Mapping Drivers.
References in the text will be made to the XML mapping.
.. configuration-block::
@@ -290,24 +284,14 @@ but you only need to choose one.
<?php
// src/Product.php
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="products")
*/
* @Entity @Table(name="products")
**/
class Product
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
*/
/** @Id @Column(type="integer") @GeneratedValue **/
protected $id;
/**
* @ORM\Column(type="string")
*/
/** @Column(type="string") **/
protected $name;
// .. (other code)
@@ -319,7 +303,7 @@ but you only need to choose one.
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
<entity name="Product" table="products">
<id name="id" type="integer">
@@ -330,10 +314,6 @@ but you only need to choose one.
</entity>
</doctrine-mapping>
.. note::
The YAML driver is deprecated and will be removed in version 3.0.
It is strongly recommended to switch to one of the other mappings.
.. code-block:: yaml
# config/yaml/Product.dcm.yml
@@ -349,31 +329,30 @@ but you only need to choose one.
name:
type: string
The top-level ``entity`` definition specifies information about
the class and table name. The primitive type ``Product#name`` is
The top-level ``entity`` definition tag specifies information about
the class and table-name. The primitive type ``Product#name`` is
defined as a ``field`` attribute. The ``id`` property is defined with
the ``id`` tag. It has a ``generator`` tag nested inside, which
specifies that the primary key generation mechanism should automatically
use the database platform's native id generation strategy (for
example, AUTO INCREMENT in the case of MySql, or Sequences in the
the ``id`` tag, this has a ``generator`` tag nested inside which
defines that the primary key generation mechanism automatically
uses the database platforms native id generation strategy (for
example AUTO INCREMENT in the case of MySql or Sequences in the
case of PostgreSql and Oracle).
Now that we have defined our first entity and its metadata,
let's update the database schema:
Now that we have defined our first entity, let's update the database:
::
$ vendor/bin/doctrine orm:schema-tool:update --force --dump-sql
Specifying both flags ``--force`` and ``--dump-sql`` will cause the DDL
statements to be executed and then printed to the screen.
Specifying both flags ``--force`` and ``-dump-sql`` prints and executes the DDL
statements.
Now, we'll create a new script to insert products into the database:
Now create a new script that will insert products into the database:
.. code-block:: php
<?php
// create_product.php <name>
// create_product.php
require_once "bootstrap.php";
$newProductName = $argv[1];
@@ -393,19 +372,22 @@ Call this script from the command-line to see how new products are created:
$ php create_product.php ORM
$ php create_product.php DBAL
What is happening here? Using the ``Product`` class is pretty standard OOP.
What is happening here? Using the ``Product`` is pretty standard OOP.
The interesting bits are the use of the ``EntityManager`` service. To
notify the EntityManager that a new entity should be inserted into the database,
you have to call ``persist()``. To initiate a transaction to actually *perform*
the insertion, you have to explicitly call ``flush()`` on the ``EntityManager``.
notify the EntityManager that a new entity should be inserted into the database
you have to call ``persist()``. To initiate a transaction to actually perform
the insertion, You have to explicitly call ``flush()`` on the ``EntityManager``.
This distinction between persist and flush is what allows the aggregation of
all database writes (INSERT, UPDATE, DELETE) into one single transaction, which
is executed when ``flush()`` is called. Using this approach, the write-performance
is significantly better than in a scenario in which writes are performed on
each entity in isolation.
This distinction between persist and flush is allows to aggregate all writes
(INSERT, UPDATE, DELETE) into one single transaction, which is executed when
flush is called. Using this approach the write-performance is significantly
better than in a scenario where updates are done for each entity in isolation.
Next, we'll fetch a list of all the Products in the database. Let's create a
Doctrine follows the UnitOfWork pattern which additionally detects all entities
that were fetched and have changed during the request. You don't have to keep track of
entities yourself, when Doctrine already knows about them.
As a next step we want to fetch a list of all the Products. Let's create a
new script for this:
.. code-block:: php
@@ -422,10 +404,10 @@ new script for this:
}
The ``EntityManager#getRepository()`` method can create a finder object (called
a repository) for every type of entity. It is provided by Doctrine and contains
some finder methods like ``findAll()``.
a repository) for every entity. It is provided by Doctrine and contains some
finder methods such as ``findAll()``.
Let's continue by creating a script to display the name of a product based on its ID:
Let's continue with displaying the name of a product based on its ID:
.. code-block:: php
@@ -443,13 +425,9 @@ Let's continue by creating a script to display the name of a product based on it
echo sprintf("-%s\n", $product->getName());
Next we'll update a product's name, given its id. This simple example will
help demonstrate Doctrine's implementation of the UnitOfWork pattern. Doctrine
keeps track of all the entities that were retrieved from the Entity Manager,
and can detect when any of those entities' properties have been modified.
As a result, rather than needing to call ``persist($entity)`` for each individual
entity whose properties were changed, a single call to ``flush()`` at the end of a
request is sufficient to update the database for all of the modified entities.
Updating a product name demonstrates the functionality UnitOfWork of pattern
discussed before. We only need to find a product entity and all changes to its
properties are written to the database:
.. code-block:: php
@@ -477,44 +455,36 @@ product name changed by calling the ``show_product.php`` script.
Adding Bug and User Entities
----------------------------
We continue with the bug tracker example by creating the ``Bug`` and ``User``
classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
We continue with the bug tracker domain, by creating the missing classes
``Bug`` and ``User`` and putting them into ``src/Bug.php`` and
``src/User.php`` respectively.
.. code-block:: php
<?php
// src/Bug.php
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="BugRepository")
* @ORM\Table(name="bugs")
* @Entity(repositoryClass="BugRepository") @Table(name="bugs")
*/
class Bug
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
* @Id @Column(type="integer") @GeneratedValue
* @var int
*/
protected $id;
/**
* @ORM\Column(type="string")
* @Column(type="string")
* @var string
*/
protected $description;
/**
* @ORM\Column(type="datetime")
* @Column(type="datetime")
* @var DateTime
*/
protected $created;
/**
* @ORM\Column(type="string")
* @Column(type="string")
* @var string
*/
protected $status;
@@ -559,25 +529,18 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
<?php
// src/User.php
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="users")
* @Entity @Table(name="users")
*/
class User
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @Id @GeneratedValue @Column(type="integer")
* @var int
*/
protected $id;
/**
* @ORM\Column(type="string")
* @Column(type="string")
* @var string
*/
protected $name;
@@ -598,15 +561,14 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
}
}
All of the properties we've seen so far are of simple types (integer, string,
and datetime). But now, we'll add properties that will store objects of
specific *entity types* in order to model the relationships between different
entities.
All of the properties discussed so far are simple string and integer values,
for example the id fields of the entities, their names, description, status and
change dates. Next we will model the dynamic relationships between the entities
by defining the references between entities.
At the database level, relationships between entities are represented by foreign
keys. But with Doctrine, you'll never have to (and never should) work with
the foreign keys directly. You should only work with objects that represent
foreign keys through their own identities.
References between objects are foreign keys in the database. You never have to
(and never should) work with the foreign keys directly, only with the objects
that represent the foreign key through their own identity.
For every foreign key you either have a Doctrine ManyToOne or OneToOne
association. On the inverse sides of these foreign keys you can have
@@ -640,7 +602,6 @@ domain model to match the requirements:
<?php
// src/User.php
use Doctrine\Common\Collections\ArrayCollection;
class User
{
// ... (previous code)
@@ -655,13 +616,12 @@ domain model to match the requirements:
}
}
.. note::
Whenever an entity is created from the database, a ``Collection``
implementation of the type ``PersistentCollection`` will be injected into
your entity instead of an ``ArrayCollection``. This helps Doctrine ORM
understand the changes that have happened to the collection that are
noteworthy for persistence.
Whenever an entity is recreated from the database, an Collection
implementation of the type Doctrine is injected into your entity
instead of an array. Compared to the ArrayCollection this
implementation helps the Doctrine ORM understand the changes that
have happened to the collection which are noteworthy for
persistence.
.. warning::
@@ -684,22 +644,24 @@ able to work with Doctrine 2. These assumptions are not unique to
Doctrine 2 but are best practices in handling database relations
and Object-Relational Mapping.
- In a one-to-one relation, the entity holding the foreign key of
the related entity on its own database table is *always* the owning
side of the relation.
- In a many-to-one relation, the Many-side is the owning side by
default because it holds the foreign key. Accordingly, the One-side
is the inverse side by default.
- In a many-to-one relation, the One-side can only be the owning side if
the relation is implemented as a ManyToMany with a join table, and the
One-side is restricted to allow only UNIQUE values per database constraint.
- In a many-to-many relation, both sides can be the owning side of
the relation. However, in a bi-directional many-to-many relation,
only one side is allowed to be the owning side.
- Changes to Collections are saved or updated, when the entity on
the *owning* side of the collection is saved or updated.
- Saving an Entity at the inverse side of a relation never
triggers a persist operation to changes to the collection.
- In a one-to-one relation the entity holding the foreign key of
the related entity on its own database table is *always* the owning
side of the relation.
- In a many-to-many relation, both sides can be the owning side of
the relation. However in a bi-directional many-to-many relation
only one is allowed to be.
- In a many-to-one relation the Many-side is the owning side by
default, because it holds the foreign key.
- The OneToMany side of a relation is inverse by default, since
the foreign key is saved on the Many side. A OneToMany relation can
only be the owning side, if its implemented using a ManyToMany
relation with join table and restricting the one side to allow only
UNIQUE values per database constraint.
.. note::
@@ -724,13 +686,13 @@ the bi-directional reference:
protected $engineer;
protected $reporter;
public function setEngineer(User $engineer)
public function setEngineer($engineer)
{
$engineer->assignedToBug($this);
$this->engineer = $engineer;
}
public function setReporter(User $reporter)
public function setReporter($reporter)
{
$reporter->addReportedBug($this);
$this->reporter = $reporter;
@@ -755,15 +717,15 @@ the bi-directional reference:
{
// ... (previous code)
protected $reportedBugs;
protected $assignedBugs;
private $reportedBugs = null;
private $assignedBugs = null;
public function addReportedBug(Bug $bug)
public function addReportedBug($bug)
{
$this->reportedBugs[] = $bug;
}
public function assignedToBug(Bug $bug)
public function assignedToBug($bug)
{
$this->assignedBugs[] = $bug;
}
@@ -779,7 +741,7 @@ You can see from ``User#addReportedBug()`` and
``User#assignedToBug()`` that using this method in userland alone
would not add the Bug to the collection of the owning side in
``Bug#reporter`` or ``Bug#engineer``. Using these methods and
calling Doctrine for persistence would not update the Collections'
calling Doctrine for persistence would not update the collections
representation in the database.
Only using ``Bug#setEngineer()`` or ``Bug#setReporter()``
@@ -787,7 +749,7 @@ correctly saves the relation information.
The ``Bug#reporter`` and ``Bug#engineer`` properties are
Many-To-One relations, which point to a User. In a normalized
relational model, the foreign key is saved on the Bug's table, hence
relational model the foreign key is saved on the Bug's table, hence
in our object-relation model the Bug is at the owning side of the
relation. You should always make sure that the use-cases of your
domain model should drive which side is an inverse or owning one in
@@ -796,7 +758,7 @@ or an engineer is assigned to the bug, we don't want to update the
User to persist the reference, but the Bug. This is the case with
the Bug being at the owning side of the relation.
Bugs reference Products by a uni-directional ManyToMany relation in
Bugs reference Products by an uni-directional ManyToMany relation in
the database that points from Bugs to Products.
.. code-block:: php
@@ -807,9 +769,9 @@ the database that points from Bugs to Products.
{
// ... (previous code)
protected $products;
protected $products = null;
public function assignToProduct(Product $product)
public function assignToProduct($product)
{
$this->products[] = $product;
}
@@ -821,7 +783,7 @@ the database that points from Bugs to Products.
}
We are now finished with the domain model given the requirements.
Lets add metadata mappings for the ``Bug`` entity, as we did for
Lets add metadata mappings for the ``User`` and ``Bug`` as we did for
the ``Product`` before:
.. configuration-block::
@@ -829,50 +791,41 @@ the ``Product`` before:
<?php
// src/Bug.php
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="bugs")
*/
* @Entity @Table(name="bugs")
**/
class Bug
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
*/
* @Id @Column(type="integer") @GeneratedValue
**/
protected $id;
/**
* @ORM\Column(type="string")
*/
* @Column(type="string")
**/
protected $description;
/**
* @ORM\Column(type="datetime")
*/
* @Column(type="datetime")
**/
protected $created;
/**
* @ORM\Column(type="string")
*/
* @Column(type="string")
**/
protected $status;
/**
* @ORM\ManyToOne(targetEntity="User", inversedBy="assignedBugs")
*/
* @ManyToOne(targetEntity="User", inversedBy="assignedBugs")
**/
protected $engineer;
/**
* @ORM\ManyToOne(targetEntity="User", inversedBy="reportedBugs")
*/
* @ManyToOne(targetEntity="User", inversedBy="reportedBugs")
**/
protected $reporter;
/**
* @ORM\ManyToMany(targetEntity="Product")
*/
* @ManyToMany(targetEntity="Product")
**/
protected $products;
// ... (other code)
@@ -884,7 +837,7 @@ the ``Product`` before:
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
<entity name="Bug" table="bugs">
<id name="id" type="integer">
@@ -902,10 +855,6 @@ the ``Product`` before:
</entity>
</doctrine-mapping>
.. note::
The YAML driver is deprecated and will be removed in version 3.0.
It is strongly recommended to switch to one of the other mappings.
.. code-block:: yaml
# config/yaml/Bug.dcm.yml
@@ -941,13 +890,13 @@ For the "created" field we have used the ``datetime`` type,
which translates the YYYY-mm-dd HH:mm:ss database format
into a PHP DateTime instance and back.
After the field definitions, the two qualified references to the
After the field definitions the two qualified references to the
user entity are defined. They are created by the ``many-to-one``
tag. The class name of the related entity has to be specified with
the ``target-entity`` attribute, which is enough information for
the database mapper to access the foreign-table. Since
``reporter`` and ``engineer`` are on the owning side of a
bi-directional relation, we also have to specify the ``inversed-by``
bi-directional relation we also have to specify the ``inversed-by``
attribute. They have to point to the field names on the inverse
side of the relationship. We will see in the next example that the ``inversed-by``
attribute has a counterpart ``mapped-by`` which makes that
@@ -958,7 +907,7 @@ holds all products where the specific bug occurs. Again
you have to define the ``target-entity`` and ``field`` attributes
on the ``many-to-many`` tag.
Finally, we'll add metadata mappings for the ``User`` entity.
The last missing definition is that of the User entity:
.. configuration-block::
@@ -966,40 +915,34 @@ Finally, we'll add metadata mappings for the ``User`` entity.
<?php
// src/User.php
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="users")
*/
* @Entity @Table(name="users")
**/
class User
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @Id @GeneratedValue @Column(type="integer")
* @var int
*/
**/
protected $id;
/**
* @ORM\Column(type="string")
* @Column(type="string")
* @var string
*/
**/
protected $name;
/**
* @ORM\OneToMany(targetEntity="Bug", mappedBy="reporter")
* @var Bug[] An ArrayCollection of Bug objects.
*/
protected $reportedBugs;
* @OneToMany(targetEntity="Bug", mappedBy="reporter")
* @var Bug[]
**/
protected $reportedBugs = null;
/**
* @ORM\OneToMany(targetEntity="Bug", mappedBy="engineer")
* @var Bug[] An ArrayCollection of Bug objects.
*/
protected $assignedBugs;
* @OneToMany(targetEntity="Bug", mappedBy="engineer")
* @var Bug[]
**/
protected $assignedBugs = null;
// .. (other code)
}
@@ -1010,7 +953,7 @@ Finally, we'll add metadata mappings for the ``User`` entity.
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
<entity name="User" table="users">
<id name="id" type="integer">
@@ -1024,13 +967,9 @@ Finally, we'll add metadata mappings for the ``User`` entity.
</entity>
</doctrine-mapping>
.. note::
The YAML driver is deprecated and will be removed in version 3.0.
It is strongly recommended to switch to one of the other mappings.
.. code-block:: yaml
# config/yaml/User.dcm.yml
# config/xml/User.dcm.yml
User:
type: entity
table: users
@@ -1057,7 +996,10 @@ means the join details have already been defined on the owning
side. Therefore we only have to specify the property on the Bug
class that holds the owning sides.
Update your database schema by running:
This example has a fair overview of the most basic features of the
metadata definition language.
Update your database running:
::
$ vendor/bin/doctrine orm:schema-tool:update --force
@@ -1066,8 +1008,7 @@ Update your database schema by running:
Implementing more Requirements
------------------------------
So far, we've seen the most basic features of the metadata definition language.
To explore additional functionality, let's first create new ``User`` entities:
For starters we need a create user entities:
.. code-block:: php
@@ -1091,22 +1032,23 @@ Now call:
$ php create_user.php beberlei
We now have the necessary data to create a new Bug entity:
We now have the data to create a bug and the code for this scenario may look
like this:
.. code-block:: php
<?php
// create_bug.php <reporter-id> <engineer-id> <product-ids>
// create_bug.php
require_once "bootstrap.php";
$reporterId = $argv[1];
$engineerId = $argv[2];
$theReporterId = $argv[1];
$theDefaultEngineerId = $argv[2];
$productIds = explode(",", $argv[3]);
$reporter = $entityManager->find("User", $reporterId);
$engineer = $entityManager->find("User", $engineerId);
$reporter = $entityManager->find("User", $theReporterId);
$engineer = $entityManager->find("User", $theDefaultEngineerId);
if (!$reporter || !$engineer) {
echo "No reporter and/or engineer found for the given id(s).\n";
echo "No reporter and/or engineer found for the input.\n";
exit(1);
}
@@ -1128,17 +1070,22 @@ We now have the necessary data to create a new Bug entity:
echo "Your new Bug Id: ".$bug->getId()."\n";
Since we only have one user and product, probably with the ID of 1, we can
call this script as follows:
Since we only have one user and product, probably with the ID of 1, we can call this script with:
::
php create_bug.php 1 1 1
See how simple it is to relate a Bug, Reporter, Engineer and Products?
Also recall that thanks to the UnitOfWork pattern, Doctrine will detect
these relations and update all of the modified entities in the database
automatically when ``flush()`` is called.
This is the first contact with the read API of the EntityManager,
showing that a call to ``EntityManager#find($name, $id)`` returns a
single instance of an entity queried by primary key. Besides this
we see the persist + flush pattern again to save the Bug into the
database.
See how simple relating Bug, Reporter, Engineer and Products is
done by using the discussed methods in the "A first prototype"
section. The UnitOfWork will detect this relations when flush is
called and relate them in the database appropriately.
Queries for Application Use-Cases
---------------------------------
@@ -1147,7 +1094,7 @@ List of Bugs
~~~~~~~~~~~~
Using the previous examples we can fill up the database quite a
bit. However, we now need to discuss how to query the underlying
bit, however we now need to discuss how to query the underlying
mapper for the required view representations. When opening the
application, bugs can be paginated through a list-view, which is
the first read-only use-case:
@@ -1203,14 +1150,15 @@ The console output of this script is then:
An important reason why DQL is favourable to the Query API of most
ORMs is its similarity to SQL. The DQL language allows query
constructs that most ORMs don't: GROUP BY even with HAVING,
constructs that most ORMs don't, GROUP BY even with HAVING,
Sub-selects, Fetch-Joins of nested classes, mixed results with
entities and scalar data such as COUNT() results and much more.
Using DQL you should seldom come to the point where you want to
throw your ORM into the dumpster, because it doesn't support some
the more powerful SQL concepts.
If you need to build your query dynamically, you can use the ``QueryBuilder`` retrieved
Instead of handwriting DQL you can use the ``QueryBuilder`` retrieved
by calling ``$entityManager->createQueryBuilder()``. There are more
details about this in the relevant part of the documentation.
@@ -1277,7 +1225,7 @@ write scenarios:
.. code-block:: php
<?php
// show_bug.php <id>
// show_bug.php
require_once "bootstrap.php";
$theBugId = $argv[1];
@@ -1352,7 +1300,7 @@ and usage of bound parameters:
.. code-block:: php
<?php
// dashboard.php <user-id>
// dashboard.php
require_once "bootstrap.php";
$theUserId = $argv[1];
@@ -1418,7 +1366,7 @@ should be able to close a bug. This looks like:
.. code-block:: php
<?php
// close_bug.php <bug-id>
// close_bug.php
require_once "bootstrap.php";
$theBugId = $argv[1];
@@ -1536,12 +1484,9 @@ we have to adjust the metadata slightly.
.. code-block:: php
<?php
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="BugRepository")
* @ORM\Table(name="bugs")
* @Entity(repositoryClass="BugRepository")
* @Table(name="bugs")
**/
class Bug
{
@@ -1553,17 +1498,13 @@ we have to adjust the metadata slightly.
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
<entity name="Bug" table="bugs" repository-class="BugRepository">
</entity>
</doctrine-mapping>
.. note::
The YAML driver is deprecated and will be removed in version 3.0.
It is strongly recommended to switch to one of the other mappings.
.. code-block:: yaml
Bug:
@@ -1594,16 +1535,6 @@ As an example here is the code of the first use case "List of Bugs":
Using EntityRepositories you can avoid coupling your model with specific query logic.
You can also re-use query logic easily throughout your application.
The method ``count()`` takes an array of fields or association keys and the values to match against.
This provides you with a convenient and lightweight way to count a resultset when you don't need to
deal with it:
.. code-block:: php
<?php
$productCount = $entityManager->getRepository(Product::class)
->count(['name' => $productName]);
Conclusion
----------

View File

@@ -5,8 +5,8 @@ There are use-cases when you'll want to sort collections when they are
retrieved from the database. In userland you do this as long as you
haven't initially saved an entity with its associations into the
database. To retrieve a sorted collection from the database you can
use the ``@OrderBy`` annotation with a collection that specifies
a DQL snippet that is appended to all queries with this
use the ``@OrderBy`` annotation with an collection that specifies
an DQL snippet that is appended to all queries with this
collection.
Additional to any ``@OneToMany`` or ``@ManyToMany`` annotation you
@@ -64,7 +64,7 @@ positional statement. Multiple Fields are separated by a comma (,).
The referenced field names have to exist on the ``targetEntity``
class of the ``@ManyToMany`` or ``@OneToMany`` annotation.
The semantics of this feature can be described as follows:
The semantics of this feature can be described as follows.
- ``@OrderBy`` acts as an implicit ORDER BY clause for the given
@@ -73,7 +73,7 @@ The semantics of this feature can be described as follows:
- All collections of the ordered type are always retrieved in an
ordered fashion.
- To keep the database impact low, these implicit ORDER BY items
are only added to a DQL Query if the collection is fetch joined in
are only added to an DQL Query if the collection is fetch joined in
the DQL query.
Given our previously defined example, the following would not add

View File

@@ -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
{
// ...

View File

@@ -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.

View File

@@ -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">

View File

@@ -19,16 +19,17 @@
namespace Doctrine\ORM;
use Doctrine\Common\Persistence\Mapping\MappingException;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping\MappingException as ORMMappingException;
use Doctrine\ORM\Query\Parameter;
use Doctrine\ORM\Cache\QueryCacheKey;
use Doctrine\DBAL\Cache\QueryCacheProfile;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Query\QueryException;
/**
* Base contract for ORM queries. Base class for Query and NativeQuery.
*
@@ -72,7 +73,7 @@ abstract class AbstractQuery
/**
* The parameter map of this query.
*
* @var ArrayCollection|Parameter[]
* @var \Doctrine\Common\Collections\ArrayCollection
*/
protected $parameters;
@@ -95,17 +96,17 @@ abstract class AbstractQuery
*
* @var array
*/
protected $_hints = [];
protected $_hints = array();
/**
* The hydration mode.
*
* @var string|int
* @var integer
*/
protected $_hydrationMode = self::HYDRATE_OBJECT;
/**
* @var \Doctrine\DBAL\Cache\QueryCacheProfile
* @param \Doctrine\DBAL\Cache\QueryCacheProfile
*/
protected $_queryCacheProfile;
@@ -117,7 +118,7 @@ abstract class AbstractQuery
protected $_expireResultCache = false;
/**
* @var \Doctrine\DBAL\Cache\QueryCacheProfile
* @param \Doctrine\DBAL\Cache\QueryCacheProfile
*/
protected $_hydrationCacheProfile;
@@ -177,6 +178,7 @@ abstract class AbstractQuery
}
/**
*
* Enable/disable second level query (result) caching for this query.
*
* @param boolean $cacheable
@@ -213,7 +215,7 @@ abstract class AbstractQuery
/**
* Obtain the name of the second level query cache region in which query results will be stored
*
* @return string|null The cache region name; NULL indicates the default region.
* @return The cache region name; NULL indicates the default region.
*/
public function getCacheRegion()
{
@@ -241,7 +243,7 @@ abstract class AbstractQuery
*
* @param integer $lifetime
*
* @return \Doctrine\ORM\AbstractQuery This query instance.
* @return static This query instance.
*/
public function setLifetime($lifetime)
{
@@ -261,7 +263,7 @@ abstract class AbstractQuery
/**
* @param integer $cacheMode
*
* @return \Doctrine\ORM\AbstractQuery This query instance.
* @return static This query instance.
*/
public function setCacheMode($cacheMode)
{
@@ -306,7 +308,7 @@ abstract class AbstractQuery
/**
* Get all defined parameters.
*
* @return ArrayCollection The defined query parameters.
* @return \Doctrine\Common\Collections\ArrayCollection The defined query parameters.
*/
public function getParameters()
{
@@ -323,20 +325,20 @@ abstract class AbstractQuery
public function getParameter($key)
{
$filteredParameters = $this->parameters->filter(
function (Query\Parameter $parameter) use ($key) : bool {
$parameterName = $parameter->getName();
return $key === $parameterName || (string) $key === (string) $parameterName;
function ($parameter) use ($key)
{
// Must not be identical because of string to integer conversion
return ($key == $parameter->getName());
}
);
return ! $filteredParameters->isEmpty() ? $filteredParameters->first() : null;
return count($filteredParameters) ? $filteredParameters->first() : null;
}
/**
* Sets a collection of query parameters.
*
* @param ArrayCollection|mixed[] $parameters
* @param \Doctrine\Common\Collections\ArrayCollection|array $parameters
*
* @return static This query instance.
*/
@@ -371,10 +373,17 @@ abstract class AbstractQuery
*/
public function setParameter($key, $value, $type = null)
{
$existingParameter = $this->getParameter($key);
$filteredParameters = $this->parameters->filter(
function ($parameter) use ($key)
{
// Must not be identical because of string to integer conversion
return ($key == $parameter->getName());
}
);
if ($existingParameter !== null) {
$existingParameter->setValue($value, $type);
if (count($filteredParameters)) {
$parameter = $filteredParameters->first();
$parameter->setValue($value, $type);
return $this;
}
@@ -389,7 +398,7 @@ abstract class AbstractQuery
*
* @param mixed $value
*
* @return array|string
* @return array
*
* @throws \Doctrine\ORM\ORMInvalidArgumentException
*/
@@ -412,24 +421,16 @@ abstract class AbstractQuery
return $value;
}
if ($value instanceof Mapping\ClassMetadata) {
return $value->name;
}
if (! is_object($value)) {
return $value;
}
try {
if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value))) {
$value = $this->_em->getUnitOfWork()->getSingleIdentifierValue($value);
if ($value === null) {
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
}
} catch (MappingException | ORMMappingException $e) {
// Silence any mapping exceptions. These can occur if the object in
// question is not a mapped entity, in which case we just don't do
// any preparation on the value.
}
if ($value instanceof Mapping\ClassMetadata) {
return $value->name;
}
return $value;
@@ -501,7 +502,7 @@ abstract class AbstractQuery
*/
public function setHydrationCacheProfile(QueryCacheProfile $profile = null)
{
if ($profile !== null && ! $profile->getResultCacheDriver()) {
if ( ! $profile->getResultCacheDriver()) {
$resultCacheDriver = $this->_em->getConfiguration()->getHydrationCacheImpl();
$profile = $profile->setResultCacheDriver($resultCacheDriver);
}
@@ -531,7 +532,7 @@ abstract class AbstractQuery
*/
public function setResultCacheProfile(QueryCacheProfile $profile = null)
{
if ($profile !== null && ! $profile->getResultCacheDriver()) {
if ( ! $profile->getResultCacheDriver()) {
$resultCacheDriver = $this->_em->getConfiguration()->getResultCacheImpl();
$profile = $profile->setResultCacheDriver($resultCacheDriver);
}
@@ -583,45 +584,21 @@ abstract class AbstractQuery
* Set whether or not to cache the results of this query and if so, for
* how long and which ID to use for the cache entry.
*
* @deprecated 2.7 Use {@see enableResultCache} and {@see disableResultCache} instead.
*
* @param bool $useCache
* @param int $lifetime
* @param string $resultCacheId
* @param boolean $bool
* @param integer $lifetime
* @param string $resultCacheId
*
* @return static This query instance.
*/
public function useResultCache($useCache, $lifetime = null, $resultCacheId = null)
public function useResultCache($bool, $lifetime = null, $resultCacheId = null)
{
return $useCache
? $this->enableResultCache($lifetime, $resultCacheId)
: $this->disableResultCache();
}
if ($bool) {
$this->setResultCacheLifetime($lifetime);
$this->setResultCacheId($resultCacheId);
/**
* Enables caching of the results of this query, for given or default amount of seconds
* and optionally specifies which ID to use for the cache entry.
*
* @param int|null $lifetime How long the cache entry is valid, in seconds.
* @param string|null $resultCacheId ID to use for the cache entry.
*
* @return static This query instance.
*/
public function enableResultCache(?int $lifetime = null, ?string $resultCacheId = null) : self
{
$this->setResultCacheLifetime($lifetime);
$this->setResultCacheId($resultCacheId);
return $this;
}
return $this;
}
/**
* Disables caching of the results of this query.
*
* @return static This query instance.
*/
public function disableResultCache() : self
{
$this->_queryCacheProfile = null;
return $this;
@@ -714,8 +691,8 @@ abstract class AbstractQuery
/**
* Defines the processing mode to be used during hydration / result set transformation.
*
* @param string|int $hydrationMode Doctrine processing mode to be used during hydration process.
* One of the Query::HYDRATE_* constants.
* @param integer $hydrationMode Doctrine processing mode to be used during hydration process.
* One of the Query::HYDRATE_* constants.
*
* @return static This query instance.
*/
@@ -729,7 +706,7 @@ abstract class AbstractQuery
/**
* Gets the hydration mode currently used by the query.
*
* @return string|int
* @return integer
*/
public function getHydrationMode()
{
@@ -741,9 +718,9 @@ abstract class AbstractQuery
*
* Alias for execute(null, $hydrationMode = HYDRATE_OBJECT).
*
* @param string|int $hydrationMode
* @param int $hydrationMode
*
* @return mixed
* @return array
*/
public function getResult($hydrationMode = self::HYDRATE_OBJECT)
{
@@ -777,7 +754,7 @@ abstract class AbstractQuery
/**
* Get exactly one result or null.
*
* @param string|int $hydrationMode
* @param int $hydrationMode
*
* @return mixed
*
@@ -815,12 +792,12 @@ abstract class AbstractQuery
* If the result is not unique, a NonUniqueResultException is thrown.
* If there is no result, a NoResultException is thrown.
*
* @param string|int $hydrationMode
* @param integer $hydrationMode
*
* @return mixed
*
* @throws NonUniqueResultException If the query result is not unique.
* @throws NoResultException If the query returned no result and hydration mode is not HYDRATE_SINGLE_SCALAR.
* @throws NoResultException If the query returned no result.
*/
public function getSingleResult($hydrationMode = null)
{
@@ -846,10 +823,10 @@ abstract class AbstractQuery
*
* Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
*
* @return mixed The scalar result.
* @return mixed
*
* @throws NoResultException If the query returned no result.
* @throws NonUniqueResultException If the query result is not unique.
* @throws NoResultException If the query returned no result.
*/
public function getSingleScalarResult()
{
@@ -886,7 +863,7 @@ abstract class AbstractQuery
/**
* Check if the query has a hint
*
* @param string $name The name of the hint
* @param string $name The name of the hint
*
* @return bool False if the query does not have any hint
*/
@@ -910,7 +887,7 @@ abstract class AbstractQuery
* iterate over the result.
*
* @param ArrayCollection|array|null $parameters The query parameters.
* @param string|int|null $hydrationMode The hydration mode to use.
* @param integer|null $hydrationMode The hydration mode to use.
*
* @return \Doctrine\ORM\Internal\Hydration\IterableResult
*/
@@ -934,7 +911,7 @@ abstract class AbstractQuery
* Executes the query.
*
* @param ArrayCollection|array|null $parameters Query parameters.
* @param string|int|null $hydrationMode Processing mode to be used during the hydration process.
* @param integer|null $hydrationMode Processing mode to be used during the hydration process.
*
* @return mixed
*/
@@ -951,7 +928,7 @@ abstract class AbstractQuery
* Execute query ignoring second level cache.
*
* @param ArrayCollection|array|null $parameters
* @param string|int|null $hydrationMode
* @param integer|null $hydrationMode
*
* @return mixed
*/
@@ -979,7 +956,7 @@ abstract class AbstractQuery
}
if ( ! $result) {
$result = [];
$result = array();
}
$setCacheEntry = function($data) use ($cache, $result, $cacheKey, $realCacheKey, $queryCacheProfile) {
@@ -1009,61 +986,39 @@ abstract class AbstractQuery
* Load from second level cache or executes the query and put into cache.
*
* @param ArrayCollection|array|null $parameters
* @param string|int|null $hydrationMode
* @param integer|null $hydrationMode
*
* @return mixed
*/
private function executeUsingQueryCache($parameters = null, $hydrationMode = null)
{
$rsm = $this->getResultSetMapping();
$querykey = new QueryCacheKey($this->getHash(), $this->lifetime, $this->cacheMode ?: Cache::MODE_NORMAL);
$queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion);
$queryKey = new QueryCacheKey(
$this->getHash(),
$this->lifetime,
$this->cacheMode ?: Cache::MODE_NORMAL,
$this->getTimestampKey()
);
$result = $queryCache->get($queryKey, $rsm, $this->_hints);
$result = $queryCache->get($querykey, $rsm, $this->_hints);
if ($result !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $queryKey);
$this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $querykey);
}
return $result;
}
$result = $this->executeIgnoreQueryCache($parameters, $hydrationMode);
$cached = $queryCache->put($queryKey, $rsm, $result, $this->_hints);
$cached = $queryCache->put($querykey, $rsm, $result, $this->_hints);
if ($this->cacheLogger) {
$this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $queryKey);
$this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $querykey);
if ($cached) {
$this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $queryKey);
$this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $querykey);
}
}
return $result;
}
/**
* @return \Doctrine\ORM\Cache\TimestampCacheKey|null
*/
private function getTimestampKey()
{
$entityName = reset($this->_resultSetMapping->aliasMap);
if (empty($entityName)) {
return null;
}
$metadata = $this->_em->getClassMetadata($entityName);
return new Cache\TimestampCacheKey($metadata->rootEntityName);
}
/**
* Get the result cache id to use to store the result set cache entry.
* Will return the configured id if it exists otherwise a hash will be
@@ -1073,7 +1028,7 @@ abstract class AbstractQuery
*/
protected function getHydrationCacheId()
{
$parameters = [];
$parameters = array();
foreach ($this->getParameters() as $parameter) {
$parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
@@ -1135,7 +1090,7 @@ abstract class AbstractQuery
{
$this->parameters = new ArrayCollection();
$this->_hints = [];
$this->_hints = array();
$this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints();
}

View File

@@ -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)
{

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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)
{

View File

@@ -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);
}
}

View File

@@ -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}
*/

View File

@@ -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);

View File

@@ -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);

View File

@@ -22,7 +22,6 @@ namespace Doctrine\ORM\Cache;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Cache\Persister\CachedPersister;
use Doctrine\ORM\Cache\Persister\Entity\CachedEntityPersister;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Mapping\ClassMetadata;
@@ -30,7 +29,6 @@ use Doctrine\ORM\PersistentCollection;
use Doctrine\Common\Proxy\Proxy;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Query;
use function assert;
/**
* Default query cache implementation.
@@ -68,7 +66,7 @@ class DefaultQueryCache implements QueryCache
/**
* @var array
*/
private static $hints = [Query::HINT_CACHE_ENABLED => true];
private static $hints = array(Query::HINT_CACHE_ENABLED => true);
/**
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
@@ -88,59 +86,49 @@ class DefaultQueryCache implements QueryCache
/**
* {@inheritdoc}
*/
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = [])
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = array())
{
if ( ! ($key->cacheMode & Cache::MODE_GET)) {
return null;
}
$cacheEntry = $this->region->get($key);
$entry = $this->region->get($key);
if ( ! $cacheEntry instanceof QueryCacheEntry) {
if ( ! $entry instanceof QueryCacheEntry) {
return null;
}
if ( ! $this->validator->isValid($key, $cacheEntry)) {
if ( ! $this->validator->isValid($key, $entry)) {
$this->region->evict($key);
return null;
}
$result = [];
$result = array();
$entityName = reset($rsm->aliasMap);
$hasRelation = ! empty($rsm->relationMap);
$hasRelation = ( ! empty($rsm->relationMap));
$persister = $this->uow->getEntityPersister($entityName);
assert($persister instanceof CachedEntityPersister);
$region = $persister->getCacheRegion();
$regionName = $region->getName();
$cm = $this->em->getClassMetadata($entityName);
$generateKeys = static function (array $entry) use ($cm) : EntityCacheKey {
return new EntityCacheKey($cm->rootEntityName, $entry['identifier']);
};
$cacheKeys = new CollectionCacheEntry(array_map($generateKeys, $cacheEntry->result));
$entries = $region->getMultiple($cacheKeys) ?? [];
$region = $persister->getCacheRegion();
$regionName = $region->getName();
// @TODO - move to cache hydration component
foreach ($cacheEntry->result as $index => $entry) {
$entityEntry = $entries[$index] ?? null;
foreach ($entry->result as $index => $entry) {
if (($entityEntry = $region->get($entityKey = new EntityCacheKey($entityName, $entry['identifier']))) === null) {
if (! $entityEntry instanceof EntityCacheEntry) {
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheMiss($regionName, $cacheKeys->identifiers[$index]);
$this->cacheLogger->entityCacheMiss($regionName, $entityKey);
}
return null;
}
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheHit($regionName, $cacheKeys->identifiers[$index]);
$this->cacheLogger->entityCacheHit($regionName, $entityKey);
}
if ( ! $hasRelation) {
$result[$index] = $this->uow->createEntity($entityEntry->class, $entityEntry->resolveAssociationEntries($this->em), self::$hints);
continue;
@@ -149,15 +137,13 @@ class DefaultQueryCache implements QueryCache
$data = $entityEntry->data;
foreach ($entry['associations'] as $name => $assoc) {
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
assert($assocPersister instanceof CachedEntityPersister);
$assocRegion = $assocPersister->getCacheRegion();
$assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']);
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
$assocRegion = $assocPersister->getCacheRegion();
if ($assoc['type'] & ClassMetadata::TO_ONE) {
if (($assocEntry = $assocRegion->get($assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assoc['identifier']))) === null) {
if (($assocEntry = $assocRegion->get($assocKey = new EntityCacheKey($assoc['targetEntity'], $assoc['identifier']))) === null) {
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey);
@@ -181,20 +167,15 @@ class DefaultQueryCache implements QueryCache
continue;
}
$generateKeys = function ($id) use ($assocMetadata): EntityCacheKey {
return new EntityCacheKey($assocMetadata->rootEntityName, $id);
};
$collection = new PersistentCollection($this->em, $assocMetadata, new ArrayCollection());
$assocKeys = new CollectionCacheEntry(array_map($generateKeys, $assoc['list']));
$assocEntries = $assocRegion->getMultiple($assocKeys);
$targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
$collection = new PersistentCollection($this->em, $targetClass, new ArrayCollection());
foreach ($assoc['list'] as $assocIndex => $assocId) {
$assocEntry = is_array($assocEntries) && array_key_exists($assocIndex, $assocEntries) ? $assocEntries[$assocIndex] : null;
if ($assocEntry === null) {
if (($assocEntry = $assocRegion->get($assocKey = new EntityCacheKey($assoc['targetEntity'], $assocId))) === null) {
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKeys->identifiers[$assocIndex]);
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey);
}
$this->uow->hydrationComplete();
@@ -207,7 +188,7 @@ class DefaultQueryCache implements QueryCache
$collection->hydrateSet($assocIndex, $element);
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKeys->identifiers[$assocIndex]);
$this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKey);
}
}
@@ -216,25 +197,6 @@ class DefaultQueryCache implements QueryCache
$collection->setInitialized(true);
}
foreach ($data as $fieldName => $unCachedAssociationData) {
// In some scenarios, such as EAGER+ASSOCIATION+ID+CACHE, the
// cache key information in `$cacheEntry` will not contain details
// for fields that are associations.
//
// This means that `$data` keys for some associations that may
// actually not be cached will not be converted to actual association
// data, yet they contain L2 cache AssociationCacheEntry objects.
//
// We need to unwrap those associations into proxy references,
// since we don't have actual data for them except for identifiers.
if ($unCachedAssociationData instanceof AssociationCacheEntry) {
$data[$fieldName] = $this->em->getReference(
$unCachedAssociationData->class,
$unCachedAssociationData->identifier
);
}
}
$result[$index] = $this->uow->createEntity($entityEntry->class, $data, self::$hints);
}
@@ -246,7 +208,7 @@ class DefaultQueryCache implements QueryCache
/**
* {@inheritdoc}
*/
public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = [])
public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = array())
{
if ($rsm->scalarMappings) {
throw new CacheException("Second level cache does not support scalar results.");
@@ -268,12 +230,13 @@ class DefaultQueryCache implements QueryCache
return false;
}
$data = [];
$data = array();
$entityName = reset($rsm->aliasMap);
$rootAlias = key($rsm->aliasMap);
$hasRelation = ( ! empty($rsm->relationMap));
$metadata = $this->em->getClassMetadata($entityName);
$persister = $this->uow->getEntityPersister($entityName);
if (! $persister instanceof CachedEntityPersister) {
if ( ! ($persister instanceof CachedPersister)) {
throw CacheException::nonCacheableEntity($entityName);
}
@@ -281,185 +244,86 @@ class DefaultQueryCache implements QueryCache
foreach ($result as $index => $entity) {
$identifier = $this->uow->getEntityIdentifier($entity);
$entityKey = new EntityCacheKey($entityName, $identifier);
$data[$index]['identifier'] = $identifier;
$data[$index]['associations'] = array();
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $region->contains($entityKey)) {
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $region->contains($entityKey = new EntityCacheKey($entityName, $identifier))) {
// Cancel put result if entity put fail
if ( ! $persister->storeEntityCache($entity, $entityKey)) {
return false;
}
}
$data[$index]['identifier'] = $identifier;
$data[$index]['associations'] = [];
if ( ! $hasRelation) {
continue;
}
// @TODO - move to cache hydration components
foreach ($rsm->relationMap as $alias => $name) {
$parentAlias = $rsm->parentAliasMap[$alias];
$parentClass = $rsm->aliasMap[$parentAlias];
$metadata = $this->em->getClassMetadata($parentClass);
$assoc = $metadata->associationMappings[$name];
$assocValue = $this->getAssociationValue($rsm, $alias, $entity);
foreach ($rsm->relationMap as $name) {
$assoc = $metadata->associationMappings[$name];
if ($assocValue === null) {
if (($assocValue = $metadata->getFieldValue($entity, $name)) === null || $assocValue instanceof Proxy) {
continue;
}
// root entity association
if ($rootAlias === $parentAlias) {
// Cancel put result if association put fail
if ( ($assocInfo = $this->storeAssociationCache($key, $assoc, $assocValue)) === null) {
return false;
if ( ! isset($assoc['cache'])) {
throw CacheException::nonCacheableEntityAssociation($entityName, $name);
}
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
$assocRegion = $assocPersister->getCacheRegion();
$assocMetadata = $assocPersister->getClassMetadata();
// Handle *-to-one associations
if ($assoc['type'] & ClassMetadata::TO_ONE) {
$assocIdentifier = $this->uow->getEntityIdentifier($assocValue);
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier))) {
// Cancel put result if association entity put fail
if ( ! $assocPersister->storeEntityCache($assocValue, $entityKey)) {
return false;
}
}
$data[$index]['associations'][$name] = $assocInfo;
$data[$index]['associations'][$name] = array(
'targetEntity' => $assocMetadata->rootEntityName,
'identifier' => $assocIdentifier,
'type' => $assoc['type']
);
continue;
}
// store single nested association
if ( ! is_array($assocValue)) {
// Cancel put result if association put fail
if ($this->storeAssociationCache($key, $assoc, $assocValue) === null) {
return false;
// Handle *-to-many associations
$list = array();
foreach ($assocValue as $assocItemIndex => $assocItem) {
$assocIdentifier = $this->uow->getEntityIdentifier($assocItem);
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier))) {
// Cancel put result if entity put fail
if ( ! $assocPersister->storeEntityCache($assocItem, $entityKey)) {
return false;
}
}
continue;
$list[$assocItemIndex] = $assocIdentifier;
}
// store array of nested association
foreach ($assocValue as $aVal) {
// Cancel put result if association put fail
if ($this->storeAssociationCache($key, $assoc, $aVal) === null) {
return false;
}
}
$data[$index]['associations'][$name] = array(
'targetEntity' => $assocMetadata->rootEntityName,
'type' => $assoc['type'],
'list' => $list,
);
}
}
return $this->region->put($key, new QueryCacheEntry($data));
}
/**
* @param \Doctrine\ORM\Cache\QueryCacheKey $key
* @param array $assoc
* @param mixed $assocValue
*
* @return array|null
*/
private function storeAssociationCache(QueryCacheKey $key, array $assoc, $assocValue)
{
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
$assocMetadata = $assocPersister->getClassMetadata();
$assocRegion = $assocPersister->getCacheRegion();
// Handle *-to-one associations
if ($assoc['type'] & ClassMetadata::TO_ONE) {
$assocIdentifier = $this->uow->getEntityIdentifier($assocValue);
$entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier);
if ( ! $assocValue instanceof Proxy && ($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) {
// Entity put fail
if ( ! $assocPersister->storeEntityCache($assocValue, $entityKey)) {
return null;
}
}
return [
'targetEntity' => $assocMetadata->rootEntityName,
'identifier' => $assocIdentifier,
'type' => $assoc['type']
];
}
// Handle *-to-many associations
$list = [];
foreach ($assocValue as $assocItemIndex => $assocItem) {
$assocIdentifier = $this->uow->getEntityIdentifier($assocItem);
$entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier);
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) {
// Entity put fail
if ( ! $assocPersister->storeEntityCache($assocItem, $entityKey)) {
return null;
}
}
$list[$assocItemIndex] = $assocIdentifier;
}
return [
'targetEntity' => $assocMetadata->rootEntityName,
'type' => $assoc['type'],
'list' => $list,
];
}
/**
* @param \Doctrine\ORM\Query\ResultSetMapping $rsm
* @param string $assocAlias
* @param object $entity
*
* @return array|object
*/
private function getAssociationValue(ResultSetMapping $rsm, $assocAlias, $entity)
{
$path = [];
$alias = $assocAlias;
while (isset($rsm->parentAliasMap[$alias])) {
$parent = $rsm->parentAliasMap[$alias];
$field = $rsm->relationMap[$alias];
$class = $rsm->aliasMap[$parent];
array_unshift($path, [
'field' => $field,
'class' => $class
]
);
$alias = $parent;
}
return $this->getAssociationPathValue($entity, $path);
}
/**
* @param mixed $value
* @param array $path
*
* @return array|object|null
*/
private function getAssociationPathValue($value, array $path)
{
$mapping = array_shift($path);
$metadata = $this->em->getClassMetadata($mapping['class']);
$assoc = $metadata->associationMappings[$mapping['field']];
$value = $metadata->getFieldValue($value, $mapping['field']);
if ($value === null) {
return null;
}
if (empty($path)) {
return $value;
}
// Handle *-to-one associations
if ($assoc['type'] & ClassMetadata::TO_ONE) {
return $this->getAssociationPathValue($value, $path);
}
$values = [];
foreach ($value as $item) {
$values[] = $this->getAssociationPathValue($item, $path);
}
return $values;
}
/**
* {@inheritdoc}
*/

View File

@@ -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
*/

View File

@@ -53,6 +53,6 @@ class Lock
*/
public static function createLockRead()
{
return new self(uniqid(time(), true));
return new self(uniqid(time()));
}
}

View File

@@ -35,7 +35,7 @@ class CacheLoggerChain implements CacheLogger
/**
* @var array<\Doctrine\ORM\Cache\Logging\CacheLogger>
*/
private $loggers = [];
private $loggers = array();
/**
* @param string $name

View File

@@ -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();
}
/**

View File

@@ -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)) {

View File

@@ -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
];
);
}
}

View File

@@ -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']);
}

View File

@@ -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
];
);
}
}

View File

@@ -27,6 +27,7 @@ use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\TimestampCacheKey;
use Doctrine\ORM\Cache\QueryCacheKey;
use Doctrine\ORM\Cache\Persister\CachedPersister;
use Doctrine\ORM\Cache\CacheException;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\EntityManagerInterface;
@@ -64,7 +65,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* @var array
*/
protected $queuedCache = [];
protected $queuedCache = array();
/**
* @var \Doctrine\ORM\Cache\Region
@@ -104,7 +105,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* Associations configured as FETCH_EAGER, as well as all inverse one-to-one associations.
*
* @var array|null
* @var array
*/
protected $joinedAssociations;
@@ -130,7 +131,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$this->cacheLogger = $cacheConfig->getCacheLogger();
$this->timestampRegion = $cacheFactory->getTimestampRegion();
$this->hydrator = $cacheFactory->buildEntityHydrator($em, $class);
$this->timestampKey = new TimestampCacheKey($this->class->rootEntityName);
$this->timestampKey = new TimestampCacheKey($this->class->getTableName());
}
/**
@@ -160,7 +161,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
public function getCountSQL($criteria = [])
public function getCountSQL($criteria = array())
{
return $this->persister->getCountSQL($criteria);
}
@@ -233,6 +234,14 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$class = $this->metadataFactory->getMetadataFor($className);
}
if ($class->containsForeignIdentifier) {
foreach ($class->associationMappings as $name => $assoc) {
if (!empty($assoc['id']) && !isset($assoc['cache'])) {
throw CacheException::nonCacheableEntityAssociation($class->name, $name);
}
}
}
$entry = $this->hydrator->buildCacheEntry($class, $key, $entity);
$cached = $this->region->put($key, $entry);
@@ -249,7 +258,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
private function storeJoinedAssociations($entity)
{
if ($this->joinedAssociations === null) {
$associations = [];
$associations = array();
foreach ($this->class->associationMappings as $name => $assoc) {
if (isset($assoc['cache']) &&
@@ -272,8 +281,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
$assocId = $this->uow->getEntityIdentifier($assocEntity);
$assocMetadata = $this->metadataFactory->getMetadataFor($assoc['targetEntity']);
$assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocId);
$assocKey = new EntityCacheKey($assoc['targetEntity'], $assocId);
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
$assocPersister->storeEntityCache($assocEntity, $assocKey);
@@ -283,21 +291,22 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* Generates a string of currently query
*
* @param array $query
* @param string $criteria
* @param array $orderBy
* @param array $query
* @param string $criteria
* @param array $orderBy
* @param integer $limit
* @param integer $offset
* @param integer $timestamp
*
* @return string
*/
protected function getHash($query, $criteria, array $orderBy = null, $limit = null, $offset = null)
protected function getHash($query, $criteria, array $orderBy = null, $limit = null, $offset = null, $timestamp = null)
{
list($params) = ($criteria instanceof Criteria)
? $this->persister->expandCriteriaParameters($criteria)
: $this->persister->expandParameters($criteria);
return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset);
return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset . $timestamp);
}
/**
@@ -361,23 +370,24 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritdoc}
*/
public function load(array $criteria, $entity = null, $assoc = null, array $hints = [], $lockMode = null, $limit = null, array $orderBy = null)
public function load(array $criteria, $entity = null, $assoc = null, array $hints = array(), $lockMode = null, $limit = null, array $orderBy = null)
{
if ($entity !== null || $assoc !== null || ! empty($hints) || $lockMode !== null) {
return $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy);
}
//handle only EntityRepository#findOneBy
$timestamp = $this->timestampRegion->get($this->timestampKey);
$query = $this->persister->getSelectSQL($criteria, null, null, $limit, null, $orderBy);
$hash = $this->getHash($query, $criteria, null, null, null);
$hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null);
$rsm = $this->getResultSetMapping();
$queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey);
$querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL);
$queryCache = $this->cache->getQueryCache($this->regionName);
$result = $queryCache->get($queryKey, $rsm);
$result = $queryCache->get($querykey, $rsm);
if ($result !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->queryCacheHit($this->regionName, $queryKey);
$this->cacheLogger->queryCacheHit($this->regionName, $querykey);
}
return $result[0];
@@ -387,15 +397,15 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
return null;
}
$cached = $queryCache->put($queryKey, $rsm, [$result]);
$cached = $queryCache->put($querykey, $rsm, array($result));
if ($this->cacheLogger) {
if ($result) {
$this->cacheLogger->queryCacheMiss($this->regionName, $queryKey);
$this->cacheLogger->queryCacheMiss($this->regionName, $querykey);
}
if ($cached) {
$this->cacheLogger->queryCachePut($this->regionName, $queryKey);
$this->cacheLogger->queryCachePut($this->regionName, $querykey);
}
}
@@ -405,33 +415,34 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritdoc}
*/
public function loadAll(array $criteria = [], array $orderBy = null, $limit = null, $offset = null)
public function loadAll(array $criteria = array(), array $orderBy = null, $limit = null, $offset = null)
{
$timestamp = $this->timestampRegion->get($this->timestampKey);
$query = $this->persister->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy);
$hash = $this->getHash($query, $criteria, null, null, null);
$hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null);
$rsm = $this->getResultSetMapping();
$queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey);
$querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL);
$queryCache = $this->cache->getQueryCache($this->regionName);
$result = $queryCache->get($queryKey, $rsm);
$result = $queryCache->get($querykey, $rsm);
if ($result !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->queryCacheHit($this->regionName, $queryKey);
$this->cacheLogger->queryCacheHit($this->regionName, $querykey);
}
return $result;
}
$result = $this->persister->loadAll($criteria, $orderBy, $limit, $offset);
$cached = $queryCache->put($queryKey, $rsm, $result);
$cached = $queryCache->put($querykey, $rsm, $result);
if ($this->cacheLogger) {
if ($result) {
$this->cacheLogger->queryCacheMiss($this->regionName, $queryKey);
$this->cacheLogger->queryCacheMiss($this->regionName, $querykey);
}
if ($cached) {
$this->cacheLogger->queryCachePut($this->regionName, $queryKey);
$this->cacheLogger->queryCachePut($this->regionName, $querykey);
}
}
@@ -452,14 +463,12 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$class = $this->metadataFactory->getMetadataFor($cacheEntry->class);
}
$cachedEntity = $this->hydrator->loadCacheEntry($class, $cacheKey, $cacheEntry, $entity);
if ($cachedEntity !== null) {
if (($entity = $this->hydrator->loadCacheEntry($class, $cacheKey, $cacheEntry, $entity)) !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->entityCacheHit($this->regionName, $cacheKey);
}
return $cachedEntity;
return $entity;
}
}
@@ -479,7 +488,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$cacheEntry = $this->hydrator->buildCacheEntry($class, $cacheKey, $entity);
$cached = $this->region->put($cacheKey, $cacheEntry);
if ($cached && (null === $this->joinedAssociations || $this->joinedAssociations)) {
if ($cached && ($this->joinedAssociations === null || count($this->joinedAssociations) > 0)) {
$this->storeJoinedAssociations($entity);
}
@@ -497,7 +506,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
public function count($criteria = [])
public function count($criteria = array())
{
return $this->persister->count($criteria);
}
@@ -507,34 +516,32 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
*/
public function loadCriteria(Criteria $criteria)
{
$orderBy = $criteria->getOrderings();
$limit = $criteria->getMaxResults();
$offset = $criteria->getFirstResult();
$query = $this->persister->getSelectSQL($criteria);
$hash = $this->getHash($query, $criteria, $orderBy, $limit, $offset);
$timestamp = $this->timestampRegion->get($this->timestampKey);
$hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null);
$rsm = $this->getResultSetMapping();
$queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey);
$querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL);
$queryCache = $this->cache->getQueryCache($this->regionName);
$cacheResult = $queryCache->get($queryKey, $rsm);
$cacheResult = $queryCache->get($querykey, $rsm);
if ($cacheResult !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->queryCacheHit($this->regionName, $queryKey);
$this->cacheLogger->queryCacheHit($this->regionName, $querykey);
}
return $cacheResult;
}
$result = $this->persister->loadCriteria($criteria);
$cached = $queryCache->put($queryKey, $rsm, $result);
$cached = $queryCache->put($querykey, $rsm, $result);
if ($this->cacheLogger) {
if ($result) {
$this->cacheLogger->queryCacheMiss($this->regionName, $queryKey);
$this->cacheLogger->queryCacheMiss($this->regionName, $querykey);
}
if ($cached) {
$this->cacheLogger->queryCachePut($this->regionName, $queryKey);
$this->cacheLogger->queryCachePut($this->regionName, $querykey);
}
}
@@ -550,28 +557,28 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$hasCache = ($persister instanceof CachedPersister);
$key = null;
if ( ! $hasCache) {
return $this->persister->loadManyToManyCollection($assoc, $sourceEntity, $coll);
}
if ($hasCache) {
$ownerId = $this->uow->getEntityIdentifier($coll->getOwner());
$key = new CollectionCacheKey($assoc['sourceEntity'], $assoc['fieldName'], $ownerId);
$list = $persister->loadCollectionCache($coll, $key);
$ownerId = $this->uow->getEntityIdentifier($coll->getOwner());
$key = $this->buildCollectionCacheKey($assoc, $ownerId);
$list = $persister->loadCollectionCache($coll, $key);
if ($list !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key);
}
if ($list !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key);
return $list;
}
return $list;
}
$list = $this->persister->loadManyToManyCollection($assoc, $sourceEntity, $coll);
$persister->storeCollectionCache($key, $list);
if ($hasCache) {
$persister->storeCollectionCache($key, $list);
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
}
}
return $list;
@@ -585,28 +592,28 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$persister = $this->uow->getCollectionPersister($assoc);
$hasCache = ($persister instanceof CachedPersister);
if ( ! $hasCache) {
return $this->persister->loadOneToManyCollection($assoc, $sourceEntity, $coll);
}
if ($hasCache) {
$ownerId = $this->uow->getEntityIdentifier($coll->getOwner());
$key = new CollectionCacheKey($assoc['sourceEntity'], $assoc['fieldName'], $ownerId);
$list = $persister->loadCollectionCache($coll, $key);
$ownerId = $this->uow->getEntityIdentifier($coll->getOwner());
$key = $this->buildCollectionCacheKey($assoc, $ownerId);
$list = $persister->loadCollectionCache($coll, $key);
if ($list !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key);
}
if ($list !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key);
return $list;
}
return $list;
}
$list = $this->persister->loadOneToManyCollection($assoc, $sourceEntity, $coll);
$persister->storeCollectionCache($key, $list);
if ($hasCache) {
$persister->storeCollectionCache($key, $list);
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
}
}
return $list;
@@ -615,7 +622,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritdoc}
*/
public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = [])
public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = array())
{
return $this->persister->loadOneToOneEntity($assoc, $sourceEntity, $identifier);
}
@@ -636,17 +643,4 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$this->persister->refresh($id, $entity, $lockMode);
}
/**
* @param array $association
* @param array $ownerId
*
* @return CollectionCacheKey
*/
protected function buildCollectionCacheKey(array $association, $ownerId)
{
/** @var ClassMetadata $metadata */
$metadata = $this->metadataFactory->getMetadataFor($association['sourceEntity']);
return new CollectionCacheKey($metadata->rootEntityName, $association['fieldName'], $ownerId);
}
}

View File

@@ -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);

View File

@@ -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;
}
/**

View File

@@ -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
];
);
}
}

View File

@@ -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

View File

@@ -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)
{

View File

@@ -45,27 +45,14 @@ class QueryCacheKey extends CacheKey
public $cacheMode;
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var TimestampCacheKey|null
* @param string $hash Result cache id
* @param integer $lifetime Query lifetime
* @param integer $cacheMode Query cache mode
*/
public $timestampKey;
/**
* @param string $hash Result cache id
* @param integer $lifetime Query lifetime
* @param int $cacheMode Query cache mode
* @param TimestampCacheKey|null $timestampKey
*/
public function __construct(
$hash,
$lifetime = 0,
$cacheMode = Cache::MODE_NORMAL,
TimestampCacheKey $timestampKey = null
) {
$this->hash = $hash;
$this->lifetime = $lifetime;
$this->cacheMode = $cacheMode;
$this->timestampKey = $timestampKey;
public function __construct($hash, $lifetime = 0, $cacheMode = Cache::MODE_NORMAL)
{
$this->hash = $hash;
$this->lifetime = $lifetime;
$this->cacheMode = $cacheMode;
}
}

Some files were not shown because too many files have changed in this diff Show More