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
1006 changed files with 17034 additions and 31322 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

3
.gitignore vendored
View File

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

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

View File

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

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/doctrine2/master.svg?style=flat-square
[Master]: https://travis-ci.org/doctrine/doctrine2
[Master coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/doctrine2/master.svg?style=flat-square
[Master coverage]: https://scrutinizer-ci.com/g/doctrine/doctrine2/?branch=master
[2.5 image]: https://img.shields.io/travis/doctrine/doctrine2/2.5.svg?style=flat-square
[2.5]: https://github.com/doctrine/doctrine2/tree/2.5
[2.5 coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/doctrine2/2.5.svg?style=flat-square
[2.5 coverage]: https://scrutinizer-ci.com/g/doctrine/doctrine2/?branch=2.5

View File

@@ -1,59 +1,5 @@
# Upgrade to 2.6
## Added `Doctrine\ORM\EntityRepository::count()` method
`Doctrine\ORM\EntityRepository::count()` has been added. This new method has different
signature than `Countable::count()` (required parameter) and therefore are not compatible.
If your repository implemented the `Countable` interface, you will have to use
`$repository->count([])` instead and not implement `Countable` interface anymore.
## Minor BC BREAK: `Doctrine\ORM\Tools\Console\ConsoleRunner` is now final
Since it's just an utilitarian class and should not be inherited.
## Minor BC BREAK: removed `Doctrine\ORM\Query\QueryException::associationPathInverseSideNotSupported()`
Method `Doctrine\ORM\Query\QueryException::associationPathInverseSideNotSupported()`
now has a required parameter `$pathExpr`.
## Minor BC BREAK: removed `Doctrine\ORM\Query\Parser#isInternalFunction()`
Method `Doctrine\ORM\Query\Parser#isInternalFunction()` was removed because
the distinction between internal function and user defined DQL was removed.
[#6500](https://github.com/doctrine/doctrine2/pull/6500)
## Minor BC BREAK: removed `Doctrine\ORM\ORMException#overwriteInternalDQLFunctionNotAllowed()`
Method `Doctrine\ORM\Query\Parser#overwriteInternalDQLFunctionNotAllowed()` was
removed because of the choice to allow users to overwrite internal functions, ie
`AVG`, `SUM`, `COUNT`, `MIN` and `MAX`. [#6500](https://github.com/doctrine/doctrine2/pull/6500)
## PHP 7.1 is now required
Doctrine 2.6 now requires PHP 7.1 or newer.
As a consequence, automatic cache setup in Doctrine\ORM\Tools\Setup::create*Configuration() was changed:
- APCu extension (ext-apcu) will now be used instead of abandoned APC (ext-apc).
- Memcached extension (ext-memcached) will be used instead of obsolete Memcache (ext-memcache).
- XCache support was dropped as it doesn't work with PHP 7.
# Upgrade to 2.5
## Minor BC BREAK: removed `Doctrine\ORM\Query\SqlWalker#walkCaseExpression()`
Method `Doctrine\ORM\Query\SqlWalker#walkCaseExpression()` was unused and part
of the internal API of the ORM, so it was removed. [#5600](https://github.com/doctrine/doctrine2/pull/5600).
## Minor BC BREAK: removed $className parameter on `AbstractEntityInheritancePersister#getSelectJoinColumnSQL()`
As `$className` parameter was not used in the method, it was safely removed.
## Minor BC BREAK: query cache key time is now a float
As of 2.5.5, the `QueryCacheEntry#time` property will contain a float value
instead of an integer in order to have more precision and also to be consistent
with the `TimestampCacheEntry#time`.
## Minor BC BREAK: discriminator map must now include all non-transient classes
It is now required that you declare the root of an inheritance in the

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

@@ -9,48 +9,40 @@
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"},
{"name": "Marco Pivetta", "email": "ocramius@gmail.com"}
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"}
],
"config": {
"sort-packages": true
},
"minimum-stability": "dev",
"require": {
"php": "^7.1",
"php": ">=5.4",
"ext-pdo": "*",
"doctrine/annotations": "~1.5",
"doctrine/cache": "~1.6",
"doctrine/collections": "^1.4",
"doctrine/common": "^2.7.1",
"doctrine/dbal": "^2.6",
"doctrine/instantiator": "~1.1",
"symfony/console": "~3.0|~4.0"
"doctrine/collections": "~1.2",
"doctrine/dbal": ">=2.5-dev,<2.6-dev",
"doctrine/instantiator": "~1.0.1",
"doctrine/common": ">=2.5-dev,<2.7-dev",
"doctrine/cache": "~1.4",
"symfony/console": "~2.5|~3.0"
},
"require-dev": {
"doctrine/coding-standard": "^1.0",
"phpunit/phpunit": "^6.5",
"squizlabs/php_codesniffer": "^3.2",
"symfony/yaml": "~3.4|~4.0"
"symfony/yaml": "~2.3|~3.0",
"phpunit/phpunit": "~4.0",
"satooshi/php-coveralls": "dev-master"
},
"suggest": {
"symfony/yaml": "If you want to use YAML Metadata Mapping Driver"
},
"autoload": {
"psr-4": { "Doctrine\\ORM\\": "lib/Doctrine/ORM" }
"psr-0": { "Doctrine\\ORM\\": "lib/" }
},
"autoload-dev": {
"psr-4": {
"Doctrine\\Tests\\": "tests/Doctrine/Tests",
"Doctrine\\Performance\\": "tests/Doctrine/Performance"
}
"psr-0": { "Doctrine\\Tests\\": "tests/" }
},
"bin": ["bin/doctrine"],
"bin": ["bin/doctrine", "bin/doctrine.php"],
"extra": {
"branch-alias": {
"dev-master": "2.6.x-dev"
}
},
"archive": {
"exclude": ["!vendor", "tests", "*phpunit.xml", ".travis.yml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp"]
"exclude": ["!vendor", "tests", "*phpunit.xml", ".travis.yml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp", "*coveralls.yml"]
}
}

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

@@ -1,9 +1,9 @@
What is new in Doctrine ORM 2.5?
================================
This document describes changes between Doctrine ORM 2.4 and 2.5.
It contains a description of all the new features and sections about
behavioral changes and potential backwards compatibility breaks.
This document describes changes between Doctrine ORM 2.4 and 2.5 (currently in
Beta). It contains a description of all the new features and sections
about behavioral changes and potential backwards compatibility breaks.
Please review this document carefully when updating to Doctrine 2.5.
First note, that with the ORM 2.5 release we are dropping support
@@ -21,7 +21,7 @@ defined then Doctrine would trigger listeners after the fields were
loaded, but before assocations are available.
- `DDC-54 <http://doctrine-project.org/jira/browse/DDC-54>`_
- `Commit #a90629 <https://github.com/doctrine/doctrine2/commit/a906295c65f1516737458fbee2f6fa96254f27a5>`_
- `Commit <https://github.com/doctrine/doctrine2/commit/a906295c65f1516737458fbee2f6fa96254f27a5>`_
Events: Add API to programatically add event listeners to Entity
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -58,11 +58,11 @@ metadata generation:
}
}
Embeddable Objects
~~~~~~~~~~~~~~~~~~
Embeddedable Objects
~~~~~~~~~~~~~~~~~~~~
Doctrine now supports creating multiple PHP objects from one database table
implementing a feature called "Embeddable Objects". Next to an ``@Entity``
implementing a feature called "Embeddedable Objects". Next to an ``@Entity``
class you can now define a class that is embeddable into a database table of an
entity using the ``@Embeddable`` annotation. Embeddable objects can never be
saved, updated or deleted on their own, only as part of an entity (called
@@ -102,7 +102,7 @@ This feature was developed by external contributor `Johannes Schmitt
<https://twitter.com/schmittjoh>`_
- `DDC-93 <http://doctrine-project.org/jira/browse/DDC-93>`_
- `Pull Request #835 <https://github.com/doctrine/doctrine2/pull/835>`_
- `Pull Request <https://github.com/doctrine/doctrine2/pull/835>`_
Second-Level-Cache
~~~~~~~~~~~~~~~~~~
@@ -160,7 +160,7 @@ instead of the database.
- `Documentation
<http://docs.doctrine-project.org/en/latest/reference/second-level-cache.html>`_
- `Pull Request #808 <https://github.com/doctrine/doctrine2/pull/808>`_
- `Pull Request <https://github.com/doctrine/doctrine2/pull/808>`_
Criteria API: Support for ManyToMany assocations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -217,8 +217,8 @@ trigger a full load of the collection.
This feature was contributed by `Michaël Gallego <https://github.com/bakura10>`_.
- `Pull Request #882 <https://github.com/doctrine/doctrine2/pull/882>`_
- `Pull Request #1032 <https://github.com/doctrine/doctrine2/pull/1032>`_
- `Pull Request #1 <https://github.com/doctrine/doctrine2/pull/882>`_
- `Pull Request #2 <https://github.com/doctrine/doctrine2/pull/1032>`_
Mapping: Allow configuring Index flags
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -241,7 +241,7 @@ only with a schema event listener before.
This feature was contributed by `Adrian Olek <https://github.com/adrianolek>`_.
- `Pull Request #973 <https://github.com/doctrine/doctrine2/pull/973>`_
- `Pull Request <https://github.com/doctrine/doctrine2/pull/973>`_
SQLFilter API: Check if a parameter is set
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -272,7 +272,7 @@ Extending on the locale example of the documentation:
This feature was contributed by `Miroslav Demovic <https://github.com/mdemo>`_
- `Pull Request #963 <https://github.com/doctrine/doctrine2/pull/963>`_
- `Pull Request <https://github.com/doctrine/doctrine2/pull/963>`_
EXTRA_LAZY Improvements
@@ -301,7 +301,7 @@ EXTRA_LAZY Improvements
This feature was contributed by `Asmir Mustafic <https://github.com/goetas>`_
- `Pull Request #937 <https://github.com/doctrine/doctrine2/pull/937>`_
- `Pull Request <https://github.com/doctrine/doctrine2/pull/937>`_
2. Add EXTRA_LAZY Support for get() for owning and inverse many-to-many
@@ -415,11 +415,11 @@ object:
This feature was contributed by `Michael Perrin
<https://github.com/michaelperrin>`_.
- `Pull Request #590 <https://github.com/doctrine/doctrine2/pull/590>`_
- `Pull Request <https://github.com/doctrine/doctrine2/pull/590>`_
- `DDC-2319 <http://doctrine-project.org/jira/browse/DDC-2319>`_
Query API: Add support for default Query Hints
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Query API: Add suport for default Query Hints
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To configure multiple different features such as custom AST Walker, fetch modes,
locking and other features affecting DQL generation we have had a feature
@@ -439,7 +439,7 @@ It is now possible to add query hints that are always enabled for every Query:
This feature was contributed by `Artur Eshenbrener
<https://github.com/Strate>`_.
- `Pull Request #863 <https://github.com/doctrine/doctrine2/pull/863>`_
- `Pull Request <https://github.com/doctrine/doctrine2/pull/863>`_
ResultSetMappingBuilder: Add support for Single-Table Inheritance
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -486,8 +486,8 @@ EntityGenerator Command: Avoid backups
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When calling the EntityGenerator for an existing entity, Doctrine would
create a backup file every time to avoid losing changes to the code. You
can now skip generating the backup file by passing the ``--no-backup``
create a backup file every time to avoid loosing changes to the code.
You can now skip generating the backup file by passing the ``--no-backup``
flag:
::
@@ -708,4 +708,3 @@ From now on, the resultset will look like this:
),
...
)

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

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

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

View File

@@ -4,7 +4,7 @@ Implementing Wakeup or Clone
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
As explained in the
`restrictions for entity classes in the manual <http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/architecture.html#entities>`_,
`restrictions for entity classes in the manual <http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities>`_,
it is usually not allowed for an entity to implement ``__wakeup``
or ``__clone``, because Doctrine makes special use of them.
However, it is quite easy to make use of these methods in a safe

View File

@@ -111,7 +111,7 @@ APC, get rid of EchoSqlLogger, and turn off
autoGenerateProxyClasses.
For more details, consult the
`Doctrine 2 Configuration documentation <http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/configuration.html>`_.
`Doctrine 2 Configuration documentation <http://www.doctrine-project.org/documentation/manual/2_0/en/configuration#configuration-options>`_.
Now to use it
-------------

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

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

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,63 +85,43 @@ the UTC time at the time of the booking and the timezone the event happened in.
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\ConversionException;
use Doctrine\DBAL\Types\DateTimeType;
class UTCDateTimeType extends DateTimeType
{
static private $utc;
static private $utc = null;
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
if ($value instanceof \DateTime) {
$value->setTimezone(self::getUtc());
if ($value === null) {
return null;
}
return parent::convertToDatabaseValue($value, $platform);
return $value->format($platform->getDateTimeFormatString(),
(self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone('UTC'))
);
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
if (null === $value || $value instanceof \DateTime) {
return $value;
if ($value === null) {
return null;
}
$converted = \DateTime::createFromFormat(
$val = \DateTime::createFromFormat(
$platform->getDateTimeFormatString(),
$value,
self::$utc ? self::$utc : self::$utc = new \DateTimeZone('UTC')
(self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone('UTC'))
);
if (! $converted) {
throw ConversionException::conversionFailedFormat(
$value,
$this->getName(),
$platform->getDateTimeFormatString()
);
if (!$val) {
throw ConversionException::conversionFailed($value, $this->getName());
}
return $converted;
return $val;
}
}
This database type makes sure that every DateTime instance is always saved in UTC, relative
to the current timezone that the passed DateTime instance has.
To actually use this new type instead of the default ``datetime`` type, you need to run following
code before bootstrapping the ORM:
.. code-block:: php
<?php
use Doctrine\DBAL\Types\Type;
use DoctrineExtensions\DBAL\Types\UTCDateTimeType;
Type::overrideType('datetime', UTCDateTimeType::class);
Type::overrideType('datetimetz', UTCDateTimeType::class);
To be able to transform these values
to the current timezone that the passed DateTime instance has. To be able to transform these values
back into their real timezone you have to save the timezone in a separate field of the entity
requiring timezoned datetimes:

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

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

View File

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

@@ -100,7 +100,7 @@ with the batching strategy that was already used for bulk inserts:
Results may be fully buffered by the database client/ connection allocating
additional memory not visible to the PHP process. For large sets this
may easily kill the process for no apparent reason.
may easily kill the process for no apparant reason.
Bulk Deletes

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

View File

@@ -79,13 +79,6 @@ Or if you prefer YAML:
$config = Setup::createYAMLMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);
.. note::
If you want to use yml mapping you should add yaml dependency to your `composer.json`:
::
"symfony/yaml": "*"
Inside the ``Setup`` methods several assumptions are made:
- If `$isDevMode` is true caching is done in memory with the ``ArrayCache``. Proxy objects are recreated on every request.

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
~~~~~~~~~~~~~
@@ -775,6 +719,8 @@ classes have to implement the base class :
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$lexer = $parser->getLexer();
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
@@ -965,7 +911,7 @@ An instance of the ``Doctrine\ORM\Query`` class represents a DQL
query. You create a Query instance be calling
``EntityManager#createQuery($dql)``, passing the DQL query string.
Alternatively you can create an empty ``Query`` instance and invoke
``Query#setDQL($dql)`` afterwards. Here are some examples:
``Query#setDql($dql)`` afterwards. Here are some examples:
.. code-block:: php
@@ -975,9 +921,9 @@ Alternatively you can create an empty ``Query`` instance and invoke
// example1: passing a DQL string
$q = $em->createQuery('select u from MyProject\Model\User u');
// example2: using setDQL
// example2: using setDql
$q = $em->createQuery();
$q->setDQL('select u from MyProject\Model\User u');
$q->setDql('select u from MyProject\Model\User u');
Query Result Formats
~~~~~~~~~~~~~~~~~~~~
@@ -1060,7 +1006,7 @@ structure:
.. code-block:: php
$dql = "SELECT u, 'some scalar string', count(g.id) AS num FROM User u JOIN u.groups g GROUP BY u.id";
$dql = "SELECT u, 'some scalar string', count(u.groups) AS num FROM User u JOIN u.groups g GROUP BY u.id";
array
[0]
@@ -1160,22 +1106,6 @@ Object hydration hydrates the result set into the object graph:
$query = $em->createQuery('SELECT u FROM CmsUser u');
$users = $query->getResult(Query::HYDRATE_OBJECT);
Sometimes the behavior in the object hydrator can be confusing, which is
why we are listing as many of the assumptions here for reference:
- Objects fetched in a FROM clause are returned as a Set, that means every
object is only ever included in the resulting array once. This is the case
even when using JOIN or GROUP BY in ways that return the same row for an
object multiple times. If the hydrator sees the same object multiple times,
then it makes sure it is only returned once.
- If an object is already in memory from a previous query of any kind, then
then the previous object is used, even if the database may contain more
recent data. Data from the database is discarded. This even happens if the
previous object is still an unloaded proxy.
This list might be incomplete.
Array Hydration
^^^^^^^^^^^^^^^
@@ -1452,13 +1382,7 @@ Given that there are 10 users and corresponding addresses in the database the ex
SELECT * FROM address WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
.. note::
Changing the fetch mode during a query mostly makes sense for one-to-one and many-to-one relations. In that case,
all the necessary IDs are available after the root entity (``user`` in the above example) has been loaded. So, one
query per association can be executed to fetch all the referred-to entities (``address``).
For one-to-many relations, changing the fetch mode to eager will cause to execute one query **for every root entity
loaded**. This gives no improvement over the ``lazy`` fetch mode which will also initialize the associations on
a one-by-one basis once they are accessed.
Changing the fetch mode during a query is only possible for one-to-one and many-to-one relations.
EBNF
@@ -1487,9 +1411,7 @@ Terminals
~~~~~~~~~
- identifier (name, email, ...) must match ``[a-z_][a-z0-9_]*``
- fully_qualified_name (Doctrine\Tests\Models\CMS\CmsUser) matches PHP's fully qualified class names
- aliased_name (CMS:CmsUser) uses two identifiers, one for the namespace alias and one for the class inside it
- identifier (name, email, ...)
- string ('foo', 'bar''s house', '%ninja%', ...)
- char ('/', '\\', ' ', ...)
- integer (-1, 0, 1, 34, ...)
@@ -1523,8 +1445,8 @@ Identifiers
/* Alias Identification declaration (the "u" of "FROM User u") */
AliasIdentificationVariable :: = identifier
/* identifier that must be a class name (the "User" of "FROM User u"), possibly as a fully qualified class name or namespace-aliased */
AbstractSchemaName ::= fully_qualified_name | aliased_name | identifier
/* identifier that must be a class name (the "User" of "FROM User u") */
AbstractSchemaName ::= identifier
/* Alias ResultVariable declaration (the "total" of "COUNT(*) AS total") */
AliasResultVariable = identifier
@@ -1623,7 +1545,7 @@ Select Expressions
SimpleSelectExpression ::= (StateFieldPathExpression | IdentificationVariable | FunctionDeclaration | AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable]
PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")"
NewObjectExpression ::= "NEW" IdentificationVariable "(" NewObjectArg {"," NewObjectArg}* ")"
NewObjectArg ::= ScalarExpression | "(" Subselect ")"
Conditional Expressions

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
@@ -653,8 +652,7 @@ preUpdate
PreUpdate is the most restrictive to use event, since it is called
right before an update statement is called for an entity inside the
``EntityManager#flush()`` method. Note that this event is not
triggered when the computed changeset is empty.
``EntityManager#flush()`` method.
Changes to associations of the updated entity are never allowed in
this event, since Doctrine cannot guarantee to correctly handle
@@ -740,7 +738,7 @@ The three post events are called inside ``EntityManager#flush()``.
Changes in here are not relevant to the persistence in the
database, but you can use these events to alter non-persistable items,
like non-mapped fields, logging or even associated classes that are
not directly mapped by Doctrine.
directly mapped by Doctrine.
postLoad
~~~~~~~~
@@ -888,9 +886,6 @@ you need to map the listener method using the event type mapping:
preRemove: [preRemoveHandler]
# ....
.. note::
The order of execution of multiple methods for the same event (e.g. multiple @PrePersist) is not guaranteed.
Entity listeners resolver

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

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

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

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

View File

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

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

View File

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

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
@@ -385,7 +385,7 @@ First you need to retrieve the metadata instances with the
)
);
$cmf = new \Doctrine\ORM\Tools\DisconnectedClassMetadataFactory();
$cmf = new DisconnectedClassMetadataFactory();
$cmf->setEntityManager($em);
$metadata = $cmf->getAllMetadata();
@@ -395,7 +395,6 @@ to yml:
.. code-block:: php
<?php
$cme = new \Doctrine\ORM\Tools\Export\ClassMetadataExporter();
$exporter = $cme->getExporter('yml', '/path/to/export/yml');
$exporter->setMetadata($metadata);
$exporter->export();
@@ -477,7 +476,7 @@ To include a new command on Doctrine Console, you need to do modify the
<?php
// doctrine.php
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Helper\Application;
// as before ...

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

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

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>`").
"Transitive Persistence").
- If X is a removed entity, it becomes managed.
- If X is a detached entity, an exception will be thrown on
flush.
@@ -286,12 +279,12 @@ as follows:
- If X is a new entity, it is ignored by the remove operation.
However, the remove operation is cascaded to entities referenced by
X, if the relationship from X to these other entities is mapped
with cascade=REMOVE or cascade=ALL (see ":ref:`Transitive Persistence <transitive-persistence>`").
with cascade=REMOVE or cascade=ALL (see "Transitive Persistence").
- If X is a managed entity, the remove operation causes it to
become removed. The remove operation is cascaded to entities
referenced by X, if the relationships from X to these other
entities is mapped with cascade=REMOVE or cascade=ALL (see
":ref:`Transitive Persistence <transitive-persistence>`").
"Transitive Persistence").
- If X is a detached entity, an InvalidArgumentException will be
thrown.
- If X is a removed entity, it is ignored by the remove operation.
@@ -357,14 +350,14 @@ as follows:
become detached. The detach operation is cascaded to entities
referenced by X, if the relationships from X to these other
entities is mapped with cascade=DETACH or cascade=ALL (see
":ref:`Transitive Persistence <transitive-persistence>`"). Entities which previously referenced X
"Transitive Persistence"). Entities which previously referenced X
will continue to reference X.
- If X is a new or detached entity, it is ignored by the detach
operation.
- If X is a removed entity, the detach operation is cascaded to
entities referenced by X, if the relationships from X to these
other entities is mapped with cascade=DETACH or cascade=ALL (see
":ref:`Transitive Persistence <transitive-persistence>`"). Entities which previously referenced X
"Transitive Persistence"). Entities which previously referenced X
will continue to reference X.
There are several situations in which an entity is detached
@@ -423,7 +416,8 @@ as follows:
- If X is a managed entity, it is ignored by the merge operation,
however, the merge operation is cascaded to entities referenced by
relationships from X if these relationships have been mapped with
the cascade element value MERGE or ALL (see ":ref:`Transitive Persistence <transitive-persistence>`").
the cascade element value MERGE or ALL (see "Transitive
Persistence").
- For all entities Y referenced by relationships from X having the
cascade element value MERGE or ALL, Y is merged recursively as Y'.
For all such Y referenced by X, X' is set to reference Y'. (Note
@@ -705,6 +699,8 @@ You can also load by owning side associations through the repository:
$number = $em->find('MyProject\Domain\Phonenumber', 1234);
$user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('phone' => $number->getId()));
Be careful that this only works by passing the ID of the associated entity, not yet by passing the associated entity itself.
The ``EntityRepository#findBy()`` method additionally accepts orderings, limit and offset as second to fourth parameters:
.. code-block:: php
@@ -733,14 +729,6 @@ examples are equivalent:
// A single user by its nickname (__call magic)
$user = $em->getRepository('MyProject\Domain\User')->findOneByNickname('romanb');
Additionally, you can just count the result of the provided conditions when you don't really need the data:
.. code-block:: php
<?php
// Check there is no user with nickname
$availableNickname = 0 === $em->getRepository('MyProject\Domain\User')->count(['nickname' => 'nonexistent']);
By Criteria
~~~~~~~~~~~
@@ -750,7 +738,8 @@ The Repository implement the ``Doctrine\Common\Collections\Selectable``
interface. That means you can build ``Doctrine\Common\Collections\Criteria``
and pass them to the ``matching($criteria)`` method.
See section `Filtering collections` of chapter :doc:`Working with Associations <working-with-associations>`
See the :ref:`Working with Associations: Filtering collections
<filtering-collections>`.
By Eager Loading
~~~~~~~~~~~~~~~~

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

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

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

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.
*
@@ -95,17 +96,17 @@ abstract class AbstractQuery
*
* @var array
*/
protected $_hints = [];
protected $_hints = array();
/**
* The hydration mode.
*
* @var string|int
* @var integer
*/
protected $_hydrationMode = self::HYDRATE_OBJECT;
/**
* @var \Doctrine\DBAL\Cache\QueryCacheProfile
* @param \Doctrine\DBAL\Cache\QueryCacheProfile
*/
protected $_queryCacheProfile;
@@ -117,7 +118,7 @@ abstract class AbstractQuery
protected $_expireResultCache = false;
/**
* @var \Doctrine\DBAL\Cache\QueryCacheProfile
* @param \Doctrine\DBAL\Cache\QueryCacheProfile
*/
protected $_hydrationCacheProfile;
@@ -177,6 +178,7 @@ abstract class AbstractQuery
}
/**
*
* Enable/disable second level query (result) caching for this query.
*
* @param boolean $cacheable
@@ -213,7 +215,7 @@ abstract class AbstractQuery
/**
* Obtain the name of the second level query cache region in which query results will be stored
*
* @return string|null The cache region name; NULL indicates the default region.
* @return The cache region name; NULL indicates the default region.
*/
public function getCacheRegion()
{
@@ -241,7 +243,7 @@ abstract class AbstractQuery
*
* @param integer $lifetime
*
* @return \Doctrine\ORM\AbstractQuery This query instance.
* @return static This query instance.
*/
public function setLifetime($lifetime)
{
@@ -261,7 +263,7 @@ abstract class AbstractQuery
/**
* @param integer $cacheMode
*
* @return \Doctrine\ORM\AbstractQuery This query instance.
* @return static This query instance.
*/
public function setCacheMode($cacheMode)
{
@@ -323,14 +325,14 @@ abstract class AbstractQuery
public function getParameter($key)
{
$filteredParameters = $this->parameters->filter(
function (Query\Parameter $parameter) use ($key) : bool {
$parameterName = $parameter->getName();
return $key === $parameterName || (string) $key === (string) $parameterName;
function ($parameter) use ($key)
{
// Must not be identical because of string to integer conversion
return ($key == $parameter->getName());
}
);
return ! $filteredParameters->isEmpty() ? $filteredParameters->first() : null;
return count($filteredParameters) ? $filteredParameters->first() : null;
}
/**
@@ -371,10 +373,17 @@ abstract class AbstractQuery
*/
public function setParameter($key, $value, $type = null)
{
$existingParameter = $this->getParameter($key);
$filteredParameters = $this->parameters->filter(
function ($parameter) use ($key)
{
// Must not be identical because of string to integer conversion
return ($key == $parameter->getName());
}
);
if ($existingParameter !== null) {
$existingParameter->setValue($value, $type);
if (count($filteredParameters)) {
$parameter = $filteredParameters->first();
$parameter->setValue($value, $type);
return $this;
}
@@ -389,7 +398,7 @@ abstract class AbstractQuery
*
* @param mixed $value
*
* @return array|string
* @return array
*
* @throws \Doctrine\ORM\ORMInvalidArgumentException
*/
@@ -412,24 +421,16 @@ abstract class AbstractQuery
return $value;
}
if ($value instanceof Mapping\ClassMetadata) {
return $value->name;
}
if (! is_object($value)) {
return $value;
}
try {
if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value))) {
$value = $this->_em->getUnitOfWork()->getSingleIdentifierValue($value);
if ($value === null) {
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
}
} catch (MappingException | ORMMappingException $e) {
// Silence any mapping exceptions. These can occur if the object in
// question is not a mapped entity, in which case we just don't do
// any preparation on the value.
}
if ($value instanceof Mapping\ClassMetadata) {
return $value->name;
}
return $value;
@@ -501,7 +502,7 @@ abstract class AbstractQuery
*/
public function setHydrationCacheProfile(QueryCacheProfile $profile = null)
{
if ($profile !== null && ! $profile->getResultCacheDriver()) {
if ( ! $profile->getResultCacheDriver()) {
$resultCacheDriver = $this->_em->getConfiguration()->getHydrationCacheImpl();
$profile = $profile->setResultCacheDriver($resultCacheDriver);
}
@@ -531,7 +532,7 @@ abstract class AbstractQuery
*/
public function setResultCacheProfile(QueryCacheProfile $profile = null)
{
if ($profile !== null && ! $profile->getResultCacheDriver()) {
if ( ! $profile->getResultCacheDriver()) {
$resultCacheDriver = $this->_em->getConfiguration()->getResultCacheImpl();
$profile = $profile->setResultCacheDriver($resultCacheDriver);
}
@@ -690,8 +691,8 @@ abstract class AbstractQuery
/**
* Defines the processing mode to be used during hydration / result set transformation.
*
* @param string|int $hydrationMode Doctrine processing mode to be used during hydration process.
* One of the Query::HYDRATE_* constants.
* @param integer $hydrationMode Doctrine processing mode to be used during hydration process.
* One of the Query::HYDRATE_* constants.
*
* @return static This query instance.
*/
@@ -705,7 +706,7 @@ abstract class AbstractQuery
/**
* Gets the hydration mode currently used by the query.
*
* @return string|int
* @return integer
*/
public function getHydrationMode()
{
@@ -717,9 +718,9 @@ abstract class AbstractQuery
*
* Alias for execute(null, $hydrationMode = HYDRATE_OBJECT).
*
* @param string|int $hydrationMode
* @param int $hydrationMode
*
* @return mixed
* @return array
*/
public function getResult($hydrationMode = self::HYDRATE_OBJECT)
{
@@ -753,7 +754,7 @@ abstract class AbstractQuery
/**
* Get exactly one result or null.
*
* @param string|int $hydrationMode
* @param int $hydrationMode
*
* @return mixed
*
@@ -791,12 +792,12 @@ abstract class AbstractQuery
* If the result is not unique, a NonUniqueResultException is thrown.
* If there is no result, a NoResultException is thrown.
*
* @param string|int $hydrationMode
* @param integer $hydrationMode
*
* @return mixed
*
* @throws NonUniqueResultException If the query result is not unique.
* @throws NoResultException If the query returned no result and hydration mode is not HYDRATE_SINGLE_SCALAR.
* @throws NoResultException If the query returned no result.
*/
public function getSingleResult($hydrationMode = null)
{
@@ -822,9 +823,10 @@ abstract class AbstractQuery
*
* Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
*
* @return mixed The scalar result, or NULL if the query returned no result.
* @return mixed
*
* @throws NonUniqueResultException If the query result is not unique.
* @throws NoResultException If the query returned no result.
*/
public function getSingleScalarResult()
{
@@ -861,7 +863,7 @@ abstract class AbstractQuery
/**
* Check if the query has a hint
*
* @param string $name The name of the hint
* @param string $name The name of the hint
*
* @return bool False if the query does not have any hint
*/
@@ -885,7 +887,7 @@ abstract class AbstractQuery
* iterate over the result.
*
* @param ArrayCollection|array|null $parameters The query parameters.
* @param string|int|null $hydrationMode The hydration mode to use.
* @param integer|null $hydrationMode The hydration mode to use.
*
* @return \Doctrine\ORM\Internal\Hydration\IterableResult
*/
@@ -909,7 +911,7 @@ abstract class AbstractQuery
* Executes the query.
*
* @param ArrayCollection|array|null $parameters Query parameters.
* @param string|int|null $hydrationMode Processing mode to be used during the hydration process.
* @param integer|null $hydrationMode Processing mode to be used during the hydration process.
*
* @return mixed
*/
@@ -926,7 +928,7 @@ abstract class AbstractQuery
* Execute query ignoring second level cache.
*
* @param ArrayCollection|array|null $parameters
* @param string|int|null $hydrationMode
* @param integer|null $hydrationMode
*
* @return mixed
*/
@@ -954,7 +956,7 @@ abstract class AbstractQuery
}
if ( ! $result) {
$result = [];
$result = array();
}
$setCacheEntry = function($data) use ($cache, $result, $cacheKey, $realCacheKey, $queryCacheProfile) {
@@ -984,61 +986,39 @@ abstract class AbstractQuery
* Load from second level cache or executes the query and put into cache.
*
* @param ArrayCollection|array|null $parameters
* @param string|int|null $hydrationMode
* @param integer|null $hydrationMode
*
* @return mixed
*/
private function executeUsingQueryCache($parameters = null, $hydrationMode = null)
{
$rsm = $this->getResultSetMapping();
$querykey = new QueryCacheKey($this->getHash(), $this->lifetime, $this->cacheMode ?: Cache::MODE_NORMAL);
$queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion);
$queryKey = new QueryCacheKey(
$this->getHash(),
$this->lifetime,
$this->cacheMode ?: Cache::MODE_NORMAL,
$this->getTimestampKey()
);
$result = $queryCache->get($queryKey, $rsm, $this->_hints);
$result = $queryCache->get($querykey, $rsm, $this->_hints);
if ($result !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $queryKey);
$this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $querykey);
}
return $result;
}
$result = $this->executeIgnoreQueryCache($parameters, $hydrationMode);
$cached = $queryCache->put($queryKey, $rsm, $result, $this->_hints);
$cached = $queryCache->put($querykey, $rsm, $result, $this->_hints);
if ($this->cacheLogger) {
$this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $queryKey);
$this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $querykey);
if ($cached) {
$this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $queryKey);
$this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $querykey);
}
}
return $result;
}
/**
* @return \Doctrine\ORM\Cache\TimestampCacheKey|null
*/
private function getTimestampKey()
{
$entityName = reset($this->_resultSetMapping->aliasMap);
if (empty($entityName)) {
return null;
}
$metadata = $this->_em->getClassMetadata($entityName);
return new Cache\TimestampCacheKey($metadata->rootEntityName);
}
/**
* Get the result cache id to use to store the result set cache entry.
* Will return the configured id if it exists otherwise a hash will be
@@ -1048,7 +1028,7 @@ abstract class AbstractQuery
*/
protected function getHydrationCacheId()
{
$parameters = [];
$parameters = array();
foreach ($this->getParameters() as $parameter) {
$parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
@@ -1110,7 +1090,7 @@ abstract class AbstractQuery
{
$this->parameters = new ArrayCollection();
$this->_hints = [];
$this->_hints = array();
$this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints();
}

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

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

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);
}
}
@@ -477,7 +488,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$cacheEntry = $this->hydrator->buildCacheEntry($class, $cacheKey, $entity);
$cached = $this->region->put($cacheKey, $cacheEntry);
if ($cached && (null === $this->joinedAssociations || $this->joinedAssociations)) {
if ($cached && ($this->joinedAssociations === null || count($this->joinedAssociations) > 0)) {
$this->storeJoinedAssociations($entity);
}
@@ -495,7 +506,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
public function count($criteria = [])
public function count($criteria = array())
{
return $this->persister->count($criteria);
}
@@ -505,34 +516,32 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
*/
public function loadCriteria(Criteria $criteria)
{
$orderBy = $criteria->getOrderings();
$limit = $criteria->getMaxResults();
$offset = $criteria->getFirstResult();
$query = $this->persister->getSelectSQL($criteria);
$hash = $this->getHash($query, $criteria, $orderBy, $limit, $offset);
$timestamp = $this->timestampRegion->get($this->timestampKey);
$hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null);
$rsm = $this->getResultSetMapping();
$queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey);
$querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL);
$queryCache = $this->cache->getQueryCache($this->regionName);
$cacheResult = $queryCache->get($queryKey, $rsm);
$cacheResult = $queryCache->get($querykey, $rsm);
if ($cacheResult !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->queryCacheHit($this->regionName, $queryKey);
$this->cacheLogger->queryCacheHit($this->regionName, $querykey);
}
return $cacheResult;
}
$result = $this->persister->loadCriteria($criteria);
$cached = $queryCache->put($queryKey, $rsm, $result);
$cached = $queryCache->put($querykey, $rsm, $result);
if ($this->cacheLogger) {
if ($result) {
$this->cacheLogger->queryCacheMiss($this->regionName, $queryKey);
$this->cacheLogger->queryCacheMiss($this->regionName, $querykey);
}
if ($cached) {
$this->cacheLogger->queryCachePut($this->regionName, $queryKey);
$this->cacheLogger->queryCachePut($this->regionName, $querykey);
}
}
@@ -548,28 +557,28 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$hasCache = ($persister instanceof CachedPersister);
$key = null;
if ( ! $hasCache) {
return $this->persister->loadManyToManyCollection($assoc, $sourceEntity, $coll);
}
if ($hasCache) {
$ownerId = $this->uow->getEntityIdentifier($coll->getOwner());
$key = new CollectionCacheKey($assoc['sourceEntity'], $assoc['fieldName'], $ownerId);
$list = $persister->loadCollectionCache($coll, $key);
$ownerId = $this->uow->getEntityIdentifier($coll->getOwner());
$key = $this->buildCollectionCacheKey($assoc, $ownerId);
$list = $persister->loadCollectionCache($coll, $key);
if ($list !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key);
}
if ($list !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key);
return $list;
}
return $list;
}
$list = $this->persister->loadManyToManyCollection($assoc, $sourceEntity, $coll);
$persister->storeCollectionCache($key, $list);
if ($hasCache) {
$persister->storeCollectionCache($key, $list);
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
}
}
return $list;
@@ -583,28 +592,28 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$persister = $this->uow->getCollectionPersister($assoc);
$hasCache = ($persister instanceof CachedPersister);
if ( ! $hasCache) {
return $this->persister->loadOneToManyCollection($assoc, $sourceEntity, $coll);
}
if ($hasCache) {
$ownerId = $this->uow->getEntityIdentifier($coll->getOwner());
$key = new CollectionCacheKey($assoc['sourceEntity'], $assoc['fieldName'], $ownerId);
$list = $persister->loadCollectionCache($coll, $key);
$ownerId = $this->uow->getEntityIdentifier($coll->getOwner());
$key = $this->buildCollectionCacheKey($assoc, $ownerId);
$list = $persister->loadCollectionCache($coll, $key);
if ($list !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key);
}
if ($list !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key);
return $list;
}
return $list;
}
$list = $this->persister->loadOneToManyCollection($assoc, $sourceEntity, $coll);
$persister->storeCollectionCache($key, $list);
if ($hasCache) {
$persister->storeCollectionCache($key, $list);
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
}
}
return $list;
@@ -613,7 +622,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritdoc}
*/
public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = [])
public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = array())
{
return $this->persister->loadOneToOneEntity($assoc, $sourceEntity, $identifier);
}
@@ -634,17 +643,4 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$this->persister->refresh($id, $entity, $lockMode);
}
/**
* @param array $association
* @param array $ownerId
*
* @return CollectionCacheKey
*/
protected function buildCollectionCacheKey(array $association, $ownerId)
{
/** @var ClassMetadata $metadata */
$metadata = $this->metadataFactory->getMetadataFor($association['sourceEntity']);
return new CollectionCacheKey($metadata->rootEntityName, $association['fieldName'], $ownerId);
}
}

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

View File

@@ -21,6 +21,7 @@
namespace Doctrine\ORM\Cache\Region;
use Doctrine\Common\Cache\MultiGetCache;
use Doctrine\ORM\Cache\Region;
use Doctrine\ORM\Cache\CollectionCacheEntry;
/**
@@ -55,7 +56,7 @@ class DefaultMultiGetRegion extends DefaultRegion
*/
public function getMultiple(CollectionCacheEntry $collection)
{
$keysToRetrieve = [];
$keysToRetrieve = array();
foreach ($collection->identifiers as $index => $key) {
$keysToRetrieve[$index] = $this->getCacheEntryKey($key);
@@ -66,7 +67,7 @@ class DefaultMultiGetRegion extends DefaultRegion
return null;
}
$returnableItems = [];
$returnableItems = array();
foreach ($keysToRetrieve as $index => $key) {
$returnableItems[$index] = $items[$key];
}

View File

@@ -102,7 +102,7 @@ class DefaultRegion implements Region
*/
public function getMultiple(CollectionCacheEntry $collection)
{
$result = [];
$result = array();
foreach ($collection->identifiers as $key) {
$entryKey = $this->getCacheEntryKey($key);

View File

@@ -114,7 +114,7 @@ class FileLockRegion implements ConcurrentRegion
/**
* @param \Doctrine\ORM\Cache\CacheKey $key
*
* @return string
* return string
*/
private function getLockFileName(CacheKey $key)
{
@@ -124,7 +124,7 @@ class FileLockRegion implements ConcurrentRegion
/**
* @param string $filename
*
* @return string
* return string
*/
private function getLockContent($filename)
{
@@ -134,7 +134,7 @@ class FileLockRegion implements ConcurrentRegion
/**
* @param string $filename
*
* @return integer
* return integer
*/
private function getLockTime($filename)
{
@@ -142,7 +142,7 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* {@inheritdoc}
* {inheritdoc}
*/
public function getName()
{
@@ -150,7 +150,7 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* {@inheritdoc}
* {inheritdoc}
*/
public function contains(CacheKey $key)
{
@@ -162,7 +162,7 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* {@inheritdoc}
* {inheritdoc}
*/
public function get(CacheKey $key)
{
@@ -186,7 +186,7 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* {@inheritdoc}
* {inheritdoc}
*/
public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null)
{
@@ -198,7 +198,7 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* {@inheritdoc}
* {inheritdoc}
*/
public function evict(CacheKey $key)
{
@@ -210,7 +210,7 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* {@inheritdoc}
* {inheritdoc}
*/
public function evictAll()
{
@@ -228,7 +228,7 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* {@inheritdoc}
* {inheritdoc}
*/
public function lock(CacheKey $key)
{
@@ -248,7 +248,7 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* {@inheritdoc}
* {inheritdoc}
*/
public function unlock(CacheKey $key, Lock $lock)
{

View File

@@ -31,12 +31,12 @@ class RegionsConfiguration
/**
* @var array
*/
private $lifetimes = [];
private $lifetimes = array();
/**
* @var array
*/
private $lockLifetimes = [];
private $lockLifetimes = array();
/**
* @var integer

View File

@@ -40,7 +40,7 @@ class TimestampCacheEntry implements CacheEntry
*/
public function __construct($time = null)
{
$this->time = $time ? (float) $time : microtime(true);
$this->time = $time ? (float)$time : microtime(true);
}
/**
@@ -49,8 +49,6 @@ class TimestampCacheEntry implements CacheEntry
* This method allow Doctrine\Common\Cache\PhpFileCache compatibility
*
* @param array $values array containing property values
*
* @return TimestampCacheEntry
*/
public static function __set_state(array $values)
{

View File

@@ -26,49 +26,15 @@ namespace Doctrine\ORM\Cache;
*/
class TimestampQueryCacheValidator implements QueryCacheValidator
{
/**
* @var TimestampRegion
*/
private $timestampRegion;
/**
* @param TimestampRegion $timestampRegion
*/
public function __construct(TimestampRegion $timestampRegion)
{
$this->timestampRegion = $timestampRegion;
}
/**
* {@inheritdoc}
*/
public function isValid(QueryCacheKey $key, QueryCacheEntry $entry)
{
if ($this->regionUpdated($key, $entry)) {
return false;
}
if ($key->lifetime == 0) {
return true;
}
return ($entry->time + $key->lifetime) > microtime(true);
}
/**
* @param QueryCacheKey $key
* @param QueryCacheEntry $entry
*
* @return bool
*/
private function regionUpdated(QueryCacheKey $key, QueryCacheEntry $entry)
{
if ($key->timestampKey === null) {
return false;
}
$timestamp = $this->timestampRegion->get($key->timestampKey);
return $timestamp && $timestamp->time > $entry->time;
return ($entry->time + $key->lifetime) > time();
}
}

View File

@@ -25,11 +25,9 @@ use Doctrine\Common\Annotations\CachedReader;
use Doctrine\Common\Annotations\SimpleAnnotationReader;
use Doctrine\Common\Cache\ArrayCache;
use Doctrine\Common\Cache\Cache as CacheDriver;
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;
use Doctrine\Common\Persistence\ObjectRepository;
use Doctrine\Common\Proxy\AbstractProxyFactory;
use Doctrine\ORM\Cache\CacheConfiguration;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;
use Doctrine\ORM\Mapping\DefaultEntityListenerResolver;
use Doctrine\ORM\Mapping\DefaultNamingStrategy;
use Doctrine\ORM\Mapping\DefaultQuoteStrategy;
@@ -100,7 +98,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function setAutoGenerateProxyClasses($autoGenerate)
{
$this->_attributes['autoGenerateProxyClasses'] = (int) $autoGenerate;
$this->_attributes['autoGenerateProxyClasses'] = (int)$autoGenerate;
}
/**
@@ -151,7 +149,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* @return AnnotationDriver
*/
public function newDefaultAnnotationDriver($paths = [], $useSimpleAnnotationReader = true)
public function newDefaultAnnotationDriver($paths = array(), $useSimpleAnnotationReader = true)
{
AnnotationRegistry::registerFile(__DIR__ . '/Mapping/Driver/DoctrineAnnotations.php');
@@ -351,7 +349,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function addNamedNativeQuery($name, $sql, Query\ResultSetMapping $rsm)
{
$this->_attributes['namedNativeQueries'][$name] = [$sql, $rsm];
$this->_attributes['namedNativeQueries'][$name] = array($sql, $rsm);
}
/**
@@ -420,9 +418,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
* @param string|callable $className Class name or a callable that returns the function.
*
* @return void
*
* @throws ORMException
*/
public function addCustomStringFunction($name, $className)
{
if (Query\Parser::isInternalFunction($name)) {
throw ORMException::overwriteInternalDQLFunctionNotAllowed($name);
}
$this->_attributes['customStringFunctions'][strtolower($name)] = $className;
}
@@ -472,9 +476,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
* @param string|callable $className Class name or a callable that returns the function.
*
* @return void
*
* @throws ORMException
*/
public function addCustomNumericFunction($name, $className)
{
if (Query\Parser::isInternalFunction($name)) {
throw ORMException::overwriteInternalDQLFunctionNotAllowed($name);
}
$this->_attributes['customNumericFunctions'][strtolower($name)] = $className;
}
@@ -524,9 +534,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
* @param string|callable $className Class name or a callable that returns the function.
*
* @return void
*
* @throws ORMException
*/
public function addCustomDatetimeFunction($name, $className)
{
if (Query\Parser::isInternalFunction($name)) {
throw ORMException::overwriteInternalDQLFunctionNotAllowed($name);
}
$this->_attributes['customDatetimeFunctions'][strtolower($name)] = $className;
}
@@ -574,7 +590,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function setCustomHydrationModes($modes)
{
$this->_attributes['customHydrationModes'] = [];
$this->_attributes['customHydrationModes'] = array();
foreach ($modes as $modeName => $hydrator) {
$this->addCustomHydrationMode($modeName, $hydrator);
@@ -626,7 +642,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
public function getClassMetadataFactoryName()
{
if ( ! isset($this->_attributes['classMetadataFactoryName'])) {
$this->_attributes['classMetadataFactoryName'] = ClassMetadataFactory::class;
$this->_attributes['classMetadataFactoryName'] = 'Doctrine\ORM\Mapping\ClassMetadataFactory';
}
return $this->_attributes['classMetadataFactoryName'];
@@ -648,7 +664,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* @param string $name The name of the filter.
*
* @return string The class name of the filter, or null if it is not
* @return string The class name of the filter, or null of it is not
* defined.
*/
public function getFilterClassName($name)
@@ -673,7 +689,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
{
$reflectionClass = new \ReflectionClass($className);
if ( ! $reflectionClass->implementsInterface(ObjectRepository::class)) {
if ( ! $reflectionClass->implementsInterface('Doctrine\Common\Persistence\ObjectRepository')) {
throw ORMException::invalidEntityRepository($className);
}
@@ -691,7 +707,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
{
return isset($this->_attributes['defaultRepositoryClassName'])
? $this->_attributes['defaultRepositoryClassName']
: EntityRepository::class;
: 'Doctrine\ORM\EntityRepository';
}
/**
@@ -865,7 +881,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function getDefaultQueryHints()
{
return isset($this->_attributes['defaultQueryHints']) ? $this->_attributes['defaultQueryHints'] : [];
return isset($this->_attributes['defaultQueryHints']) ? $this->_attributes['defaultQueryHints'] : array();
}
/**

View File

@@ -19,16 +19,14 @@
namespace Doctrine\ORM;
use Exception;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\Query\FilterCollection;
use Doctrine\Common\Util\ClassUtils;
use Throwable;
/**
* The EntityManager is the central access point to ORM functionality.
@@ -238,9 +236,9 @@ use Throwable;
$this->conn->commit();
return $return ?: true;
} catch (Throwable $e) {
} catch (Exception $e) {
$this->close();
$this->conn->rollBack();
$this->conn->rollback();
throw $e;
}
@@ -259,7 +257,7 @@ use Throwable;
*/
public function rollback()
{
$this->conn->rollBack();
$this->conn->rollback();
}
/**
@@ -291,7 +289,7 @@ use Throwable;
$query = new Query($this);
if ( ! empty($dql)) {
$query->setDQL($dql);
$query->setDql($dql);
}
return $query;
@@ -312,7 +310,7 @@ use Throwable;
{
$query = new NativeQuery($this);
$query->setSQL($sql);
$query->setSql($sql);
$query->setResultSetMapping($rsm);
return $query;
@@ -350,7 +348,6 @@ use Throwable;
*
* @throws \Doctrine\ORM\OptimisticLockException If a version check on an entity that
* makes use of optimistic locking fails.
* @throws ORMException
*/
public function flush($entity = null)
{
@@ -381,16 +378,12 @@ use Throwable;
{
$class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
if ($lockMode !== null) {
$this->checkLockRequirements($lockMode, $class);
}
if ( ! is_array($id)) {
if ($class->isIdentifierComposite) {
throw ORMInvalidArgumentException::invalidCompositeIdentifier();
}
$id = [$class->identifier[0] => $id];
$id = array($class->identifier[0] => $id);
}
foreach ($id as $i => $value) {
@@ -403,7 +396,7 @@ use Throwable;
}
}
$sortedId = [];
$sortedId = array();
foreach ($class->identifier as $identifier) {
if ( ! isset($id[$identifier])) {
@@ -446,15 +439,24 @@ use Throwable;
switch (true) {
case LockMode::OPTIMISTIC === $lockMode:
if ( ! $class->isVersioned) {
throw OptimisticLockException::notVersioned($class->name);
}
$entity = $persister->load($sortedId);
$unitOfWork->lock($entity, $lockMode, $lockVersion);
return $entity;
case LockMode::NONE === $lockMode:
case LockMode::PESSIMISTIC_READ === $lockMode:
case LockMode::PESSIMISTIC_WRITE === $lockMode:
return $persister->load($sortedId, null, null, [], $lockMode);
if ( ! $this->getConnection()->isTransactionActive()) {
throw TransactionRequiredException::transactionRequired();
}
return $persister->load($sortedId, null, null, array(), $lockMode);
default:
return $persister->loadById($sortedId);
@@ -469,10 +471,10 @@ use Throwable;
$class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
if ( ! is_array($id)) {
$id = [$class->identifier[0] => $id];
$id = array($class->identifier[0] => $id);
}
$sortedId = [];
$sortedId = array();
foreach ($class->identifier as $identifier) {
if ( ! isset($id[$identifier])) {
@@ -480,11 +482,6 @@ use Throwable;
}
$sortedId[$identifier] = $id[$identifier];
unset($id[$identifier]);
}
if ($id) {
throw ORMException::unrecognizedIdentifierFields($class->name, array_keys($id));
}
// Check identity map first, if its already in there just return it.
@@ -496,9 +493,13 @@ use Throwable;
return $this->find($entityName, $sortedId);
}
if ( ! is_array($sortedId)) {
$sortedId = array($class->identifier[0] => $sortedId);
}
$entity = $this->proxyFactory->getProxy($class->name, $sortedId);
$this->unitOfWork->registerManaged($entity, $sortedId, []);
$this->unitOfWork->registerManaged($entity, $sortedId, array());
return $entity;
}
@@ -516,14 +517,14 @@ use Throwable;
}
if ( ! is_array($identifier)) {
$identifier = [$class->identifier[0] => $identifier];
$identifier = array($class->identifier[0] => $identifier);
}
$entity = $class->newInstance();
$class->setIdentifierValues($entity, $identifier);
$this->unitOfWork->registerManaged($entity, $identifier, []);
$this->unitOfWork->registerManaged($entity, $identifier, array());
$this->unitOfWork->markReadOnly($entity);
return $entity;
@@ -536,22 +537,10 @@ use Throwable;
* @param string|null $entityName if given, only entities of this type will get detached
*
* @return void
*
* @throws ORMInvalidArgumentException if a non-null non-string value is given
* @throws \Doctrine\Common\Persistence\Mapping\MappingException if a $entityName is given, but that entity is not
* found in the mappings
*/
public function clear($entityName = null)
{
if (null !== $entityName && ! is_string($entityName)) {
throw ORMInvalidArgumentException::invalidEntityName($entityName);
}
$this->unitOfWork->clear(
null === $entityName
? null
: $this->metadataFactory->getMetadataFor($entityName)->getName()
);
$this->unitOfWork->clear($entityName);
}
/**
@@ -578,12 +567,11 @@ use Throwable;
* @return void
*
* @throws ORMInvalidArgumentException
* @throws ORMException
*/
public function persist($entity)
{
if ( ! is_object($entity)) {
throw ORMInvalidArgumentException::invalidObject('EntityManager#persist()', $entity);
throw ORMInvalidArgumentException::invalidObject('EntityManager#persist()' , $entity);
}
$this->errorIfClosed();
@@ -602,12 +590,11 @@ use Throwable;
* @return void
*
* @throws ORMInvalidArgumentException
* @throws ORMException
*/
public function remove($entity)
{
if ( ! is_object($entity)) {
throw ORMInvalidArgumentException::invalidObject('EntityManager#remove()', $entity);
throw ORMInvalidArgumentException::invalidObject('EntityManager#remove()' , $entity);
}
$this->errorIfClosed();
@@ -624,12 +611,11 @@ use Throwable;
* @return void
*
* @throws ORMInvalidArgumentException
* @throws ORMException
*/
public function refresh($entity)
{
if ( ! is_object($entity)) {
throw ORMInvalidArgumentException::invalidObject('EntityManager#refresh()', $entity);
throw ORMInvalidArgumentException::invalidObject('EntityManager#refresh()' , $entity);
}
$this->errorIfClosed();
@@ -653,7 +639,7 @@ use Throwable;
public function detach($entity)
{
if ( ! is_object($entity)) {
throw ORMInvalidArgumentException::invalidObject('EntityManager#detach()', $entity);
throw ORMInvalidArgumentException::invalidObject('EntityManager#detach()' , $entity);
}
$this->unitOfWork->detach($entity);
@@ -669,12 +655,11 @@ use Throwable;
* @return object The managed copy of the entity.
*
* @throws ORMInvalidArgumentException
* @throws ORMException
*/
public function merge($entity)
{
if ( ! is_object($entity)) {
throw ORMInvalidArgumentException::invalidObject('EntityManager#merge()', $entity);
throw ORMInvalidArgumentException::invalidObject('EntityManager#merge()' , $entity);
}
$this->errorIfClosed();
@@ -706,7 +691,7 @@ use Throwable;
*
* @param string $entityName The name of the entity.
*
* @return \Doctrine\Common\Persistence\ObjectRepository|\Doctrine\ORM\EntityRepository The repository class.
* @return \Doctrine\ORM\EntityRepository The repository class.
*/
public function getRepository($entityName)
{
@@ -830,59 +815,39 @@ use Throwable;
/**
* Factory method to create EntityManager instances.
*
* @param array|Connection $connection An array with the connection parameters or an existing Connection instance.
* @param Configuration $config The Configuration instance to use.
* @param EventManager $eventManager The EventManager instance to use.
* @param mixed $conn An array with the connection parameters or an existing Connection instance.
* @param Configuration $config The Configuration instance to use.
* @param EventManager $eventManager The EventManager instance to use.
*
* @return EntityManager The created EntityManager.
*
* @throws \InvalidArgumentException
* @throws ORMException
*/
public static function create($connection, Configuration $config, EventManager $eventManager = null)
public static function create($conn, Configuration $config, EventManager $eventManager = null)
{
if ( ! $config->getMetadataDriverImpl()) {
throw ORMException::missingMappingDriverImpl();
}
$connection = static::createConnection($connection, $config, $eventManager);
switch (true) {
case (is_array($conn)):
$conn = \Doctrine\DBAL\DriverManager::getConnection(
$conn, $config, ($eventManager ?: new EventManager())
);
break;
return new EntityManager($connection, $config, $connection->getEventManager());
}
case ($conn instanceof Connection):
if ($eventManager !== null && $conn->getEventManager() !== $eventManager) {
throw ORMException::mismatchedEventManager();
}
break;
/**
* Factory method to create Connection instances.
*
* @param array|Connection $connection An array with the connection parameters or an existing Connection instance.
* @param Configuration $config The Configuration instance to use.
* @param EventManager $eventManager The EventManager instance to use.
*
* @return Connection
*
* @throws \InvalidArgumentException
* @throws ORMException
*/
protected static function createConnection($connection, Configuration $config, EventManager $eventManager = null)
{
if (is_array($connection)) {
return DriverManager::getConnection($connection, $config, $eventManager ?: new EventManager());
default:
throw new \InvalidArgumentException("Invalid argument: " . $conn);
}
if ( ! $connection instanceof Connection) {
throw new \InvalidArgumentException(
sprintf(
'Invalid $connection argument of type %s given%s.',
is_object($connection) ? get_class($connection) : gettype($connection),
is_object($connection) ? '' : ': "' . $connection . '"'
)
);
}
if ($eventManager !== null && $connection->getEventManager() !== $eventManager) {
throw ORMException::mismatchedEventManager();
}
return $connection;
return new EntityManager($conn, $config, $conn->getEventManager());
}
/**
@@ -912,26 +877,4 @@ use Throwable;
{
return null !== $this->filterCollection;
}
/**
* @param int $lockMode
* @param ClassMetadata $class
* @throws OptimisticLockException
* @throws TransactionRequiredException
*/
private function checkLockRequirements(int $lockMode, ClassMetadata $class): void
{
switch ($lockMode) {
case LockMode::OPTIMISTIC:
if (!$class->isVersioned) {
throw OptimisticLockException::notVersioned($class->name);
}
break;
case LockMode::PESSIMISTIC_READ:
case LockMode::PESSIMISTIC_WRITE:
if (!$this->getConnection()->isTransactionActive()) {
throw TransactionRequiredException::transactionRequired();
}
}
}
}

View File

@@ -7,9 +7,9 @@
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION); HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* (INCLUDING NEGLIGENCE OR OTHERWISE); ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
@@ -150,7 +150,7 @@ interface EntityManagerInterface extends ObjectManager
* @param string $entityName The name of the entity type.
* @param mixed $id The entity identifier.
*
* @return object|null The entity reference.
* @return object The entity reference.
*
* @throws ORMException
*/
@@ -174,7 +174,7 @@ interface EntityManagerInterface extends ObjectManager
* @param string $entityName The name of the entity type.
* @param mixed $identifier The entity identifier.
*
* @return object|null The (partial) entity reference.
* @return object The (partial) entity reference.
*/
public function getPartialReference($entityName, $identifier);
@@ -249,7 +249,7 @@ interface EntityManagerInterface extends ObjectManager
*
* @deprecated
*
* @param string|int $hydrationMode
* @param int $hydrationMode
*
* @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator
*/
@@ -258,7 +258,7 @@ interface EntityManagerInterface extends ObjectManager
/**
* Create a new instance for the given hydration mode.
*
* @param string|int $hydrationMode
* @param int $hydrationMode
*
* @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator
*

View File

@@ -37,7 +37,7 @@ class EntityNotFoundException extends ORMException
*/
public static function fromClassNameAndIdentifier($className, array $id)
{
$ids = [];
$ids = array();
foreach ($id as $key => $value) {
$ids[] = $key . '(' . $value . ')';

View File

@@ -19,11 +19,11 @@
namespace Doctrine\ORM;
use Doctrine\Common\Inflector\Inflector;
use Doctrine\ORM\Query\ResultSetMappingBuilder;
use Doctrine\Common\Persistence\ObjectRepository;
use Doctrine\Common\Collections\Selectable;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\ArrayCollection;
/**
* An EntityRepository serves as a repository for entities with generic as well as
@@ -61,7 +61,7 @@ class EntityRepository implements ObjectRepository, Selectable
* @param EntityManager $em The EntityManager to use.
* @param Mapping\ClassMetadata $class The class descriptor.
*/
public function __construct(EntityManagerInterface $em, Mapping\ClassMetadata $class)
public function __construct($em, Mapping\ClassMetadata $class)
{
$this->_entityName = $class->name;
$this->_em = $em;
@@ -161,7 +161,7 @@ class EntityRepository implements ObjectRepository, Selectable
*/
public function findAll()
{
return $this->findBy([]);
return $this->findBy(array());
}
/**
@@ -184,7 +184,7 @@ class EntityRepository implements ObjectRepository, Selectable
/**
* Finds a single entity by a set of criteria.
*
* @param array $criteria
* @param array $criteria
* @param array|null $orderBy
*
* @return object|null The entity instance or NULL if the entity can not be found.
@@ -193,52 +193,68 @@ class EntityRepository implements ObjectRepository, Selectable
{
$persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
return $persister->load($criteria, null, null, [], null, 1, $orderBy);
return $persister->load($criteria, null, null, array(), null, 1, $orderBy);
}
/**
* Counts entities by a set of criteria.
*
* @todo Add this method to `ObjectRepository` interface in the next major release
*
* @param array $criteria
*
* @return int The cardinality of the objects that match the given criteria.
*/
public function count(array $criteria)
{
return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->count($criteria);
}
/**
* Adds support for magic method calls.
* Adds support for magic finders.
*
* @param string $method
* @param array $arguments
*
* @return mixed The returned value from the resolved method.
* @return array|object The found entity/entities.
*
* @throws ORMException
* @throws \BadMethodCallException If the method called is invalid
* @throws \BadMethodCallException If the method called is an invalid find* method
* or no find* method at all and therefore an invalid
* method call.
*/
public function __call($method, $arguments)
{
if (0 === strpos($method, 'findBy')) {
return $this->resolveMagicCall('findBy', substr($method, 6), $arguments);
switch (true) {
case (0 === strpos($method, 'findBy')):
$by = substr($method, 6);
$method = 'findBy';
break;
case (0 === strpos($method, 'findOneBy')):
$by = substr($method, 9);
$method = 'findOneBy';
break;
default:
throw new \BadMethodCallException(
"Undefined method '$method'. The method name must start with ".
"either findBy or findOneBy!"
);
}
if (0 === strpos($method, 'findOneBy')) {
return $this->resolveMagicCall('findOneBy', substr($method, 9), $arguments);
if (empty($arguments)) {
throw ORMException::findByRequiresParameter($method . $by);
}
if (0 === strpos($method, 'countBy')) {
return $this->resolveMagicCall('count', substr($method, 7), $arguments);
$fieldName = lcfirst(\Doctrine\Common\Util\Inflector::classify($by));
if ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName)) {
switch (count($arguments)) {
case 1:
return $this->$method(array($fieldName => $arguments[0]));
case 2:
return $this->$method(array($fieldName => $arguments[0]), $arguments[1]);
case 3:
return $this->$method(array($fieldName => $arguments[0]), $arguments[1], $arguments[2]);
case 4:
return $this->$method(array($fieldName => $arguments[0]), $arguments[1], $arguments[2], $arguments[3]);
default:
// Do nothing
}
}
throw new \BadMethodCallException(
"Undefined method '$method'. The method name must start with ".
"either findBy, findOneBy or countBy!"
);
throw ORMException::invalidFindByCall($this->_entityName, $fieldName, $method.$by);
}
/**
@@ -287,30 +303,4 @@ class EntityRepository implements ObjectRepository, Selectable
return new LazyCriteriaCollection($persister, $criteria);
}
/**
* Resolves a magic method call to the proper existent method at `EntityRepository`.
*
* @param string $method The method to call
* @param string $by The property name used as condition
* @param array $arguments The arguments to pass at method call
*
* @throws ORMException If the method called is invalid or the requested field/association does not exist
*
* @return mixed
*/
private function resolveMagicCall($method, $by, array $arguments)
{
if (! $arguments) {
throw ORMException::findByRequiresParameter($method . $by);
}
$fieldName = lcfirst(Inflector::classify($by));
if (! ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName))) {
throw ORMException::invalidMagicCall($this->_entityName, $fieldName, $method . $by);
}
return $this->$method([$fieldName => $arguments[0]], ...array_slice($arguments, 1));
}
}

View File

@@ -15,7 +15,7 @@
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
*/
namespace Doctrine\ORM\Event;

View File

@@ -62,8 +62,8 @@ class ListenersInvoker
/**
* Get the subscribed event systems
*
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param string $eventName The entity lifecycle event.
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param string $eventName The entity lifecycle event.
*
* @return integer Bitmask of subscribed event systems.
*/
@@ -89,21 +89,21 @@ class ListenersInvoker
/**
* Dispatches the lifecycle event of the given entity.
*
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param string $eventName The entity lifecycle event.
* @param object $entity The Entity on which the event occurred.
* @param \Doctrine\Common\EventArgs $event The Event args.
* @param integer $invoke Bitmask to invoke listeners.
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param string $eventName The entity lifecycle event.
* @param object $entity The Entity on which the event occurred.
* @param \Doctrine\Common\EventArgs $event The Event args.
* @param integer $invoke Bitmask to invoke listeners.
*/
public function invoke(ClassMetadata $metadata, $eventName, $entity, EventArgs $event, $invoke)
{
if ($invoke & self::INVOKE_CALLBACKS) {
if($invoke & self::INVOKE_CALLBACKS) {
foreach ($metadata->lifecycleCallbacks[$eventName] as $callback) {
$entity->$callback($event);
}
}
if ($invoke & self::INVOKE_LISTENERS) {
if($invoke & self::INVOKE_LISTENERS) {
foreach ($metadata->entityListeners[$eventName] as $listener) {
$class = $listener['class'];
$method = $listener['method'];
@@ -113,8 +113,8 @@ class ListenersInvoker
}
}
if ($invoke & self::INVOKE_MANAGER) {
if($invoke & self::INVOKE_MANAGER) {
$this->eventManager->dispatchEvent($eventName, $event);
}
}
}
}

View File

@@ -30,7 +30,7 @@ use Doctrine\Common\Persistence\Event\LoadClassMetadataEventArgs as BaseLoadClas
* Note: method annotations are used instead of method overrides (due to BC policy)
*
* @method __construct(\Doctrine\ORM\Mapping\ClassMetadata $classMetadata, \Doctrine\ORM\EntityManager $objectManager)
* @method \Doctrine\ORM\Mapping\ClassMetadata getClassMetadata()
* @method \Doctrine\ORM\EntityManager getClassMetadata()
*/
class LoadClassMetadataEventArgs extends BaseLoadClassMetadataEventArgs
{

View File

@@ -26,8 +26,8 @@ abstract class AbstractIdGenerator
/**
* Generates an identifier for an entity.
*
* @param EntityManager $em
* @param object|null $entity
* @param EntityManager|EntityManager $em
* @param \Doctrine\ORM\Mapping\Entity $entity
* @return mixed
*/
abstract public function generate(EntityManager $em, $entity);

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