Compare commits

..

111 Commits

Author SHA1 Message Date
Benjamin Eberlei 0363a5548d Release 2.4.2 2014-02-08 17:35:09 +01:00
Benjamin Eberlei 3764e49e6c Merge branch 'DDC-2895' into 2.4 2014-02-08 16:01:57 +01:00
Geoffrey Wagner 6ee20204a5 Fix some code standard things 2014-02-08 16:01:41 +01:00
Geoffrey Wagner d9b0c87ded Fix some code standard things 2014-02-08 16:01:41 +01:00
Geoffrey Wagner 8594e5c4da Add a test
addLifecycleCallback now only allows a callback once so we do not hook them twice
2014-02-08 16:01:41 +01:00
Geoffrey Wagner 5f821f3b98 Fix Lifecycle Callbacks
Remove a bit of code that breaks lifecycle callbacks of parent MappedSuperclasses
2014-02-08 16:01:41 +01:00
Benjamin Eberlei b566525099 Merge branch 'DDC-2931' into 2.4 2014-02-08 15:53:12 +01:00
Marco Pivetta 215c4a03e1 DDC-2931 - Removing previous broken fix for DDC-2931 - hardened 2014-02-08 15:52:46 +01:00
Marco Pivetta b3ccd6466b DDC-2931 - Safe comparison between proxies and entities when refreshing objects 2014-02-08 15:52:46 +01:00
Marco Pivetta b596bbb29f DDC-2931 - adding test that verifies that fetch-joined entities get refreshed with hints 2014-02-08 15:52:46 +01:00
Marco Pivetta c204e6c6a1 DDC-2931 - removing old comments 2014-02-08 15:52:46 +01:00
Marco Pivetta 0bc94589e1 DDC-2931 - Removing refresh hints when fetching association data in hydrators 2014-02-08 15:52:45 +01:00
Marco Pivetta f37856829f DDC-2931 - Detailed explanation 2014-02-08 15:52:45 +01:00
Marco Pivetta 157c793810 DDC-2931 - cleaning up code formatting/simplifying test case 2014-02-08 15:52:45 +01:00
root 72d838a804 [DDC-2931] testcase to reproduce Jira 2931 2014-02-08 15:52:45 +01:00
Benjamin Eberlei 58f8dc5d4c Update UPGRADE.md notes with BC mention. 2014-02-08 15:42:09 +01:00
Benjamin Eberlei 7d3ecd9481 Merge branch 'DDC-2947' into 2.4 2014-02-08 15:31:56 +01:00
Tim Lieberman 1bb55703a7 Make SchemaTool and SchemaValidator use EntityManagerInterface instead of EntityManager 2014-02-08 15:31:08 +01:00
Tim Lieberman 56cbcec13d Substitute EntityManagerInterface for EntityManager in Console EntityManagerHelper 2014-02-08 15:31:07 +01:00
Tim Lieberman 837c19bfc0 Console EntityManagerHelper now accepts EntityManagerInterface as constructor argument, instead of insisting on an EntityManager 2014-02-08 15:31:07 +01:00
Benjamin Eberlei 7b8f09ee4a Merge branch 'DDC-2700' into 2.4 2014-01-02 23:51:07 +01:00
Benjamin Eberlei 488a4dc78a [DDC-2700] Add test and fix CS. 2014-01-02 23:50:37 +01:00
Alex Pogodin 1364b6acc6 Identifier can be empty for MappedSuperclasses
When MappedSuperclass is inspected without identifier column been assigned, always return false. Solves "Undefined offset" notice.
2014-01-02 23:50:37 +01:00
Benjamin Eberlei 3dbe181762 Merge branch 'DDC-2732' into 2.4 2014-01-02 23:34:44 +01:00
Benjamin Eberlei a3acaab65c [DDC-2732] Add tests for XML id options fix. 2014-01-02 23:34:17 +01:00
Eduardo f183d25a33 Options not respected for ID Fields in XML Mapping Driver (XSD update)
XSD update.

The same bug of the yaml driver: see http://www.doctrine-project.org/jira/browse/DDC-2661
2014-01-02 23:34:17 +01:00
Eduardo 7c8350094e Options not respected for ID Fields in XML Mapping Driver
Same bug of the YAML driver, see: http://www.doctrine-project.org/jira/browse/DDC-2661
2014-01-02 23:34:17 +01:00
Benjamin Eberlei c613410ba6 Merge branch 'DDC-2764' into 2.4 2014-01-02 23:16:56 +01:00
Sander Marechal 6bb7581dd7 Add rootAlias to Criteria where clauses 2014-01-02 23:16:35 +01:00
Sander Marechal ab71dab7d1 Set rootAlias outside loop 2014-01-02 23:15:31 +01:00
Sander Marechal 2c114756bd [DDC-2764] Prefix criteria orderBy with rootAlias 2014-01-02 23:15:31 +01:00
Benjamin Eberlei 45496f040d Merge branch 'DDC-2775' into 2.4 2014-01-02 23:11:16 +01:00
Benjamin Eberlei b40866c624 [DDC-2775] cleanup test. 2014-01-02 23:11:07 +01:00
Matthieu Napoli a89cc7abea Inlined the model for the DCC2775 test case inside the test class 2014-01-02 23:07:53 +01:00
Matthieu Napoli 5ac111e5f8 Cleaned up tests for DDC-2775 2014-01-02 23:07:53 +01:00
Matthieu Napoli c5f66e6e7f Fixed tests failing in pgsql because of used of a reserved keyword 2014-01-02 23:07:53 +01:00
Matthieu Napoli b59f495875 Fixed tests for pgsql: was using reserved keyword as table name 2014-01-02 23:07:53 +01:00
Matthieu Napoli 3829b9c28b [DDC-2775] Bugfix 2014-01-02 23:07:53 +01:00
Matthieu Napoli 65bcdbf4c7 [DDC-2775] Tests reproducing DDC-2775 2014-01-02 23:07:53 +01:00
Benjamin Eberlei 95d000e51b Merge branch 'DDC-2692' into 2.4 2014-01-02 22:17:20 +01:00
Stefan Kleff 3657df3b01 Listener class prefix 2014-01-02 22:16:59 +01:00
Stefan Kleff 1661ffae9a removed unused use statements, fixed typo and group tag 2014-01-02 22:16:59 +01:00
Stefan Kleff b424a5cf14 Added unit test 2014-01-02 22:16:59 +01:00
Stefan Kleff 2767a4eec4 Multiple invokation of listeners on PreFlush event
Only lifecycle callbacks and entity listeners should be triggered here. The preFlush listener event is already triggered at the beginning of commit()
2014-01-02 22:16:59 +01:00
Benjamin Eberlei 9486867279 Merge branch 'DDC-2645' into 2.4 2013-12-15 23:34:57 +01:00
Pouyan Savoli 6f2bb08972 [DDC-2645] Apply patch to fix issue 2013-12-15 23:34:34 +01:00
Aaron Muylaert da2d3b406e Create failing test for DDC-2645.
Merge not dealing correctly with composite primary keys.
2013-12-15 23:34:34 +01:00
Benjamin Eberlei c4b7d3fbea Bump version to 2.4.2 2013-11-12 13:40:15 +01:00
Benjamin Eberlei 84373d05a4 Release 2.4.1 2013-11-12 13:40:13 +01:00
Benjamin Eberlei e82e7147fa Merge branch 'DDC-2715' into 2.4 2013-10-29 09:25:13 +01:00
jan brunnert e23ed2250d Removed unnecessary is_object() check 2013-10-29 09:24:52 +01:00
jan brunnert 192bb6fd21 When the OptimisticLockingException is generated with the static function lockFailedVersionMismatch and the passed parameters are DateTime instances, the exception could not be thrown because the DateTime object is not implicitly converted to a string. 2013-10-29 09:24:52 +01:00
Benjamin Eberlei 0f3679f034 Merge branch 'DDC-2759' into 2.4 2013-10-26 11:17:34 +02:00
Benjamin Eberlei 1d2cd82706 [DDC-2759] Fix regression in ArrayHydrator introduced in DDC-1884 at SHA c7b4c9bf0f 2013-10-26 11:16:53 +02:00
Chris Collins b983d86612 Added a failing test case for DDC-2759. 2013-10-26 11:16:53 +02:00
Benjamin Eberlei b11f01643c Merge branch 'DDC-2668' into 2.4 2013-09-26 23:24:14 +02:00
Fabio B. Silva b58fb8f5d4 [DDC-2668] Fix trim leading zero string 2013-09-26 23:23:49 +02:00
Benjamin Eberlei 925a22b71d Merge branch 'DDC-2608' into 2.4 2013-09-08 16:01:38 +02:00
Benjamin Eberlei 0f0d8abd67 [DDC-2608][DDC-2662] Fix SequenceGenerator requiring "sequenceName" and now throw exception. Fix a bug in quoting the sequenceName. 2013-09-08 16:00:14 +02:00
Benjamin Eberlei 470c15ce05 Merge branch 'DDC-2660' into 2.4 2013-09-08 14:39:54 +02:00
Benjamin Eberlei 3cc5fc0252 [DDC-2660] Fix error with NativeSQL, ResultSetMappingBuilder and Associations as Primary Key. 2013-09-08 14:39:25 +02:00
Benjamin Eberlei fd0657089a Merge branch 'DDC-2661' into 2.4 2013-09-08 10:38:03 +02:00
Benjamin Eberlei de3b237292 [DDC-2661] Fix bug in YamlDriver not passing options from id to mapField() 2013-09-08 10:37:42 +02:00
Benjamin Eberlei 1221cc3a2a More excludes 2013-09-07 18:27:20 +02:00
Benjamin Eberlei 9efbc1fa71 Bump version to 2.4.1 2013-09-07 18:19:57 +02:00
Benjamin Eberlei 57705e0d78 Release 2.4.0 2013-09-07 18:19:56 +02:00
Benjamin Eberlei 82bb6b78cd Travis should prefer dist. 2013-09-07 13:20:35 +02:00
Benjamin Eberlei 64c56b21aa Remove minimum stability from 2.4 composer.json 2013-09-07 13:08:14 +02:00
Benjamin Eberlei b04e2e6364 Adjust composer.json to pending 2.4 stable release 2013-09-07 12:59:17 +02:00
Benjamin Eberlei a70f9b7f49 Fix branch alias 2013-09-07 12:57:56 +02:00
Benjamin Eberlei c88a7c1ffe New Build process
- Switch from Phing to Ant
- Remove PEAR packaging
- Add Composer archiving
2013-09-07 12:57:38 +02:00
Benjamin Eberlei c206728c96 Merge branch 'DDC-2638' into 2.4 2013-09-07 09:04:34 +02:00
Attila Fulop e8d420c641 Fix for entity generator discriminator column 2013-09-07 09:04:26 +02:00
Benjamin Eberlei fdcab7eae8 Merge branch 'DDC-2640' into 2.4 2013-09-07 09:01:01 +02:00
Maks Feltrin 45d7d5234f DO NOT OVERRIDE CUSTOM TREE WALKERS IN getIterator() 2013-09-07 09:00:06 +02:00
Benjamin Eberlei 159ca79b81 Merge origin/2.4 into local branch 2013-09-07 08:55:15 +02:00
Benjamin Eberlei 2b148a27e0 Merge Oracle test fixes to 2.4 branch 2013-09-07 08:54:23 +02:00
Guilherme Blanco 0aef57f60c Fixing missing table aliases when using Many2Many persister. 2013-08-26 10:29:45 -04:00
Benjamin Eberlei fef1e0286c Merge branch 'DDC-2235' into 2.4 2013-08-20 10:08:21 +02:00
Guilherme Blanco 4a38534150 Fixed DDC-2235. 2013-08-20 10:08:07 +02:00
Benjamin Eberlei 1de22adb16 Merge branch 'DDC-2506' into 2.4 2013-08-20 09:56:54 +02:00
Guilherme Blanco 62b4160887 Fixed DDC-2506 by manually updating code. Closes PR #708. 2013-08-20 09:56:39 +02:00
Benjamin Eberlei dbb7c4d2bf Merge branch 'DDC-2607' into 2.4 2013-08-20 09:51:19 +02:00
Dustin Thomson e8978ee365 Modified executeInserts method in JoinedSubclassPersister to only check for the presence of columns in a composite primary key 2013-08-20 09:51:01 +02:00
Benjamin Eberlei c095b88804 Merge branch 'DDC-2578' into 2.4 2013-08-10 18:14:24 +02:00
Guilherme Blanco efe4208ba6 Kept BC. 2013-08-10 18:14:07 +02:00
Guilherme Blanco 453a56670d Modified Hydrators to be per-query instances instead of a singleton-like approach. 2013-08-10 18:14:07 +02:00
Benjamin Eberlei ec36e2c866 Merge branch 'DDC-2579' into 2.4 2013-08-10 17:54:37 +02:00
Fabio B. Silva e250572cb4 fix DDC-2579 2013-08-10 17:53:50 +02:00
Benjamin Eberlei 758955e183 Merge branch 'DDC-2582' into 2.4 2013-08-10 17:48:20 +02:00
Guilherme Blanco 5b8d6a1486 CS fixes. 2013-08-10 17:48:03 +02:00
Guilherme Blanco 3f1003fee9 Fixed DDC-1884. 2013-08-10 17:48:03 +02:00
Benjamin Eberlei 7e241e89b8 Merge branch 'DDC-2548' into 2.4 2013-08-10 17:43:40 +02:00
Michaël Gallego 67c1e1d2b1 Allow to have non-distinct queries 2013-08-10 17:43:26 +02:00
Benjamin Eberlei 261eacdbfc Merge branch 'DDC-2565' into 2.4 2013-08-10 17:27:47 +02:00
Austin Morris 43df821691 convert PersistentCollection functional tests to unit tests 2013-08-10 17:27:30 +02:00
Austin Morris 11d09702da remove redundant require_once for TestInit.php 2013-08-10 17:27:30 +02:00
Austin Morris f9f14139cf do not initialize coll on add() 2013-08-10 17:27:30 +02:00
Austin Morris 39f4d46d36 Initialize coll when using Collection methods inside PersistentCollection 2013-08-10 17:27:30 +02:00
Austin Morris 1dae8d318f PersistentCollection - initialize coll - create failing tests 2013-08-10 17:27:30 +02:00
Benjamin Eberlei a361a7c1cb Merge branch 'DDC-2542' into 2.4 2013-08-10 17:02:34 +02:00
Roger Llopart Pla 6a73608baf Fixed name colision. 2013-08-10 17:02:10 +02:00
Roger Llopart Pla f9955152b2 Added a test which verifies that the tree walkers are kept. 2013-08-10 17:02:10 +02:00
Roger Llopart Pla 5aad1df149 Added docblock. 2013-08-10 17:02:10 +02:00
Roger Llopart Pla 243832555b Appending the Paginator tree walker hint, instead of removing all the other hints. 2013-08-10 17:02:10 +02:00
Benjamin Eberlei ae12fa6b5b Merge branch 'DDC-2577' into 2.4 2013-08-10 16:28:35 +02:00
Konstantin.Myakshin edaf9b6813 Skip not mapped public properties in SchemaValidator 2013-08-10 16:28:27 +02:00
Benjamin Eberlei b324a21abf Merge branch 'DDC-2587' into 2.4 2013-08-10 16:25:04 +02:00
J. Bruni ff34aaaa2c Updated EntityGeneratorTest::testEntityTypeAlias 2013-08-10 16:24:43 +02:00
J. Bruni 9767a814a6 Updated EntityGeneratorTest::testEntityTypeAlias 2013-08-10 16:24:43 +02:00
J Bruni e6007575e1 Corrected PHP type for "decimal" mapping type
"Basic Mapping" documentation says:
"decimal: Type that maps a SQL DECIMAL to a PHP string."
2013-08-10 16:24:43 +02:00
1563 changed files with 70522 additions and 108812 deletions
+4
View File
@@ -0,0 +1,4 @@
# for php-coveralls
service_name: travis-ci
src_dir: lib
coverage_clover: build/logs/clover.xml
-49
View File
@@ -1,49 +0,0 @@
{
"active": true,
"name": "Object Relational Mapper",
"shortName": "ORM",
"slug": "orm",
"docsSlug": "doctrine-orm",
"versions": [
{
"name": "3.0",
"branchName": "master",
"slug": "latest",
"upcoming": true
},
{
"name": "2.8",
"branchName": "2.8.x",
"slug": "2.8",
"upcoming": true
},
{
"name": "2.7",
"branchName": "2.7",
"slug": "2.7",
"current": true,
"aliases": [
"current",
"stable"
]
},
{
"name": "2.6",
"branchName": "2.6",
"slug": "2.6",
"maintained": false
},
{
"name": "2.5",
"branchName": "2.5",
"slug": "2.5",
"maintained": false
},
{
"name": "2.4",
"branchName": "2.4",
"slug": "2.4",
"maintained": false
}
]
}
-2
View File
@@ -10,5 +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
composer.lock export-ignore
-3
View File
@@ -1,3 +0,0 @@
patreon: phpdoctrine
tidelift: packagist/doctrine/orm
custom: https://www.doctrine-project.org/sponsorship.html
-37
View File
@@ -1,37 +0,0 @@
---
name: 💥 BC Break
about: Have you encountered an issue during upgrade? 💣
---
<!--
Before reporting a BC break, please consult the upgrading document to make sure it's not an expected change: https://github.com/doctrine/orm/blob/master/UPGRADE.md
-->
### BC Break Report
<!-- Fill in the relevant information below to help triage your issue. -->
| Q | A
|------------ | ------
| BC Break | yes
| Version | x.y.z
#### Summary
<!-- Provide a summary describing the problem you are experiencing. -->
#### Previous behavior
<!-- What was the previous (working) behavior? -->
#### Current behavior
<!-- What is the current (broken) behavior? -->
#### How to reproduce
<!--
Provide steps to reproduce the BC break.
If possible, also add a code snippet with relevant configuration, entity mappings, DQL etc.
Adding a failing Unit or Functional Test would help us a lot - you can submit it in a Pull Request separately, referencing this bug report.
-->
-34
View File
@@ -1,34 +0,0 @@
---
name: 🐞 Bug Report
about: Something is broken? 🔨
---
### Bug Report
<!-- Fill in the relevant information below to help triage your issue. -->
| Q | A
|------------ | ------
| BC Break | yes/no
| Version | x.y.z
#### Summary
<!-- Provide a summary describing the problem you are experiencing. -->
#### Current behavior
<!-- What is the current (buggy) behavior? -->
#### How to reproduce
<!--
Provide steps to reproduce the bug.
If possible, also add a code snippet with relevant configuration, entity mappings, DQL etc.
Adding a failing Unit or Functional Test would help us a lot - you can submit one in a Pull Request separately, referencing this bug report.
-->
#### Expected behavior
<!-- What was the expected (correct) behavior? -->
-18
View File
@@ -1,18 +0,0 @@
---
name: 🎉 Feature Request
about: You have a neat idea that should be implemented? 🎩
---
### Feature Request
<!-- Fill in the relevant information below to help triage your issue. -->
| Q | A
|------------ | ------
| New Feature | yes
| RFC | yes/no
| BC Break | yes/no
#### Summary
<!-- Provide a summary of the feature you would like to see implemented. -->
@@ -1,20 +0,0 @@
---
name: ❓ Support Question
about: Have a problem that you can't figure out? 🤔
---
<!-- Fill in the relevant information below to help triage your issue. -->
| Q | A
|------------ | -----
| Version | x.y.z
<!--
Before asking question here, please try asking on Gitter or Slack first.
Find out more about Doctrine support channels here: https://www.doctrine-project.org/community/
Keep in mind that GitHub is primarily an issue tracker.
-->
### Support Question
<!-- Describe the issue you are facing here. -->
@@ -1,19 +0,0 @@
---
name: 🐞 Failing Test
about: You found a bug and have a failing Unit or Functional test? 🔨
---
### Failing Test
<!-- Fill in the relevant information below to help triage your issue. -->
| Q | A
|------------ | ------
| BC Break | yes/no
| Version | x.y.z
#### Summary
<!-- Provide a summary of the failing scenario. -->
@@ -1,18 +0,0 @@
---
name: ⚙ Improvement
about: You have some improvement to make Doctrine better? 🎁
---
### Improvement
<!-- Fill in the relevant information below to help triage your issue. -->
| Q | A
|------------ | ------
| New Feature | yes
| RFC | yes/no
| BC Break | yes/no
#### Summary
<!-- Provide a summary of the improvement you are submitting. -->
@@ -1,26 +0,0 @@
---
name: 🎉 New Feature
about: You have implemented some neat idea that you want to make part of Doctrine? 🎩
---
<!--
Thank you for submitting new feature!
Pick the target branch based according to these criteria:
* submitting a bugfix: target the lowest active stable branch: 2.7
* submitting a new feature: target the next minor branch: 2.8.x
* submitting a BC-breaking change: target the master branch
-->
### New Feature
<!-- Fill in the relevant information below to help triage your issue. -->
| Q | A
|------------ | ------
| New Feature | yes
| RFC | yes/no
| BC Break | yes/no
#### Summary
<!-- Provide a summary of the feature you have implemented. -->
-47
View File
@@ -1,47 +0,0 @@
name: CI
on:
pull_request:
push:
branches:
- master
jobs:
coding-standards:
name: "Coding Standards"
runs-on: ubuntu-18.04
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '7.4'
extensions: mbstring
tools: composer, cs2pr
- name: composer install
run: "composer install --no-progress --no-suggest --no-interaction --prefer-dist --optimize-autoloader"
- name: phpcs
run: "php vendor/bin/phpcs -q --report=checkstyle --no-colors | cs2pr"
static-analysis:
name: "Static Analysis"
runs-on: ubuntu-18.04
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '7.4'
extensions: mbstring
tools: composer, cs2pr
- name: composer install
run: "composer install --no-progress --no-suggest --no-interaction --prefer-dist --optimize-autoloader"
- name: phpstan
run: "php vendor/bin/phpstan analyse --error-format=checkstyle --no-progress | cs2pr"
+1 -6
View File
@@ -7,13 +7,8 @@ lib/api/
lib/Doctrine/Common
lib/Doctrine/DBAL
/.settings/
*.iml
.buildpath
.project
.idea
vendor/
composer.phar
/tests/Doctrine/Performance/history.db
/.phpcs-cache
phpbench.phar
phpbench.phar.pubkey
composer.lock
-32
View File
@@ -1,32 +0,0 @@
build:
nodes:
analysis:
environment:
php:
version: 7.4
cache:
disabled: false
directories:
- ~/.composer/cache
project_setup:
override: true
tests:
override:
- php-scrutinizer-run
- phpcs-run
dependencies:
override:
- composer install --no-interaction --prefer-dist
tools:
external_code_coverage:
timeout: 3600
filter:
excluded_paths:
- docs
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
+13 -71
View File
@@ -1,82 +1,24 @@
dist: trusty
sudo: false
language: php
php:
- 7.3
- 7.4
- 5.3
- 5.4
- 5.5
env:
- DB=mariadb
- DB=mysql
- DB=pgsql
- DB=sqlite
before_install:
- |
if [[ "$COVERAGE" != "1" ]]; then
phpenv config-rm ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini || echo "xdebug is not installed"
fi
- echo "memory_limit=-1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
- travis_retry composer self-update
before_script:
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'DROP DATABASE IF EXISTS doctrine_tests;' -U postgres; fi"
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'DROP DATABASE IF EXISTS doctrine_tests_tmp;' -U postgres; fi"
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'create database doctrine_tests;' -U postgres; fi"
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'create database doctrine_tests_tmp;' -U postgres; fi"
- sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS doctrine_tests_tmp;create database IF NOT EXISTS doctrine_tests;'; fi"
- composer install --prefer-dist --dev
install:
- rm composer.lock
- travis_retry composer update --no-interaction --prefer-dist --no-suggest --no-progress
script: phpunit --configuration tests/travis/$DB.travis.xml
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
# temporarily disabled
#- 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.4"
- stage: Test
env: DB=sqlite DEPENDENCIES=low
install:
- travis_retry composer update --no-interaction --prefer-dist --no-suggest --no-progress --prefer-lowest
- stage: Test
if: type = cron
php: 7.3
env: DB=sqlite DEV_DEPENDENCIES
install:
- rm composer.lock
- composer config minimum-stability dev
- travis_retry composer update --no-interaction --prefer-dist --no-suggest --no-progress
- stage: Test
env: DB=sqlite COVERAGE
before_script:
- 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 BENCHMARK
before_script:
- wget https://phpbench.github.io/phpbench/phpbench.phar https://phpbench.github.io/phpbench/phpbench.phar.pubkey
script:
- php phpbench.phar run --bootstrap=tests/Doctrine/Tests/TestInit.php -l dots --report=default
allow_failures:
# temporarily disabled
- env: DB=mysql
- env: DB=mariadb
- env: DB=pgsql
cache:
directories:
- $HOME/.composer/cache
after_script:
- php vendor/bin/coveralls -v
+38 -59
View File
@@ -1,81 +1,67 @@
# Contributing to Doctrine ORM
# Contribute to Doctrine
Thank you for contributing to Doctrine ORM!
Thank you for contributing to Doctrine!
Before we can merge your pull request here are some guidelines that you need to follow.
Before we can merge your Pull-Request here are some guidelines that you need to follow.
These guidelines exist not to annoy you, but to keep the code base clean,
unified and future proof.
## Obtaining a copy
## We only accept PRs to "master"
In order to submit a pull request, you will need to [fork the project][Fork] and obtain a
fresh copy of the source code:
Our branching strategy is summed up with "everything to master first", even
bugfixes and we then merge them into the stable branches. You should only
open pull requests against the master branch. Otherwise we cannot accept the PR.
```sh
git clone git@github.com:<your-github-name>/orm.git
cd orm
```
Then you will have to run a Composer installation in the project:
```sh
curl -sS https://getcomposer.org/installer | php
./composer.phar install
```
## Choosing the branch
* I am submitting a bugfix for a stable release
* Your PR should target the [lowest active stable branch (2.7)][2.7].
* I am submitting a new feature
* Your PR should target the [master branch (3.0)][Master].
* I am submitting a BC-breaking change
* Your PR must target the [master branch (3.0)][Master].
* Please also try to provide a deprecation path in a PR targeting the [2.8 branch][2.8].
Please always create a new branch for your changes (i.e. do not commit directly into `master`
in your fork), otherwise you would run into troubles with creating multiple pull requests.
There is one exception to the rule, when we merged a bug into some stable branches
we do occasionally accept pull requests that merge the same bug fix into earlier
branches.
## Coding Standard
We follow the [Doctrine Coding Standard][CS].
Please refer to this repository to learn about the rules your code should follow.
You can also use `vendor/bin/phpcs` to validate your changes locally.
We use PSR-1 and PSR-2:
## Tests
* https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md
* https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md
Please try to add a test for your pull request.
with some exceptions/differences:
* Keep the nesting of control structures per method as small as possible
* Align equals (=) signs
* Add spaces between assignment, control and return statements
* Prefer early exit over nesting conditions
* Add spaces around a negation if condition ``if ( ! $cond)``
## Unit-Tests
Always add a test for your pull-request.
* If you want to fix a bug or provide a reproduce case, create a test file in
``tests/Doctrine/Tests/ORM/Functional/Ticket`` with the identifier of the issue,
i.e. ``GH1234Test.php`` for an issue with id `#1234`.
* If you want to contribute new functionality, add unit or functional tests
``tests/Doctrine/Tests/ORM/Functional/Ticket`` with the name of the ticket,
``DDC1234Test.php`` for example.
* If you want to contribute new functionality add unit- or functional tests
depending on the scope of the feature.
You can run the tests by calling ``vendor/bin/phpunit`` from the root of the project.
It will run all the tests with an in-memory SQLite database.
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.
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
phpunit -c mysql.phpunit.xml
Tips for creating unit tests:
## Travis
1. If you put a test into the `Ticket` namespace as described above, put the testcase
and all entities into the same file.
See [DDC2306Test][Test Example] for an example.
We automatically run your pull request through [Travis CI](http://www.travis-ci.org)
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.
## CI
## DoctrineBot, Tickets and Jira
We automatically run all pull requests through [Travis CI][Travis].
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:
* The test suite is ran against SQLite, MySQL, MariaDB and PostgreSQL on all supported PHP versions.
* The code is validated against our [Coding Standard](#coding-standard).
* The code is checked by a static analysis tool.
If you break the tests, we cannot merge your code,
so please make sure that your code is working before opening a pull request.
"[DDC-123] My Pull Request"
## Getting merged
@@ -84,10 +70,3 @@ everything as fast as possible, but cannot always live up to our own expectation
Thank you very much again for your contribution!
[Master]: https://github.com/doctrine/orm/tree/master
[2.8]: https://github.com/doctrine/orm/tree/2.8.x
[2.7]: https://github.com/doctrine/orm/tree/2.7
[CS]: https://github.com/doctrine/coding-standard
[Fork]: https://guides.github.com/activities/forking/
[Travis]: https://www.travis-ci.org
[Test Example]: https://github.com/doctrine/orm/tree/master/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2306Test.php
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 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
+25
View File
@@ -0,0 +1,25 @@
# Doctrine 2 ORM
Master: [![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png?branch=master)](http://travis-ci.org/doctrine/doctrine2)
2.3: [![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png?branch=2.3)](http://travis-ci.org/doctrine/doctrine2)
2.2: [![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png?branch=2.2)](http://travis-ci.org/doctrine/doctrine2)
2.1: [![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png?branch=2.1.x)](http://travis-ci.org/doctrine/doctrine2)
Master: [![Coverage Status](https://coveralls.io/repos/doctrine/doctrine2/badge.png?branch=master)](https://coveralls.io/r/doctrine/doctrine2?branch=master)
[![Latest Stable Version](https://poser.pugx.org/doctrine/orm/v/stable.png)](https://packagist.org/packages/doctrine/orm) [![Total Downloads](https://poser.pugx.org/doctrine/orm/downloads.png)](https://packagist.org/packages/doctrine/orm)
Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.2+ 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 Hibernates 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)
-52
View File
@@ -1,52 +0,0 @@
[![Tidelift](https://tidelift.com/badges/github/doctrine/orm)](https://tidelift.com/subscription/pkg/packagist-doctrine-orm?utm_source=packagist-doctrine-orm&utm_medium=referral&utm_campaign=readme)
| [Master][Master] | [2.8][2.8] | [2.7][2.7] |
|:----------------:|:----------:|:----------:|
| [![Build status][Master image]][Master] | [![Build status][2.8 image]][2.8] | [![Build status][2.7 image]][2.7] |
| [![Coverage Status][Master coverage image]][Master coverage] | [![Coverage Status][2.8 coverage image]][2.8 coverage] | [![Coverage Status][2.7 coverage image]][2.7 coverage] |
##### :warning: You are browsing the code of upcoming Doctrine 3.0.
##### Things changed a lot here and major code changes should be expected. If you are rather looking for a stable version, refer to the [2.7 branch][2.7] for the current stable release or [2.8 branch][2.8] for the upcoming release. If you are submitting a pull request, please see the _[Which branch should I choose?](#which-branch-should-i-choose)_ section below.
-----
Doctrine 3 is an object-relational mapper (ORM) for PHP 7.2+ 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.
-----
### Which branch should I choose?
Please see [Choosing the branch](CONTRIBUTING.md#choosing-the-branch) to get more information about which branch
you should target your pull request at.
## Doctrine ORM for enterprise
Available as part of the Tidelift Subscription.
The maintainers of Doctrine ORM and thousands of other packages are working with Tidelift to deliver commercial support
and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve
code health, while paying the maintainers of the exact dependencies you use.
[Learn more.](https://tidelift.com/subscription/pkg/packagist-doctrine-orm?utm_source=packagist-doctrine-orm&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
## More resources:
* [Website](http://www.doctrine-project.org)
* [Documentation](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/index.html)
[Master image]: https://img.shields.io/travis/doctrine/orm/master.svg?style=flat-square
[Master]: https://travis-ci.org/doctrine/orm
[Master coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/orm/master.svg?style=flat-square
[Master coverage]: https://scrutinizer-ci.com/g/doctrine/orm/?branch=master
[2.8 image]: https://img.shields.io/travis/doctrine/orm/2.8.x.svg?style=flat-square
[2.8]: https://github.com/doctrine/orm/tree/2.8.x
[2.8 coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/orm/2.8.x.svg?style=flat-square
[2.8 coverage]: https://scrutinizer-ci.com/g/doctrine/orm/?branch=2.8.x
[2.7 image]: https://img.shields.io/travis/doctrine/orm/2.7.svg?style=flat-square
[2.7]: https://github.com/doctrine/orm/tree/2.7
[2.7 coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/orm/2.7.svg?style=flat-square
[2.7 coverage]: https://scrutinizer-ci.com/g/doctrine/orm/?branch=2.7
-18
View File
@@ -1,18 +0,0 @@
Security
========
The Doctrine library is operating very close to your database and as such needs
to handle and make assumptions about SQL injection vulnerabilities.
It is vital that you understand how Doctrine approaches security, because
we cannot protect you from SQL injection.
Please read the documentation chapter on Security in Doctrine DBAL and ORM to
understand the assumptions we make.
- [DBAL Security Page](https://github.com/doctrine/dbal/blob/master/docs/en/reference/security.rst)
- [ORM Security Page](https://github.com/doctrine/orm/blob/master/docs/en/reference/security.rst)
If you find a Security bug in Doctrine, please report it on Jira and change the
Security Level to "Security Issues". It will be visible to Doctrine Core
developers and you only.
+2 -541
View File
@@ -1,533 +1,3 @@
# Upgrade to 3.0
## BC Break: Removed ability to clear cache via console with some cache drivers
The console commands `orm:clear-cache:metadata`, `orm:clear-cache:result`,
and `orm:clear-cache:query` cannot be used with the `ApcCache`, `ApcuCache`,
or `XcacheCache` because the memory is only available to the webserver process.
## BC Break: `orm:run-dql` command's `$depth` parameter removed
The `$depth` parameter has been removed, the dumping functionality
is now provided by [`symfony/var-dumper`](https://github.com/symfony/var-dumper).
## BC Break: Dropped `Doctrine\ORM\Tools\Setup::registerAutoloadDirectory()`
This method used deprecated Doctrine Autoloader and has been removed. Please rely on Composer autoloading instead.
## BC Break: Dropped automatic discriminator map discovery
Automatic discriminator map discovery exhibited multiple flaws
that can't be reliably addressed and supported:
* discovered entries are not namespaced which leads to collisions,
* the class name is part of the discriminator map, therefore the class
must never be renamed.
As a consequence this feature has been dropped.
If your code relied on this feature, please build the discriminator map for
your inheritance tree manually where each entry is an unqualified lowercase
name of the member entities.
## BC Break: Missing type declaration added for identifier generators
The interfaces `Doctrine\ORM\Sequencing\Generator` and
`Doctrine\ORM\Sequencing\Planning\ValueGenerationPlan` now uses explicit type
declaration for parameters and return (as much as possible).
## BC Break: Removed possibility to extend the doctrine mapping xml schema with anything
If you want to extend it now you have to provide your own validation schema.
## BC Break: Entity Listeners no long support naming convention methods
If you want their behavior to be kept, please add the necessary Annotation methods (in case XML driver is used,
no changes are necessary).
## BC Break: Removed `Doctrine\ORM\Mapping\Exporter\VariableExporter` constants
This constant has been removed
* `Doctrine\ORM\Mapping\Exporter\VariableExporter::INDENTATION`
## BC Break: Removed support for named queries and named native queries
These classes have been removed:
* `Doctrine/ORM/Annotation/NamedQueries`
* `Doctrine/ORM/Annotation/NamedQuery`
* `Doctrine/ORM/Annotation/NamedNativeQueries`
* `Doctrine/ORM/Annotation/NamedNativeQuery`
* `Doctrine/ORM/Annotation/ColumnResult`
* `Doctrine/ORM/Annotation/FieldResult`
* `Doctrine/ORM/Annotation/EntityResult`
* `Doctrine/ORM/Annotation/SqlResultSetMapping`
* `Doctrine/ORM/Annotation/SqlResultSetMappings`
These methods have been removed:
* `Doctrine/ORM/Configuration::addNamedQuery()`
* `Doctrine/ORM/Configuration::getNamedQuery()`
* `Doctrine/ORM/Configuration::addNamedNativeQuery()`
* `Doctrine/ORM/Configuration::getNamedNativeQuery()`
* `Doctrine/ORM/Decorator/EntityManagerDecorator::createNamedQuery()`
* `Doctrine/ORM/Decorator/EntityManagerDecorator::createNamedNativeQuery()`
* `Doctrine/ORM/EntityManager::createNamedQuery()`
* `Doctrine/ORM/EntityManager::createNamedNativeQuery()`
* `Doctrine/ORM/EntityManagerInterface::createNamedQuery()`
* `Doctrine/ORM/EntityManagerInterface::createNamedNativeQuery()`
* `Doctrine/ORM/EntityRepository::createNamedQuery()`
* `Doctrine/ORM/EntityRepository::createNamedNativeQuery()`
* `Doctrine/ORM/Mapping/ClassMetadata::getNamedQuery()`
* `Doctrine/ORM/Mapping/ClassMetadata::getNamedQueries()`
* `Doctrine/ORM/Mapping/ClassMetadata::addNamedQuery()`
* `Doctrine/ORM/Mapping/ClassMetadata::hasNamedQuery()`
* `Doctrine/ORM/Mapping/ClassMetadata::getNamedNativeQuery()`
* `Doctrine/ORM/Mapping/ClassMetadata::getNamedNativeQueries()`
* `Doctrine/ORM/Mapping/ClassMetadata::addNamedNativeQuery()`
* `Doctrine/ORM/Mapping/ClassMetadata::hasNamedNativeQuery()`
* `Doctrine\ORM\Mapping\ClassMetadata::addSqlResultSetMapping()`
* `Doctrine\ORM\Mapping\ClassMetadata::getSqlResultSetMapping()`
* `Doctrine\ORM\Mapping\ClassMetadata::getSqlResultSetMappings()`
* `Doctrine\ORM\Mapping\ClassMetadata::hasSqlResultSetMapping()`
## BC Break: Removed support for entity namespace aliases
The support for namespace aliases has been removed.
Please migrate to using `::class` for referencing classes.
These methods have been removed:
* `Doctrine\ORM\Configuration::addEntityNamespace()`
* `Doctrine\ORM\Configuration::getEntityNamespace()`
* `Doctrine\ORM\Configuration::setEntityNamespaces()`
* `Doctrine\ORM\Configuration::getEntityNamespaces()`
* `Doctrine\ORM\Mapping\AbstractClassMetadataFactory::getFqcnFromAlias()`
* `Doctrine\ORM\ORMException::unknownEntityNamespace()`
## BC Break: Removed same-namespace class name resolution
Support for same-namespace class name resolution in mappings has been removed.
If you're using annotation driver, please migrate to references using `::class`.
If you're using XML driver, please migrate to fully qualified references.
These methods have been removed:
* Doctrine\ORM\Mapping\ClassMetadata::fullyQualifiedClassName()
## BC Break: Removed code generators and related console commands
These console commands have been removed:
* `orm:convert-mapping`
* `orm:generate:entities`
* `orm:generate-repositories`
These classes have been removed:
* `Doctrine\ORM\Tools\EntityGenerator`
* `Doctrine\ORM\Tools\EntityRepositoryGenerator`
The whole Doctrine\ORM\Tools\Export namespace with all its members has been removed as well.
## BC Break: proxies no longer implement `Doctrine\ORM\Proxy\Proxy`
Proxy objects no longer implement `Doctrine\ORM\Proxy\Proxy` nor
`Doctrine\Common\Persistence\Proxy`: instead, they implement
`ProxyManager\Proxy\GhostObjectInterface`.
These related classes have been removed:
* `Doctrine\ORM\Proxy\ProxyFactory` - replaced by `Doctrine\ORM\Proxy\Factory\StaticProxyFactory`
and `Doctrine\ORM\Proxy\Factory\ProxyFactory`
* `Doctrine\ORM\Proxy\Proxy`
* `Doctrine\ORM\Proxy\Autoloader` - we suggest using the composer autoloader instead
* `Doctrine\ORM\Reflection\RuntimePublicReflectionProperty`
These methods have been removed:
* `Doctrine\ORM\Configuration#getProxyDir()`
* `Doctrine\ORM\Configuration#getAutoGenerateProxyClasses()`
* `Doctrine\ORM\Configuration#getProxyNamespace()`
Proxy class names change: the generated proxies now follow
the [`ClassNameInflector`](https://github.com/Ocramius/ProxyManager/blob/2.1.1/src/ProxyManager/Inflector/ClassNameInflector.php)
naming.
Proxies are also always generated if not found: fatal errors due to missing
proxy classes should no longer occur with ORM default settings.
In addition to that, the following changes affect entity lazy-loading semantics:
* `final` methods are now allowed
* `__clone` is no longer called by the ORM
* `__wakeup` is no longer called by the ORM
* `serialize($proxy)` will lead to full recursive proxy initialization: please mitigate
the recursive initialization by implementing
the [`Serializable`](https://secure.php.net/manual/en/class.serializable.php) interface
* `clone $proxy` will lead to full initialization of the cloned instance, not the
original instance
* lazy-loading a detached proxy no longer causes the proxy identifiers to be reset
to `null`
* identifier properties are always set when the ORM produces a proxy instance
* calling a method on a proxy no longer causes proxy lazy-loading if the method does
not access any un-initialized proxy state
* accessing entity private state, even with reflection, will trigger lazy-loading
## BC Break: Removed `Doctrine\ORM\Version`
The `Doctrine\ORM\Version` class is no longer available: please refrain from checking the ORM version at runtime.
## BC Break: Removed `EntityManager#merge()` and `EntityManager#detach()` methods
Merge and detach semantics were a poor fit for the PHP "share-nothing" architecture.
In addition to that, merging/detaching caused multiple issues with data integrity
in the managed entity graph, which was constantly spawning more edge-case bugs/scenarios.
The following API methods were therefore removed:
* `EntityManager#merge()`
* `EntityManager#detach()`
* `UnitOfWork#merge()`
* `UnitOfWork#detach()`
Users are encouraged to migrate `EntityManager#detach()` calls to `EntityManager#clear()`.
In order to maintain performance on batch processing jobs, it is endorsed to enable
the second level cache (http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/second-level-cache.html)
on entities that are frequently reused across multiple `EntityManager#clear()` calls.
An alternative to `EntityManager#merge()` is not provided by ORM 3.0, since the merging
semantics should be part of the business domain rather than the persistence domain of an
application. If your application relies heavily on CRUD-alike interactions and/or `PATCH`
restful operations, you should look at alternatives such as [JMSSerializer](https://github.com/schmittjoh/serializer).
## BC Break: Added the final keyword for `EntityManager`
Final keyword has been added to the ``EntityManager::class`` in order to ensure that EntityManager is not used as valid extension point. Valid extension point should be EntityManagerInterface.
## BC Break: ``EntityManagerInterface`` is now used instead of ``EntityManager`` in typehints
`Sequencing\Generator#generate()` now takes ``EntityManagerInterface`` as its first argument instead of ``EntityManager``. If you have any custom generators, please update your code accordingly.
## BC Break: Removed `EntityManager#flush($entity)` and `EntityManager#flush($entities)`
If your code relies on single entity flushing optimisations via
`EntityManager#flush($entity)`, the signature has been changed to
`EntityManager#flush()`.
Said API was affected by multiple data integrity bugs due to the fact
that change tracking was being restricted upon a subset of the managed
entities. The ORM cannot support committing subsets of the managed
entities while also guaranteeing data integrity, therefore this
utility was removed.
The `flush()` semantics remain the same, but the change tracking will be performed
on all entities managed by the unit of work, and not just on the provided
`$entity` or `$entities`, as the parameter is now completely ignored.
The same applies to `UnitOfWork#commit($entity)`, which now is simply
`UnitOfWork#commit()`.
If you would still like to perform batching operations over small `UnitOfWork`
instances, it is suggested to follow these paths instead:
* eagerly use `EntityManager#clear()` in conjunction with a specific second level
cache configuration (see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/second-level-cache.html)
* use an explicit change tracking policy (see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/change-tracking-policies.html)
## BC Break: Removed ``YAML`` mapping drivers.
If your code relies on ``YamlDriver`` or ``SimpleYamlDriver``, you **MUST** change to
annotation or XML drivers instead.
## BC Break: Changed methods in ``ClassMetadata``
* ``ClassMetadata::addInheritedProperty``
* ``ClassMetadata::setDiscriminatorColumn``
## BC Break: Removed methods in ``ClassMetadata``
* ``ClassMetadata::getTypeOfField`` (to be removed, part of Common API)
## BC Break: Removed methods in ``ClassMetadata``
* ``ClassMetadata::setTableName`` => Use ``ClassMetadata::setPrimaryTable(['name' => ...])``
* ``ClassMetadata::getFieldMapping`` => Use ``ClassMetadata::getProperty()`` and its methods
* ``ClassMetadata::getQuotedColumnName`` => Use ``ClassMetadata::getProperty()::getQuotedColumnName()``
* ``ClassMetadata::getQuotedTableName``
* ``ClassMetadata::getQuotedJoinTableName``
* ``ClassMetadata::getQuotedIdentifierColumnNames``
* ``ClassMetadata::getIdentifierColumnNames`` => Use ``ClassMetadata::getIdentifierColumns($entityManager)``
* ``ClassMetadata::setVersionMetadata``
* ``ClassMetadata::setVersioned``
* ``ClassMetadata::invokeLifecycleCallbacks``
* ``ClassMetadata::isInheritedField`` => Use ``ClassMetadata::getProperty()::isInherited()``
* ``ClassMetadata::isUniqueField`` => Use ``ClassMetadata::getProperty()::isUnique()``
* ``ClassMetadata::isNullable`` => Use ``ClassMetadata::getProperty()::isNullable()``
* ``ClassMetadata::getTypeOfColumn()`` => Use ``PersisterHelper::getTypeOfColumn()``
## BC Break: Removed ``quoted`` index from table, field and sequence mappings
Quoting is now always called. Implement your own ``Doctrine\ORM\Mapping\NamingStrategy`` to manipulate
your schema, tables and column names to your custom desired naming convention.
## BC Break: Removed ``ClassMetadata::$fieldMappings[$fieldName]['requireSQLConversion']``
ORM Type SQL conversion is now always being applied, minimizing the risks of error prone code in ORM internals
## BC Break: Removed ``ClassMetadata::$columnNames``
If your code relies on this property, you should search/replace from this:
$metadata->columnNames[$fieldName]
To this:
$metadata->getProperty($fieldName)->getColumnName()
## BC Break: Renamed ``ClassMetadata::setIdentifierValues()`` to ``ClassMetadata::assignIdentifier()``
Provides a more meaningful name to method.
## BC Break: Removed ``ClassMetadata::$namespace``
The namespace property in ClassMetadata was only used when using association
classes in the same namespace and it was used to speedup ClassMetadata
creation purposes. Namespace could be easily inferred by asking ``\ReflectionClass``
which was already stored internally.
### BC Break: Removed ``ClassMetadata::$isVersioned``
Switched to a method alternative: ``ClassMetadata::isVersioned()``
## BC Break: Removed ``Doctrine\ORM\Mapping\ClassMetadataInfo``
There was no reason to keep a blank class. All references are now pointing
to ``Doctrine\ORM\Mapping\ClassMetadata``.
## BC Break: Annotations classes namespace change
All Annotations classes got moved from ``Doctrine\ORM\Mapping`` into a more
pertinent namespace ``Doctrine\ORM\Annotation``. This change was done to add
room for Metadata namespace refactoring.
## Minor BC break: Mappings now store ``DBAL\Type`` instances instead of strings
This leads to manual ``ResultSetMapping`` building instances to also hold Types in meta results.
Example:
$rsm->addMetaResult('e ', 'e_discr', 'discr', false, Type::getType('string'));
## Enhancement: Mappings now store their declaring ``ClassMetadata``
Every field, association or embedded now contains a pointer to its declaring ``ClassMetadata``.
## Enhancement: Mappings now store their corresponding table name
Every field, association join column or inline embedded field/association holds a reference to its owning table name.
# Upgrade to 2.6
## Added `Doctrine\ORM\EntityRepository::count()` method
`Doctrine\ORM\EntityRepository::count()` has been added. This new method has different
signature than `Countable::count()` (required parameter) and therefore are not compatible.
If your repository implemented the `Countable` interface, you will have to use
`$repository->count([])` instead and not implement `Countable` interface anymore.
## Minor BC BREAK: `Doctrine\ORM\Tools\Console\ConsoleRunner` is now final
Since it's just an utilitarian class and should not be inherited.
## Minor BC BREAK: removed `Doctrine\ORM\Query\QueryException::associationPathInverseSideNotSupported()`
Method `Doctrine\ORM\Query\QueryException::associationPathInverseSideNotSupported()`
now has a required parameter `$pathExpr`.
## Minor BC BREAK: removed `Doctrine\ORM\Query\Parser#isInternalFunction()`
Method `Doctrine\ORM\Query\Parser#isInternalFunction()` was removed because
the distinction between internal function and user defined DQL was removed.
[#6500](https://github.com/doctrine/orm/pull/6500)
## Minor BC BREAK: removed `Doctrine\ORM\ORMException#overwriteInternalDQLFunctionNotAllowed()`
Method `Doctrine\ORM\Query\Parser#overwriteInternalDQLFunctionNotAllowed()` was
removed because of the choice to allow users to overwrite internal functions, ie
`AVG`, `SUM`, `COUNT`, `MIN` and `MAX`. [#6500](https://github.com/doctrine/orm/pull/6500)
## Minor BC BREAK: removed $className parameter on `AbstractEntityInheritancePersister#getSelectJoinColumnSQL()`
As `$className` parameter was not used in the method, it was safely removed.
## PHP 7.1 is now required
Doctrine 2.6 now requires PHP 7.1 or newer.
As a consequence, automatic cache setup in Doctrine\ORM\Tools\Setup::create*Configuration() was changed:
- APCu extension (ext-apcu) will now be used instead of abandoned APC (ext-apc).
- Memcached extension (ext-memcached) will be used instead of obsolete Memcache (ext-memcache).
- XCache support was dropped as it doesn't work with PHP 7.
# Upgrade to 2.5
## Minor BC BREAK: removed `Doctrine\ORM\Query\SqlWalker#walkCaseExpression()`
Method `Doctrine\ORM\Query\SqlWalker#walkCaseExpression()` was unused and part
of the internal API of the ORM, so it was removed. [#5600](https://github.com/doctrine/orm/pull/5600).
## Minor BC BREAK: 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
discriminator map.
When declaring an inheritance map, it was previously possible to skip the root
of the inheritance in the discriminator map. This was actually a validation
mistake by Doctrine2 and led to problems when trying to persist instances of
that class.
If you don't plan to persist instances some classes in your inheritance, then
either:
- make those classes `abstract`
- map those classes as `MappedSuperclass`
## Minor BC BREAK: ``EntityManagerInterface`` instead of ``EntityManager`` in type-hints
As of 2.5, classes requiring the ``EntityManager`` in any method signature will now require
an ``EntityManagerInterface`` instead.
If you are extending any of the following classes, then you need to check following
signatures:
- ``Doctrine\ORM\Tools\DebugUnitOfWorkListener#dumpIdentityMap(EntityManagerInterface $em)``
- ``Doctrine\ORM\Mapping\ClassMetadataFactory#setEntityManager(EntityManagerInterface $em)``
## Minor BC BREAK: Custom Hydrators API change
As of 2.5, `AbstractHydrator` does not enforce the usage of cache as part of
API, and now provides you a clean API for column information through the method
`hydrateColumnInfo($column)`.
Cache variable being passed around by reference is no longer needed since
Hydrators are per query instantiated since Doctrine 2.4.
## Minor BC BREAK: Entity based ``EntityManager#clear()`` calls follow cascade detach
Whenever ``EntityManager#clear()`` method gets called with a given entity class
name, until 2.4, it was only detaching the specific requested entity.
As of 2.5, ``EntityManager`` will follow configured cascades, providing a better
memory management since associations will be garbage collected, optimizing
resources consumption on long running jobs.
## BC BREAK: NamingStrategy interface changes
1. A new method ``embeddedFieldToColumnName($propertyName, $embeddedColumnName)``
This method generates the column name for fields of embedded objects. If you implement your custom NamingStrategy, you
now also need to implement this new method.
2. A change to method ``joinColumnName()`` to include the $className
## Updates on entities scheduled for deletion are no longer processed
In Doctrine 2.4, if you modified properties of an entity scheduled for deletion, UnitOfWork would
produce an UPDATE statement to be executed right before the DELETE statement. The entity in question
was therefore present in ``UnitOfWork#entityUpdates``, which means that ``preUpdate`` and ``postUpdate``
listeners were (quite pointlessly) called. In ``preFlush`` listeners, it used to be possible to undo
the scheduled deletion for updated entities (by calling ``persist()`` if the entity was found in both
``entityUpdates`` and ``entityDeletions``). This does not work any longer, because the entire changeset
calculation logic is optimized away.
## Minor BC BREAK: Default lock mode changed from LockMode::NONE to null in method signatures
A misconception concerning default lock mode values in method signatures lead to unexpected behaviour
in SQL statements on SQL Server. With a default lock mode of ``LockMode::NONE`` throughout the
method signatures in ORM, the table lock hint ``WITH (NOLOCK)`` was appended to all locking related
queries by default. This could result in unpredictable results because an explicit ``WITH (NOLOCK)``
table hint tells SQL Server to run a specific query in transaction isolation level READ UNCOMMITTED
instead of the default READ COMMITTED transaction isolation level.
Therefore there now is a distinction between ``LockMode::NONE`` and ``null`` to be able to tell
Doctrine whether to add table lock hints to queries by intention or not. To achieve this, the following
method signatures have been changed to declare ``$lockMode = null`` instead of ``$lockMode = LockMode::NONE``:
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#getSelectSQL()``
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#load()``
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#refresh()``
- ``Doctrine\ORM\Decorator\EntityManagerDecorator#find()``
- ``Doctrine\ORM\EntityManager#find()``
- ``Doctrine\ORM\EntityRepository#find()``
- ``Doctrine\ORM\Persisters\BasicEntityPersister#getSelectSQL()``
- ``Doctrine\ORM\Persisters\BasicEntityPersister#load()``
- ``Doctrine\ORM\Persisters\BasicEntityPersister#refresh()``
- ``Doctrine\ORM\Persisters\EntityPersister#getSelectSQL()``
- ``Doctrine\ORM\Persisters\EntityPersister#load()``
- ``Doctrine\ORM\Persisters\EntityPersister#refresh()``
- ``Doctrine\ORM\Persisters\JoinedSubclassPersister#getSelectSQL()``
You should update signatures for these methods if you have subclassed one of the above classes.
Please also check the calling code of these methods in your application and update if necessary.
**Note:**
This in fact is really a minor BC BREAK and should not have any affect on database vendors
other than SQL Server because it is the only one that supports and therefore cares about
``LockMode::NONE``. It's really just a FIX for SQL Server environments using ORM.
## Minor BC BREAK: `__clone` method not called anymore when entities are instantiated via metadata API
As of PHP 5.6, instantiation of new entities is deferred to the
[`doctrine/instantiator`](https://github.com/doctrine/instantiator) library, which will avoid calling `__clone`
or any public API on instantiated objects.
## BC BREAK: `Doctrine\ORM\Repository\DefaultRepositoryFactory` is now `final`
Please implement the `Doctrine\ORM\Repository\RepositoryFactory` interface instead of extending
the `Doctrine\ORM\Repository\DefaultRepositoryFactory`.
## BC BREAK: New object expression DQL queries now respects user provided aliasing and not return consumed fields
When executing DQL queries with new object expressions, instead of returning DTOs numerically indexes, it will now respect user provided aliases. Consider the following query:
SELECT new UserDTO(u.id,u.name) as user,new AddressDTO(a.street,a.postalCode) as address, a.id as addressId FROM User u INNER JOIN u.addresses a WITH a.isPrimary = true
Previously, your result would be similar to this:
array(
0=>array(
0=>{UserDTO object},
1=>{AddressDTO object},
2=>{u.id scalar},
3=>{u.name scalar},
4=>{a.street scalar},
5=>{a.postalCode scalar},
'addressId'=>{a.id scalar},
),
...
)
From now on, the resultset will look like this:
array(
0=>array(
'user'=>{UserDTO object},
'address'=>{AddressDTO object},
'addressId'=>{a.id scalar}
),
...
)
## Minor BC BREAK: added second parameter $indexBy in EntityRepository#createQueryBuilder method signature
Added way to access the underlying QueryBuilder#from() method's 'indexBy' parameter when using EntityRepository#createQueryBuilder()
# Upgrade to 2.4
## BC BREAK: Compatibility Bugfix in PersistentCollection#matching()
@@ -572,7 +42,6 @@ Now parenthesis are considered, the previous DQL will generate:
SELECT 100 / (2 * 2) FROM my_entity
# Upgrade to 2.3
## Auto Discriminator Map breaks userland implementations with Listener
@@ -642,7 +111,6 @@ Also, following mapping drivers have been deprecated, please use their replaceme
* `Doctrine\ORM\Mapping\Driver\PHPDriver` => `Doctrine\Common\Persistence\Mapping\Driver\PHPDriver`
* `Doctrine\ORM\Mapping\Driver\StaticPHPDriver` => `Doctrine\Common\Persistence\Mapping\Driver\StaticPHPDriver`
# Upgrade to 2.2
## ResultCache implementation rewritten
@@ -726,7 +194,6 @@ Also, Doctrine 2.2 now is around 10-15% faster than 2.1.
Previously EntityManager#find(null) returned null. It now throws an exception.
# Upgrade to 2.1
## Interface for EntityRepository
@@ -751,7 +218,6 @@ The annotation reader was heavily refactored between 2.0 and 2.1-RC1. In theory
This is already done inside the ``$config->newDefaultAnnotationDriver``, so everything should automatically work if you are using this method. You can verify if everything still works by executing a console command such as schema-validate that loads all metadata into memory.
# Update from 2.0-BETA3 to 2.0-BETA4
## XML Driver <change-tracking-policy /> element demoted to attribute
@@ -760,7 +226,6 @@ We changed how the XML Driver allows to define the change-tracking-policy. The w
<entity change-tracking-policy="DEFERRED_IMPLICT" />
# Update from 2.0-BETA2 to 2.0-BETA3
## Serialization of Uninitialized Proxies
@@ -823,12 +288,10 @@ don't loose anything through this.
The default allocation size for sequences has been changed from 10 to 1. This step was made
to not cause confusion with users and also because it is partly some kind of premature optimization.
# Update from 2.0-BETA1 to 2.0-BETA2
There are no backwards incompatible changes in this release.
# Upgrade from 2.0-ALPHA4 to 2.0-BETA1
## EntityRepository deprecates access to protected variables
@@ -899,6 +362,7 @@ access all entities.
Xml and Yaml Drivers work as before!
## New inversedBy attribute
It is now *mandatory* that the owning side of a bidirectional association specifies the
@@ -962,7 +426,7 @@ you need to use the following, explicit syntax:
## XML Mapping Driver
The 'inheritance-type' attribute changed to take last bit of ClassMetadata constant names, i.e.
NONE, SINGLE_TABLE, JOINED
NONE, SINGLE_TABLE, INHERITANCE_TYPE_JOINED
## YAML Mapping Driver
@@ -994,7 +458,6 @@ The Collection interface in the Common package has been updated with some missin
that were present only on the default implementation, ArrayCollection. Custom collection
implementations need to be updated to adhere to the updated interface.
# Upgrade from 2.0-ALPHA3 to 2.0-ALPHA4
## CLI Controller changes
@@ -1031,8 +494,6 @@ With new required method AbstractTask::buildDocumentation, its implementation de
database schema without deleting any unused tables, sequences or foreign keys.
* Use "doctrine schema-tool --complete-update" to do a full incremental update of
your schema.
# Upgrade from 2.0-ALPHA2 to 2.0-ALPHA3
This section details the changes made to Doctrine 2.0-ALPHA3 to make it easier for you
-2
View File
@@ -1,6 +1,4 @@
#!/usr/bin/env php
<?php
declare(strict_types=1);
include('doctrine.php');
+18 -4
View File
@@ -1,7 +1,21 @@
<?php
declare(strict_types=1);
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* 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
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (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
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
require_once 'Doctrine/Common/ClassLoader.php';
@@ -17,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
);
}
Regular → Executable
+21 -16
View File
@@ -1,23 +1,28 @@
<?php
declare(strict_types=1);
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* 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
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (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
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
use Symfony\Component\Console\Helper\HelperSet;
use Doctrine\ORM\Tools\Console\ConsoleRunner;
$autoloadFiles = [
__DIR__ . '/../vendor/autoload.php',
__DIR__ . '/../../../autoload.php'
];
(@include_once __DIR__ . '/../vendor/autoload.php') || @include_once __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) {
@@ -38,7 +43,7 @@ if ( ! is_readable($configFile)) {
exit(1);
}
$commands = [];
$commands = array();
$helperSet = require $configFile;
@@ -51,4 +56,4 @@ if ( ! ($helperSet instanceof HelperSet)) {
}
}
ConsoleRunner::run($helperSet, $commands);
\Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet, $commands);
+3
View File
@@ -0,0 +1,3 @@
# Version class and file
project.version_class = Doctrine\\ORM\\Version
project.version_file = lib/Doctrine/ORM/Version.php
+23
View File
@@ -38,6 +38,29 @@
</exec>
</target>
<target name="make-release" depends="check-git-checkout-clean,prepare,php">
<replace file="${project.version_file}" token="-DEV" value="" failOnNoReplacements="true" />
<exec executable="${php_executable}" outputproperty="doctrine.current_version" failonerror="true">
<arg value="-r" />
<arg value="require_once '${project.version_file}';echo ${project.version_class}::VERSION;" />
</exec>
<exec executable="${php_executable}" outputproperty="doctrine.next_version" failonerror="true">
<arg value="-r" />
<arg value="$parts = explode('.', str_ireplace(array('-DEV', '-ALPHA', '-BETA'), '', '${doctrine.current_version}'));
if (count($parts) != 3) {
throw new \InvalidArgumentException('Version is assumed in format x.y.z, ${doctrine.current_version} given');
}
$parts[2]++;
echo implode('.', $parts);
" />
</exec>
<git-commit file="${project.version_file}" message="Release ${doctrine.current_version}" />
<git-tag version="${doctrine.current_version}" />
<replace file="${project.version_file}" token="${doctrine.current_version}" value="${doctrine.next_version}-DEV" />
<git-commit file="${project.version_file}" message="Bump version to ${doctrine.next_version}" />
</target>
<target name="check-git-checkout-clean">
<exec executable="git" failonerror="true">
<arg value="diff-index" />
+20 -51
View File
@@ -1,71 +1,40 @@
{
"name": "doctrine/orm",
"type": "library",
"description": "PHP object relational mapper (ORM) that 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). This provides developers with a powerful alternative to SQL that maintains flexibility without requiring unnecessary code duplication.",
"keywords": [
"php",
"orm",
"mysql",
"object",
"data",
"mapper",
"mapping",
"query",
"dql"
],
"homepage": "https://www.doctrine-project.org/projects/orm.html",
"description": "Object-Relational-Mapper for PHP",
"keywords": ["orm", "database"],
"homepage": "http://www.doctrine-project.org",
"license": "MIT",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"},
{"name": "Marco Pivetta", "email": "ocramius@gmail.com"}
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"}
],
"support": {
"chat": "https://www.doctrine-project.org/slack",
"docs": "https://www.doctrine-project.org/projects/orm.html",
"email": "doctrine-user@googlegroups.com",
"issues": "https://github.com/doctrine/orm/issues",
"rss": "https://github.com/doctrine/orm/releases.atom",
"source": "https://github.com/doctrine/orm"
},
"config": {
"sort-packages": true
},
"require": {
"php": "^7.3",
"ext-ctype": "*",
"doctrine/annotations": "~1.7",
"doctrine/cache": "~1.6",
"doctrine/collections": "^1.4",
"doctrine/dbal": "dev-missed-commits",
"doctrine/event-manager": "^1.0",
"doctrine/inflector": "~1.0",
"doctrine/instantiator": "~1.1",
"doctrine/persistence": "^1.1",
"doctrine/reflection": "^1.0",
"ocramius/package-versions": "^1.1.2",
"ocramius/proxy-manager": "^2.1.1",
"symfony/console": "~4.0|~5.0",
"symfony/var-dumper": "^4.1"
"php": ">=5.3.2",
"ext-pdo": "*",
"doctrine/collections": "~1.1",
"doctrine/dbal": "~2.4",
"symfony/console": "~2.0"
},
"require-dev": {
"doctrine/coding-standard": "^6.0",
"phpstan/phpstan": "^0.11",
"phpunit/phpunit": "^7.0"
"symfony/yaml": "~2.1",
"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"
"bin": ["bin/doctrine", "bin/doctrine.php"],
"extra": {
"branch-alias": {
"dev-master": "2.4.x-dev"
}
},
"bin": ["bin/doctrine"],
"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"]
}
}
Generated
-4214
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -0,0 +1,4 @@
en/_exts/configurationblock.pyc
build
en/_build
.idea
+3
View File
@@ -0,0 +1,3 @@
[submodule "en/_theme"]
path = en/_theme
url = https://github.com/doctrine/doctrine-sphinx-theme.git
-361
View File
@@ -1,361 +0,0 @@
The Doctrine2 documentation is licensed under [CC BY-NC-SA 3.0](https://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US)
Creative Commons Legal Code
Attribution-NonCommercial-ShareAlike 3.0 Unported
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR
DAMAGES RESULTING FROM ITS USE.
License
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE
TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY
BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS
CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
CONDITIONS.
1. Definitions
a. "Adaptation" means a work based upon the Work, or upon the Work and
other pre-existing works, such as a translation, adaptation,
derivative work, arrangement of music or other alterations of a
literary or artistic work, or phonogram or performance and includes
cinematographic adaptations or any other form in which the Work may be
recast, transformed, or adapted including in any form recognizably
derived from the original, except that a work that constitutes a
Collection will not be considered an Adaptation for the purpose of
this License. For the avoidance of doubt, where the Work is a musical
work, performance or phonogram, the synchronization of the Work in
timed-relation with a moving image ("synching") will be considered an
Adaptation for the purpose of this License.
b. "Collection" means a collection of literary or artistic works, such as
encyclopedias and anthologies, or performances, phonograms or
broadcasts, or other works or subject matter other than works listed
in Section 1(g) below, which, by reason of the selection and
arrangement of their contents, constitute intellectual creations, in
which the Work is included in its entirety in unmodified form along
with one or more other contributions, each constituting separate and
independent works in themselves, which together are assembled into a
collective whole. A work that constitutes a Collection will not be
considered an Adaptation (as defined above) for the purposes of this
License.
c. "Distribute" means to make available to the public the original and
copies of the Work or Adaptation, as appropriate, through sale or
other transfer of ownership.
d. "License Elements" means the following high-level license attributes
as selected by Licensor and indicated in the title of this License:
Attribution, Noncommercial, ShareAlike.
e. "Licensor" means the individual, individuals, entity or entities that
offer(s) the Work under the terms of this License.
f. "Original Author" means, in the case of a literary or artistic work,
the individual, individuals, entity or entities who created the Work
or if no individual or entity can be identified, the publisher; and in
addition (i) in the case of a performance the actors, singers,
musicians, dancers, and other persons who act, sing, deliver, declaim,
play in, interpret or otherwise perform literary or artistic works or
expressions of folklore; (ii) in the case of a phonogram the producer
being the person or legal entity who first fixes the sounds of a
performance or other sounds; and, (iii) in the case of broadcasts, the
organization that transmits the broadcast.
g. "Work" means the literary and/or artistic work offered under the terms
of this License including without limitation any production in the
literary, scientific and artistic domain, whatever may be the mode or
form of its expression including digital form, such as a book,
pamphlet and other writing; a lecture, address, sermon or other work
of the same nature; a dramatic or dramatico-musical work; a
choreographic work or entertainment in dumb show; a musical
composition with or without words; a cinematographic work to which are
assimilated works expressed by a process analogous to cinematography;
a work of drawing, painting, architecture, sculpture, engraving or
lithography; a photographic work to which are assimilated works
expressed by a process analogous to photography; a work of applied
art; an illustration, map, plan, sketch or three-dimensional work
relative to geography, topography, architecture or science; a
performance; a broadcast; a phonogram; a compilation of data to the
extent it is protected as a copyrightable work; or a work performed by
a variety or circus performer to the extent it is not otherwise
considered a literary or artistic work.
h. "You" means an individual or entity exercising rights under this
License who has not previously violated the terms of this License with
respect to the Work, or who has received express permission from the
Licensor to exercise rights under this License despite a previous
violation.
i. "Publicly Perform" means to perform public recitations of the Work and
to communicate to the public those public recitations, by any means or
process, including by wire or wireless means or public digital
performances; to make available to the public Works in such a way that
members of the public may access these Works from a place and at a
place individually chosen by them; to perform the Work to the public
by any means or process and the communication to the public of the
performances of the Work, including by public digital performance; to
broadcast and rebroadcast the Work by any means including signs,
sounds or images.
j. "Reproduce" means to make copies of the Work by any means including
without limitation by sound or visual recordings and the right of
fixation and reproducing fixations of the Work, including storage of a
protected performance or phonogram in digital form or other electronic
medium.
2. Fair Dealing Rights. Nothing in this License is intended to reduce,
limit, or restrict any uses free from copyright or rights arising from
limitations or exceptions that are provided for in connection with the
copyright protection under copyright law or other applicable laws.
3. License Grant. Subject to the terms and conditions of this License,
Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
perpetual (for the duration of the applicable copyright) license to
exercise the rights in the Work as stated below:
a. to Reproduce the Work, to incorporate the Work into one or more
Collections, and to Reproduce the Work as incorporated in the
Collections;
b. to create and Reproduce Adaptations provided that any such Adaptation,
including any translation in any medium, takes reasonable steps to
clearly label, demarcate or otherwise identify that changes were made
to the original Work. For example, a translation could be marked "The
original work was translated from English to Spanish," or a
modification could indicate "The original work has been modified.";
c. to Distribute and Publicly Perform the Work including as incorporated
in Collections; and,
d. to Distribute and Publicly Perform Adaptations.
The above rights may be exercised in all media and formats whether now
known or hereafter devised. The above rights include the right to make
such modifications as are technically necessary to exercise the rights in
other media and formats. Subject to Section 8(f), all rights not expressly
granted by Licensor are hereby reserved, including but not limited to the
rights described in Section 4(e).
4. Restrictions. The license granted in Section 3 above is expressly made
subject to and limited by the following restrictions:
a. You may Distribute or Publicly Perform the Work only under the terms
of this License. You must include a copy of, or the Uniform Resource
Identifier (URI) for, this License with every copy of the Work You
Distribute or Publicly Perform. You may not offer or impose any terms
on the Work that restrict the terms of this License or the ability of
the recipient of the Work to exercise the rights granted to that
recipient under the terms of the License. You may not sublicense the
Work. You must keep intact all notices that refer to this License and
to the disclaimer of warranties with every copy of the Work You
Distribute or Publicly Perform. When You Distribute or Publicly
Perform the Work, You may not impose any effective technological
measures on the Work that restrict the ability of a recipient of the
Work from You to exercise the rights granted to that recipient under
the terms of the License. This Section 4(a) applies to the Work as
incorporated in a Collection, but this does not require the Collection
apart from the Work itself to be made subject to the terms of this
License. If You create a Collection, upon notice from any Licensor You
must, to the extent practicable, remove from the Collection any credit
as required by Section 4(d), as requested. If You create an
Adaptation, upon notice from any Licensor You must, to the extent
practicable, remove from the Adaptation any credit as required by
Section 4(d), as requested.
b. You may Distribute or Publicly Perform an Adaptation only under: (i)
the terms of this License; (ii) a later version of this License with
the same License Elements as this License; (iii) a Creative Commons
jurisdiction license (either this or a later license version) that
contains the same License Elements as this License (e.g.,
Attribution-NonCommercial-ShareAlike 3.0 US) ("Applicable License").
You must include a copy of, or the URI, for Applicable License with
every copy of each Adaptation You Distribute or Publicly Perform. You
may not offer or impose any terms on the Adaptation that restrict the
terms of the Applicable License or the ability of the recipient of the
Adaptation to exercise the rights granted to that recipient under the
terms of the Applicable License. You must keep intact all notices that
refer to the Applicable License and to the disclaimer of warranties
with every copy of the Work as included in the Adaptation You
Distribute or Publicly Perform. When You Distribute or Publicly
Perform the Adaptation, You may not impose any effective technological
measures on the Adaptation that restrict the ability of a recipient of
the Adaptation from You to exercise the rights granted to that
recipient under the terms of the Applicable License. This Section 4(b)
applies to the Adaptation as incorporated in a Collection, but this
does not require the Collection apart from the Adaptation itself to be
made subject to the terms of the Applicable License.
c. You may not exercise any of the rights granted to You in Section 3
above in any manner that is primarily intended for or directed toward
commercial advantage or private monetary compensation. The exchange of
the Work for other copyrighted works by means of digital file-sharing
or otherwise shall not be considered to be intended for or directed
toward commercial advantage or private monetary compensation, provided
there is no payment of any monetary compensation in con-nection with
the exchange of copyrighted works.
d. If You Distribute, or Publicly Perform the Work or any Adaptations or
Collections, You must, unless a request has been made pursuant to
Section 4(a), keep intact all copyright notices for the Work and
provide, reasonable to the medium or means You are utilizing: (i) the
name of the Original Author (or pseudonym, if applicable) if supplied,
and/or if the Original Author and/or Licensor designate another party
or parties (e.g., a sponsor institute, publishing entity, journal) for
attribution ("Attribution Parties") in Licensor's copyright notice,
terms of service or by other reasonable means, the name of such party
or parties; (ii) the title of the Work if supplied; (iii) to the
extent reasonably practicable, the URI, if any, that Licensor
specifies to be associated with the Work, unless such URI does not
refer to the copyright notice or licensing information for the Work;
and, (iv) consistent with Section 3(b), in the case of an Adaptation,
a credit identifying the use of the Work in the Adaptation (e.g.,
"French translation of the Work by Original Author," or "Screenplay
based on original Work by Original Author"). The credit required by
this Section 4(d) may be implemented in any reasonable manner;
provided, however, that in the case of a Adaptation or Collection, at
a minimum such credit will appear, if a credit for all contributing
authors of the Adaptation or Collection appears, then as part of these
credits and in a manner at least as prominent as the credits for the
other contributing authors. For the avoidance of doubt, You may only
use the credit required by this Section for the purpose of attribution
in the manner set out above and, by exercising Your rights under this
License, You may not implicitly or explicitly assert or imply any
connection with, sponsorship or endorsement by the Original Author,
Licensor and/or Attribution Parties, as appropriate, of You or Your
use of the Work, without the separate, express prior written
permission of the Original Author, Licensor and/or Attribution
Parties.
e. For the avoidance of doubt:
i. Non-waivable Compulsory License Schemes. In those jurisdictions in
which the right to collect royalties through any statutory or
compulsory licensing scheme cannot be waived, the Licensor
reserves the exclusive right to collect such royalties for any
exercise by You of the rights granted under this License;
ii. Waivable Compulsory License Schemes. In those jurisdictions in
which the right to collect royalties through any statutory or
compulsory licensing scheme can be waived, the Licensor reserves
the exclusive right to collect such royalties for any exercise by
You of the rights granted under this License if Your exercise of
such rights is for a purpose or use which is otherwise than
noncommercial as permitted under Section 4(c) and otherwise waives
the right to collect royalties through any statutory or compulsory
licensing scheme; and,
iii. Voluntary License Schemes. The Licensor reserves the right to
collect royalties, whether individually or, in the event that the
Licensor is a member of a collecting society that administers
voluntary licensing schemes, via that society, from any exercise
by You of the rights granted under this License that is for a
purpose or use which is otherwise than noncommercial as permitted
under Section 4(c).
f. Except as otherwise agreed in writing by the Licensor or as may be
otherwise permitted by applicable law, if You Reproduce, Distribute or
Publicly Perform the Work either by itself or as part of any
Adaptations or Collections, You must not distort, mutilate, modify or
take other derogatory action in relation to the Work which would be
prejudicial to the Original Author's honor or reputation. Licensor
agrees that in those jurisdictions (e.g. Japan), in which any exercise
of the right granted in Section 3(b) of this License (the right to
make Adaptations) would be deemed to be a distortion, mutilation,
modification or other derogatory action prejudicial to the Original
Author's honor and reputation, the Licensor will waive or not assert,
as appropriate, this Section, to the fullest extent permitted by the
applicable national law, to enable You to reasonably exercise Your
right under Section 3(b) of this License (right to make Adaptations)
but not otherwise.
5. Representations, Warranties and Disclaimer
UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING AND TO THE
FULLEST EXTENT PERMITTED BY APPLICABLE LAW, LICENSOR OFFERS THE WORK AS-IS
AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE
WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT
LIMITATION, WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS,
ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT
DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED
WARRANTIES, SO THIS EXCLUSION MAY NOT APPLY TO YOU.
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE
LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR
ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES
ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
7. Termination
a. This License and the rights granted hereunder will terminate
automatically upon any breach by You of the terms of this License.
Individuals or entities who have received Adaptations or Collections
from You under this License, however, will not have their licenses
terminated provided such individuals or entities remain in full
compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will
survive any termination of this License.
b. Subject to the above terms and conditions, the license granted here is
perpetual (for the duration of the applicable copyright in the Work).
Notwithstanding the above, Licensor reserves the right to release the
Work under different license terms or to stop distributing the Work at
any time; provided, however that any such election will not serve to
withdraw this License (or any other license that has been, or is
required to be, granted under the terms of this License), and this
License will continue in full force and effect unless terminated as
stated above.
8. Miscellaneous
a. Each time You Distribute or Publicly Perform the Work or a Collection,
the Licensor offers to the recipient a license to the Work on the same
terms and conditions as the license granted to You under this License.
b. Each time You Distribute or Publicly Perform an Adaptation, Licensor
offers to the recipient a license to the original Work on the same
terms and conditions as the license granted to You under this License.
c. If any provision of this License is invalid or unenforceable under
applicable law, it shall not affect the validity or enforceability of
the remainder of the terms of this License, and without further action
by the parties to this agreement, such provision shall be reformed to
the minimum extent necessary to make such provision valid and
enforceable.
d. No term or provision of this License shall be deemed waived and no
breach consented to unless such waiver or consent shall be in writing
and signed by the party to be charged with such waiver or consent.
e. This License constitutes the entire agreement between the parties with
respect to the Work licensed here. There are no understandings,
agreements or representations with respect to the Work not specified
here. Licensor shall not be bound by any additional provisions that
may appear in any communication from You. This License may not be
modified without the mutual written agreement of the Licensor and You.
f. The rights granted under, and the subject matter referenced, in this
License were drafted utilizing the terminology of the Berne Convention
for the Protection of Literary and Artistic Works (as amended on
September 28, 1979), the Rome Convention of 1961, the WIPO Copyright
Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996
and the Universal Copyright Convention (as revised on July 24, 1971).
These rights and subject matter take effect in the relevant
jurisdiction in which the License terms are sought to be enforced
according to the corresponding provisions of the implementation of
those treaty provisions in the applicable national law. If the
standard suite of rights granted under applicable copyright law
includes additional rights not granted under this License, such
additional rights are deemed to be included in the License; this
License is not intended to restrict the license of any rights under
applicable law.
Creative Commons Notice
Creative Commons is not a party to this License, and makes no warranty
whatsoever in connection with the Work. Creative Commons will not be
liable to You or any party on any legal theory for any damages
whatsoever, including without limitation any general, special,
incidental or consequential damages arising in connection to this
license. Notwithstanding the foregoing two (2) sentences, if Creative
Commons has expressly identified itself as the Licensor hereunder, it
shall have all rights and obligations of Licensor.
Except for the limited purpose of indicating to the public that the
Work is licensed under the CCPL, Creative Commons does not authorize
the use by either party of the trademark "Creative Commons" or any
related trademark or logo of Creative Commons without the prior
written consent of Creative Commons. Any permitted use will be in
compliance with Creative Commons' then-current trademark usage
guidelines, as may be published on its website or otherwise made
available upon request from time to time. For the avoidance of doubt,
this trademark restriction does not form part of this License.
Creative Commons may be contacted at https://creativecommons.org/.
+8
View File
@@ -0,0 +1,8 @@
# Doctrine ORM Documentation
## 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.
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
EXECPATH=`dirname $0`
cd $EXECPATH
cd ..
rm build -Rf
sphinx-build en build
sphinx-build -b latex en build/pdf
rubber --into build/pdf --pdf build/pdf/Doctrine2ORM.tex
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
sudo apt-get install python25 python25-dev texlive-full rubber
sudo easy_install pygments
sudo easy_install sphinx
+89
View File
@@ -0,0 +1,89 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Doctrine2ORM.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Doctrine2ORM.qhc"
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
"run these through (pdf)latex."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
+93
View File
@@ -0,0 +1,93 @@
#Copyright (c) 2010 Fabien Potencier
#
#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 the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is furnished
#to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in all
#copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#THE SOFTWARE.
from docutils.parsers.rst import Directive, directives
from docutils import nodes
from string import upper
class configurationblock(nodes.General, nodes.Element):
pass
class ConfigurationBlock(Directive):
has_content = True
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = True
option_spec = {}
formats = {
'html': 'HTML',
'xml': 'XML',
'php': 'PHP',
'yaml': 'YAML',
'jinja': 'Twig',
'html+jinja': 'Twig',
'jinja+html': 'Twig',
'php+html': 'PHP',
'html+php': 'PHP',
'ini': 'INI',
'php-annotations': 'Annotations',
}
def run(self):
env = self.state.document.settings.env
node = nodes.Element()
node.document = self.state.document
self.state.nested_parse(self.content, self.content_offset, node)
entries = []
for i, child in enumerate(node):
if isinstance(child, nodes.literal_block):
# add a title (the language name) before each block
#targetid = "configuration-block-%d" % env.new_serialno('configuration-block')
#targetnode = nodes.target('', '', ids=[targetid])
#targetnode.append(child)
innernode = nodes.emphasis(self.formats[child['language']], self.formats[child['language']])
para = nodes.paragraph()
para += [innernode, child]
entry = nodes.list_item('')
entry.append(para)
entries.append(entry)
resultnode = configurationblock()
resultnode.append(nodes.bullet_list('', *entries))
return [resultnode]
def visit_configurationblock_html(self, node):
self.body.append(self.starttag(node, 'div', CLASS='configuration-block'))
def depart_configurationblock_html(self, node):
self.body.append('</div>\n')
def visit_configurationblock_latex(self, node):
pass
def depart_configurationblock_latex(self, node):
pass
def setup(app):
app.add_node(configurationblock,
html=(visit_configurationblock_html, depart_configurationblock_html),
latex=(visit_configurationblock_latex, depart_configurationblock_latex))
app.add_directive('configuration-block', ConfigurationBlock)
+1
Submodule docs/en/_theme added at 68795c5888
+201
View File
@@ -0,0 +1,201 @@
# -*- coding: utf-8 -*-
#
# Doctrine 2 ORM documentation build configuration file, created by
# sphinx-quickstart on Fri Dec 3 18:10:24 2010.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
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
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.append(os.path.abspath('_exts'))
# -- General configuration -----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['configurationblock']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Doctrine 2 ORM'
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
# built documents.
#
# The short X.Y version.
version = '2'
# The full version, including alpha/beta/rc tags.
release = '2'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
language = 'en'
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of documents that shouldn't be included in the build.
#unused_docs = []
# List of directories, relative to source directory, that shouldn't be searched
# for source files.
exclude_trees = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
show_authors = True
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
html_theme = 'doctrine'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = ['_theme']
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_use_modindex = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = ''
# Output file base name for HTML help builder.
htmlhelp_basename = 'Doctrine2ORMdoc'
# -- Options for LaTeX output --------------------------------------------------
# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'Doctrine2ORM.tex', u'Doctrine 2 ORM Documentation',
u'Doctrine Project Team', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# Additional stuff for the LaTeX preamble.
#latex_preamble = ''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_use_modindex = True
primary_domain = "dcorm"
def linkcode_resolve(domain, info):
if domain == 'dcorm':
return 'http://'
return None
@@ -4,18 +4,18 @@ Advanced field value conversion using custom mapping types
.. sectionauthor:: Jan Sorgalla <jsorgalla@googlemail.com>
When creating entities, you sometimes have the need to transform field values
before they are saved to the database. In Doctrine you can use Custom Mapping
before they are saved to the database. In Doctrine you can use Custom Mapping
Types to solve this (see: :ref:`reference-basic-mapping-custom-mapping-types`).
There are several ways to achieve this: converting the value inside the Type
class, converting the value on the database-level or a combination of both.
This article describes the third way by implementing the MySQL specific column
type `Point <https://dev.mysql.com/doc/refman/8.0/en/gis-class-point.html>`_.
type `Point <http://dev.mysql.com/doc/refman/5.5/en/gis-class-point.html>`_.
The ``Point`` type is part of the `Spatial extension <https://dev.mysql.com/doc/refman/8.0/en/spatial-extensions.html>`_
The ``Point`` type is part of the `Spatial extension <http://dev.mysql.com/doc/refman/5.5/en/spatial-extensions.html>`_
of MySQL and enables you to store a single location in a coordinate space by
using x and y coordinates. You can use the Point type to store a
using x and y coordinates. You can use the Point type to store a
longitude/latitude pair to represent a geographic location.
The entity
@@ -29,25 +29,23 @@ The entity class:
.. code-block:: php
<?php
namespace Geo\Entity;
use Doctrine\ORM\Annotation as ORM;
/**
* @ORM\Entity
* @Entity
*/
class Location
{
/**
* @ORM\Column(type="point")
* @Column(type="point")
*
* @var \Geo\ValueObject\Point
*/
private $point;
/**
* @ORM\Column(type="string")
* @Column(type="string")
*
* @var string
*/
@@ -86,7 +84,7 @@ The entity class:
}
}
We use the custom type ``point`` in the ``@Column`` docblock annotation of the
We use the custom type ``point`` in the ``@Column`` docblock annotation of the
``$point`` field. We will create this custom mapping type in the next chapter.
The point class:
@@ -94,7 +92,7 @@ The point class:
.. code-block:: php
<?php
namespace Geo\ValueObject;
class Point
@@ -152,7 +150,7 @@ Now we're going to create the ``point`` type and implement all required methods.
return self::POINT;
}
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
return 'POINT';
}
@@ -194,11 +192,11 @@ object into a string representation before saving to the database (in the
``convertToDatabaseValue`` method) and back into an object after fetching the
value from the database (in the ``convertToPHPValue`` method).
The format of the string representation format is called
`Well-known text (WKT) <https://en.wikipedia.org/wiki/Well-known_text>`_.
The advantage of this format is, that it is both human readable and parsable by MySQL.
The format of the string representation format is called `Well-known text (WKT)
<http://en.wikipedia.org/wiki/Well-known_text>`_. The advantage of this format
is, that it is both human readable and parsable by MySQL.
Internally, MySQL stores geometry values in a binary format that is not
Internally, MySQL stores geometry values in a binary format that is not
identical to the WKT format. So, we need to let MySQL transform the WKT
representation into its internal format.
@@ -206,19 +204,19 @@ This is where the ``convertToPHPValueSQL`` and ``convertToDatabaseValueSQL``
methods come into play.
This methods wrap a sql expression (the WKT representation of the Point) into
MySQL functions `ST_PointFromText <https://dev.mysql.com/doc/refman/8.0/en/gis-wkt-functions.html#function_st-pointfromtext>`_
and `ST_AsText <https://dev.mysql.com/doc/refman/8.0/en/gis-format-conversion-functions.html#function_st-astext>`_
MySQL functions `PointFromText <http://dev.mysql.com/doc/refman/5.5/en/creating-spatial-values.html#function_pointfromtext>`_
and `AsText <http://dev.mysql.com/doc/refman/5.5/en/functions-to-convert-geometries-between-formats.html#function_astext>`_
which convert WKT strings to and from the internal format of MySQL.
.. note::
When using DQL queries, the ``convertToPHPValueSQL`` and
When using DQL queries, the ``convertToPHPValueSQL`` and
``convertToDatabaseValueSQL`` methods only apply to identification variables
and path expressions in SELECT clauses. Expressions in WHERE clauses are
and path expressions in SELECT clauses. Expressions in WHERE clauses are
**not** wrapped!
If you want to use Point values in WHERE clauses, you have to implement a
:doc:`user defined function <dql-user-defined-functions>` for
:doc:`user defined function <dql-user-defined-functions>` for
``PointFromText``.
Example usage
@@ -234,7 +232,7 @@ Example usage
// Setup custom mapping type
use Doctrine\DBAL\Types\Type;
Type::addType('point', 'Geo\Types\PointType');
Type::addType('point', 'Geo\Types\Point');
$em->getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping('point', 'point');
// Store a Location object
@@ -254,5 +252,5 @@ Example usage
$query = $em->createQuery("SELECT l FROM Geo\Entity\Location WHERE l.address = '1600 Amphitheatre Parkway, Mountain View, CA'");
$location = $query->getSingleResult();
/** @var Geo\ValueObject\Point $point */
/* @var Geo\ValueObject\Point */
$point = $location->getPoint();
+45 -39
View File
@@ -32,28 +32,29 @@ Our entities look like:
.. code-block:: php
<?php
namespace Bank\Entities;
use Doctrine\ORM\Annotation as ORM;
/**
* @ORM\Entity
* @Entity
*/
class Account
{
/** @ORM\Id @ORM\GeneratedValue @ORM\Column(type="integer") */
/** @Id @GeneratedValue @Column(type="integer") */
private $id;
/** @ORM\Column(type="string", unique=true) */
/** @Column(type="string", unique=true) */
private $no;
/** @ORM\OneToMany(targetEntity="Entry", mappedBy="account", cascade={"persist"}) */
/**
* @OneToMany(targetEntity="Entry", mappedBy="account", cascade={"persist"})
*/
private $entries;
/** @ORM\Column(type="integer") */
/**
* @Column(type="integer")
*/
private $maxCredit = 0;
public function __construct($no, $maxCredit = 0)
{
$this->no = $no;
@@ -61,28 +62,32 @@ Our entities look like:
$this->entries = new \Doctrine\Common\Collections\ArrayCollection();
}
}
/**
* @ORM\Entity
* @Entity
*/
class Entry
{
/** @ORM\Id @ORM\GeneratedValue @ORM\Column(type="integer") */
/** @Id @GeneratedValue @Column(type="integer") */
private $id;
/** @ORM\ManyToOne(targetEntity="Account", inversedBy="entries") */
/**
* @ManyToOne(targetEntity="Account", inversedBy="entries")
*/
private $account;
/** @ORM\Column(type="integer") */
/**
* @Column(type="integer")
*/
private $amount;
public function __construct($account, $amount)
{
$this->account = $account;
$this->amount = $amount;
// more stuff here, from/to whom, stated reason, execution date and such
}
public function getAmount()
{
return $this->amount;
@@ -144,7 +149,7 @@ collection, which means we can compute this value at runtime:
public function getBalance()
{
$balance = 0;
foreach ($this->entries as $entry) {
foreach ($this->entries AS $entry) {
$balance += $entry->getAmount();
}
return $balance;
@@ -173,7 +178,7 @@ relation with this method:
public function addEntry($amount)
{
$this->assertAcceptEntryAllowed($amount);
$e = new Entry($this, $amount);
$this->entries[] = $e;
return $e;
@@ -191,18 +196,18 @@ Now look at the following test-code for our entities:
{
$account = new Account("123456", $maxCredit = 200);
$this->assertEquals(0, $account->getBalance());
$account->addEntry(500);
$this->assertEquals(500, $account->getBalance());
$account->addEntry(-700);
$this->assertEquals(-200, $account->getBalance());
}
public function testExceedMaxLimit()
{
$account = new Account("123456", $maxCredit = 200);
$this->setExpectedException("Exception");
$account->addEntry(-1000);
}
@@ -261,19 +266,19 @@ entries collection) we want to add an aggregate field called
class Account
{
/**
* @ORM\Column(type="integer")
* @Column(type="integer")
*/
private $balance = 0;
public function getBalance()
{
return $this->balance;
}
public function addEntry($amount)
{
$this->assertAcceptEntryAllowed($amount);
$e = new Entry($this, $amount);
$this->entries[] = $e;
$this->balance += $amount;
@@ -304,20 +309,20 @@ potentially lead to inconsistent state. See this example:
// The Account $accId has a balance of 0 and a max credit limit of 200:
// request 1 account
$account1 = $em->find('Bank\Entities\Account', $accId);
// request 2 account
$account2 = $em->find('Bank\Entities\Account', $accId);
$account1->addEntry(-200);
$account2->addEntry(-200);
// now request 1 and 2 both flush the changes.
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.
@@ -327,9 +332,9 @@ Optimistic locking is as easy as adding a version column:
.. code-block:: php
<?php
class Account
class Amount
{
/** @ORM\Column(type="integer") @ORM\Version */
/** @Column(type="integer") @Version */
private $version;
}
@@ -345,7 +350,7 @@ the database using a FOR UPDATE.
<?php
use Doctrine\DBAL\LockMode;
$account = $em->find('Bank\Entities\Account', $accId, LockMode::PESSIMISTIC_READ);
Keeping Updates and Deletes in Sync
@@ -368,3 +373,4 @@ the related objects that make up an aggregate value. Finally I
showed how you can ensure that your aggregate fields do not get out
of sync due to race-conditions and concurrent access.
-101
View File
@@ -1,101 +0,0 @@
Custom Mapping Types
====================
Doctrine allows you to create new mapping types. This can come in
handy when you're missing a specific mapping type or when you want
to replace the existing implementation of a mapping type.
In order to create a new mapping type you need to subclass
``Doctrine\DBAL\Types\Type`` and implement/override the methods as
you wish. Here is an example skeleton of such a custom type class:
.. code-block:: php
<?php
namespace My\Project\Types;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;
/**
* My custom datatype.
*/
class MyType extends Type
{
const MYTYPE = 'mytype'; // modify to match your type name
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
// return the SQL used to create your column type. To create a portable column type, use the $platform.
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
// This is executed when the value is read from the database. Make your conversions here, optionally using the $platform.
}
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
// This is executed when the value is written to the database. Make your conversions here, optionally using the $platform.
}
public function getName()
{
return self::MYTYPE; // modify to match your constant name
}
}
.. note::
The following assumptions are applied to mapping types by the ORM:
- 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
castable to string. Hence, when using custom types that map to PHP
objects as IDs, such objects must implement the ``__toString()`` magic
method.
When you have implemented the type you still need to let Doctrine
know about it. This can be achieved through the
``Doctrine\DBAL\Types\Type#addType($name, $className)``
method. See the following example:
.. code-block:: php
<?php
// in bootstrapping code
// ...
use Doctrine\DBAL\Types\Type;
// ...
// Register my type
Type::addType('mytype', 'My\Project\Types\MyType');
To convert the underlying database type of your
new "mytype" directly into an instance of ``MyType`` when performing
schema operations, the type has to be registered with the database
platform as well:
.. code-block:: php
<?php
$conn = $em->getConnection();
$conn->getDatabasePlatform()->registerDoctrineTypeMapping('db_mytype', 'mytype');
When registering the custom types in the configuration you specify a unique
name for the mapping type and map that to the corresponding fully qualified
class name. Now the new type can be used when mapping columns:
.. code-block:: php
<?php
class MyPersistentClass
{
/** @ORM\Column(type="mytype") */
private $field;
}
+90 -97
View File
@@ -3,48 +3,45 @@ Persisting the Decorator Pattern
.. sectionauthor:: Chris Woodford <chris.woodford@gmail.com>
This recipe will show you a simple example of how you can use
This recipe will show you a simple example of how you can use
Doctrine 2 to persist an implementation of the
`Decorator Pattern <https://en.wikipedia.org/wiki/Decorator_pattern>`_
`Decorator Pattern <http://en.wikipedia.org/wiki/Decorator_pattern>`_
Component
---------
The ``Component`` class needs to be persisted, so it's going to
be an ``Entity``. As the top of the inheritance hierarchy, it's going
to have to define the persistent inheritance. For this example, we
will use Single Table Inheritance, but Class Table Inheritance
would work as well. In the discriminator map, we will define two
concrete subclasses, ``ConcreteComponent`` and ``ConcreteDecorator``.
The ``Component`` class needs to be persisted, so it's going to
be an ``Entity``. As the top of the inheritance hierarchy, it's going
to have to define the persistent inheritance. For this example, we
will use Single Table Inheritance, but Class Table Inheritance
would work as well. In the discriminator map, we will define two
concrete subclasses, ``ConcreteComponent`` and ``ConcreteDecorator``.
.. code-block:: php
<?php
namespace Test;
use Doctrine\ORM\Annotation as ORM;
/**
* @ORM\Entity
* @ORM\InheritanceType("SINGLE_TABLE")
* @ORM\DiscriminatorColumn(name="discr", type="string")
* @ORM\DiscriminatorMap({
* "cc" = "Test\Component\ConcreteComponent",
* "cd" = "Test\Decorator\ConcreteDecorator"
* })
* @Entity
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"cc" = "Test\Component\ConcreteComponent",
"cd" = "Test\Decorator\ConcreteDecorator"})
*/
abstract class Component
{
/**
* @ORM\Id @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
* @Id @Column(type="integer")
* @GeneratedValue(strategy="AUTO")
*/
protected $id;
/** @ORM\Column(type="string", nullable=true) */
/** @Column(type="string", nullable=true) */
protected $name;
/**
* Get id
* @return integer $id
@@ -53,7 +50,7 @@ concrete subclasses, ``ConcreteComponent`` and ``ConcreteDecorator``.
{
return $this->id;
}
/**
* Set name
* @param string $name
@@ -62,7 +59,7 @@ concrete subclasses, ``ConcreteComponent`` and ``ConcreteDecorator``.
{
$this->name = $name;
}
/**
* Get name
* @return string $name
@@ -71,34 +68,33 @@ concrete subclasses, ``ConcreteComponent`` and ``ConcreteDecorator``.
{
return $this->name;
}
}
ConcreteComponent
-----------------
The ``ConcreteComponent`` class is pretty simple and doesn't do much
more than extend the abstract ``Component`` class (only for the
The ``ConcreteComponent`` class is pretty simple and doesn't do much
more than extend the abstract ``Component`` class (only for the
purpose of keeping this example simple).
.. code-block:: php
<?php
namespace Test\Component;
use Doctrine\ORM\Annotation as ORM;
use Test\Component;
/** @ORM\Entity */
/** @Entity */
class ConcreteComponent extends Component
{}
Decorator
---------
The ``Decorator`` class doesn't need to be persisted, but it does
need to define an association with a persisted ``Entity``. We can
The ``Decorator`` class doesn't need to be persisted, but it does
need to define an association with a persisted ``Entity``. We can
use a ``MappedSuperclass`` for this.
.. code-block:: php
@@ -106,19 +102,17 @@ use a ``MappedSuperclass`` for this.
<?php
namespace Test;
use Doctrine\ORM\Annotation as ORM;
/** @ORM\MappedSuperclass */
/** @MappedSuperclass */
abstract class Decorator extends Component
{
/**
* @ORM\OneToOne(targetEntity="Test\Component", cascade={"all"})
* @ORM\JoinColumn(name="decorates", referencedColumnName="id")
* @OneToOne(targetEntity="Test\Component", cascade={"all"})
* @JoinColumn(name="decorates", referencedColumnName="id")
*/
protected $decorates;
/**
* initialize the decorator
* @param Component $c
@@ -127,7 +121,7 @@ use a ``MappedSuperclass`` for this.
{
$this->setDecorates($c);
}
/**
* (non-PHPdoc)
* @see Test.Component::getName()
@@ -136,7 +130,7 @@ use a ``MappedSuperclass`` for this.
{
return 'Decorated ' . $this->getDecorates()->getName();
}
/**
* the component being decorated
* @return Component
@@ -145,7 +139,7 @@ use a ``MappedSuperclass`` for this.
{
return $this->decorates;
}
/**
* sets the component being decorated
* @param Component $c
@@ -154,53 +148,52 @@ use a ``MappedSuperclass`` for this.
{
$this->decorates = $c;
}
}
All operations on the ``Decorator`` (i.e. persist, remove, etc) will
cascade from the ``Decorator`` to the ``Component``. This means that
when we persist a ``Decorator``, Doctrine will take care of
persisting the chain of decorated objects for us. A ``Decorator`` can
be treated exactly as a ``Component`` when it comes time to
All operations on the ``Decorator`` (i.e. persist, remove, etc) will
cascade from the ``Decorator`` to the ``Component``. This means that
when we persist a ``Decorator``, Doctrine will take care of
persisting the chain of decorated objects for us. A ``Decorator`` can
be treated exactly as a ``Component`` when it comes time to
persisting it.
The ``Decorator's`` constructor accepts an instance of a
``Component``, as defined by the ``Decorator`` pattern. The
setDecorates/getDecorates methods have been defined as protected to
hide the fact that a ``Decorator`` is decorating a ``Component`` and
keeps the ``Component`` interface and the ``Decorator`` interface
The ``Decorator's`` constructor accepts an instance of a
``Component``, as defined by the ``Decorator`` pattern. The
setDecorates/getDecorates methods have been defined as protected to
hide the fact that a ``Decorator`` is decorating a ``Component`` and
keeps the ``Component`` interface and the ``Decorator`` interface
identical.
To illustrate the intended result of the ``Decorator`` pattern, the
getName() method has been overridden to append a string to the
To illustrate the intended result of the ``Decorator`` pattern, the
getName() method has been overridden to append a string to the
``Component's`` getName() method.
ConcreteDecorator
-----------------
The final class required to complete a simple implementation of the
Decorator pattern is the ``ConcreteDecorator``. In order to further
illustrate how the ``Decorator`` can alter data as it moves through
the chain of decoration, a new field, "special", has been added to
this class. The getName() has been overridden and appends the value
of the getSpecial() method to its return value.
The final class required to complete a simple implementation of the
Decorator pattern is the ``ConcreteDecorator``. In order to further
illustrate how the ``Decorator`` can alter data as it moves through
the chain of decoration, a new field, "special", has been added to
this class. The getName() has been overridden and appends the value
of the getSpecial() method to its return value.
.. code-block:: php
<?php
namespace Test\Decorator;
use Doctrine\ORM\Annotation as ORM;
use Test\Decorator;
/** @ORM\Entity */
/** @Entity */
class ConcreteDecorator extends Decorator
{
/** @ORM\Column(type="string", nullable=true) */
/** @Column(type="string", nullable=true) */
protected $special;
/**
* Set special
* @param string $special
@@ -209,7 +202,7 @@ of the getSpecial() method to its return value.
{
$this->special = $special;
}
/**
* Get special
* @return string $special
@@ -218,7 +211,7 @@ of the getSpecial() method to its return value.
{
return $this->special;
}
/**
* (non-PHPdoc)
* @see Test.Component::getName()
@@ -226,55 +219,55 @@ of the getSpecial() method to its return value.
public function getName()
{
return '[' . $this->getSpecial()
. '] ' . parent::getName();
. '] ' . parent::getName();
}
}
Examples
--------
Here is an example of how to persist and retrieve your decorated
Here is an example of how to persist and retrieve your decorated
objects
.. code-block:: php
<?php
use Test\Component\ConcreteComponent,
Test\Decorator\ConcreteDecorator;
// assumes Doctrine 2 is configured and an instance of
// an EntityManager is available as $em
// create a new concrete component
$c = new ConcreteComponent();
$c->setName('Test Component 1');
$em->persist($c); // assigned unique ID = 1
// create a new concrete decorator
$c = new ConcreteComponent();
$c->setName('Test Component 2');
$d = new ConcreteDecorator($c);
$d->setSpecial('Really');
$em->persist($d);
$em->persist($d);
// assigns c as unique ID = 2, and d as unique ID = 3
$em->flush();
$c = $em->find('Test\Component', 1);
$d = $em->find('Test\Component', 3);
echo get_class($c);
// prints: Test\Component\ConcreteComponent
echo $c->getName();
// prints: Test Component 1
echo get_class($d)
// prints: Test Component 1
echo get_class($d)
// prints: Test\Component\ConcreteDecorator
echo $d->getName();
// prints: [Really] Decorated Test Component 2
+14 -11
View File
@@ -14,7 +14,7 @@ In Doctrine 1 the DQL language was not implemented using a real
parser. This made modifications of the DQL by the user impossible.
Doctrine 2 in contrast has a real parser for the DQL language,
which transforms the DQL statement into an
`Abstract Syntax Tree <https://en.wikipedia.org/wiki/Abstract_syntax_tree>`_
`Abstract Syntax Tree <http://en.wikipedia.org/wiki/Abstract_syntax_tree>`_
and generates the appropriate SQL statement for it. Since this
process is deterministic Doctrine heavily caches the SQL that is
generated from any given DQL query, which reduces the performance
@@ -28,6 +28,7 @@ generating the SQL statement.
There are two types of custom tree walkers that you can hook into
the DQL parser:
- An output walker. This one actually generates the SQL, and there
is only ever one of them. We implemented the default SqlWalker
implementation for it.
@@ -39,6 +40,7 @@ Now this is all awfully technical, so let me come to some use-cases
fast to keep you motivated. Using walker implementation you can for
example:
- Modify the AST to generate a Count Query to be used with a
paginator for any given DQL query.
- Modify the Output Walker to generate vendor-specific SQL
@@ -86,7 +88,7 @@ API would look for this use-case:
$pageNum = 1;
$query = $em->createQuery($dql);
$query->setFirstResult( ($pageNum-1) * 20)->setMaxResults(20);
$totalResults = Paginate::count($query);
$results = $query->getResult();
@@ -99,18 +101,18 @@ The ``Paginate::count(Query $query)`` looks like:
{
static public function count(Query $query)
{
/** @var Query $countQuery */
/* @var $countQuery Query */
$countQuery = clone $query;
$countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('DoctrineExtensions\Paginate\CountSqlWalker'));
$countQuery->setFirstResult(null)->setMaxResults(null);
return $countQuery->getSingleScalarResult();
}
}
It clones the query, resets the limit clause first and max results
and registers the ``CountSqlWalker`` custom tree walker which
and registers the ``CountSqlWalker`` customer tree walker which
will modify the AST to execute a count query. The walkers
implementation is:
@@ -128,20 +130,20 @@ implementation is:
{
$parent = null;
$parentName = null;
foreach ($this->getQueryComponents() as $dqlAlias => $qComp) {
foreach ($this->_getQueryComponents() AS $dqlAlias => $qComp) {
if ($qComp['parent'] === null && $qComp['nestingLevel'] == 0) {
$parent = $qComp;
$parentName = $dqlAlias;
break;
}
}
$pathExpression = new PathExpression(
PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName,
$parent['metadata']->getSingleIdentifierFieldName()
);
$pathExpression->type = PathExpression::TYPE_STATE_FIELD;
$AST->selectClause->selectExpressions = array(
new SelectExpression(
new AggregateExpression('count', $pathExpression, true), null
@@ -194,7 +196,7 @@ SQL\_NO\_CACHE on those queries that need it:
public function walkSelectClause($selectClause)
{
$sql = parent::walkSelectClause($selectClause);
if ($this->getQuery()->getHint('mysqlWalker.sqlNoCache') === true) {
if ($selectClause->isDistinct) {
$sql = str_replace('SELECT DISTINCT', 'SELECT DISTINCT SQL_NO_CACHE', $sql);
@@ -202,7 +204,7 @@ SQL\_NO\_CACHE on those queries that need it:
$sql = str_replace('SELECT', 'SELECT SQL_NO_CACHE', $sql);
}
}
return $sql;
}
}
@@ -212,3 +214,4 @@ understanding of the DQL Parser and Walkers, but may offer your
huge benefits with using vendor specific features. This would still
allow you write DQL queries instead of NativeQueries to make use of
vendor specific features.
+20 -28
View File
@@ -17,10 +17,11 @@ and gain access to vendor specific functionalities using the
``EntityManager#createNativeQuery()`` API as described in
the :doc:`Native Query <../reference/native-sql>` chapter.
The DQL Parser has hooks to register functions that can then be
used in your DQL queries and transformed into SQL, allowing to
extend Doctrines Query capabilities to the vendors strength. This
post explains the User-Defined Functions API (UDF) of the Dql
post explains the Used-Defined Functions API (UDF) of the Dql
Parser and shows some examples to give you some hints how you would
extend DQL.
@@ -44,7 +45,7 @@ configuration:
$config->addCustomStringFunction($name, $class);
$config->addCustomNumericFunction($name, $class);
$config->addCustomDatetimeFunction($name, $class);
$em = EntityManager::create($dbParams, $config);
The ``$name`` is the name the function will be referred to in the
@@ -52,24 +53,13 @@ DQL query. ``$class`` is a string of a class-name which has to
extend ``Doctrine\ORM\Query\Node\FunctionNode``. This is a class
that offers all the necessary API and methods to implement a UDF.
Instead of providing the function class name, you can also provide
a callable that returns the function object:
.. code-block:: php
<?php
$config = new \Doctrine\ORM\Configuration();
$config->addCustomStringFunction($name, function () {
return new MyCustomFunction();
});
In this post we will implement some MySql specific Date calculation
methods, which are quite handy in my opinion:
Date Diff
---------
`Mysql's DateDiff function <https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_datediff>`_
`Mysql's DateDiff function <http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_datediff>`_
takes two dates as argument and calculates the difference in days
with ``date1-date2``.
@@ -95,7 +85,7 @@ discuss it step by step:
// (1)
public $firstDateExpression = null;
public $secondDateExpression = null;
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER); // (2)
@@ -105,7 +95,7 @@ discuss it step by step:
$this->secondDateExpression = $parser->ArithmeticPrimary(); // (6)
$parser->match(Lexer::T_CLOSE_PARENTHESIS); // (3)
}
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
return 'DATEDIFF(' .
@@ -131,7 +121,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
@@ -163,7 +153,7 @@ Date Add
Often useful it the ability to do some simple date calculations in
your DQL query using
`MySql's DATE_ADD function <https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_date-add>`_.
`MySql's DATE\_ADD function <http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date-add>`_.
I'll skip the blah and show the code for this function:
@@ -179,28 +169,28 @@ I'll skip the blah and show the code for this function:
public $firstDateExpression = null;
public $intervalExpression = null;
public $unit = null;
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->firstDateExpression = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_COMMA);
$parser->match(Lexer::T_IDENTIFIER);
$this->intervalExpression = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_IDENTIFIER);
/** @var Lexer $lexer */
/* @var $lexer Lexer */
$lexer = $parser->getLexer();
$this->unit = $lexer->token['value'];
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
return 'DATE_ADD(' .
@@ -245,4 +235,6 @@ vendor sql functions and extend the DQL languages scope.
Code for this Extension to DQL and other Doctrine Extensions can be
found
`in the GitHub DoctrineExtensions repository <https://github.com/beberlei/DoctrineExtensions>`_.
`in my Github DoctrineExtensions repository <http://github.com/beberlei/DoctrineExtensions>`_.
+38 -25
View File
@@ -12,14 +12,12 @@ this working.
Merging entity into an EntityManager
------------------------------------
In Doctrine, an entity objects has to be "managed" by an EntityManager to be
updated. Entities saved into the session are not managed in the next request
In Doctrine an entity objects has to be "managed" by an EntityManager to be
updateable. Entities saved into the session are not managed in the next request
anymore. This means that you have to register these entities with an
EntityManager again if you want to change them or use them as part of
references between other entities.
It is a good idea to avoid storing entities in serialized formats such as
``$_SESSION``: instead, store the entity identifiers or raw data.
references between other entities. You can achieve this by calling
``EntityManager#merge()``.
For a representative User object the code to get turn an instance from
the session into a managed Doctrine object looks like this:
@@ -28,28 +26,43 @@ the session into a managed Doctrine object looks like this:
<?php
require_once 'bootstrap.php';
$em = GetEntityManager(); // creates an EntityManager
$em = GetEntityManager(); // creates an EntityManager
session_start();
if (isset($_SESSION['user'])) {
$user = $em->find(User::class, $_SESSION['user']);
if (! $user instanceof User) {
// user not found in the database
$_SESSION['user'] = null;
}
if (isset($_SESSION['user']) && $_SESSION['user'] instanceof User) {
$user = $_SESSION['user'];
$user = $em->merge($user);
}
Serializing entities into the session
-------------------------------------
.. note::
Serializing entities in the session means serializing also all associated
entities and collections. While this might look like a quick solution in
simple applications, you will encounter problems due to the fact that the
data in the session is stale.
A frequent mistake is not to get the merged user object from the return
value of ``EntityManager#merge()``. The entity object passed to merge is
not necessarily the same object that is returned from the method.
Serializing entity into the session
-----------------------------------
Entities that are serialized into the session normally contain references to
other entities as well. Think of the user entity has a reference to his
articles, groups, photos or many other different entities. If you serialize
this object into the session then you don't want to serialize the related
entities as well. This is why you should call ``EntityManager#detach()`` on this
object or implement the __sleep() magic method on your entity.
.. code-block:: php
<?php
require_once 'bootstrap.php';
$em = GetEntityManager(); // creates an EntityManager
$user = $em->find("User", 1);
$em->detach($user);
$_SESSION['user'] = $user;
.. note::
When you called detach on your objects they get "unmanaged" with that
entity manager. This means you cannot use them as part of write operations
during ``EntityManager#flush()`` anymore in this request.
In order to prevent working with stale data, try saving only minimal
information about your entities in your session, without storing entire
entity objects. Should you need the full information of an object, so it
is suggested to re-query the database, which is usually the most
authoritative source of information in typical PHP applications.
@@ -6,7 +6,7 @@ Implementing ArrayAccess for Domain Objects
This recipe will show you how to implement ArrayAccess for your
domain objects in order to allow more uniform access, for example
in templates. In these examples we will implement ArrayAccess on a
`Layer Supertype <https://martinfowler.com/eaaCatalog/layerSupertype.html>`_
`Layer Supertype <http://martinfowler.com/eaaCatalog/layerSupertype.html>`_
for all our domain objects.
Option 1
@@ -16,6 +16,7 @@ In this implementation we will make use of PHPs highly dynamic
nature to dynamically access properties of a subtype in a supertype
at runtime. Note that this implementation has 2 main caveats:
- It will not work with private fields
- It will not go through any getters/setters
@@ -27,15 +28,15 @@ at runtime. Note that this implementation has 2 main caveats:
public function offsetExists($offset) {
return isset($this->$offset);
}
public function offsetSet($offset, $value) {
$this->$offset = $value;
}
public function offsetGet($offset) {
return $this->$offset;
}
public function offsetUnset($offset) {
$this->$offset = null;
}
@@ -49,6 +50,7 @@ Again we use PHPs dynamic nature to invoke methods on a subtype
from a supertype at runtime. This implementation has the following
caveats:
- It relies on a naming convention
- The semantics of offsetExists can differ
- offsetUnset will not work with typehinted setters
@@ -63,15 +65,15 @@ caveats:
$value = $this->{"get$offset"}();
return $value !== null;
}
public function offsetSet($offset, $value) {
$this->{"set$offset"}($value);
}
public function offsetGet($offset) {
return $this->{"get$offset"}();
}
public function offsetUnset($offset) {
$this->{"set$offset"}(null);
}
@@ -93,17 +95,18 @@ exception (i.e. BadMethodCallException).
public function offsetExists($offset) {
// option 1 or option 2
}
public function offsetSet($offset, $value) {
throw new BadMethodCallException("Array access of class " . get_class($this) . " is read-only!");
}
public function offsetGet($offset) {
// option 1 or option 2
}
public function offsetUnset($offset) {
throw new BadMethodCallException("Array access of class " . get_class($this) . " is read-only!");
}
}
@@ -7,7 +7,7 @@ The NOTIFY change-tracking policy is the most effective
change-tracking policy provided by Doctrine but it requires some
boilerplate code. This recipe will show you how this boilerplate
code should look like. We will implement it on a
`Layer Supertype <https://martinfowler.com/eaaCatalog/layerSupertype.html>`_
`Layer Supertype <http://martinfowler.com/eaaCatalog/layerSupertype.html>`_
for all our domain objects.
Implementing NotifyPropertyChanged
@@ -24,15 +24,15 @@ implement the ``NotifyPropertyChanged`` interface from the
<?php
use Doctrine\Common\NotifyPropertyChanged;
use Doctrine\Common\PropertyChangedListener;
abstract class DomainObject implements NotifyPropertyChanged
{
private $listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener) {
$this->listeners[] = $listener;
}
/** Notifies listeners of a change. */
protected function onPropertyChanged($propName, $oldValue, $newValue) {
if ($this->listeners) {
@@ -50,12 +50,12 @@ listeners:
.. code-block:: php
<?php
// Mapping not shown, either in annotations or xml as usual
// Mapping not shown, either in annotations, xml or yaml as usual
class MyEntity extends DomainObject
{
private $data;
// ... other fields as usual
public function setData($data) {
if ($data != $this->data) { // check: is it actually modified?
$this->onPropertyChanged('data', $this->data, $data);
@@ -69,3 +69,4 @@ not mandatory but recommended. That way you can avoid unnecessary
updates and also have full control over when you consider a
property changed.
@@ -0,0 +1,78 @@
Implementing Wakeup or Clone
============================
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
As explained in the
`restrictions for entity classes in the manual <http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities>`_,
it is usually not allowed for an entity to implement ``__wakeup``
or ``__clone``, because Doctrine makes special use of them.
However, it is quite easy to make use of these methods in a safe
way by guarding the custom wakeup or clone code with an entity
identity check, as demonstrated in the following sections.
Safely implementing \_\_wakeup
------------------------------
To safely implement ``__wakeup``, simply enclose your
implementation code in an identity check as follows:
.. code-block:: php
<?php
class MyEntity
{
private $id; // This is the identifier of the entity.
//...
public function __wakeup()
{
// If the entity has an identity, proceed as normal.
if ($this->id) {
// ... Your code here as normal ...
}
// otherwise do nothing, do NOT throw an exception!
}
//...
}
Safely implementing \_\_clone
-----------------------------
Safely implementing ``__clone`` is pretty much the same:
.. code-block:: php
<?php
class MyEntity
{
private $id; // This is the identifier of the entity.
//...
public function __clone()
{
// If the entity has an identity, proceed as normal.
if ($this->id) {
// ... Your code here as normal ...
}
// otherwise do nothing, do NOT throw an exception!
}
//...
}
Summary
-------
As you have seen, it is quite easy to safely make use of
``__wakeup`` and ``__clone`` in your entities without adding any
really Doctrine-specific or Doctrine-dependant code.
These implementations are possible and safe because when Doctrine
invokes these methods, the entities never have an identity (yet).
Furthermore, it is possibly a good idea to check for the identity
in your code anyway, since it's rarely the case that you want to
unserialize or clone an entity with no identity.
@@ -0,0 +1,140 @@
Integrating with CodeIgniter
============================
This is recipe for using Doctrine 2 in your
`CodeIgniter <http://www.codeigniter.com>`_ framework.
.. note::
This might not work for all CodeIgniter versions and may require
slight adjustments.
Here is how to set it up:
Make a CodeIgniter library that is both a wrapper and a bootstrap
for Doctrine 2.
Setting up the file structure
-----------------------------
Here are the steps:
- Add a php file to your system/application/libraries folder
called Doctrine.php. This is going to be your wrapper/bootstrap for
the D2 entity manager.
- Put the Doctrine folder (the one that contains Common, DBAL, and
ORM) inside that same libraries folder.
- Your system/application/libraries folder now looks like this:
system/applications/libraries -Doctrine -Doctrine.php -index.html
- If you want, open your config/autoload.php file and autoload
your Doctrine library.
<?php $autoload['libraries'] = array('doctrine');
Creating your Doctrine CodeIgniter library
------------------------------------------
Now, here is what your Doctrine.php file should look like.
Customize it to your needs.
.. code-block:: php
<?php
use Doctrine\Common\ClassLoader,
Doctrine\ORM\Configuration,
Doctrine\ORM\EntityManager,
Doctrine\Common\Cache\ArrayCache,
Doctrine\DBAL\Logging\EchoSQLLogger;
class Doctrine {
public $em = null;
public function __construct()
{
// load database configuration from CodeIgniter
require_once APPPATH.'config/database.php';
// Set up class loading. You could use different autoloaders, provided by your favorite framework,
// if you want to.
require_once APPPATH.'libraries/Doctrine/Common/ClassLoader.php';
$doctrineClassLoader = new ClassLoader('Doctrine', APPPATH.'libraries');
$doctrineClassLoader->register();
$entitiesClassLoader = new ClassLoader('models', rtrim(APPPATH, "/" ));
$entitiesClassLoader->register();
$proxiesClassLoader = new ClassLoader('Proxies', APPPATH.'models/proxies');
$proxiesClassLoader->register();
// Set up caches
$config = new Configuration;
$cache = new ArrayCache;
$config->setMetadataCacheImpl($cache);
$driverImpl = $config->newDefaultAnnotationDriver(array(APPPATH.'models/Entities'));
$config->setMetadataDriverImpl($driverImpl);
$config->setQueryCacheImpl($cache);
$config->setQueryCacheImpl($cache);
// Proxy configuration
$config->setProxyDir(APPPATH.'/models/proxies');
$config->setProxyNamespace('Proxies');
// Set up logger
$logger = new EchoSQLLogger;
$config->setSQLLogger($logger);
$config->setAutoGenerateProxyClasses( TRUE );
// Database connection information
$connectionOptions = array(
'driver' => 'pdo_mysql',
'user' => $db['default']['username'],
'password' => $db['default']['password'],
'host' => $db['default']['hostname'],
'dbname' => $db['default']['database']
);
// Create EntityManager
$this->em = EntityManager::create($connectionOptions, $config);
}
}
Please note that this is a development configuration; for a
production system you'll want to use a real caching system like
APC, get rid of EchoSqlLogger, and turn off
autoGenerateProxyClasses.
For more details, consult the
`Doctrine 2 Configuration documentation <http://www.doctrine-project.org/documentation/manual/2_0/en/configuration#configuration-options>`_.
Now to use it
-------------
Whenever you need a reference to the entity manager inside one of
your controllers, views, or models you can do this:
.. code-block:: php
<?php
$em = $this->doctrine->em;
That's all there is to it. Once you get the reference to your
EntityManager do your Doctrine 2.0 voodoo as normal.
Note: If you do not choose to autoload the Doctrine library, you
will need to put this line before you get a reference to it:
.. code-block:: php
<?php
$this->load->library('doctrine');
Good luck!
+10 -29
View File
@@ -43,16 +43,13 @@ entities:
.. code-block:: php
<?php
use Doctrine\ORM\Annotation as ORM;
/** @ORM\Entity */
/** @Entity */
class Article
{
const STATUS_VISIBLE = 'visible';
const STATUS_INVISIBLE = 'invisible';
/** @ORM\Column(type="string") */
/** @Column(type="string") */
private $status;
public function setStatus($status)
@@ -70,13 +67,10 @@ the **columnDefinition** attribute.
.. code-block:: php
<?php
use Doctrine\ORM\Annotation as ORM;
/** @ORM\Entity */
/** @Entity */
class Article
{
/** @ORM\Column(type="string", columnDefinition="ENUM('visible', 'invisible')") */
/** @Column(type="string", columnDefinition="ENUM('visible', 'invisible')") */
private $status;
}
@@ -102,9 +96,9 @@ For example for the previous enum type:
const STATUS_VISIBLE = 'visible';
const STATUS_INVISIBLE = 'invisible';
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
return "ENUM('visible', 'invisible')";
return "ENUM('visible', 'invisible') COMMENT '(DC2Type:enumvisibility)'";
}
public function convertToPHPValue($value, AbstractPlatform $platform)
@@ -124,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');``.
@@ -137,13 +126,10 @@ Then in your entity you can just use this type:
.. code-block:: php
<?php
use Doctrine\ORM\Annotation as ORM;
/** @ORM\Entity */
/** @Entity */
class Article
{
/** @ORM\Column(type="enumvisibility") */
/** @Column(type="enumvisibility") */
private $status;
}
@@ -162,11 +148,11 @@ You can generalize this approach easily to create a base class for enums:
protected $name;
protected $values = array();
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
$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)
@@ -186,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:
@@ -3,7 +3,7 @@ Keeping your Modules independent
.. versionadded:: 2.2
One of the goals of using modules is to create discrete units of functionality
One of the goals of using modules is to create discreet units of functionality
that do not have many (if any) dependencies, allowing you to use that
functionality in other applications without including unnecessary items.
@@ -40,7 +40,6 @@ A Customer entity
.. code-block:: php
<?php
// src/Acme/AppModule/Entity/Customer.php
namespace Acme\AppModule\Entity;
@@ -63,12 +62,11 @@ An Invoice entity
.. code-block:: php
<?php
// src/Acme/InvoiceModule/Entity/Invoice.php
namespace Acme\InvoiceModule\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping AS ORM;
use Acme\InvoiceModule\Model\InvoiceSubjectInterface;
/**
@@ -90,7 +88,6 @@ An InvoiceSubjectInterface
.. code-block:: php
<?php
// src/Acme/InvoiceModule/Model/InvoiceSubjectInterface.php
namespace Acme\InvoiceModule\Model;
@@ -119,15 +116,13 @@ the targetEntity resolution will occur reliably:
.. code-block:: php
<?php
$evm = new \Doctrine\Common\EventManager;
$rtel = new \Doctrine\ORM\Tools\ResolveTargetEntityListener;
$evm = new \Doctrine\Common\EventManager;
// Adds a target-entity class
$rtel = new \Doctrine\ORM\Tools\ResolveTargetEntityListener;
$rtel->addResolveTargetEntity('Acme\\InvoiceModule\\Model\\InvoiceSubjectInterface', 'Acme\\CustomerModule\\Entity\\Customer', array());
// Add the ResolveTargetEntityListener
$evm->addEventListener(Doctrine\ORM\Events::loadClassMetadata, $rtel);
$evm->addEventSubscriber($rtel);
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);
@@ -139,3 +134,4 @@ bundles, keeping them usable by themselves, but still being able to
define relationships between different objects. By using this method,
I've found my bundles end up being easier to maintain independently.
+15 -20
View File
@@ -23,33 +23,26 @@ appropriate autoloaders.
.. code-block:: php
<?php
namespace DoctrineExtensions;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
use Doctrine\ORM\Mapping;
use \Doctrine\ORM\Event\LoadClassMetadataEventArgs;
class TablePrefix
{
protected $prefix = '';
public function __construct($prefix)
{
$this->prefix = (string) $prefix;
}
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
{
$classMetadata = $eventArgs->getClassMetadata();
if ($classMetadata->inheritanceType !== Mapping\InheritanceType::SINGLE_TABLE ||
$classMetadata->getName() === $classMetadata->rootEntityName) {
$classMetadata->setTableName($this->prefix . $classMetadata->getTableName());
}
foreach ($classMetadata->associationMappings as $fieldName => $mapping) {
if ($mapping['type'] == Mapping\ClassMetadata::MANY_TO_MANY && $mapping['isOwningSide']) {
$mappedTableName = $mapping['joinTable']['name'];
$classMetadata->setTableName($this->prefix . $classMetadata->getTableName());
foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) {
if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY) {
$mappedTableName = $classMetadata->associationMappings[$fieldName]['joinTable']['name'];
$classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix . $mappedTableName;
}
}
@@ -69,17 +62,19 @@ before the prefix has been set.
If you set this listener up, be aware that you will need
to clear your caches and drop then recreate your database schema.
.. code-block:: php
<?php
// $connectionOptions and $config set earlier
$evm = new \Doctrine\Common\EventManager;
// Table Prefix
$tablePrefix = new \DoctrineExtensions\TablePrefix('prefix_');
$evm->addEventListener(\Doctrine\ORM\Events::loadClassMetadata, $tablePrefix);
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);
@@ -3,7 +3,7 @@ Strategy-Pattern
This recipe will give you a short introduction on how to design
similar entities without using expensive (i.e. slow) inheritance
but with not more than *the well-known strategy pattern* event
but with not more than \* the well-known strategy pattern \* event
listeners
Scenario / Problem
@@ -12,6 +12,7 @@ Scenario / Problem
Given a Content-Management-System, we probably want to add / edit
some so-called "blocks" and "panels". What are they for?
- A block might be a registration form, some text content, a table
with information. A good example might also be a small calendar.
- A panel is by definition a block that can itself contain blocks.
@@ -22,6 +23,7 @@ So, in this scenario, when building your CMS, you will surely add
lots of blocks and panels to your pages and you will find yourself
highly uncomfortable because of the following:
- Every existing page needs to know about the panels it contains -
therefore, you'll have an association to your panels. But if you've
got several types of panels - what do you do? Add an association to
@@ -56,6 +58,7 @@ the middle of your page, for example).
Such an interface could look like this:
.. code-block:: php
<?php
@@ -84,12 +87,12 @@ Such an interface could look like this:
* @return \Zend_View_Helper_Interface
*/
public function setView(\Zend_View_Interface $view);
/**
* @return \Zend_View_Interface
*/
public function getView();
/**
* Renders this strategy. This method will be called when the user
* displays the site.
@@ -97,7 +100,7 @@ Such an interface could look like this:
* @return string
*/
public function renderFrontend();
/**
* Renders the backend of this block. This method will be called when
* a user tries to reconfigure this block instance.
@@ -115,21 +118,21 @@ Such an interface could look like this:
* @return array
*/
public function getRequiredPanelTypes();
/**
* Determines whether a Block is able to use a given type or not
* @param string $typeName The typename
* @return boolean
*/
public function canUsePanelType($typeName);
public function setBlockEntity(AbstractBlock $block);
public function getBlockEntity();
}
As you can see, we have a method "setBlockEntity" which ties a potential strategy to an object of type AbstractBlock. This type will simply define the basic behaviour of our blocks and could potentially look something like this:
.. code-block:: php
<?php
@@ -152,7 +155,7 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg
* that is used for this blockitem. (This string (!) value will be persisted by Doctrine 2)
*
* This is a doctrine field, so make sure that you use an @column annotation or setup your
* xml files correctly
* yaml or xml files correctly
* @var string
*/
protected $strategyClassName;
@@ -174,7 +177,7 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg
public function getStrategyClassName() {
return $this->strategyClassName;
}
/**
* Returns the instantiated strategy
*
@@ -183,7 +186,7 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg
public function getStrategyInstance() {
return $this->strategyInstance;
}
/**
* Sets the strategy this block / panel should work as. Make sure that you've used
* this method before persisting the block!
@@ -210,28 +213,28 @@ This might look like this:
.. code-block:: php
<?php
use Doctrine\ORM,
Doctrine\Common;
use \Doctrine\ORM,
\Doctrine\Common;
/**
* The BlockStrategyEventListener will initialize a strategy after the
* block itself was loaded.
*/
class BlockStrategyEventListener implements Common\EventSubscriber {
protected $view;
public function __construct(\Zend_View_Interface $view) {
$this->view = $view;
}
public function getSubscribedEvents() {
return array(ORM\Events::postLoad);
}
public function postLoad(ORM\Event\LifecycleEventArgs $args) {
$blockItem = $args->getEntity();
// Both blocks and panels are instances of Block\AbstractBlock
if ($blockItem instanceof Block\AbstractBlock) {
$strategy = $blockItem->getStrategyClassName();
@@ -248,3 +251,4 @@ This might look like this:
In this example, even some variables are set - like a view object
or a specific configuration object.
+17 -19
View File
@@ -15,6 +15,7 @@ What we offer are hooks to execute any kind of validation.
perform validations in value setters or any other method of your
entities that are used in your code.
Entities can register lifecycle event methods with Doctrine that
are called on different occasions. For validation we would need to
hook into the events called before persisting and updating. Even
@@ -35,12 +36,12 @@ is allowed to:
public function assertCustomerAllowedBuying()
{
$orderLimit = $this->customer->getOrderLimit();
$amount = 0;
foreach ($this->orderLines as $line) {
foreach ($this->orderLines AS $line) {
$amount += $line->getAmount();
}
if ($amount > $orderLimit) {
throw new CustomerOrderLimitExceededException();
}
@@ -57,17 +58,14 @@ First Annotations:
.. code-block:: php
<?php
use Doctrine\ORM\Annotation as ORM;
/**
* @ORM\Entity
* @ORM\HasLifecycleCallbacks
* @Entity
* @HasLifecycleCallbacks
*/
class Order
{
/**
* @ORM\PrePersist @ORM\PreUpdate
* @PrePersist @PreUpdate
*/
public function assertCustomerAllowedBuying() {}
}
@@ -79,16 +77,19 @@ In XML Mappings:
<doctrine-mapping>
<entity name="Order">
<lifecycle-callbacks>
<lifecycle-callback type="prePersist" method="assertCustomerAllowedBuying" />
<lifecycle-callback type="preUpdate" method="assertCustomerAllowedBuying" />
<lifecycle-callback type="prePersist" method="assertCustomerallowedBuying" />
<lifecycle-callback type="preUpdate" method="assertCustomerallowedBuying" />
</lifecycle-callbacks>
</entity>
</doctrine-mapping>
YAML needs some little change yet, to allow multiple lifecycle
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,
@@ -98,24 +99,21 @@ validation callbacks.
.. code-block:: php
<?php
use Doctrine\ORM\Annotation as ORM;
class Order
{
/**
* @ORM\PrePersist @ORM\PreUpdate
* @PrePersist @PreUpdate
*/
public function validate()
{
if (!($this->plannedShipDate instanceof DateTime)) {
throw new ValidateException();
}
if ($this->plannedShipDate->format('U') < time()) {
throw new ValidateException();
}
if ($this->customer == null) {
throw new OrderRequiresCustomerException();
}
@@ -136,4 +134,4 @@ instances. This was already discussed in the previous blog post on
the Versionable extension, which requires another type of event
called "onFlush".
Further readings: :ref:`reference-events-lifecycle-events`
Further readings: :doc:`Lifecycle Events <../reference/events>`
+24 -55
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.
@@ -15,13 +15,10 @@ these comparisons are always made **BY REFERENCE**. That means the following cha
.. code-block:: php
<?php
use Doctrine\ORM\Annotation as ORM;
/** @ORM\Entity */
/** @Entity */
class Article
{
/** @ORM\Column(type="datetime") */
/** @Column(type="datetime") */
private $updated;
public function setUpdated()
@@ -52,19 +49,18 @@ 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
in Databases <https://derickrethans.nl/storing-date-time-in-database.html>`_.
in Databases <http://derickrethans.nl/storing-date-time-in-database.html>`_.
The problem is simple. Not a single database vendor saves the timezone, only the differences to UTC.
However with frequent daylight saving and political timezone changes you can have a UTC offset that moves
@@ -89,87 +85,60 @@ 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::getUtc()
(self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone('UTC'))
);
if (! $converted) {
throw ConversionException::conversionFailedFormat(
$value,
$this->getName(),
$platform->getDateTimeFormatString()
);
if (!$val) {
throw ConversionException::conversionFailed($value, $this->getName());
}
return $converted;
}
private static function getUtc()
{
return self::$utc ? self::$utc : self::$utc = new \DateTimeZone('UTC');
return $val;
}
}
This database type makes sure that every DateTime instance is always saved in UTC, relative
to the current timezone that the passed DateTime instance has.
To actually use this new type instead of the default ``datetime`` type, you need to run following
code before bootstrapping the ORM:
.. code-block:: php
<?php
use Doctrine\DBAL\Types\Type;
use DoctrineExtensions\DBAL\Types\UTCDateTimeType;
Type::overrideType('datetime', UTCDateTimeType::class);
Type::overrideType('datetimetz', UTCDateTimeType::class);
To be able to transform these values
to the current timezone that the passed DateTime instance has. To be able to transform these values
back into their real timezone you have to save the timezone in a separate field of the entity
requiring timezoned datetimes:
.. code-block:: php
<?php
namespace Shipping;
use Doctrine\ORM\Annotation as ORM;
/**
* @ORM\Entity
* @Entity
*/
class Event
{
/** @ORM\Column(type="datetime") */
/** @Column(type="datetime") */
private $created;
/** @ORM\Column(type="string") */
/** @Column(type="string") */
private $timezone;
/**
+108 -10
View File
@@ -1,8 +1,10 @@
ORM Documentation
=================
Welcome to Doctrine 2 ORM's documentation!
==========================================
The Doctrine ORM documentation is comprised of tutorials, a reference section and
cookbook articles that explain different parts of the Object Relational Mapper.
The Doctrine documentation is comprised of tutorials, a reference section and
cookbook articles that explain different parts of the Object Relational mapper.
Doctrine DBAL and Doctrine Common both have their own documentation.
Getting Help
------------
@@ -11,14 +13,110 @@ If this documentation is not helping to answer questions you have about
Doctrine ORM don't panic. You can get help from different sources:
- There is a :doc:`FAQ <reference/faq>` with answers to frequent questions.
- The `Doctrine Mailing List <https://groups.google.com/group/doctrine-user>`_
- Slack chat room `#orm <https://www.doctrine-project.org/slack>`_
- Report a bug on `GitHub <https://github.com/doctrine/orm/issues>`_.
- The `Doctrine Mailing List <http://groups.google.com/group/doctrine-user>`_
- Internet Relay Chat (IRC) in `#doctrine on Freenode <irc://irc.freenode.net/doctrine>`_
- Report a bug on `JIRA <http://www.doctrine-project.org/jira>`_.
- On `Twitter <https://twitter.com/search/%23doctrine2>`_ with ``#doctrine2``
- On `StackOverflow <https://stackoverflow.com/questions/tagged/doctrine-orm>`_
- On `StackOverflow <http://stackoverflow.com/questions/tagged/doctrine2>`_
If you need more structure over the different topics you can browse the :doc:`table
of contents <toc>`.
Getting Started
---------------
The best way to get started is with the :doc:`Getting Started with Doctrine <tutorials/getting-started>` tutorial.
Use the sidebar to browse other tutorials and documentation for the Doctrine PHP ORM.
* **Tutorial**:
:doc:`Getting Started with Doctrine <tutorials/getting-started>`
* **Setup**:
:doc:`Installation & Configuration <reference/configuration>`
Mapping Objects onto a Database
-------------------------------
* **Mapping**:
:doc:`Objects <reference/basic-mapping>` |
:doc:`Associations <reference/association-mapping>` |
:doc:`Inheritance <reference/inheritance-mapping>`
* **Drivers**:
:doc:`Docblock Annotations <reference/annotations-reference>` |
:doc:`XML <reference/xml-mapping>` |
:doc:`YAML <reference/yaml-mapping>` |
:doc:`PHP <reference/php-mapping>`
Working with Objects
--------------------
* **Basic Reference**:
:doc:`Entities <reference/working-with-objects>` |
:doc:`Associations <reference/working-with-associations>` |
:doc:`Events <reference/events>`
* **Query Reference**:
:doc:`DQL <reference/dql-doctrine-query-language>` |
:doc:`QueryBuilder <reference/query-builder>` |
:doc:`Native SQL <reference/native-sql>`
* **Internals**:
:doc:`Internals explained <reference/unitofwork>` |
:doc:`Associations <reference/unitofwork-associations>`
Advanced Topics
---------------
* :doc:`Architecture <reference/architecture>`
* :doc:`Advanced Configuration <reference/advanced-configuration>`
* :doc:`Limitations and knowns issues <reference/limitations-and-known-issues>`
* :doc:`Commandline Tools <reference/tools>`
* :doc:`Transactions and Concurrency <reference/transactions-and-concurrency>`
* :doc:`Filters <reference/filters>`
* :doc:`NamingStrategy <reference/namingstrategy>`
* :doc:`Improving Performance <reference/improving-performance>`
* :doc:`Caching <reference/caching>`
* :doc:`Partial Objects <reference/partial-objects>`
* :doc:`Change Tracking Policies <reference/change-tracking-policies>`
* :doc:`Best Practices <reference/best-practices>`
* :doc:`Metadata Drivers <reference/metadata-drivers>`
Tutorials
---------
* :doc:`Indexed associations <tutorials/working-with-indexed-associations>`
* :doc:`Extra Lazy Associations <tutorials/extra-lazy-associations>`
* :doc:`Composite Primary Keys <tutorials/composite-primary-keys>`
* :doc:`Ordered associations <tutorials/ordered-associations>`
* :doc:`Pagination <tutorials/pagination>`
* :doc:`Override Field/Association Mappings In Subclasses <tutorials/override-field-association-mappings-in-subclasses>`
Cookbook
--------
* **Patterns**:
:doc:`Aggregate Fields <cookbook/aggregate-fields>` |
:doc:`Decorator Pattern <cookbook/decorator-pattern>` |
:doc:`Strategy Pattern <cookbook/strategy-cookbook-introduction>`
* **DQL Extension Points**:
:doc:`DQL Custom Walkers <cookbook/dql-custom-walkers>` |
:doc:`DQL User-Defined-Functions <cookbook/dql-user-defined-functions>`
* **Implementation**:
:doc:`Array Access <cookbook/implementing-arrayaccess-for-domain-objects>` |
:doc:`Notify ChangeTracking Example <cookbook/implementing-the-notify-changetracking-policy>` |
:doc:`Using Wakeup Or Clone <cookbook/implementing-wakeup-or-clone>` |
:doc:`Working with DateTime <cookbook/working-with-datetime>` |
:doc:`Validation <cookbook/validation-of-entities>` |
:doc:`Entities in the Session <cookbook/entities-in-session>` |
:doc:`Keeping your Modules independent <cookbook/resolve-target-entity-listener>`
* **Integration into Frameworks/Libraries**
:doc:`CodeIgniter <cookbook/integrating-with-codeigniter>`
* **Hidden Gems**
:doc:`Prefixing Table Name <cookbook/sql-table-prefixes>`
* **Custom Datatypes**
:doc:`MySQL Enums <cookbook/mysql-enums>`
:doc:`Advanced Field Value Conversion <cookbook/advanced-field-value-conversion-using-custom-mapping-types>`
+113
View File
@@ -0,0 +1,113 @@
@ECHO OFF
REM Command file for Sphinx documentation
set SPHINXBUILD=sphinx-build
set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^<target^>` where ^<target^> is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. changes to make an overview over all changed/added/deprecated items
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Doctrine2ORM.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Doctrine2ORM.ghc
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
:end
+119 -126
View File
@@ -9,18 +9,17 @@ steps of configuration.
.. code-block:: php
<?php
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Configuration;
use Doctrine\Common\Proxy\ProxyFactory;
use Doctrine\ORM\EntityManager,
Doctrine\ORM\Configuration;
// ...
if ($applicationMode == "development") {
$cache = new \Doctrine\Common\Cache\ArrayCache;
} else {
$cache = new \Doctrine\Common\Cache\ApcuCache;
$cache = new \Doctrine\Common\Cache\ApcCache;
}
$config = new Configuration;
$config->setMetadataCacheImpl($cache);
$driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
@@ -28,17 +27,18 @@ steps of configuration.
$config->setQueryCacheImpl($cache);
$config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies');
$config->setProxyNamespace('MyProject\Proxies');
$config->setAutoGenerateProxyClasses($applicationMode === 'development')
if ('development' === $applicationMode) {
$config->setAutoGenerateProxyClasses(ProxyFactory::AUTOGENERATE_EVAL);
if ($applicationMode == "development") {
$config->setAutoGenerateProxyClasses(true);
} else {
$config->setAutoGenerateProxyClasses(false);
}
$connectionOptions = [
$connectionOptions = array(
'driver' => 'pdo_sqlite',
'path' => 'database.sqlite'
];
);
$em = EntityManager::create($connectionOptions, $config);
.. note::
@@ -50,8 +50,9 @@ steps of configuration.
conversions with the query cache. These 2 caches require only an
absolute minimum of memory yet they heavily improve the runtime
performance of Doctrine. The recommended cache driver to use with
Doctrine is `APCu <https://php.net/apcu>`_. APCu provides you with
a very fast in-memory cache storage that you can use for the metadata and
Doctrine is `APC <http://www.php.net/apc>`_. APC provides you with
an opcode-cache (which is highly recommended anyway) and a very
fast in-memory cache storage that you can use for the metadata and
query caches as seen in the previous code snippet.
Configuration Options
@@ -60,32 +61,30 @@ Configuration Options
The following sections describe all the configuration options
available on a ``Doctrine\ORM\Configuration`` instance.
Proxy Directory
~~~~~~~~~~~~~~~
Proxy Directory (***REQUIRED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?php
$config->setProxyDir($dir);
$config->getProxyDir();
Sets the directory where Doctrine generates any proxy
Gets or sets the directory where Doctrine generates any proxy
classes. For a detailed explanation on proxy classes and how they
are used in Doctrine, refer to the "Proxy Objects" section further
down.
Setting the proxy target directory will also implicitly cause a
call to ``Doctrine\ORM\Configuration#setAutoGenerateProxyClasses()``
with a value of ``Doctrine\Common\Proxy\ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``.
Proxy Namespace
~~~~~~~~~~~~~~~
Proxy Namespace (***REQUIRED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?php
$config->setProxyNamespace($namespace);
$config->getProxyNamespace();
Sets the namespace to use for generated proxy classes. For
Gets or sets the namespace to use for generated proxy classes. For
a detailed explanation on proxy classes and how they are used in
Doctrine, refer to the "Proxy Objects" section further down.
@@ -104,13 +103,16 @@ classes.
There are currently 4 available implementations:
- ``Doctrine\ORM\Mapping\Driver\AnnotationDriver``
- ``Doctrine\ORM\Mapping\Driver\XmlDriver``
- ``Doctrine\ORM\Mapping\Driver\YamlDriver``
- ``Doctrine\ORM\Mapping\Driver\DriverChain``
Throughout the most part of this manual the AnnotationDriver is
used in the examples. For information on the usage of the XmlDriver
please refer to the dedicated chapters ``XML Mapping``.
or YamlDriver please refer to the dedicated chapters
``XML Mapping`` and ``YAML Mapping``.
The annotation driver can be configured with a factory method on
the ``Doctrine\ORM\Configuration``:
@@ -139,7 +141,7 @@ Metadata Cache (***RECOMMENDED***)
Gets or sets the cache implementation to use for caching metadata
information, that is, all the information you supply via
annotations or xml, so that they do not need to be parsed and
annotations, xml or yaml, so that they do not need to be parsed and
loaded from scratch on every single request which is a waste of
resources. The cache implementation must implement the
``Doctrine\Common\Cache\Cache`` interface.
@@ -148,7 +150,8 @@ Usage of a metadata cache is highly recommended.
The recommended implementations for production are:
- ``Doctrine\Common\Cache\ApcuCache``
- ``Doctrine\Common\Cache\ApcCache``
- ``Doctrine\Common\Cache\MemcacheCache``
- ``Doctrine\Common\Cache\XcacheCache``
- ``Doctrine\Common\Cache\RedisCache``
@@ -178,7 +181,8 @@ Usage of a query cache is highly recommended.
The recommended implementations for production are:
- ``Doctrine\Common\Cache\ApcuCache``
- ``Doctrine\Common\Cache\ApcCache``
- ``Doctrine\Common\Cache\MemcacheCache``
- ``Doctrine\Common\Cache\XcacheCache``
- ``Doctrine\Common\Cache\RedisCache``
@@ -203,103 +207,40 @@ implementation that logs to the standard output using ``echo`` and
``var_dump`` can be found at
``Doctrine\DBAL\Logging\EchoSQLLogger``.
Auto-generating Proxy Classes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Proxy classes can either be generated manually through the Doctrine
Console or automatically at runtime by Doctrine. The configuration
option that controls this behavior is:
Auto-generating Proxy Classes (***OPTIONAL***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?php
$config->setAutoGenerateProxyClasses($mode);
$config->setAutoGenerateProxyClasses($bool);
$config->getAutoGenerateProxyClasses();
Possible values for ``$mode`` are:
- ``Doctrine\Common\Proxy\ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``
Generate the proxy class when the proxy file does not exist.
This strategy can potentially cause disk access.
Note that autoloading will be attempted before falling back
to generating a proxy class: if an already existing proxy class
is found, then no file write operations will be performed.
- ``Doctrine\Common\Proxy\ProxyFactory::AUTOGENERATE_EVAL``
Generate the proxy classes and evaluate them on the fly via ``eval()``,
avoiding writing the proxies to disk.
This strategy is only sane for development and long running
processes.
- ``Doctrine\Common\Proxy\ProxyFactory::AUTOGENERATE_NEVER``
This flag is deprecated, and is an alias
of ``Doctrine\Common\Proxy\ProxyFactory::AUTOGENERATE_EVAL``
- ``Doctrine\Common\Proxy\ProxyFactory::AUTOGENERATE_ALWAYS``
This flag is deprecated, and is an alias
of ``Doctrine\Common\Proxy\ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``
Before v2.4, ``setAutoGenerateProxyClasses`` would accept a boolean
value. This is still possible, ``FALSE`` being equivalent to
AUTOGENERATE_NEVER and ``TRUE`` to AUTOGENERATE_ALWAYS.
Manually generating Proxy Classes for performance
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
While the ORM can generate proxy classes when required, it is suggested
to not let this happen for production environments, as it has a major
impact on your application's performance.
In a production environment, it is highly recommended to use
``Doctrine\Common\Proxy\ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``
in combination with a well-configured
`composer class autoloader<https://getcomposer.org/doc/01-basic-usage.md#autoloading>`_.
Here is an example of such setup:
.. code-block:: json
{
"autoload": {
"psr-4": {
"MyProject\\": "path/to/project/sources/",
"GeneratedProxies\\": "path/to/generated/proxies/"
}
}
}
You would then configure the ORM to use the ``"GeneratedProxies"``
and the ``"path/to/generated/proxies/"`` for the proxy classes:
.. code-block:: php
<?php
$config->setProxyDir('path/to/generated/proxies/');
$config->setProxyNamespace('GeneratedProxies');
To make sure proxies are never generated by Doctrine, you'd forcefully
generate them during deployment operations:
.. code-block:: sh
$ ./vendor/bin/doctrine orm:generate-proxies
$ composer dump-autoload
Gets or sets whether proxy classes should be generated
automatically at runtime by Doctrine. If set to ``FALSE``, proxy
classes must be generated manually through the doctrine command
line task ``generate-proxies``. The strongly recommended value for
a production environment is ``FALSE``.
Development vs Production Configuration
---------------------------------------
You should code your Doctrine2 bootstrapping with two different
runtime models in mind. There are some serious benefits of using
APCu or Memcache in production. In development however this will
APC or Memcache in production. In development however this will
frequently give you fatal errors, when you change your entities and
the cache still keeps the outdated metadata. That is why we
recommend the ``ArrayCache`` for development.
Furthermore you should disable the Auto-generating Proxy Classes
option in production.
Furthermore you should have the Auto-generating Proxy Classes
option to true in development and to false in production. If this
option is set to ``TRUE`` it can seriously hurt your script
performance if several proxy classes are re-generated during script
execution. Filesystem calls of that magnitude can even slower than
all the database queries Doctrine issues. Additionally writing a
proxy sets an exclusive file lock which can cause serious
performance bottlenecks in systems with regular concurrent
requests.
Connection Options
------------------
@@ -310,7 +251,7 @@ instance of ``Doctrine\DBAL\Connection``. If an array is passed it
is directly passed along to the DBAL Factory
``Doctrine\DBAL\DriverManager::getConnection()``. The DBAL
configuration is explained in the
`DBAL section <https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/configuration.html>`_.
`DBAL section <./../../../../../dbal/2.0/docs/reference/configuration/en>`_.
Proxy Objects
-------------
@@ -348,17 +289,16 @@ identifier. You could simply do this:
<?php
// $em instanceof EntityManager, $cart instanceof MyProject\Model\Cart
// $itemId comes from somewhere, probably a request parameter
$item = $em->getReference(\MyProject\Model\Item::class, $itemId);
$item = $em->getReference('MyProject\Model\Item', $itemId);
$cart->addItem($item);
Here, we added an ``Item`` to a ``Cart`` without loading the Item from the
database.
If you access any persistent state that isn't yet available in the ``Item``
instance, the proxying mechanism would fully initialize the object's state
transparently from the database.
Here ``$item`` is actually an instance of the proxy class that was generated
for the ``Item`` class but your code does not need to care. In fact it
**should not care**. Proxy objects should be transparent to your code.
Here, we added an Item to a Cart without loading the Item from the
database. If you invoke any method on the Item instance, it would
fully initialize its state transparently from the database. Here
$item is actually an instance of the proxy class that was generated
for the Item class but your code does not need to care. In fact it
**should not care**. Proxy objects should be transparent to your
code.
Association proxies
~~~~~~~~~~~~~~~~~~~
@@ -366,7 +306,7 @@ Association proxies
The second most important situation where Doctrine uses proxy
objects is when querying for objects. Whenever you query for an
object that has a single-valued association to another object that
is configured ``LAZY``, without joining that association in the same
is configured LAZY, without joining that association in the same
query, Doctrine puts proxy objects in place where normally the
associated object would be. Just like other proxies it will
transparently initialize itself on first access.
@@ -378,12 +318,64 @@ transparently initialize itself on first access.
This will override the 'fetch' option specified in the mapping for
that association, but only for that query.
Generating Proxy classes
~~~~~~~~~~~~~~~~~~~~~~~~
Proxy classes can either be generated manually through the Doctrine
Console or automatically by Doctrine. The configuration option that
controls this behavior is:
.. code-block:: php
<?php
$config->setAutoGenerateProxyClasses($bool);
$config->getAutoGenerateProxyClasses();
The default value is ``TRUE`` for convenient development. However,
this setting is not optimal for performance and therefore not
recommended for a production environment. To eliminate the overhead
of proxy class generation during runtime, set this configuration
option to ``FALSE``. When you do this in a development environment,
note that you may get class/file not found errors if certain proxy
classes are not available or failing lazy-loads if new methods were
added to the entity class that are not yet in the proxy class. In
such a case, simply use the Doctrine Console to (re)generate the
proxy classes like so:
.. code-block:: php
$ ./doctrine orm:generate-proxies
Autoloading Proxies
-------------------
When you deserialize proxy objects from the session or any other storage
it is necessary to have an autoloading mechanism in place for these classes.
For implementation reasons Proxy class names are not PSR-0 compliant. This
means that you have to register a special autoloader for these classes:
.. code-block:: php
<?php
use Doctrine\ORM\Proxy\Autoloader;
$proxyDir = "/path/to/proxies";
$proxyNamespace = "MyProxies";
Autoloader::register($proxyDir, $proxyNamespace);
If you want to execute additional logic to intercept the proxy file not found
state you can pass a closure as the third argument. It will be called with
the arguments proxydir, namespace and className when the proxy file could not
be found.
Multiple Metadata Sources
-------------------------
When using different components using Doctrine 2 you may end up
with them using two different metadata drivers, for example XML and
annotationsL. You can use the DriverChain Metadata implementations to
YAML. You can use the DriverChain Metadata implementations to
aggregate these drivers based on namespaces:
.. code-block:: php
@@ -393,7 +385,7 @@ aggregate these drivers based on namespaces:
$chain = new DriverChain();
$chain->addDriver($xmlDriver, 'Doctrine\Tests\Models\Company');
$chain->addDriver($annotationDriver, 'Doctrine\Tests\ORM\Mapping');
$chain->addDriver($yamlDriver, 'Doctrine\Tests\ORM\Mapping');
Based on the namespace of the entity the loading of entities is
delegated to the appropriate driver. The chain semantics come from
@@ -403,6 +395,7 @@ the entity class name against the namespace using a
correctly if sub-namespaces use different metadata driver
implementations.
Default Repository (***OPTIONAL***)
-----------------------------------
+71 -255
View File
@@ -1,32 +1,6 @@
Annotations Reference
=====================
You've probably used docblock annotations in some form already,
most likely to provide documentation metadata for a tool like
``PHPDocumentor`` (@author, @link, ...). Docblock annotations are a
tool to embed metadata inside the documentation section which can
then be processed by some tool. Doctrine 2 generalizes the concept
of docblock annotations so that they can be used for any kind of
metadata and so that it is easy to define new docblock annotations.
In order to allow more involved annotation values and to reduce the
chances of clashes with other docblock annotations, the Doctrine 2
docblock annotations feature an alternative syntax that is heavily
inspired by the Annotation syntax introduced in Java 5.
The implementation of these enhanced docblock annotations is
located in the ``Doctrine\Common\Annotations`` namespace and
therefore part of the Common package. Doctrine 2 docblock
annotations support namespaces and nested annotations among other
things. The Doctrine 2 ORM defines its own set of docblock
annotations for supplying object-relational mapping metadata.
.. note::
If you're not comfortable with the concept of docblock
annotations, don't worry, as mentioned earlier Doctrine 2 provides
the XML alternative and you could easily implement your own
favourite mechanism for defining ORM metadata.
In this chapter a reference of every Doctrine 2 Annotation is given
with short explanations on their context and usage.
@@ -35,13 +9,9 @@ Index
- :ref:`@Column <annref_column>`
- :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>`
@@ -102,40 +72,15 @@ Optional attributes:
string values for you.
- **precision**: The precision for a decimal (exact numeric) column
(applies only for decimal column), which is the maximum number of
digits that are stored for the values.
(Applies only for decimal column)
- **scale**: The scale for a decimal (exact numeric) column (applies
only for decimal column), which represents the number of digits
to the right of the decimal point and must not be greater than
*precision*.
- **scale**: The scale for a decimal (exact numeric) column (Applies
only for decimal column)
- **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.
- **options**: Array of additional options:
- ``default``: The default value to set for the column if no value
is supplied.
- ``unsigned``: Boolean value to determine if the column should
be capable of representing only non-negative integers
(applies only for integer column and might not be supported by
all vendors).
- ``fixed``: Boolean value to determine if the specified length of
a string column should be fixed or varying (applies only for
string/binary column and might not be supported by all vendors).
- ``comment``: The comment of the column in the schema (might not
be supported by all vendors).
- ``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).
- **nullable**: Determines if NULL values allowed for this column.
- **columnDefinition**: DDL SQL snippet that starts after the column
name and specifies the complete (non-portable!) column definition.
@@ -148,12 +93,7 @@ Optional attributes:
attribute still handles the conversion between PHP and Database
values. If you use this attribute on a column that is used for
joins between tables you should also take a look at
:ref:`@JoinColumn <annref_joincolumn>`.
.. note::
For more detailed information on each attribute, please refer to
the DBAL ``Schema-Representation`` documentation.
:ref:`@JoinColumn <annref_joincolumn>`.
Examples:
@@ -164,27 +104,17 @@ Examples:
* @Column(type="string", length=32, unique=true, nullable=false)
*/
protected $username;
/**
* @Column(type="string", columnDefinition="CHAR(2) NOT NULL")
*/
protected $country;
/**
* @Column(type="decimal", precision=2, scale=1)
*/
protected $height;
/**
* @Column(type="string", length=2, options={"fixed":true, "comment":"Initial letters of first and last name"})
*/
protected $initials;
/**
* @Column(type="integer", name="login_count", nullable=false, options={"unsigned":true, "default":0})
*/
protected $loginCount;
.. _annref_column_result:
@ColumnResult
@@ -196,17 +126,6 @@ Required attributes:
- **name**: The name of a column in the SELECT clause of a SQL query
.. _annref_cache:
@Cache
~~~~~~~~~~~~~~
Add caching strategy to a root entity or a collection.
Optional attributes:
- **usage**: One of ``READ_ONLY``, ``READ_WRITE`` or ``NONSTRICT_READ_WRITE``, By default this is ``READ_ONLY``.
- **region**: An specific region name
.. _annref_changetrackingpolicy:
@ChangeTrackingPolicy
@@ -237,50 +156,25 @@ 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:
- **name**: The column name of the discriminator. This name is also
used during Array hydration as key to specify the class-name.
Optional attributes:
- **type**: By default this is string.
- **length**: By default this is 255.
@@ -290,11 +184,11 @@ Optional attributes:
~~~~~~~~~~~~~~~~~~~~~
The discriminator map is a required annotation on the
topmost/super class in an inheritance hierarchy. Its only argument is an
array which defines which class should be saved under
top-most/super class in an inheritance hierarchy. It takes an array
as only argument which defines which class should be saved under
which name in the database. Keys are the database value and values
are the classes, either as fully- or as unqualified class names
depending on whether the classes are in the namespace or not.
depending if the classes are in the namespace or not.
.. code-block:: php
@@ -310,73 +204,17 @@ 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
~~~~~~~
Required annotation to mark a PHP class as an entity. Doctrine manages
the persistence of all classes marked as entities.
Required annotation to mark a PHP class as Entity. Doctrine manages
the persistence of all classes marked as entity.
Optional attributes:
- **repositoryClass**: Specifies the FQCN of a subclass of the
EntityRepository. Use of repositories for entities is encouraged to keep
specialized DQL and SQL operations separated from the Model/Domain
@@ -391,11 +229,11 @@ Example:
<?php
/**
* @Entity(repositoryClass="MyProject\UserRepository", readOnly=true)
* @Entity(repositoryClass="MyProject\UserRepository")
*/
class User
{
// ...
//...
}
.. _annref_entity_result:
@@ -426,6 +264,7 @@ Required attributes:
- **name**: Name of the persistent field or property of the class.
Optional attributes:
- **column**: Name of the column in the SELECT clause.
@@ -443,12 +282,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``, ``CUSTOM`` and ``NONE``, explained
in the :ref:`Identifier Generation Strategies <identifier-generation-strategies>` section.
If not specified, default value is AUTO.
Valid values are AUTO, SEQUENCE, TABLE, IDENTITY, UUID, CUSTOM and NONE.
Example:
@@ -468,7 +306,7 @@ Example:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Annotation which has to be set on the entity-class PHP DocBlock to
notify Doctrine that this entity has entity lifecycle callback
notify Doctrine that this entity has entity life-cycle callback
annotations set on at least one of its methods. Using @PostLoad,
@PrePersist, @PostPersist, @PreRemove, @PostRemove, @PreUpdate or
@PostUpdate without this marker annotation will make Doctrine
@@ -497,23 +335,17 @@ Example:
~~~~~~~
Annotation is used inside the :ref:`@Table <annref_table>` annotation on
the entity-class level. It provides a hint to the SchemaTool to
the entity-class level. It allows to hint the SchemaTool to
generate a database index on the specified table columns. It only
has meaning in the SchemaTool schema generation context.
Required attributes:
- **name**: Name of the Index
- **columns**: Array of columns.
Optional attributes:
- **options**: Array of platform specific options:
- ``where``: SQL WHERE condition to be used for partial indexes. It will
only have effect on supported platforms.
Basic example:
Example:
.. code-block:: php
@@ -526,19 +358,6 @@ Basic example:
{
}
Example with partial indexes:
.. code-block:: php
<?php
/**
* @Entity
* @Table(name="ecommerce_products",indexes={@Index(name="search_idx", columns={"name", "email"}, options={"where": "(((id IS NOT NULL) AND (name IS NULL)) AND (email IS NULL))"})})
*/
class ECommerceProduct
{
}
.. _annref_id:
@Id
@@ -590,7 +409,7 @@ Examples:
{
// ...
}
/**
* @Entity
* @InheritanceType("JOINED")
@@ -610,12 +429,13 @@ Examples:
This annotation is used in the context of relations in
:ref:`@ManyToOne <annref_manytoone>`, :ref:`@OneToOne <annref_onetoone>` fields
and in the Context of :ref:`@JoinTable <annref_jointable>` nested inside
a @ManyToMany. This annotation is not required. If it is not
a @ManyToMany. This annotation is not required. If its not
specified the attributes *name* and *referencedColumnName* are
inferred from the table and primary key names.
Required attributes:
- **name**: Column name that holds the foreign key identifier for
this relation. In the context of @JoinTable it specifies the column
name in the join table.
@@ -624,15 +444,16 @@ Required attributes:
Optional attributes:
- **unique**: Determines whether this relation is exclusive between the
affected entities and should be enforced as such on the database
- **unique**: Determines if this relation exclusive between the
affected entities and should be enforced so on the database
constraint level. Defaults to false.
- **nullable**: Determine whether the related entity is required, or if
- **nullable**: Determine if the related entity is required, or if
null is an allowed state for the relation. Defaults to true.
- **onDelete**: Cascade Action (Database-level)
- **columnDefinition**: DDL SQL snippet that starts after the column
name and specifies the complete (non-portable!) column definition.
This attribute enables the use of advanced RMDBS features. Using
This attribute allows to make use of advanced RMDBS features. Using
this attribute on @JoinColumn is necessary if you need slightly
different column definitions for joining columns, for example
regarding NULL/NOT NULL defaults. However by default a
@@ -672,7 +493,8 @@ details of the database join table. If you do not specify
@JoinTable on these relations reasonable mapping defaults apply
using the affected table and the column names.
Optional attributes:
Required attributes:
- **name**: Database name of the join-table
- **joinColumns**: An array of @JoinColumn annotations describing the
@@ -705,12 +527,14 @@ describes a many-to-one relationship between two entities.
Required attributes:
- **targetEntity**: FQCN of the referenced target entity. Can be the
unqualified class name if both classes are in the same namespace.
*IMPORTANT:* No leading backslash!
Optional attributes:
- **cascade**: Cascade Option
- **fetch**: One of LAZY or EAGER
- inversedBy - The inversedBy attribute designates the field in
@@ -731,7 +555,7 @@ Example:
@ManyToMany
~~~~~~~~~~~~~~
Defines that the annotated instance variable holds a many-to-many relationship
Defines an instance variable holds a many-to-many relationship
between two entities. :ref:`@JoinTable <annref_jointable>` is an
additional, optional annotation that has reasonable default
configuration values using the table and names of the two related
@@ -739,16 +563,18 @@ entities.
Required attributes:
- **targetEntity**: FQCN of the referenced target entity. Can be the
unqualified class name if both classes are in the same namespace.
*IMPORTANT:* No leading backslash!
Optional attributes:
- **mappedBy**: This option specifies the property name on the
targetEntity that is the owning side of this relation. It is a
targetEntity that is the owning side of this relation. Its a
required attribute for the inverse side of a relationship.
- **inversedBy**: The inversedBy attribute designates the field in the
- **inversedBy**: The inversedBy attribute designates the eld in the
entity that is the inverse side of the relationship.
- **cascade**: Cascade Option
- **fetch**: One of LAZY, EXTRA_LAZY or EAGER
@@ -776,7 +602,7 @@ Example:
* )
*/
private $groups;
/**
* Inverse Side
*
@@ -789,7 +615,7 @@ Example:
@MappedSuperclass
~~~~~~~~~~~~~~~~~~~~~
A mapped superclass is an abstract or concrete class that provides
An mapped superclass is an abstract or concrete class that provides
persistent entity state and mapping information for its subclasses,
but which is not itself an entity. This annotation is specified on
the Class docblock and has no additional attributes.
@@ -800,6 +626,7 @@ The @MappedSuperclass annotation cannot be used in conjunction with
Optional attributes:
- **repositoryClass**: (>= 2.2) Specifies the FQCN of a subclass of the EntityRepository.
That will be inherited for all subclasses of that Mapped Superclass.
@@ -836,11 +663,13 @@ Required attributes:
- **name**: The name used to refer to the query with the EntityManager methods that create query objects.
- **query**: The SQL query string.
Optional attributes:
- **resultClass**: The class of the result.
- **resultSetMapping**: The name of a SqlResultSetMapping, as defined in metadata.
Example:
.. code-block:: php
@@ -899,25 +728,27 @@ Example:
~~~~~~~~~~~~~~
The @OneToOne annotation works almost exactly as the
:ref:`@ManyToOne <annref_manytoone>` with one additional option which can
:ref:`@ManyToOne <annref_manytoone>` with one additional option that can
be specified. The configuration defaults for
:ref:`@JoinColumn <annref_joincolumn>` using the target entity table and
primary key column names apply here too.
Required attributes:
- **targetEntity**: FQCN of the referenced target entity. Can be the
unqualified class name if both classes are in the same namespace.
*IMPORTANT:* No leading backslash!
Optional attributes:
- **cascade**: Cascade Option
- **fetch**: One of LAZY or EAGER
- **orphanRemoval**: Boolean that specifies if orphans, inverse
OneToOne entities that are not connected to any owning instance,
should be removed by Doctrine. Defaults to false.
- **inversedBy**: The inversedBy attribute designates the field in the
- **inversedBy**: The inversedBy attribute designates the eld in the
entity that is the inverse side of the relationship.
Example:
@@ -938,12 +769,14 @@ Example:
Required attributes:
- **targetEntity**: FQCN of the referenced target entity. Can be the
unqualified class name if both classes are in the same namespace.
*IMPORTANT:* No leading backslash!
Optional attributes:
- **cascade**: Cascade Option
- **orphanRemoval**: Boolean that specifies if orphans, inverse
OneToOne entities that are not connected to any owning instance,
@@ -960,7 +793,7 @@ Example:
<?php
/**
* @OneToMany(targetEntity="Phonenumber", mappedBy="user", cascade={"persist", "remove"}, orphanRemoval=true)
* @OneToMany(targetEntity="Phonenumber", mappedBy="user", cascade={"persist", "remove", "merge"}, orphanRemoval=true)
*/
public $phonenumbers;
@@ -1062,21 +895,23 @@ DocBlock.
@SequenceGenerator
~~~~~~~~~~~~~~~~~~~~~
For use with @GeneratedValue(strategy="SEQUENCE") this
For the use with @generatedValue(strategy="SEQUENCE") this
annotation allows to specify details about the sequence, such as
the increment size and initial values of the sequence.
Required attributes:
- **sequenceName**: Name of the sequence
Optional attributes:
- **allocationSize**: Increment the sequence by the allocation size
when its fetched. A value larger than 1 allows optimization for
when its fetched. A value larger than 1 allows to optimize for
scenarios where you create more than one new entity per request.
Defaults to 10
- **initialValue**: Where the sequence starts, defaults to 1.
- **initialValue**: Where does the sequence start, defaults to 1.
Example:
@@ -1102,6 +937,7 @@ Required attributes:
- **name**: The name given to the result set mapping, and used to refer to it in the methods of the Query API.
Optional attributes:
- **entities**: Array of @EntityResult, Specifies the result set mapping to entities.
@@ -1197,18 +1033,19 @@ Example:
Annotation describes the table an entity is persisted in. It is
placed on the entity-class PHP DocBlock and is optional. If it is
not specified the table name will default to the entity's
not specified the table name will default to the entities
unqualified classname.
Required attributes:
- **name**: Name of the table
Optional attributes:
- **indexes**: Array of @Index annotations
- **uniqueConstraints**: Array of @UniqueConstraint annotations.
- **schema**: (>= 2.5) Name of the schema the table lies in.
Example:
@@ -1220,7 +1057,6 @@ Example:
* @Table(name="user",
* uniqueConstraints={@UniqueConstraint(name="user_unique",columns={"username"})},
* indexes={@Index(name="user_idx", columns={"email"})}
* schema="schema_name"
* )
*/
class User { }
@@ -1238,17 +1074,11 @@ context.
Required attributes:
- **name**: Name of the Index
- **columns**: Array of columns.
Optional attributes:
- **options**: Array of platform specific options:
- ``where``: SQL WHERE condition to be used for partial indexes. It will
only have effect on supported platforms.
Basic example:
Example:
.. code-block:: php
@@ -1261,29 +1091,15 @@ Basic example:
{
}
Example with partial indexes:
.. code-block:: php
<?php
/**
* @Entity
* @Table(name="ecommerce_products",uniqueConstraints={@UniqueConstraint(name="search_idx", columns={"name", "email"}, options={"where": "(((id IS NOT NULL) AND (name IS NULL)) AND (email IS NULL))"})})
*/
class ECommerceProduct
{
}
.. _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:
+29 -14
View File
@@ -12,13 +12,13 @@ As the term ORM already hints at, Doctrine 2 aims to simplify the
translation between database rows and the PHP object model. The
primary use case for Doctrine are therefore applications that
utilize the Object-Oriented Programming Paradigm. For applications
that do not primarily work with objects Doctrine 2 is not suited very
that not primarily work with objects Doctrine 2 is not suited very
well.
Requirements
------------
Doctrine 2 requires a minimum of PHP 7.1. For greatly improved
Doctrine 2 requires a minimum of PHP 5.3.0. For greatly improved
performance it is also recommended that you use APC with PHP.
Doctrine 2 Packages
@@ -34,6 +34,7 @@ This manual mainly covers the ORM package, sometimes touching parts
of the underlying DBAL and Common packages. The Doctrine code base
is split in to these packages for a few reasons and they are to...
- ...make things more maintainable and decoupled
- ...allow you to use the code in Doctrine Common without the ORM
or DBAL
@@ -71,16 +72,27 @@ Entities
An entity is a lightweight, persistent domain object. An entity can
be any regular PHP class observing the following restrictions:
- An entity class must not be final or contain final methods.
- All persistent properties/field of any entity class should
always be private or protected, otherwise lazy-loading might not
work as expected. In case you serialize entities (for example Session)
properties should be protected (See Serialize section below).
- An entity class must not implement ``__clone`` or
:doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
- An entity class must not implement ``__wakeup`` or
:doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
Also consider implementing
`Serializable <http://de3.php.net/manual/en/class.serializable.php>`_
instead.
- Any two entity classes in a class hierarchy that inherit
directly or indirectly from one another must not have a mapped
property with the same name. That is, if B inherits from A then B
must not have a mapped field with the same name as an already
mapped field that is inherited from A.
- An entity cannot make use of func_get_args() to implement variable parameters.
Generated proxies do not support this for performance reasons and your code might
actually fail to work when violating this restriction.
Entities support inheritance, polymorphic associations, and
polymorphic queries. Both abstract and concrete classes can be
@@ -94,12 +106,14 @@ classes, and non-entity classes may extend entity classes.
never calls entity constructors, thus you are free to use them as
you wish and even have it require arguments of any type.
Entity states
~~~~~~~~~~~~~
An entity instance can be characterized as being NEW, MANAGED,
DETACHED or REMOVED.
- A NEW entity instance has no persistent identity, and is not yet
associated with an EntityManager and a UnitOfWork (i.e. those just
created with the "new" operator).
@@ -136,18 +150,18 @@ subsequent access must be through the interface type.
Serializing entities
~~~~~~~~~~~~~~~~~~~~
Serializing entities is generally to be avoided.
If you intend to serialize (and unserialize) entity
Serializing entities can be problematic and is not really
recommended, at least not as long as an entity instance still holds
references to proxy objects or is still managed by an
EntityManager. If you intend to serialize (and unserialize) entity
instances that still hold references to proxy objects you may run
into problems, because all proxy properties will be initialized
recursively, leading to large serialized object graphs, especially
for circular associations.
If you really must serialize entities, regardless if proxies are
involved or not, then consider implementing the ``Serializable``
interface and manually checking for cyclic dependencies in your
object graph.
into problems with private properties because of technical
limitations. Proxy objects implement ``__sleep`` and it is not
possible for ``__sleep`` to return names of private properties in
parent classes. On the other hand it is not a solution for proxy
objects to implement ``Serializable`` because Serializable does not
work well with any potential cyclic object references (at least we
did not find a way yet, if you did, please contact us).
The EntityManager
~~~~~~~~~~~~~~~~~
@@ -175,8 +189,9 @@ The Unit of Work
Internally an ``EntityManager`` uses a ``UnitOfWork``, which is a
typical implementation of the
`Unit of Work pattern <https://martinfowler.com/eaaCatalog/unitOfWork.html>`_,
`Unit of Work pattern <http://martinfowler.com/eaaCatalog/unitOfWork.html>`_,
to keep track of all the things that need to be done the next time
``flush`` is invoked. You usually do not directly interact with a
``UnitOfWork`` but with the ``EntityManager`` instead.
File diff suppressed because it is too large Load Diff
+466 -244
View File
@@ -1,71 +1,77 @@
Basic Mapping
=============
This guide explains the basic mapping of entities and properties.
After working through this guide you should know:
This chapter explains the basic mapping of objects and properties.
Mapping of associations will be covered in the next chapter
"Association Mapping".
- How to create PHP objects that can be saved to the database with Doctrine;
- How to configure the mapping between columns on tables and properties on
entities;
- What Doctrine mapping types are;
- Defining primary keys and how identifiers are generated by Doctrine;
- How quoting of reserved symbols works in Doctrine.
Mapping Drivers
---------------
Mapping of associations will be covered in the next chapter on
:doc:`Association Mapping <association-mapping>`.
Doctrine provides several different ways for specifying
object-relational mapping metadata:
Guide Assumptions
-----------------
You should have already :doc:`installed and configure <configuration>`
Doctrine.
- Docblock Annotations
- XML
- YAML
Creating Classes for the Database
---------------------------------
Every PHP object that you want to save in the database using Doctrine
is called an "Entity". The term "Entity" describes objects
that have an identity over many independent requests. This identity is
usually achieved by assigning a unique identifier to an entity.
In this tutorial the following ``Message`` PHP class will serve as the
example Entity:
.. code-block:: php
<?php
class Message
{
private $id;
private $text;
private $postedAt;
}
Because Doctrine is a generic library, it only knows about your
entities because you will describe their existence and structure using
mapping metadata, which is configuration that tells Doctrine how your
entity should be stored in the database. The documentation will often
speak of "mapping something", which means writing the mapping metadata
that describes your entity.
Doctrine provides several different ways to specify object-relational
mapping metadata:
- :doc:`Docblock Annotations <annotations-reference>`
- :doc:`XML <xml-mapping>`
- :doc:`PHP code <php-mapping>`
This manual will usually show mapping metadata via docblock annotations, though
many examples also show the equivalent configuration in XML.
This manual usually mentions docblock annotations in all the examples
that are spread throughout all chapters, however for many examples
alternative YAML and XML examples are given as well. There are dedicated
reference chapters for XML and YAML mapping, respectively that explain them
in more detail. There is also an Annotation reference chapter.
.. note::
All metadata drivers perform equally. Once the metadata of a class has been
read from the source (annotations or xml) it is stored in an instance
of the ``Doctrine\ORM\Mapping\ClassMetadata`` class and these instances are
stored in the metadata cache. If you're not using a metadata cache (not
recommended!) then the XML driver is the fastest.
If you're wondering which mapping driver gives the best
performance, the answer is: They all give exactly the same performance.
Once the metadata of a class has
been read from the source (annotations, xml or yaml) it is stored
in an instance of the ``Doctrine\ORM\Mapping\ClassMetadata`` class
and these instances are stored in the metadata cache. Therefore at
the end of the day all drivers perform equally well. If you're not
using a metadata cache (not recommended!) then the XML driver might
have a slight edge in performance due to the powerful native XML
support in PHP.
Marking our ``Message`` class as an entity for Doctrine is straightforward:
Introduction to Docblock Annotations
------------------------------------
You've probably used docblock annotations in some form already,
most likely to provide documentation metadata for a tool like
``PHPDocumentor`` (@author, @link, ...). Docblock annotations are a
tool to embed metadata inside the documentation section which can
then be processed by some tool. Doctrine 2 generalizes the concept
of docblock annotations so that they can be used for any kind of
metadata and so that it is easy to define new docblock annotations.
In order to allow more involved annotation values and to reduce the
chances of clashes with other docblock annotations, the Doctrine 2
docblock annotations feature an alternative syntax that is heavily
inspired by the Annotation syntax introduced in Java 5.
The implementation of these enhanced docblock annotations is
located in the ``Doctrine\Common\Annotations`` namespace and
therefore part of the Common package. Doctrine 2 docblock
annotations support namespaces and nested annotations among other
things. The Doctrine 2 ORM defines its own set of docblock
annotations for supplying object-relational mapping metadata.
.. note::
If you're not comfortable with the concept of docblock
annotations, don't worry, as mentioned earlier Doctrine 2 provides
XML and YAML alternatives and you could easily implement your own
favourite mechanism for defining ORM metadata.
Persistent classes
------------------
In order to mark a class for object-relational persistence it needs
to be designated as an entity. This can be done through the
``@Entity`` marker annotation.
.. configuration-block::
@@ -73,22 +79,28 @@ Marking our ``Message`` class as an entity for Doctrine is straightforward:
<?php
/** @Entity */
class Message
class MyPersistentClass
{
// ...
//...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="Message">
<entity name="MyPersistentClass">
<!-- ... -->
</entity>
</doctrine-mapping>
With no additional information, Doctrine expects the entity to be saved
into a table with the same name as the class in our case ``Message``.
You can change this by configuring information about the table:
.. code-block:: yaml
MyPersistentClass:
type: entity
# ...
By default, the entity will be persisted to a table with the same
name as the class name. In order to change that, you can use the
``@Table`` annotation as follows:
.. configuration-block::
@@ -97,165 +109,94 @@ You can change this by configuring information about the table:
<?php
/**
* @Entity
* @Table(name="message")
* @Table(name="my_persistent_class")
*/
class Message
class MyPersistentClass
{
// ...
//...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="Message" table="message">
<entity name="MyPersistentClass" table="my_persistent_class">
<!-- ... -->
</entity>
</doctrine-mapping>
Now the class ``Message`` will be saved and fetched from the table ``message``.
.. code-block:: yaml
Property Mapping
----------------
MyPersistentClass:
type: entity
table: my_persistent_class
# ...
The next step after marking a PHP class as an entity is mapping its properties
to columns in a table.
To configure a property use the ``@Column`` docblock annotation. The ``type``
attribute specifies the :ref:`Doctrine Mapping Type <reference-mapping-types>`
to use for the field. If the type is not specified, ``string`` is used as the
default.
.. configuration-block::
.. code-block:: php
<?php
/** @Entity */
class Message
{
/** @Column(type="integer") */
private $id;
/** @Column(length=140) */
private $text;
/** @Column(type="datetime", name="posted_at") */
private $postedAt;
}
.. code-block:: xml
<doctrine-mapping>
<entity name="Message">
<field name="id" type="integer" />
<field name="text" length="140" />
<field name="postedAt" column="posted_at" type="datetime" />
</entity>
</doctrine-mapping>
When we don't explicitly specify a column name via the ``name`` option, Doctrine
assumes the field name is also the column name. This means that:
* the ``id`` property will map to the column ``id`` using the type ``integer``;
* the ``text`` property will map to the column ``text`` with the default mapping type ``string``;
* the ``postedAt`` property will map to the ``posted_at`` column with the ``datetime`` type.
The Column annotation has some more attributes. Here is a complete
list:
- ``type``: (optional, defaults to 'string') The mapping type to
use for the column.
- ``name``: (optional, defaults to field name) The name of the
column in the database.
- ``length``: (optional, default 255) The length of the column in
the database. (Applies only if a string-valued column is used).
- ``unique``: (optional, default FALSE) Whether the column is a
unique key.
- ``nullable``: (optional, default FALSE) Whether the database
column is nullable.
- ``precision``: (optional, default 0) The precision for a decimal
(exact numeric) column (applies only for decimal column),
which is the maximum number of digits that are stored for the values.
- ``scale``: (optional, default 0) The scale for a decimal (exact
numeric) column (applies only for decimal column), which represents
the number of digits to the right of the decimal point and must
not be greater than *precision*.
- ``columnDefinition``: (optional) Allows to define a custom
DDL snippet that is used to create the column. Warning: This normally
confuses the SchemaTool to always detect the column as changed.
- ``options``: (optional) Key-value pairs of options that get passed
to the underlying database platform when generating DDL statements.
.. _reference-mapping-types:
Now instances of MyPersistentClass will be persisted into a table
named ``my_persistent_class``.
Doctrine Mapping Types
----------------------
The ``type`` option used in the ``@Column`` accepts any of the existing
Doctrine types or even your own custom types. A Doctrine type defines
the conversion between PHP and SQL types, independent from the database vendor
you are using. All Mapping Types that ship with Doctrine are fully portable
between the supported database systems.
A Doctrine Mapping Type defines the mapping between a PHP type and
a SQL type. All Doctrine Mapping Types that ship with Doctrine are
fully portable between different RDBMS. You can even write your own
custom mapping types that might or might not be portable, which is
explained later in this chapter.
As an example, the Doctrine Mapping Type ``string`` defines the
For example, the Doctrine Mapping Type ``string`` defines the
mapping from a PHP string to a SQL VARCHAR (or VARCHAR2 etc.
depending on the RDBMS brand). Here is a quick overview of the
built-in mapping types:
- ``string``: Type that maps an SQL VARCHAR to a PHP string.
- ``integer``: Type that maps an SQL INT to a PHP integer.
- ``string``: Type that maps a SQL VARCHAR to a PHP string.
- ``integer``: Type that maps a SQL INT to a PHP integer.
- ``smallint``: Type that maps a database SMALLINT to a PHP
integer.
- ``bigint``: Type that maps a database BIGINT to a PHP string.
- ``boolean``: Type that maps an SQL boolean or equivalent (TINYINT) to a PHP boolean.
- ``decimal``: Type that maps an SQL DECIMAL to a PHP string.
- ``date``: Type that maps an SQL DATETIME to a PHP DateTime
- ``boolean``: Type that maps a SQL boolean to a PHP boolean.
- ``decimal``: Type that maps a SQL DECIMAL to a PHP string.
- ``date``: Type that maps a SQL DATETIME to a PHP DateTime
object.
- ``date_immutable``: Type that maps an SQL DATETIME to a PHP DateTimeImmutable
object.
- ``time``: Type that maps an SQL TIME to a PHP DateTime object.
- ``time_immutable``: Type that maps an SQL TIME to a PHP DateTimeImmutable object.
- ``datetime``: Type that maps an SQL DATETIME/TIMESTAMP to a PHP DateTime
object with the current timezone.
- ``datetimetz``: Type that maps an SQL DATETIME/TIMESTAMP to a PHP DateTime
object with the timezone specified in the value from the database.
- ``datetime_immutable``: Type that maps an SQL DATETIME/TIMESTAMP to a PHP DateTimeImmutable
object with the current timezone.
- ``datetimetz_immutable``: Type that maps an SQL DATETIME/TIMESTAMP to a PHP DateTimeImmutable
object with the timezone specified in the value from the database.
- ``dateinterval``: Type that maps an interval to a PHP DateInterval object
- ``text``: Type that maps an SQL CLOB to a PHP string.
- ``object``: Type that maps an SQL CLOB to a PHP object using
- ``time``: Type that maps a SQL TIME to a PHP DateTime object.
- ``datetime``: Type that maps a SQL DATETIME/TIMESTAMP to a PHP
DateTime object.
- ``datetimetz``: Type that maps a SQL DATETIME/TIMESTAMP to a PHP
DateTime object with timezone.
- ``text``: Type that maps a SQL CLOB to a PHP string.
- ``object``: Type that maps a SQL CLOB to a PHP object using
``serialize()`` and ``unserialize()``
- ``array``: Type that maps an SQL CLOB to a PHP array using
- ``array``: Type that maps a SQL CLOB to a PHP array using
``serialize()`` and ``unserialize()``
- ``simple_array``: Type that maps an SQL CLOB to a one-dimensional PHP array using
- ``simple_array``: Type that maps a SQL CLOB to a PHP array using
``implode()`` and ``explode()``, with a comma as delimiter. *IMPORTANT*
Only use this type if you are sure that your values cannot contain a ",".
- ``json_array``: Type that maps an SQL CLOB to a PHP array using
``json_encode()`` and ``json_decode()``. This one has been deprecated in favor
of ``json`` type.
- ``json``: Type that maps an SQL CLOB to a PHP array using
``json_encode()`` and ``json_decode()``. An empty value is correctly represented as ``null``
- ``float``: Type that maps an SQL Float (Double Precision) to a
- ``json_array``: Type that maps a SQL CLOB to a PHP array using
``json_encode()`` and ``json_decode()``
- ``float``: Type that maps a SQL Float (Double Precision) to a
PHP double. *IMPORTANT*: Works only with locale settings that use
decimal points as separator.
- ``guid``: Type that maps a database GUID/UUID to a PHP string. Defaults to
varchar but uses a specific type if the platform supports it.
- ``blob``: Type that maps an SQL BLOB to a PHP resource stream
- ``binary``: Type that maps an SQL binary to a PHP resource stream
A cookbook article shows how to define :doc:`your own custom mapping types
<../cookbook/custom-mapping-types>`.
- ``blob``: Type that maps a SQL BLOB to a PHP resource stream
.. note::
DateTime and Object types are compared by reference, not by value. Doctrine
updates this values if the reference changes and therefore behaves as if
these objects are immutable value objects.
Doctrine Mapping Types are NOT SQL types and NOT PHP
types! They are mapping types between 2 types.
Additionally Mapping types are *case-sensitive*. For example, using
a DateTime column will NOT match the datetime type that ships with
Doctrine 2.
.. note::
DateTime and Object types are compared by reference, not by value. Doctrine updates this values
if the reference changes and therefore behaves as if these objects are immutable value objects.
.. warning::
All Date types assume that you are exclusively using the default timezone
set by `date_default_timezone_set() <https://php.net/manual/en/function.date-default-timezone-set.php>`_
set by `date_default_timezone_set() <http://docs.php.net/manual/en/function.date-default-timezone-set.php>`_
or by the php.ini configuration ``date.timezone``. Working with
different timezones will cause troubles and unexpected behavior.
@@ -265,46 +206,319 @@ A cookbook article shows how to define :doc:`your own custom mapping types
on working with datetimes that gives hints for implementing
multi timezone applications.
Identifiers / Primary Keys
--------------------------
Every entity class must have an identifier/primary key. You can select
the field that serves as the identifier with the ``@Id``
annotation.
Property Mapping
----------------
After a class has been marked as an entity it can specify mappings
for its instance fields. Here we will only look at simple fields
that hold scalar values like strings, numbers, etc. Associations to
other objects are covered in the chapter "Association Mapping".
To mark a property for relational persistence the ``@Column``
docblock annotation is used. This annotation usually requires at
least 1 attribute to be set, the ``type``. The ``type`` attribute
specifies the Doctrine Mapping Type to use for the field. If the
type is not specified, 'string' is used as the default mapping type
since it is the most flexible.
Example:
.. configuration-block::
.. code-block:: php
<?php
class Message
/** @Entity */
class MyPersistentClass
{
/**
* @Id
* @Column(type="integer")
* @GeneratedValue
*/
/** @Column(type="integer") */
private $id;
// ...
/** @Column(length=50) */
private $name; // type defaults to string
//...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="Message">
<id name="id" type="integer">
<generator strategy="AUTO" />
</id>
<!-- -->
<entity name="MyPersistentClass">
<field name="id" type="integer" />
<field name="name" length="50" />
</entity>
</doctrine-mapping>
In most cases using the automatic generator strategy (``@GeneratedValue``) is
what you want. It defaults to the identifier generation mechanism your current
database vendor prefers: AUTO_INCREMENT with MySQL, SERIAL with PostgreSQL,
Sequences with Oracle and so on.
.. code-block:: yaml
.. _identifier-generation-strategies:
MyPersistentClass:
type: entity
fields:
id:
type: integer
name:
length: 50
In that example we mapped the field ``id`` to the column ``id``
using the mapping type ``integer`` and the field ``name`` is mapped
to the column ``name`` with the default mapping type ``string``. As
you can see, by default the column names are assumed to be the same
as the field names. To specify a different name for the column, you
can use the ``name`` attribute of the Column annotation as
follows:
.. configuration-block::
.. code-block:: php
<?php
/** @Column(name="db_name") */
private $name;
.. code-block:: xml
<doctrine-mapping>
<entity name="MyPersistentClass">
<field name="name" column="db_name" />
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyPersistentClass:
type: entity
fields:
name:
length: 50
column: db_name
The Column annotation has some more attributes. Here is a complete
list:
- ``type``: (optional, defaults to 'string') The mapping type to
use for the column.
- ``column``: (optional, defaults to field name) The name of the
column in the database.
- ``length``: (optional, default 255) The length of the column in
the database. (Applies only if a string-valued column is used).
- ``unique``: (optional, default FALSE) Whether the column is a
unique key.
- ``nullable``: (optional, default FALSE) Whether the database
column is nullable.
- ``precision``: (optional, default 0) The precision for a decimal
(exact numeric) column. (Applies only if a decimal column is used.)
- ``scale``: (optional, default 0) The scale for a decimal (exact
numeric) column. (Applies only if a decimal column is used.)
.. _reference-basic-mapping-custom-mapping-types:
Custom Mapping Types
--------------------
Doctrine allows you to create new mapping types. This can come in
handy when you're missing a specific mapping type or when you want
to replace the existing implementation of a mapping type.
In order to create a new mapping type you need to subclass
``Doctrine\DBAL\Types\Type`` and implement/override the methods as
you wish. Here is an example skeleton of such a custom type class:
.. code-block:: php
<?php
namespace My\Project\Types;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;
/**
* My custom datatype.
*/
class MyType extends Type
{
const MYTYPE = 'mytype'; // modify to match your type name
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
// return the SQL used to create your column type. To create a portable column type, use the $platform.
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
// This is executed when the value is read from the database. Make your conversions here, optionally using the $platform.
}
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
// This is executed when the value is written to the database. Make your conversions here, optionally using the $platform.
}
public function getName()
{
return self::MYTYPE; // modify to match your constant name
}
}
Restrictions to keep in mind:
- 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.
When you have implemented the type you still need to let Doctrine
know about it. This can be achieved through the
``Doctrine\DBAL\Types\Type#addType($name, $className)``
method. See the following example:
.. code-block:: php
<?php
// in bootstrapping code
// ...
use Doctrine\DBAL\Types\Type;
// ...
// Register my type
Type::addType('mytype', 'My\Project\Types\MyType');
As can be seen above, when registering the custom types in the
configuration you specify a unique name for the mapping type and
map that to the corresponding fully qualified class name. Now you
can use your new type in your mapping like this:
.. code-block:: php
<?php
class MyPersistentClass
{
/** @Column(type="mytype") */
private $field;
}
To have Schema-Tool convert the underlying database type of your
new "mytype" directly into an instance of ``MyType`` you have to
additionally register this mapping with your database platform:
.. code-block:: php
<?php
$conn = $em->getConnection();
$conn->getDatabasePlatform()->registerDoctrineTypeMapping('db_mytype', 'mytype');
Now using Schema-Tool, whenever it detects a column having the
``db_mytype`` it will convert it into a ``mytype`` Doctrine Type
instance for Schema representation. Keep in mind that you can
easily produce clashes this way, each database type can only map to
exactly one Doctrine mapping type.
Custom ColumnDefinition
-----------------------
You can define a custom definition for each column using the "columnDefinition"
attribute of ``@Column``. You have to define all the definitions that follow
the name of a column here.
.. note::
Using columnDefinition will break change-detection in SchemaTool.
Identifiers / Primary Keys
--------------------------
Every entity class needs an identifier/primary key. You designate
the field that serves as the identifier with the ``@Id`` marker
annotation. Here is an example:
.. configuration-block::
.. code-block:: php
<?php
class MyPersistentClass
{
/** @Id @Column(type="integer") */
private $id;
//...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyPersistentClass">
<id name="id" type="integer" />
<field name="name" length="50" />
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyPersistentClass:
type: entity
id:
id:
type: integer
fields:
name:
length: 50
Without doing anything else, the identifier is assumed to be
manually assigned. That means your code would need to properly set
the identifier property before passing a new entity to
``EntityManager#persist($entity)``.
A common alternative strategy is to use a generated value as the
identifier. To do this, you use the ``@GeneratedValue`` annotation
like this:
.. configuration-block::
.. code-block:: php
<?php
class MyPersistentClass
{
/**
* @Id @Column(type="integer")
* @GeneratedValue
*/
private $id;
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyPersistentClass">
<id name="id" type="integer">
<generator strategy="AUTO" />
</id>
<field name="name" length="50" />
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyPersistentClass:
type: entity
id:
id:
type: integer
generator:
strategy: AUTO
fields:
name:
length: 50
This tells Doctrine to automatically generate a value for the
identifier. How this value is generated is specified by the
``strategy`` attribute, which is optional and defaults to 'AUTO'. A
value of ``AUTO`` tells Doctrine to use the generation strategy
that is preferred by the currently used database platform. See
below for details.
Identifier Generation Strategies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -312,23 +526,23 @@ Identifier Generation Strategies
The previous example showed how to use the default identifier
generation strategy without knowing the underlying database with
the AUTO-detection strategy. It is also possible to specify the
identifier generation strategy more explicitly, which allows you to
identifier generation strategy more explicitly, which allows to
make use of some additional features.
Here is the list of possible generation strategies:
- ``AUTO`` (default): Tells Doctrine to pick the strategy that is
preferred by the used database platform. The preferred strategies
are IDENTITY for MySQL, SQLite, MsSQL and SQL Anywhere and SEQUENCE
for Oracle and PostgreSQL. This strategy provides full portability.
are IDENTITY for MySQL, SQLite and MsSQL and SEQUENCE for Oracle
and PostgreSQL. This strategy provides full portability.
- ``SEQUENCE``: Tells Doctrine to use a database sequence for ID
generation. This strategy does currently not provide full
portability. Sequences are supported by Oracle, PostgreSql and
SQL Anywhere.
portability. Sequences are supported by Oracle and PostgreSql.
- ``IDENTITY``: Tells Doctrine to use special identity columns in
the database that generate a value on insertion of a row. This
strategy does currently not provide full portability and is
supported by the following platforms: MySQL/SQLite/SQL Anywhere
supported by the following platforms: MySQL/SQLite
(AUTO\_INCREMENT), MSSQL (IDENTITY) and PostgreSQL (SERIAL).
- ``TABLE``: Tells Doctrine to use a separate table for ID
generation. This strategy provides full portability.
@@ -337,8 +551,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
^^^^^^^^^^^^^^^^^^
@@ -352,27 +564,40 @@ besides specifying the sequence's name:
.. code-block:: php
<?php
class Message
class User
{
/**
* @Id
* @GeneratedValue(strategy="SEQUENCE")
* @SequenceGenerator(sequenceName="message_seq", initialValue=1, allocationSize=100)
* @SequenceGenerator(sequenceName="tablename_seq", initialValue=1, allocationSize=100)
*/
protected $id = null;
// ...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="Message">
<entity name="User">
<id name="id" type="integer">
<generator strategy="SEQUENCE" />
<sequence-generator sequence-name="message_seq" allocation-size="100" initial-value="1" />
<sequence-generator sequence-name="tablename_seq" allocation-size="100" initial-value="1" />
</id>
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyPersistentClass:
type: entity
id:
id:
type: integer
generator:
strategy: SEQUENCE
sequenceGenerator:
sequenceName: tablename_seq
allocationSize: 100
initialValue: 1
The initial value specifies at which value the sequence should
start.
@@ -397,6 +622,7 @@ need to access the sequence once to generate the identifiers for
configuration option is never larger than the actual sequences
INCREMENT BY value, otherwise you may get duplicate keys.
.. note::
It is possible to use strategy="AUTO" and at the same time
@@ -405,25 +631,29 @@ need to access the sequence once to generate the identifiers for
of the underlying platform is SEQUENCE, such as for Oracle and
PostgreSQL.
Composite Keys
~~~~~~~~~~~~~~
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
values yourself before calling ``EntityManager#persist()`` on the entity.
Doctrine 2 allows to use composite primary keys. There are however
some restrictions opposed to using a single identifier. The use of
the ``@GeneratedValue`` annotation is only supported for simple
(not composite) primary keys, which means you can only use
composite keys if you generate the primary key values yourself
before calling ``EntityManager#persist()`` on the entity.
More details on composite primary keys are discussed in a :doc:`dedicated tutorial
<../tutorials/composite-primary-keys>`.
To designate a composite primary key / identifier, simply put the
@Id marker annotation on all fields that make up the primary key.
Quoting Reserved Words
----------------------
Sometimes it is necessary to quote a column or table name because of reserved
word conflicts. Doctrine does not quote identifiers automatically, because it
leads to more problems than it would solve. Quoting tables and column names
needs to be done explicitly using ticks in the definition.
It may sometimes be necessary to quote a column or table name
because it conflicts with a reserved word of the particular RDBMS
in use. This is often referred to as "Identifier Quoting". To let
Doctrine know that you would like a table or column name to be
quoted in all SQL statements, enclose the table or column name in
backticks. Here is an example:
.. code-block:: php
@@ -436,26 +666,18 @@ according to the used database platform.
.. warning::
Identifier Quoting does not work for join column names or discriminator
column names unless you are using a custom ``QuoteStrategy``.
Identifier Quoting is not supported for join column
names or discriminator column names.
.. _reference-basic-mapping-custom-mapping-types:
.. warning::
.. versionadded: 2.3
Identifier Quoting is a feature that is mainly intended
to support legacy database schemas. The use of reserved words and
identifier quoting is generally discouraged. Identifier quoting
should not be used to enable the use non-standard-characters such
as a dash in a hypothetical column ``test-name``. Also Schema-Tool
will likely have troubles when quoting is used for case-sensitivity
reasons (in Oracle for example).
For more control over column quoting the ``Doctrine\ORM\Mapping\QuoteStrategy`` interface
was introduced in 2.3. It is invoked for every column, table, alias and other
SQL names. You can implement the QuoteStrategy and set it by calling
``Doctrine\ORM\Configuration#setQuoteStrategy()``.
.. versionadded: 2.4
The ANSI Quote Strategy was added, which assumes quoting is not necessary for any SQL name.
You can use it with the following code:
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\AnsiQuoteStrategy;
$configuration->setQuoteStrategy(new AnsiQuoteStrategy());
+11 -20
View File
@@ -15,6 +15,7 @@ especially what the strategies presented here provide help with.
you use the tools for your particular RDBMS for these bulk
operations.
Bulk Inserts
------------
@@ -41,8 +42,6 @@ internally but also mean more work during ``flush``.
$em->clear(); // Detaches all objects from Doctrine!
}
}
$em->flush(); // Persist objects that did not make up an entire batch
$em->clear();
Bulk Updates
------------
@@ -74,10 +73,10 @@ with the batching strategy that was already used for bulk inserts:
<?php
$batchSize = 20;
$i = 1;
$i = 0;
$q = $em->createQuery('select u from MyProject\Model\User u');
$iterableResult = $q->iterate();
foreach ($iterableResult as $row) {
foreach($iterableResult AS $row) {
$user = $row[0];
$user->increaseCredit();
$user->calculateNewBonuses();
@@ -95,11 +94,6 @@ with the batching strategy that was already used for bulk inserts:
fetch-join a collection-valued association. The nature of such SQL
result sets is not suitable for incremental hydration.
.. note::
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.
Bulk Deletes
------------
@@ -134,7 +128,7 @@ The following example shows how to do this:
<?php
$batchSize = 20;
$i = 1;
$i = 0;
$q = $em->createQuery('select u from MyProject\Model\User u');
$iterableResult = $q->iterate();
while (($row = $iterableResult->next()) !== false) {
@@ -153,6 +147,7 @@ The following example shows how to do this:
fetch-join a collection-valued association. The nature of such SQL
result sets is not suitable for incremental hydration.
Iterating Large Results for Data-Processing
-------------------------------------------
@@ -165,13 +160,13 @@ problems using the following approach:
.. code-block:: php
<?php
$q = $this->em->createQuery('select u from MyProject\Model\User u');
$q = $this->_em->createQuery('select u from MyProject\Model\User u');
$iterableResult = $q->iterate();
foreach ($iterableResult as $row) {
foreach ($iterableResult AS $row) {
// do stuff with the data in the row, $row[0] is always the object
// detach all entities from Doctrine, so that Garbage-Collection can kick in immediately
$this->em->clear();
// detach from Doctrine, so that it can be Garbage-Collected immediately
$this->_em->detach($row[0]);
}
.. note::
@@ -180,9 +175,5 @@ problems using the following approach:
fetch-join a collection-valued association. The nature of such SQL
result sets is not suitable for incremental hydration.
Packages for easing Batch Processing
------------------------------------
You can implement batch processing yourself, or use an existing
package such as `DoctrineBatchUtils <https://github.com/Ocramius/DoctrineBatchUtils>`_,
which already provides the logic described above in an encapsulated format.
+23 -4
View File
@@ -6,18 +6,36 @@ design generally refer to best practices when working with Doctrine
and do not necessarily reflect best practices for database design
in general.
Don't use public properties on entities
---------------------------------------
It is very important that you don't map public properties on
entities, but only protected or private ones. The reason for this
is simple, whenever you access a public property of a proxy object
that hasn't been initialized yet the return value will be null.
Doctrine cannot hook into this process and magically make the
entity lazy load.
This can create situations where it is very hard to debug the
current failure. We therefore urge you to map only private and
protected properties on entities and use getter methods or magic
\_\_get() to access them.
Constrain relationships as much as possible
-------------------------------------------
It is important to constrain relationships as much as possible.
This means:
- Impose a traversal direction (avoid bidirectional associations
if possible)
- Eliminate nonessential associations
This has several benefits:
- Reduced coupling in your domain model
- Simpler code in your domain model (no need to maintain
bidirectionality properly)
@@ -41,7 +59,7 @@ should use events judiciously.
Use cascades judiciously
------------------------
Automatic cascades of the persist/remove/refresh/etc. operations are
Automatic cascades of the persist/remove/merge/etc. operations are
very handy but should be used wisely. Do NOT simply add all
cascades to all associations. Think about which cascades actually
do make sense for you for a particular association, given the
@@ -52,7 +70,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
----------------------------
@@ -72,11 +90,11 @@ collections in entities in the constructor. Example:
<?php
namespace MyProject\Model;
use Doctrine\Common\Collections\ArrayCollection;
class User {
private $addresses;
private $articles;
public function __construct() {
$this->addresses = new ArrayCollection;
$this->articles = new ArrayCollection;
@@ -106,3 +124,4 @@ queries generally don't have any noticeable performance impact, it
is still preferable to use fewer, well-defined transactions that
are established through explicit transaction boundaries.
+108 -138
View File
@@ -4,51 +4,50 @@ Caching
Doctrine provides cache drivers in the ``Common`` package for some
of the most popular caching implementations such as APC, Memcache
and Xcache. We also provide an ``ArrayCache`` driver which stores
the data in a PHP array. Obviously, when using ``ArrayCache``, the
cache does not persist between requests, but this is useful for
testing in a development environment.
the data in a PHP array. Obviously, the cache does not live between
requests but this is useful for testing in a development
environment.
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
this interface.
base class ``Doctrine\Common\Cache\AbstractCache`` which implements
the before mentioned interface.
The interface defines the following public methods for you to implement:
The interface defines the following methods for you to publicly
use.
- 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
- delete($id) - Deletes a cache entry
Each driver extends the ``CacheProvider`` class which defines a few
- 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.
- delete($id) - Deletes a cache entry.
Each driver extends the ``AbstractCache`` class which defines a few
abstract protected methods that each of the drivers must
implement:
implement.
- doFetch($id)
- doContains($id)
- doSave($id, $data, $lifeTime = false)
- doDelete($id)
The public methods ``fetch()``, ``contains()`` etc. use the
above protected methods which are implemented by the drivers. The
- \_doFetch($id)
- \_doContains($id)
- \_doSave($id, $data, $lifeTime = false)
- \_doDelete($id)
The public methods ``fetch()``, ``contains()``, etc. utilize the
above protected methods that 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
~~~
In order to use the APC cache driver you must have it compiled and
enabled in your php.ini. You can read about APC
`in the PHP Documentation <https://php.net/apc>`_. It will give
`in the PHP Documentation <http://us2.php.net/apc>`_. It will give
you a little background information about what it is and how you
can use it as well as how to install it.
@@ -61,30 +60,12 @@ 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 <https://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
~~~~~~~~
In order to use the Memcache cache driver you must have it compiled
and enabled in your php.ini. You can read about Memcache
`on the PHP website <https://php.net/memcache>`_. It will
` on the PHP website <http://us2.php.net/memcache>`_. 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.
@@ -96,42 +77,17 @@ driver by itself.
<?php
$memcache = new Memcache();
$memcache->connect('memcache_host', 11211);
$cacheDriver = new \Doctrine\Common\Cache\MemcacheCache();
$cacheDriver->setMemcache($memcache);
$cacheDriver->save('cache_id', 'my_data');
Memcached
~~~~~~~~~
Memcached is a more recent and complete alternative extension to
Memcache.
In order to use the Memcached cache driver you must have it compiled
and enabled in your php.ini. You can read about Memcached
`on the PHP website <https://php.net/memcached>`_. 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 Memcached cache
driver by itself.
.. code-block:: php
<?php
$memcached = new Memcached();
$memcached->addServer('memcache_host', 11211);
$cacheDriver = new \Doctrine\Common\Cache\MemcachedCache();
$cacheDriver->setMemcached($memcached);
$cacheDriver->save('cache_id', 'my_data');
Xcache
~~~~~~
In order to use the Xcache cache driver you must have it compiled
and enabled in your php.ini. You can read about Xcache
`here <https://xcache.lighttpd.net/>`_. It will give you a little
`here <http://xcache.lighttpd.net/>`_. It will give you a little
background information about what it is and how you can use it as
well as how to install it.
@@ -148,10 +104,10 @@ Redis
~~~~~
In order to use the Redis cache driver you must have it compiled
and enabled in your php.ini. You can read about what Redis is
`from here <https://redis.io/>`_. Also check
`A PHP extension for Redis <https://github.com/nicolasff/phpredis/>`_ for how you can use
and install the Redis PHP extension.
and enabled in your php.ini. You can read about what is Redis
`from here <http://redis.io/>`_. Also check
`here <https://github.com/nicolasff/phpredis/>`_ for how you can use
and install Redis PHP extension.
Below is a simple example of how you could use the Redis cache
driver by itself.
@@ -170,8 +126,8 @@ Using Cache Drivers
-------------------
In this section we'll describe how you can fully utilize the API of
the cache drivers to save data to a cache, check if some cached data
exists, fetch the cached data and delete the cached data. We'll use the
the cache drivers to save cache, check if some cache exists, fetch
the cached data and delete the cached data. We'll use the
``ArrayCache`` implementation as our example here.
.. code-block:: php
@@ -182,7 +138,7 @@ exists, fetch the cached data and delete the cached data. We'll use the
Saving
~~~~~~
Saving some data to the cache driver is as simple as using the
To save some data to the cache driver it is as simple as using the
``save()`` method.
.. code-block:: php
@@ -191,7 +147,8 @@ Saving some data to the cache driver is as simple as using the
$cacheDriver->save('cache_id', 'my_data');
The ``save()`` method accepts three arguments which are described
below:
below.
- ``$id`` - The cache id
- ``$data`` - The cache entry/data.
@@ -213,7 +170,7 @@ object, etc.
Checking
~~~~~~~~
Checking whether cached data exists is very simple: just use the
Checking whether some cache exists is very simple, just use the
``contains()`` method. It accepts a single argument which is the ID
of the cache entry.
@@ -231,7 +188,7 @@ Fetching
Now if you want to retrieve some cache entry you can use the
``fetch()`` method. It also accepts a single argument just like
``contains()`` which is again the ID of the cache entry.
``contains()`` which is the ID of the cache entry.
.. code-block:: php
@@ -242,8 +199,9 @@ Deleting
~~~~~~~~
As you might guess, deleting is just as easy as saving, checking
and fetching. You can delete by an individual ID, or you can
delete all entries.
and fetching. We have a few ways to delete cache entries. You can
delete by an individual ID, regular expression, prefix, suffix or
you can delete all entries.
By Cache ID
^^^^^^^^^^^
@@ -267,7 +225,7 @@ the ``deleteAll()`` method.
Namespaces
~~~~~~~~~~
If you heavily use caching in your application and use it in
If you heavily use caching in your application and utilize it in
multiple parts of your application, or use it in different
applications on the same server you may have issues with cache
naming collisions. This can be worked around by using namespaces.
@@ -283,8 +241,8 @@ Integrating with the ORM
------------------------
The Doctrine ORM package is tightly integrated with the cache
drivers to allow you to improve the performance of various aspects of
Doctrine by simply making some additional configurations and
drivers to allow you to improve performance of various aspects of
Doctrine by just simply making some additional configurations and
method calls.
Query Cache
@@ -302,19 +260,20 @@ 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
~~~~~~~~~~~~
The result cache can be used to cache the results of your queries
so that we don't have to query the database again after the first time.
You just need to configure the result cache implementation.
so that we don't have to query the database or hydrate the data
again after the first time. You just need to configure the result
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.
@@ -331,7 +290,7 @@ result cache driver.
.. code-block:: php
<?php
$query->setResultCacheDriver(new \Doctrine\Common\Cache\ApcuCache());
$query->setResultCacheDriver(new \Doctrine\Common\Cache\ApcCache());
.. note::
@@ -344,6 +303,7 @@ result cache driver.
<?php
$query->useResultCache(false);
If you want to set the time the cache has to live you can use the
``setResultCacheLifetime()`` method.
@@ -373,7 +333,7 @@ Metadata Cache
~~~~~~~~~~~~~~
Your class metadata can be parsed from a few different sources like
XML, Annotations, etc. Instead of parsing this information on
YAML, XML, Annotations, etc. Instead of parsing this information on
each request we should cache it using one of the cache drivers.
Just like the query and result cache we need to configure it
@@ -382,7 +342,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.
@@ -390,80 +350,90 @@ the cache driver.
Clearing the Cache
------------------
We've already shown you how you can use the API of the
We've already shown you previously how you can use the API of the
cache drivers to manually delete cache entries. For your
convenience we offer command line tasks to help you with
convenience we offer a command line task for you to help you with
clearing the query, result and metadata cache.
From the Doctrine command line you can run the following commands:
To clear the query cache use the ``orm:clear-cache:query`` task.
From the Doctrine command line you can run the following command.
.. code-block:: php
$ ./doctrine orm:clear-cache:query
$ ./doctrine clear-cache
To clear the metadata cache use the ``orm:clear-cache:metadata`` task.
Running this task with no arguments will clear all the cache for
all the configured drivers. If you want to be more specific about
what you clear you can use the following options.
To clear the query cache use the ``--query`` option.
.. code-block:: php
$ ./doctrine orm:clear-cache:metadata
$ ./doctrine clear-cache --query
To clear the result cache use the ``orm:clear-cache:result`` task.
To clear the metadata cache use the ``--metadata`` option.
.. code-block:: php
$ ./doctrine orm:clear-cache:result
$ ./doctrine clear-cache --metadata
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.
To clear the result cache use the ``--result`` option.
.. 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,
]);
$ ./doctrine clear-cache --result
When you use the ``--result`` option you can use some other options
to be more specific about what queries result sets you want to
clear.
Just like the API of the cache drivers you can clear based on an
ID, regular expression, prefix or suffix.
.. code-block:: php
$ ./doctrine clear-cache --result --id=cache_id
Or if you want to clear based on a regular expressions.
.. code-block:: php
$ ./doctrine clear-cache --result --regex=users_.*
Or with a prefix.
.. code-block:: php
$ ./doctrine clear-cache --result --prefix=users_
And finally with a suffix.
.. code-block:: php
$ ./doctrine clear-cache --result --suffix=_my_account
.. note::
Using the ``--id``, ``--regex``, etc. options with the
``--query`` and ``--metadata`` are not allowed as it is not
necessary to be specific about what you clear. You only ever need
to completely clear the cache to remove stale entries.
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
-----------
Something to be careful of when using the cache drivers is
"cache slams". Imagine you have a heavily trafficked website with some
Something to be careful of when utilizing the cache drivers is
cache slams. If you have a heavily trafficked website with some
code that checks for the existence of a cache record and if it does
not exist it generates the information and saves it to the cache.
Now, if 100 requests were issued all at the same time and each one
sees the cache does not exist and they all try to insert the same
Now if 100 requests were issued all at the same time and each one
sees the cache does not exist and they all try and insert the same
cache entry it could lock up APC, Xcache, etc. and cause problems.
Ways exist to work around this, like pre-populating your cache and
not letting your users' requests populate the cache.
not letting your users requests populate the cache.
You can read more about cache slams
`in this blog post <http://notmysock.org/blog/php/user-cache-timebomb.html>`_.
+14 -13
View File
@@ -73,7 +73,7 @@ follows:
<?php
use Doctrine\Common\NotifyPropertyChanged,
Doctrine\Common\PropertyChangedListener;
/**
* @Entity
* @ChangeTrackingPolicy("NOTIFY")
@@ -81,12 +81,12 @@ follows:
class MyEntity implements NotifyPropertyChanged
{
// ...
private $listeners = array();
private $_listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener)
{
$this->listeners[] = $listener;
$this->_listeners[] = $listener;
}
}
@@ -99,30 +99,30 @@ behaviour:
<?php
// ...
class MyEntity implements NotifyPropertyChanged
{
// ...
protected function onPropertyChanged($propName, $oldValue, $newValue)
protected function _onPropertyChanged($propName, $oldValue, $newValue)
{
if ($this->listeners) {
foreach ($this->listeners as $listener) {
if ($this->_listeners) {
foreach ($this->_listeners as $listener) {
$listener->propertyChanged($this, $propName, $oldValue, $newValue);
}
}
}
public function setData($data)
{
if ($data != $this->data) {
$this->onPropertyChanged('data', $this->data, $data);
$this->_onPropertyChanged('data', $this->data, $data);
$this->data = $data;
}
}
}
You have to invoke ``onPropertyChanged`` inside every method that
You have to invoke ``_onPropertyChanged`` inside every method that
changes the persistent state of ``MyEntity``.
The check whether the new value is different from the old one is
@@ -148,3 +148,4 @@ effectiveness. It has the best performance characteristics of the 3
policies with larger units of work and a flush() operation is very
cheap when nothing has changed.
+21 -9
View File
@@ -1,7 +1,9 @@
Installation and Configuration
==============================
Doctrine can be installed with `Composer <https://getcomposer.org>`_.
Doctrine can be installed with `Composer <http://www.getcomposer.org>`_. For
older versions we still have `PEAR packages
<http://pear.doctrine-project.org>`_.
Define the following requirement in your ``composer.json`` file:
@@ -14,7 +16,8 @@ Define the following requirement in your ``composer.json`` file:
}
Then call ``composer install`` from your command line. If you don't know
how Composer works, check out their `Getting Started <https://getcomposer.org/doc/00-intro.md>`_ to set up.
how Composer works, check out their `Getting Started
<http://getcomposer.org/doc/00-intro.md>`_ to set up.
Class loading
-------------
@@ -44,7 +47,7 @@ access point to ORM functionality provided by Doctrine.
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;
$paths = array("/path/to/entity-files");
$paths = array("/path/to/entities-or-mapping-files");
$isDevMode = false;
// the connection configuration
@@ -63,18 +66,27 @@ Or if you prefer XML:
.. code-block:: php
<?php
$paths = array("/path/to/xml-mappings");
$config = Setup::createXMLMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);
Or if you prefer YAML:
.. code-block:: php
<?php
$config = Setup::createYAMLMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);
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.
- If `$isDevMode` is false, check for Caches in the order APC, Xcache, Memcache (127.0.0.1:11211), Redis (127.0.0.1:6379) unless `$cache` is passed as fourth argument.
- If `$isDevMode` is false, set then proxy classes have to be explicitly created through the command line.
- If `$devMode` is true always use an ``ArrayCache`` (in-memory) and regenerate proxies on every request.
- If `$devMode` is false, check for Caches in the order APC, Xcache, Memcache (127.0.0.1:11211), Redis (127.0.0.1:6379) unless `$cache` is passed as fourth argument.
- If `$devMode` is false, set then proxy classes have to be explicitly created
through the command line.
- If third argument `$proxyDir` is not set, use the systems temporary directory.
If you want to configure Doctrine in more detail, take a look at the :doc:`Advanced Configuration <reference/advanced-configuration>` section.
If you want to configure Doctrine in more detail, take a look at the :doc:`Advanced
Configuration <reference/advanced-configuration>` section.
.. note::
@@ -88,7 +100,7 @@ Doctrine ships with a number of command line tools that are very helpful
during development. You can call this command from the Composer binary
directory:
.. code-block:: sh
.. code-block::
$ php vendor/bin/doctrine
+128 -263
View File
@@ -1,8 +1,8 @@
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
@@ -18,6 +18,7 @@ querying that storage to pick a certain subset of your objects.
need to think about DQL as a query language for your object model,
not for your relational schema.
DQL is case in-sensitive, except for namespace, class and field
names, which are case sensitive.
@@ -33,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
@@ -48,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
@@ -58,6 +63,7 @@ Here is an example that selects all users with an age > 20:
Lets examine the query:
- ``u`` is a so called identification variable or alias that
refers to the ``MyProject\Model\User`` class. By placing this alias
in the SELECT clause we specify that we want all instances of the
@@ -77,57 +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
~~~~~
@@ -177,15 +140,16 @@ not need to lazy load the association with another query.
Doctrine allows you to walk all the associations between
all the objects in your domain model. Objects that were not already
loaded from the database are replaced with lazy loading proxy
instances. Non-loaded Collections are also replaced by lazy-loading
loaded from the database are replaced with lazy load proxy
instances. Non-loaded Collections are also replaced by lazy-load
instances that fetch all the contained objects upon first access.
However relying on the lazy-loading mechanism leads to many small
However relying on the lazy-load mechanism leads to many small
queries executed against the database, which can significantly
affect the performance of your application. **Fetch Joins** are the
solution to hydrate most or all of the entities that you need in a
single SELECT query.
Named and Positional Parameters
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -204,8 +168,7 @@ This section contains a large set of DQL queries and some
explanations of what is happening. The actual result also depends
on the hydration mode.
Hydrate all User entities
^^^^^^^^^^^^^^^^^^^^^^^^^
Hydrate all User entities:
.. code-block:: php
@@ -213,8 +176,7 @@ Hydrate all User entities
$query = $em->createQuery('SELECT u FROM MyProject\Model\User u');
$users = $query->getResult(); // array of User objects
Retrieve the IDs of all CmsUsers
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Retrieve the IDs of all CmsUsers:
.. code-block:: php
@@ -222,8 +184,7 @@ Retrieve the IDs of all CmsUsers
$query = $em->createQuery('SELECT u.id FROM CmsUser u');
$ids = $query->getResult(); // array of CmsUser ids
Retrieve the IDs of all users that have written an article
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Retrieve the IDs of all users that have written an article:
.. code-block:: php
@@ -231,10 +192,6 @@ Retrieve the IDs of all users that have written an article
$query = $em->createQuery('SELECT DISTINCT u.id FROM CmsArticle a JOIN a.user u');
$ids = $query->getResult(); // array of CmsUser ids
Retrieve all articles and sort them by username
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Retrieve all articles and sort them by the name of the articles
users instance:
@@ -244,8 +201,7 @@ users instance:
$query = $em->createQuery('SELECT a FROM CmsArticle a JOIN a.user u ORDER BY u.name ASC');
$articles = $query->getResult(); // array of CmsArticle objects
Retrieve the Username and Name of a CmsUser
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Retrieve the Username and Name of a CmsUser:
.. code-block:: php
@@ -254,8 +210,7 @@ Retrieve the Username and Name of a CmsUser
$users = $query->getResult(); // array of CmsUser username and name values
echo $users[0]['username'];
Retrieve a ForumUser and his single associated entity
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Retrieve a ForumUser and his single associated entity:
.. code-block:: php
@@ -264,8 +219,7 @@ Retrieve a ForumUser and his single associated entity
$users = $query->getResult(); // array of ForumUser objects with the avatar association loaded
echo get_class($users[0]->getAvatar());
Retrieve a CmsUser and fetch join all owning phonenumbers
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Retrieve a CmsUser and fetch join all the phonenumbers he has:
.. code-block:: php
@@ -274,8 +228,7 @@ Retrieve a CmsUser and fetch join all owning phonenumbers
$users = $query->getResult(); // array of CmsUser objects with the phonenumbers association loaded
$phonenumbers = $users[0]->getPhonenumbers();
Hydrate a result in Ascending
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Hydrate a result in Ascending:
.. code-block:: php
@@ -283,8 +236,7 @@ Hydrate a result in Ascending
$query = $em->createQuery('SELECT u FROM ForumUser u ORDER BY u.id ASC');
$users = $query->getResult(); // array of ForumUser objects
Hydrate a result in Descending Order
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Or in Descending Order:
.. code-block:: php
@@ -292,8 +244,7 @@ Hydrate a result in Descending Order
$query = $em->createQuery('SELECT u FROM ForumUser u ORDER BY u.id DESC');
$users = $query->getResult(); // array of ForumUser objects
Using Aggregate Functions
^^^^^^^^^^^^^^^^^^^^^^^^^
Using Aggregate Functions:
.. code-block:: php
@@ -304,8 +255,7 @@ Using Aggregate Functions
$query = $em->createQuery('SELECT u, count(g.id) FROM Entities\User u JOIN u.groups g GROUP BY u.id');
$result = $query->getResult();
Using WHERE Clause and Positional Parameter
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
With WHERE Clause and Positional Parameter:
.. code-block:: php
@@ -314,8 +264,7 @@ Using WHERE Clause and Positional Parameter
$query->setParameter(1, 321);
$users = $query->getResult(); // array of ForumUser objects
Using WHERE Clause and Named Parameter
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
With WHERE Clause and Named Parameter:
.. code-block:: php
@@ -324,13 +273,12 @@ Using WHERE Clause and Named Parameter
$query->setParameter('name', 'Bob');
$users = $query->getResult(); // array of ForumUser objects
Using Nested Conditions in WHERE Clause
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
With Nested Conditions in WHERE Clause:
.. code-block:: php
<?php
$query = $em->createQuery('SELECT u FROM ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id');
$query = $em->createQuery('SELECT u from ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id');
$query->setParameters(array(
'name' => 'Bob',
'name2' => 'Alice',
@@ -338,8 +286,7 @@ Using Nested Conditions in WHERE Clause
));
$users = $query->getResult(); // array of ForumUser objects
COUNT DISTINCT
^^^^^^^^^^^^^^
With COUNT DISTINCT:
.. code-block:: php
@@ -347,8 +294,7 @@ COUNT DISTINCT
$query = $em->createQuery('SELECT COUNT(DISTINCT u.name) FROM CmsUser');
$users = $query->getResult(); // array of ForumUser objects
Using Arithmetic Expression in WHERE clause
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
With Arithmetic Expression in WHERE clause:
.. code-block:: php
@@ -356,20 +302,6 @@ Using Arithmetic Expression in WHERE clause
$query = $em->createQuery('SELECT u FROM CmsUser u WHERE ((u.id + 5000) * u.id + 3) < 10000000');
$users = $query->getResult(); // array of ForumUser objects
Hide aliased columns from the result
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Retrieve user entities with Arithmetic Expression in ORDER clause, using the ``HIDDEN`` keyword:
.. code-block:: php
<?php
$query = $em->createQuery('SELECT u, u.posts_count + u.likes_count AS HIDDEN score FROM CmsUser u ORDER BY score');
$users = $query->getResult(); // array of User objects
Select all user-ids and optionally associated article-ids
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Using a LEFT JOIN to hydrate all user-ids and optionally associated
article-ids:
@@ -379,8 +311,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
@@ -389,8 +320,7 @@ Restricting a JOIN clause by additional conditions specified by WITH
$query->setParameter('foo', '%foo%');
$users = $query->getResult();
Using several Fetch JOINs
^^^^^^^^^^^^^^^^^^^^^^^^^
Using several Fetch JOINs:
.. code-block:: php
@@ -398,8 +328,7 @@ Using several Fetch JOINs
$query = $em->createQuery('SELECT u, a, p, c FROM CmsUser u JOIN u.articles a JOIN u.phonenumbers p JOIN a.comments c');
$users = $query->getResult();
BETWEEN in WHERE clause
^^^^^^^^^^^^^^^^^^^^^^^
BETWEEN in WHERE clause:
.. code-block:: php
@@ -409,8 +338,7 @@ BETWEEN in WHERE clause
$query->setParameter(2, 321);
$usernames = $query->getResult();
DQL Functions in WHERE clause
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
DQL Functions in WHERE clause:
.. code-block:: php
@@ -418,8 +346,7 @@ DQL Functions in WHERE clause
$query = $em->createQuery("SELECT u.name FROM CmsUser u WHERE TRIM(u.name) = 'someone'");
$usernames = $query->getResult();
IN() Expression
^^^^^^^^^^^^^^^
IN() Expression:
.. code-block:: php
@@ -433,8 +360,7 @@ IN() Expression
$query = $em->createQuery('SELECT u FROM CmsUser u WHERE u.id NOT IN (1)');
$users = $query->getResult();
CONCAT() DQL Function
^^^^^^^^^^^^^^^^^^^^^
CONCAT() DQL Function:
.. code-block:: php
@@ -448,7 +374,6 @@ CONCAT() DQL Function
$idUsernames = $query->getResult();
EXISTS in WHERE clause with correlated Subquery
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
@@ -456,8 +381,7 @@ EXISTS in WHERE clause with correlated Subquery
$query = $em->createQuery('SELECT u.id FROM CmsUser u WHERE EXISTS (SELECT p.phonenumber FROM CmsPhonenumber p WHERE p.user = u.id)');
$ids = $query->getResult();
Get all users who are members of $group
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Get all users who are members of $group.
.. code-block:: php
@@ -467,7 +391,6 @@ Get all users who are members of $group
$ids = $query->getResult();
Get all users that have more than 1 phonenumber
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
@@ -476,7 +399,6 @@ Get all users that have more than 1 phonenumber
$users = $query->getResult();
Get all users that have no phonenumber
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
@@ -484,11 +406,8 @@ Get all users that have no phonenumber
$query = $em->createQuery('SELECT u FROM CmsUser u WHERE u.phonenumbers IS EMPTY');
$users = $query->getResult();
Get all instances of a specific type
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Get all instances of a specific type, for use with inheritance hierarchies. These queries can be useful for
:doc:`inheritance mapping <inheritance-mapping>`.
Get all instances of a specific type, for use with inheritance
hierarchies:
.. versionadded:: 2.1
@@ -499,10 +418,7 @@ Get all instances of a specific type, for use with inheritance hierarchies. Thes
$query = $em->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF ?1');
$query = $em->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u NOT INSTANCE OF ?1');
Using IDENTITY() in queries
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Get all users visible on a given website that have chosen certain gender.
Get all users visible on a given website that have chosen certain gender:
.. versionadded:: 2.2
@@ -511,56 +427,13 @@ Get all users visible on a given website that have chosen certain gender.
<?php
$query = $em->createQuery('SELECT u FROM User u WHERE u.gender IN (SELECT IDENTITY(agl.gender) FROM Site s JOIN s.activeGenderList agl WHERE s.id = ?1)');
.. versionadded:: 2.4
Starting with 2.4, the IDENTITY() DQL function also works for composite primary keys
Starting with 2.4, the IDENTITY() DQL function also works for composite primary keys:
.. code-block:: php
<?php
$query = $em->createQuery("SELECT IDENTITY(c.location, 'latitude') AS latitude, IDENTITY(c.location, 'longitude') AS longitude FROM Checkpoint c WHERE c.user = ?1");
$query = $em->createQuery('SELECT IDENTITY(c.location, 'latitude') AS latitude, IDENTITY(c.location, 'longitude') AS longitude FROM Checkpoint c WHERE c.user = ?1');
Arbitrary Join
^^^^^^^^^^^^^^
Joins between entities without associations were not possible until version
2.4, where you can generate an arbitrary join with the following syntax:
.. code-block:: php
<?php
$query = $em->createQuery('SELECT u FROM User u JOIN Banlist b WITH u.email = b.email');
With an arbitrary join the result differs from the joins using a mapped property.
The result of an arbitrary join is an one dimensional array with a mix of the entity from the ``SELECT``
and the joined entity fitting to the filtering of the query. In case of the example with ``User``
and ``Banlist``, it can look like this:
- User
- Banlist
- Banlist
- User
- Banlist
- User
- Banlist
- Banlist
- Banlist
In this form of join, the ``Banlist`` entities found by the filtering in the ``WITH`` part are not fetched by an accessor
method on ``User``, but are already part of the result. In case the accessor method for Banlists is invoked on a User instance,
it loads all the related ``Banlist`` objects corresponding to this ``User``. This change of behaviour needs to be considered
when the DQL is switched to an arbitrary join.
.. 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
^^^^^^^^^^^^^^^^^^^^^
@@ -596,9 +469,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:
@@ -627,8 +500,6 @@ And then use the ``NEW`` DQL keyword :
$query = $em->createQuery('SELECT NEW CustomerDTO(c.name, e.email, a.city, SUM(o.value)) FROM Customer c JOIN c.email e JOIN c.address a JOIN c.orders o GROUP BY c');
$users = $query->getResult(); // array of CustomerDTO
Note that you can only pass scalar expressions to the constructor.
Using INDEX BY
~~~~~~~~~~~~~~
@@ -693,6 +564,7 @@ clause and using sub-selects.
``EntityManager#clear()`` and retrieve new instances of any
affected entity.
DELETE queries
--------------
@@ -710,14 +582,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
~~~~~~~~~~~~~
@@ -725,12 +595,13 @@ DQL Functions
The following functions are supported in SELECT, WHERE and HAVING
clauses:
- IDENTITY(single_association_path_expression [, fieldMapping]) - Retrieve the foreign key column of association of the owning side
- ABS(arithmetic_expression)
- IDENTITY(single\_association\_path\_expression [, fieldMapping]) - Retrieve the foreign key column of association of the owning side
- ABS(arithmetic\_expression)
- CONCAT(str1, str2)
- CURRENT_DATE() - Return the current date
- CURRENT_TIME() - Returns the current time
- CURRENT_TIMESTAMP() - Returns a timestamp of the current date
- CURRENT\_DATE() - Return the current date
- CURRENT\_TIME() - Returns the current time
- CURRENT\_TIMESTAMP() - Returns a timestamp of the current date
and time.
- LENGTH(str) - Returns the length of the given string
- LOCATE(needle, haystack [, offset]) - Locate the first
@@ -745,8 +616,8 @@ clauses:
- TRIM([LEADING \| TRAILING \| BOTH] ['trchar' FROM] str) - Trim
the string by the given trim char, defaults to whitespaces.
- UPPER(str) - Return the upper-case of the given string.
- DATE_ADD(date, days, unit) - Add the number of days to a given date. (Supported units are YEAR, MONTH, WEEK, DAY, HOUR, MINUTE, SECOND)
- DATE_SUB(date, days, unit) - Substract the number of days from a given date. (Supported units are YEAR, MONTH, WEEK, DAY, HOUR, MINUTE, SECOND)
- DATE_ADD(date, days, unit) - Add the number of days to a given date. (Supported units are DAY, MONTH)
- DATE_SUB(date, days, unit) - Substract the number of days from a given date. (Supported units are DAY, MONTH)
- DATE_DIFF(date1, date2) - Calculate the difference in days between date1-date2.
Arithmetic operators
@@ -770,6 +641,7 @@ Other Expressions
DQL offers a wide-range of additional expressions that are known
from SQL, here is a list of all the supported constructs:
- ``ALL/ANY/SOME`` - Used in a WHERE clause followed by a
sub-select this works like the equivalent constructs in SQL.
- ``BETWEEN a AND b`` and ``NOT BETWEEN a AND b`` can be used to
@@ -828,6 +700,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);
@@ -856,7 +730,7 @@ what type of results to expect.
Single Table
~~~~~~~~~~~~
`Single Table Inheritance <https://martinfowler.com/eaaCatalog/singleTableInheritance.html>`_
`Single Table Inheritance <http://martinfowler.com/eaaCatalog/singleTableInheritance.html>`_
is an inheritance mapping strategy where all classes of a hierarchy
are mapped to a single database table. In order to distinguish
which row represents which type in the hierarchy a so-called
@@ -949,7 +823,7 @@ entities:
Class Table Inheritance
~~~~~~~~~~~~~~~~~~~~~~~
`Class Table Inheritance <https://martinfowler.com/eaaCatalog/classTableInheritance.html>`_
`Class Table Inheritance <http://martinfowler.com/eaaCatalog/classTableInheritance.html>`_
is an inheritance mapping strategy where each class in a hierarchy
is mapped to several tables: its own table and the tables of all
parent classes. The table of a child class is linked to the table
@@ -994,6 +868,7 @@ you'll notice some differences:
) ENGINE = InnoDB;
ALTER TABLE Employee ADD FOREIGN KEY (id) REFERENCES Person(id) ON DELETE CASCADE
- The data is split between two tables
- A foreign key exists between the two tables
@@ -1009,6 +884,7 @@ automatically for you:
FROM Employee e1_ INNER JOIN Person p0_ ON e1_.id = p0_.id
WHERE p0_.name = ?
The Query class
---------------
@@ -1016,7 +892,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
@@ -1026,9 +902,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
~~~~~~~~~~~~~~~~~~~~
@@ -1039,13 +915,13 @@ mode specifies a particular way in which a SQL result set is
transformed. Each hydration mode has its own dedicated method on
the Query class. Here they are:
- ``Query#getResult()``: Retrieves a collection of objects. The
result is either a plain collection of objects (pure) or an array
where the objects are nested in the result rows (mixed).
- ``Query#getSingleResult()``: Retrieves a single object. If the
result contains more than one object, an ``NonUniqueResultException``
is thrown. If the result contains no objects, an ``NoResultException``
is thrown. The pure/mixed distinction does not apply.
result contains more than one or no object, an exception is thrown. The
pure/mixed distinction does not apply.
- ``Query#getOneOrNullResult()``: Retrieve a single object. If no
object is found null will be returned.
- ``Query#getArrayResult()``: Retrieves an array graph (a nested
@@ -1058,6 +934,8 @@ the Query class. Here they are:
graph in certain scenarios due to the difference of the identity
semantics between arrays and objects.
- ``Query#getScalarResult()``: Retrieves a flat/rectangular result
set of scalar values that can contain duplicate data. The
pure/mixed distinction does not apply.
@@ -1108,7 +986,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]
@@ -1135,11 +1013,13 @@ clause, we get a mixed result.
Conventions for mixed results are as follows:
- The object fetched in the FROM clause is always positioned with the key '0'.
- Every scalar without a name is numbered in the order given in the query, starting with 1.
- Every aliased scalar is given with its alias-name as the key. The case of the name is kept.
- If several objects are fetched from the FROM clause they alternate every row.
Here is how the result could look like:
.. code-block:: php
@@ -1179,6 +1059,7 @@ will return the rows iterating the different top-level entities.
[2] => Object (User)
[3] => Object (Group)
Hydration Modes
~~~~~~~~~~~~~~~
@@ -1188,10 +1069,11 @@ make best use of the different result formats:
The constants for the different hydration modes are:
- ``Query::HYDRATE_OBJECT``
- ``Query::HYDRATE_ARRAY``
- ``Query::HYDRATE_SCALAR``
- ``Query::HYDRATE_SINGLE_SCALAR``
- Query::HYDRATE\_OBJECT
- Query::HYDRATE\_ARRAY
- Query::HYDRATE\_SCALAR
- Query::HYDRATE\_SINGLE\_SCALAR
Object Hydration
^^^^^^^^^^^^^^^^
@@ -1204,22 +1086,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
^^^^^^^^^^^^^^^
@@ -1255,14 +1121,15 @@ object graph you can use scalar hydration:
The following assumptions are made about selected fields using
Scalar Hydration:
1. Fields from classes are prefixed by the DQL alias in the result.
A query of the kind 'SELECT u.name ..' returns a key 'u_name' in
A query of the kind 'SELECT u.name ..' returns a key 'u\_name' in
the result rows.
Single Scalar Hydration
^^^^^^^^^^^^^^^^^^^^^^^
If you have a query which returns just a single scalar value you can use
If you a query which returns just a single scalar value you can use
single scalar hydration:
.. code-block:: php
@@ -1290,14 +1157,13 @@ creating a class which extends ``AbstractHydrator``:
<?php
namespace MyProject\Hydrators;
use Doctrine\DBAL\FetchMode;
use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
class CustomHydrator extends AbstractHydrator
{
protected function _hydrateAll()
{
return $this->stmt->fetchAll(FetchMode::Associative);
return $this->_stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
@@ -1323,7 +1189,7 @@ There are situations when a query you want to execute returns a
very large result-set that needs to be processed. All the
previously described hydration modes completely load a result-set
into memory which might not be feasible with large result sets. See
the `Batch Processing <batch-processing.html>`_ section on details how
the `Batch Processing <batch-processing>`_ section on details how
to iterate large result sets.
Functions
@@ -1339,6 +1205,7 @@ Prepared Statements that use numerical or named wildcards require
additional parameters to be executable against the database. To
pass parameters to the query the following methods can be used:
- ``AbstractQuery::setParameter($param, $value)`` - Set the
numerical or named wildcard to the given value.
- ``AbstractQuery::setParameters(array $params)`` - Set an array
@@ -1393,6 +1260,7 @@ Result Cache API:
``Doctrine\ORM\Configuration`` instance so that it is passed to
every ``Query`` and ``NativeQuery`` instance.
Query Hints
^^^^^^^^^^^
@@ -1402,21 +1270,22 @@ exist mostly internal query hints that are not be consumed in
userland. However the following few hints are to be used in
userland:
- ``Query::HINT_FORCE_PARTIAL_LOAD`` - Allows to hydrate objects
- Query::HINT\_FORCE\_PARTIAL\_LOAD - Allows to hydrate objects
although not all their columns are fetched. This query hint can be
used to handle memory consumption problems with large result-sets
that contain char or binary data. Doctrine has no way of implicitly
reloading this data. Partially loaded objects have to be passed to
``EntityManager::refresh()`` if they are to be reloaded fully from
the database.
- ``Query::HINT_REFRESH`` - This query is used internally by
- Query::HINT\_REFRESH - This query is used internally by
``EntityManager::refresh()`` and can be used in userland as well.
If you specify this hint and a query returns the data for an entity
that is already managed by the UnitOfWork, the fields of the
existing entity will be refreshed. In normal operation a result-set
that loads data of an already existing entity is discarded in favor
of the already existing entity.
- ``Query::HINT_CUSTOM_TREE_WALKERS`` - An array of additional
- Query::HINT\_CUSTOM\_TREE\_WALKERS - An array of additional
``Doctrine\ORM\Query\TreeWalker`` instances that are attached to
the DQL query parsing process.
@@ -1437,6 +1306,7 @@ default. This also means you don't regularly need to fiddle with
the parameters of the Query Cache, however if you do there are
several methods to interact with it:
- ``Query::setQueryCacheDriver($driver)`` - Allows to set a Cache
instance
- ``Query::setQueryCacheLifeTime($seconds = 3600)`` - Set lifetime
@@ -1455,6 +1325,7 @@ well as specify the starting offset, Doctrine then uses a strategy
of manipulating the select query to return only the requested
number of results:
- ``Query::setMaxResults($maxResults)``
- ``Query::setFirstResult($offset)``
@@ -1480,7 +1351,7 @@ can mark a many-to-one or one-to-one association as fetched temporarily to batch
<?php
$query = $em->createQuery("SELECT u FROM MyProject\User u");
$query->setFetchMode("MyProject\User", "address", \Doctrine\ORM\Mapping\FetchMode::EAGER);
$query->setFetchMode("MyProject\User", "address", "EAGER");
$query->execute();
Given that there are 10 users and corresponding addresses in the database the executed queries will look something like:
@@ -1490,14 +1361,6 @@ Given that there are 10 users and corresponding addresses in the database the ex
SELECT * FROM users;
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.
EBNF
----
@@ -1510,6 +1373,7 @@ correct syntax for a particular query should be.
Document syntax:
~~~~~~~~~~~~~~~~
- non-terminals begin with an upper case character
- terminals begin with a lower case character
- parentheses (...) are used for grouping
@@ -1517,14 +1381,14 @@ Document syntax:
e.g. zero or one time
- curly brackets {...} are used for repetition, e.g. zero or more
times
- double quotation marks "..." define a terminal string
- a vertical bar \| represents an alternative
- double quotation marks "..." define a terminal string a vertical
bar \| represents an alternative
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
- identifier (name, email, ...)
- string ('foo', 'bar''s house', '%ninja%', ...)
- char ('/', '\\', ' ', ...)
- integer (-1, 0, 1, 34, ...)
@@ -1558,14 +1422,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 | identifier
/* Alias ResultVariable declaration (the "total" of "COUNT(*) AS total") */
AliasResultVariable = identifier
/* ResultVariable identifier usage of mapped field aliases (the "total" of "COUNT(*) AS total") */
ResultVariable = identifier
/* identifier that must be a class name (the "User" of "FROM User u") */
AbstractSchemaName ::= identifier
/* identifier that must be a field (the "name" of "u.name") */
/* This is responsible to know if the field exists in Object, no matter if it's a relation or a simple field */
@@ -1577,13 +1435,19 @@ Identifiers
/* identifier that must be a single-valued association field (to-one) (the "Group" of "u.Group") */
SingleValuedAssociationField ::= FieldIdentificationVariable
/* identifier that must be an embedded class state field */
/* identifier that must be an embedded class state field (for the future) */
EmbeddedClassStateField ::= FieldIdentificationVariable
/* identifier that must be a simple state field (name, email, ...) (the "name" of "u.name") */
/* The difference between this and FieldIdentificationVariable is only semantical, because it points to a single field (not mapping to a relation) */
SimpleStateField ::= FieldIdentificationVariable
/* Alias ResultVariable declaration (the "total" of "COUNT(*) AS total") */
AliasResultVariable = identifier
/* ResultVariable identifier usage of mapped field aliases (the "total" of "COUNT(*) AS total") */
ResultVariable = identifier
Path Expressions
~~~~~~~~~~~~~~~~
@@ -1633,7 +1497,7 @@ Items
.. code-block:: php
UpdateItem ::= SingleValuedPathExpression "=" NewValue
OrderByItem ::= (SimpleArithmeticExpression | SingleValuedPathExpression | ScalarExpression | ResultVariable | FunctionDeclaration) ["ASC" | "DESC"]
OrderByItem ::= (SimpleArithmeticExpression | SingleValuedPathExpression | ScalarExpression | ResultVariable) ["ASC" | "DESC"]
GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression
NewValue ::= SimpleArithmeticExpression | "NULL"
@@ -1642,11 +1506,11 @@ From, Join and Index by
.. code-block:: php
IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}*
SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration
IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}*
SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable)
JoinVariableDeclaration ::= Join [IndexBy]
RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy]
Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" (JoinAssociationDeclaration | RangeVariableDeclaration) ["WITH" ConditionalExpression]
Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression ["AS"] AliasIdentificationVariable ["WITH" ConditionalExpression]
IndexBy ::= "INDEX" "BY" StateFieldPathExpression
Select Expressions
@@ -1654,12 +1518,10 @@ Select Expressions
.. code-block:: php
SelectExpression ::= (IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression | NewObjectExpression) [["AS"] ["HIDDEN"] AliasResultVariable]
SelectExpression ::= (IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression) [["AS"] ["HIDDEN"] AliasResultVariable]
SimpleSelectExpression ::= (StateFieldPathExpression | IdentificationVariable | FunctionDeclaration | AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable]
PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")"
NewObjectArg ::= ScalarExpression | "(" Subselect ")"
Conditional Expressions
~~~~~~~~~~~~~~~~~~~~~~~
@@ -1675,6 +1537,7 @@ Conditional Expressions
EmptyCollectionComparisonExpression | CollectionMemberExpression |
InstanceOfExpression
Collection Expressions
~~~~~~~~~~~~~~~~~~~~~~
@@ -1720,7 +1583,7 @@ Scalar and Type Expressions
.. code-block:: php
ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | StateFieldPathExpression | BooleanPrimary | CaseExpression | InstanceOfExpression
StringExpression ::= StringPrimary | ResultVariable | "(" Subselect ")"
StringExpression ::= StringPrimary | "(" Subselect ")"
StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression
BooleanExpression ::= BooleanPrimary | "(" Subselect ")"
BooleanPrimary ::= StateFieldPathExpression | boolean | InputParameter
@@ -1738,7 +1601,8 @@ Aggregate Expressions
.. code-block:: php
AggregateExpression ::= ("AVG" | "MAX" | "MIN" | "SUM" | "COUNT") "(" ["DISTINCT"] SimpleArithmeticExpression ")"
AggregateExpression ::= ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" |
"COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedPathExpression) ")"
Case Expressions
~~~~~~~~~~~~~~~~
@@ -1764,11 +1628,11 @@ QUANTIFIED/BETWEEN/COMPARISON/LIKE/NULL/EXISTS
QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")"
BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression
ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression )
InExpression ::= ArithmeticExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")"
InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")"
InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")")
InstanceOfParameter ::= AbstractSchemaName | InputParameter
LikeExpression ::= StringExpression ["NOT"] "LIKE" StringPrimary ["ESCAPE" char]
NullComparisonExpression ::= (InputParameter | NullIfExpression | CoalesceExpression | AggregateExpression | FunctionDeclaration | IdentificationVariable | SingleValuedPathExpression | ResultVariable) "IS" ["NOT"] "NULL"
NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL"
ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")"
ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!="
@@ -1803,5 +1667,6 @@ Functions
"TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" |
"LOWER" "(" StringPrimary ")" |
"UPPER" "(" StringPrimary ")" |
"IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")"
"IDENTITY" "(" SingleValuedAssociationPathExpression ")"
+127 -157
View File
@@ -20,12 +20,12 @@ manager.
$evm = new EventManager();
Now we can add some event listeners to the ``$evm``. Let's create a
``TestEvent`` class to play around with.
``EventTest`` class to play around with.
.. code-block:: php
<?php
class TestEvent
class EventTest
{
const preFoo = 'preFoo';
const postFoo = 'postFoo';
@@ -52,15 +52,15 @@ Now we can add some event listeners to the ``$evm``. Let's create a
}
// Create a new instance
$test = new TestEvent($evm);
$test = new EventTest($evm);
Events can be dispatched by using the ``dispatchEvent()`` method.
.. code-block:: php
<?php
$evm->dispatchEvent(TestEvent::preFoo);
$evm->dispatchEvent(TestEvent::postFoo);
$evm->dispatchEvent(EventTest::preFoo);
$evm->dispatchEvent(EventTest::postFoo);
You can easily remove a listener with the ``removeEventListener()``
method.
@@ -129,15 +129,17 @@ with camelcase and the value of the corresponding constant should
be the name of the constant itself, even with spelling. This has
several reasons:
- It is easy to read.
- Simplicity.
- Each method within an EventSubscriber is named after the
corresponding constant's value. If the constant's name and value differ
it contradicts the intention of using the constant and makes your code
harder to maintain.
corresponding constant. If constant name and constant value differ,
you MUST use the new value and thus, your code might be subject to
codechanges when the value changes. This contradicts the intention
of a constant.
An example for a correct notation can be found in the example
``TestEvent`` above.
``EventTest`` above.
.. _reference-events-lifecycle-events:
@@ -147,6 +149,7 @@ Lifecycle Events
The EntityManager and UnitOfWork trigger a bunch of events during
the life-time of their registered entities.
- preRemove - The preRemove event occurs for a given entity before
the respective EntityManager remove operation for that entity is
executed. It is not called for a DQL DELETE statement.
@@ -156,14 +159,13 @@ the life-time of their registered entities.
- prePersist - The prePersist event occurs for a given entity
before the respective EntityManager persist operation for that
entity is executed. It should be noted that this event is only triggered on
*initial* persist of an entity (i.e. it does not trigger on future updates).
*initial* persist of an entity
- postPersist - The postPersist event occurs for an entity after
the entity has been made persistent. It will be invoked after the
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
@@ -171,13 +173,9 @@ the life-time of their registered entities.
database or after the refresh operation has been applied to it.
- loadClassMetadata - The loadClassMetadata event occurs after the
mapping metadata for a class has been loaded from a mapping source
(annotations/xml). This event is not a lifecycle callback.
- onClassMetadataNotFound - Loading class metadata for a particular
requested class name failed. Manipulating the given event args instance
allows providing fallback metadata even when no actual metadata exists
or could be found. This event is not a lifecycle callback.
(annotations/xml/yaml).
- 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.
@@ -185,22 +183,15 @@ the life-time of their registered entities.
event is not a lifecycle callback.
- onClear - The onClear event occurs when the EntityManager#clear() operation is
invoked, after all references to entities have been removed from the unit of
work. This event is not a lifecycle callback.
work.
.. warning::
Note that, when using ``Doctrine\ORM\AbstractQuery#iterate()``, ``postLoad``
events will be executed immediately after objects are being hydrated, and therefore
associations are not guaranteed to be initialized. It is not safe to combine
usage of ``Doctrine\ORM\AbstractQuery#iterate()`` and ``postLoad`` event
handlers.
Note that the postLoad event occurs for an entity
before any associations have been initialized. Therefore it is not
safe to access associations in a postLoad callback or event
handler.
.. warning::
Note that the postRemove event or any events triggered after an entity removal
can receive an uninitializable proxy in case you have configured an entity to
cascade remove relations. In this case, you should load yourself the proxy in
the associated pre event.
You can access the Event constants from the ``Events`` class in the
ORM package.
@@ -214,14 +205,12 @@ ORM package.
These can be hooked into by two different types of event
listeners:
- Lifecycle Callbacks are methods on the entity classes that are
called when the event is triggered. As of v2.4 they receive some kind
of ``EventArgs`` instance.
- Lifecycle Event Listeners and Subscribers are classes with specific callback
methods that receives some kind of ``EventArgs`` instance.
The EventArgs instance received by the listener gives access to the entity,
EntityManager and other relevant data.
- Lifecycle Callbacks are methods on the entity classes that are
called when the event is triggered. They receives some kind of ``EventArgs``.
- Lifecycle Event Listeners and Subscribers are classes with specific callback
methods that receives some kind of ``EventArgs`` instance which
give access to the entity, EntityManager or other relevant data.
.. note::
@@ -231,14 +220,14 @@ EntityManager and other relevant data.
:ref:`reference-events-implementing-listeners` section very carefully
to understand which operations are allowed in which lifecycle event.
Lifecycle Callbacks
-------------------
Lifecycle Callbacks are defined on an entity class. They allow you to
trigger callbacks whenever an instance of that entity class experiences
a relevant lifecycle event. More than one callback can be defined for each
lifecycle event. Lifecycle Callbacks are best used for simple operations
specific to a particular entity class's lifecycle.
A lifecycle event is a regular event with the additional feature of
providing a mechanism to register direct callbacks inside the
corresponding entity classes that are executed when the lifecycle
event occurs.
.. code-block:: php
@@ -288,12 +277,25 @@ specific to a particular entity class's lifecycle.
}
}
Note that the methods set as lifecycle callbacks need to be public and,
when using these annotations, you have to apply the
``@HasLifecycleCallbacks`` marker annotation on the entity class.
Note that when using annotations you have to apply the
@HasLifecycleCallbacks marker annotation on the entity class.
If you want to register lifecycle callbacks from XML it would look
something like this:
If you want to register lifecycle callbacks from YAML or XML you
can do it with the following.
.. code-block:: yaml
User:
type: entity
fields:
# ...
name:
type: string(50)
lifecycleCallbacks:
prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ]
postPersist: [ doStuffOnPostPersist ]
XML would look something like this:
.. code-block:: xml
@@ -302,7 +304,7 @@ 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">
@@ -315,14 +317,9 @@ something like this:
</doctrine-mapping>
In XML the ``type`` of the lifecycle-callback entry is the event that you
are triggering on and the ``method`` is the method to call. The allowed event
types are the ones listed in the previous Lifecycle Events section.
When using XML you need to remember to create public methods to match the
callback names you defined. E.g. in these examples ``doStuffOnPrePersist()``,
``doOtherStuffOnPrePersist()`` and ``doStuffOnPostPersist()`` methods need to be
defined on your ``User`` model.
You just need to make sure a public ``doStuffOnPrePersist()`` and
``doStuffOnPostPersist()`` method is defined on your ``User``
model.
.. code-block:: php
@@ -338,19 +335,18 @@ defined on your ``User`` model.
// ...
}
public function doOtherStuffOnPrePersist()
{
// ...
}
public function doStuffOnPostPersist()
{
// ...
}
}
The ``key`` of the lifecycleCallbacks is the name of the method and
the value is the event type. The allowed event types are the ones
listed in the previous Lifecycle Events section.
Lifecycle Callbacks Event Argument
----------------------------------
-----------------------------------
.. versionadded:: 2.4
@@ -379,13 +375,11 @@ Listening and subscribing to Lifecycle Events
Lifecycle event listeners are much more powerful than the simple
lifecycle callbacks that are defined on the entity classes. They
sit at a level above the entities and allow you to implement re-usable
behaviors across different entity classes.
Note that they require much more detailed knowledge about the inner
allow to implement re-usable behaviors between different entity
classes, yet 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
@@ -412,7 +406,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
@@ -482,8 +476,8 @@ data and lost updates/persists/removes.
For the described events that are also lifecycle callback events
the restrictions apply as well, with the additional restriction
that (prior to version 2.4) you do not have access to the
EntityManager or UnitOfWork APIs inside these events.
that you do not have access to the EntityManager or UnitOfWork APIs
inside these events.
prePersist
~~~~~~~~~~
@@ -503,12 +497,15 @@ which has access to the entity and the entity manager.
The following restrictions apply to ``prePersist``:
- If you are using a PrePersist Identity Generator such as
sequences the ID value will *NOT* be available within any
PrePersist events.
- Doctrine will not recognize changes made to relations in a prePersist
event. This includes modifications to
collections such as additions, removals or replacement.
- Doctrine will not recognize changes made to relations in a pre
persist event called by "reachability" through a cascade persist
unless you use the internal ``UnitOfWork`` API. We do not recommend
such operations in the persistence by reachability context, so do
this at your own risk and possibly supported by unit-tests.
preRemove
~~~~~~~~~
@@ -550,6 +547,7 @@ OnFlush is a very powerful event. It is called inside
entities and their associations have been computed. This means, the
``onFlush`` event has access to the sets of:
- Entities scheduled for insert
- Entities scheduled for update
- Entities scheduled for removal
@@ -570,23 +568,23 @@ mentioned sets. See this example:
$em = $eventArgs->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityInsertions() as $entity) {
foreach ($uow->getScheduledEntityInsertions() AS $entity) {
}
foreach ($uow->getScheduledEntityUpdates() as $entity) {
foreach ($uow->getScheduledEntityUpdates() AS $entity) {
}
foreach ($uow->getScheduledEntityDeletions() as $entity) {
foreach ($uow->getScheduledEntityDeletions() AS $entity) {
}
foreach ($uow->getScheduledCollectionDeletions() as $col) {
foreach ($uow->getScheduledCollectionDeletions() AS $col) {
}
foreach ($uow->getScheduledCollectionUpdates() as $col) {
foreach ($uow->getScheduledCollectionUpdates() AS $col) {
}
}
@@ -594,13 +592,14 @@ mentioned sets. See this example:
The following restrictions apply to the onFlush event:
- If you create and persist a new entity in ``onFlush``, then
- If you create and persist a new entity in "onFlush", then
calling ``EntityManager#persist()`` is not enough.
You have to execute an additional call to
``$unitOfWork->computeChangeSet($classMetadata, $entity)``.
- Changing primitive fields or associations requires you to
explicitly trigger a re-computation of the changeset of the
affected entity. This can be done by calling
affected entity. This can be done by either calling
``$unitOfWork->recomputeSingleEntityChangeSet($classMetadata, $entity)``.
postFlush
@@ -628,8 +627,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
@@ -642,6 +640,7 @@ This means you have access to all the fields that have changed for
this entity with their old and new value. The following methods are
available on the ``PreUpdateEventArgs``:
- ``getEntity()`` to get access to the actual entity.
- ``getEntityChangeSet()`` to get a copy of the changeset array.
Changes to this returned array do not affect updating.
@@ -695,12 +694,12 @@ lifecycle callback when there are expensive validations to call:
Restrictions for this event:
- Changes to associations of the passed entities are not
recognized by the flush operation anymore.
- Changes to fields of the passed entities are not recognized by
the flush operation anymore, use the computed change-set passed to
the event to modify primitive field values, e.g. use
``$eventArgs->setNewValue($field, $value);`` as in the Alice to Bob example above.
the event to modify primitive field values.
- Any calls to ``EntityManager#persist()`` or
``EntityManager#remove()``, even in combination with the UnitOfWork
API are strongly discouraged and don't work as expected outside the
@@ -713,7 +712,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
~~~~~~~~
@@ -726,9 +725,9 @@ Entity listeners
.. versionadded:: 2.4
An entity listener is a lifecycle listener class used for an entity.
An entity listeners is a lifecycle listener classes used for an entity.
- The entity listener's mapping may be applied to an entity class or mapped superclass.
- The entity listeners mapping may be applied to an entity class or mapped superclass.
- An entity listener is defined by mapping the entity class with the corresponding mapping.
.. configuration-block::
@@ -753,6 +752,13 @@ An entity listener is a lifecycle listener class used for an entity.
<!-- .... -->
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyProject\Entity\User:
type: entity
entityListeners:
UserListener:
# ....
.. _reference-entity-listeners:
@@ -763,10 +769,9 @@ An ``Entity Listener`` could be any class, by default it should be a class with
- Different from :ref:`reference-events-implementing-listeners` an ``Entity Listener`` is invoked just to the specified entity
- An entity listener method receives two arguments, the entity instance and the lifecycle event.
- The callback method can be defined by naming convention or specifying a method mapping.
- When a listener mapping is not given the parser will use the naming convention to look for a matching method,
e.g. it will look for a public ``preUpdate()`` method if you are listening to the ``preUpdate`` event.
- When a listener mapping is given the parser will not look for any methods using the naming convention.
- A callback method could be defined by naming convention or specifying a method mapping.
- When the listener mapping is not given the parser will lookup for methods that match with the naming convention.
- When the listener mapping is given the parser won't lookup for any naming convention.
.. code-block:: php
@@ -779,8 +784,8 @@ An ``Entity Listener`` could be any class, by default it should be a class with
}
}
To define a specific event listener method (one that does not follow the naming convention)
you need to map the listener method using the event type mapping:
To define a specific event listener method
you should map the listener method using the event type mapping.
.. configuration-block::
@@ -835,16 +840,32 @@ you need to map the listener method using the event type mapping:
<!-- .... -->
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyProject\Entity\User:
type: entity
entityListeners:
UserListener:
preFlush: [preFlushHandler]
postLoad: [postLoadHandler]
postPersist: [postPersistHandler]
prePersist: [prePersistHandler]
postUpdate: [postUpdateHandler]
preUpdate: [preUpdateHandler]
postRemove: [postRemoveHandler]
preRemove: [preRemoveHandler]
# ....
.. note::
The order of execution of multiple methods for the same event (e.g. multiple @PrePersist) is not guaranteed.
Entity listeners resolver
~~~~~~~~~~~~~~~~~~~~~~~~~
Doctrine invokes the listener resolver to get the listener instance.
~~~~~~~~~~~~~~~~~~~~~~~~~~
Doctrine invoke the listener resolver to get the listener instance.
- A resolver allows you register a specific entity listener instance.
- An resolver allows you register a specific ``Entity Listener`` instance.
- You can also implement your own resolver by extending ``Doctrine\ORM\Mapping\DefaultEntityListenerResolver`` or implementing ``Doctrine\ORM\Mapping\EntityListenerResolver``
Specifying an entity listener instance :
@@ -899,25 +920,25 @@ Implementing your own resolver :
}
}
// Configure the listener resolver only before instantiating the EntityManager
$configurations->setEntityListenerResolver(new MyEntityListenerResolver);
EntityManager::create(.., $configurations, ..);
// configure the listener resolver.
$em->getConfiguration()->setEntityListenerResolver($container->get('my_resolver'));
Load ClassMetadata Event
------------------------
When the mapping information for an entity is read, it is populated
in to a ``Doctrine\ORM\Mapping\ClassMetadata`` instance. You can hook in to this
in to a ``ClassMetadataInfo`` instance. You can hook in to this
process and manipulate the instance.
.. code-block:: php
<?php
$test = new TestEventListener();
$test = new EventTest();
$metadataFactory = $em->getMetadataFactory();
$evm = $em->getEventManager();
$evm->addEventListener(Doctrine\ORM\Events::loadClassMetadata, $test);
$evm->addEventListener(Events::loadClassMetadata, $test);
class TestEventListener
class EventTest
{
public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs)
{
@@ -931,55 +952,4 @@ process and manipulate the instance.
}
}
SchemaTool Events
-----------------
It is possible to access the schema metadata during schema changes that are happening in ``Doctrine\ORM\Tools\SchemaTool``.
There are two different events where you can hook in.
postGenerateSchemaTable
~~~~~~~~~~~~~~~~~~~~~~~
This event is fired for each ``Doctrine\DBAL\Schema\Table`` instance, after one was created and built up with the current class metadata
of an entity. It is possible to access to the current state of ``Doctrine\DBAL\Schema\Schema``, the current table schema
instance and class metadata.
.. code-block:: php
<?php
$test = new TestEventListener();
$evm = $em->getEventManager();
$evm->addEventListener(\Doctrine\ORM\Tools\ToolEvents::postGenerateSchemaTable, $test);
class TestEventListener
{
public function postGenerateSchemaTable(\Doctrine\ORM\Tools\Event\GenerateSchemaTableEventArgs $eventArgs)
{
$classMetadata = $eventArgs->getClassMetadata();
$schema = $eventArgs->getSchema();
$table = $eventArgs->getClassTable();
}
}
postGenerateSchema
~~~~~~~~~~~~~~~~~~
This event is fired after the schema instance was successfully built and before SQL queries are generated from the
schema information of ``Doctrine\DBAL\Schema\Schema``. It allows to access the full object representation of the database schema
and the EntityManager.
.. code-block:: php
<?php
$test = new TestEventListener();
$evm = $em->getEventManager();
$evm->addEventListener(\Doctrine\ORM\Tools\ToolEvents::postGenerateSchema, $test);
class TestEventListener
{
public function postGenerateSchema(\Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs $eventArgs)
{
$schema = $eventArgs->getSchema();
$em = $eventArgs->getEntityManager();
}
}
+30 -6
View File
@@ -13,7 +13,7 @@ Database Schema
How do I set the charset and collation for MySQL tables?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can't set these values inside the annotations or xml mapping files. To make a database
You can't set these values inside the annotations, yml or xml mapping files. To make a database
work with the default charset and collation you should configure MySQL to use it as default charset,
or create the database with charset and collation details. This way they get inherited to all newly
created database tables and columns.
@@ -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?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -52,7 +58,7 @@ or adding entities to a collection twice. You have to check for both conditions
in the code before calling ``$em->flush()`` if you know that unique constraint failures
can occur.
In `Symfony2 <https://www.symfony.com>`_ for example there is a Unique Entity Validator
In `Symfony2 <http://www.symfony.com>`_ for example there is a Unique Entity Validator
to achieve this task.
For collections you can check with ``$collection->contains($entity)`` if an entity is already
@@ -102,6 +108,7 @@ foreign keys as primary keys feature of Doctrine introduced in version 2.1.
See :doc:`the tutorial on composite primary keys for more information<../tutorials/composite-primary-keys>`.
How can i paginate fetch-joined collections?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -111,8 +118,8 @@ over this collection using a LIMIT statement (or vendor equivalent).
Doctrine does not offer a solution for this out of the box but there are several extensions
that do:
* `DoctrineExtensions <https://github.com/beberlei/DoctrineExtensions>`_
* `Pagerfanta <https://github.com/whiteoctober/pagerfanta>`_
* `DoctrineExtensions <http://github.com/beberlei/DoctrineExtensions>`_
* `Pagerfanta <http://github.com/whiteoctober/pagerfanta>`_
Why does pagination not work correctly with fetch joins?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -129,19 +136,36 @@ Inheritance
Can I use Inheritance with Doctrine 2?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Yes, you can use Single- or Joined-Table Inheritance in Doctrine 2.
See the documentation chapter on :doc:`inheritance mapping <inheritance-mapping>` for
the details.
Why does Doctrine not create proxy objects for my inheritance hierarchy?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you set a many-to-one or one-to-one association target-entity to any parent class of
an inheritance hierarchy Doctrine does not know what PHP class the foreign is actually of.
To find this out it has to execute a SQL query to look this information up in the database.
EntityGenerator
---------------
Why does the EntityGenerator not do X?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The EntityGenerator is not a full fledged code-generator that solves all tasks. Code-Generation
is not a first-class priority in Doctrine 2 anymore (compared to Doctrine 1). The EntityGenerator
is supposed to kick-start you, but not towards 100%.
Why does the EntityGenerator not generate inheritance correctly?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Just from the details of the discriminator map the EntityGenerator cannot guess the inheritance hierarchy.
This is why the generation of inherited entities does not fully work. You have to adjust some additional
code to get this one working correctly.
Performance
-----------
+5 -1
View File
@@ -16,6 +16,7 @@ By adding SQL to the conditional clauses of queries, the filter system filters
out rows belonging to the entities at the level of the SQL result set. This
means that the filtered entities are never hydrated (which can be expensive).
Example filter class
--------------------
Throughout this document the example ``MyLocaleFilter`` class will be used to
@@ -46,7 +47,7 @@ proper quoting of parameters.
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
{
// Check if the entity implements the LocalAware interface
if (!$targetEntity->getReflectionClass()->implementsInterface('LocaleAware')) {
if (!$targetEntity->reflClass->implementsInterface('LocaleAware')) {
return "";
}
@@ -54,6 +55,7 @@ proper quoting of parameters.
}
}
Configuration
-------------
Filter classes are added to the configuration as following:
@@ -63,9 +65,11 @@ Filter classes are added to the configuration as following:
<?php
$config->addFilter("locale", "\Doctrine\Tests\ORM\Functional\MyLocaleFilter");
The ``Configuration#addFilter()`` method takes a name for the filter and the name of the
class responsible for the actual filtering.
Disabling/Enabling Filters and Setting Parameters
---------------------------------------------------
Filters can be disabled and enabled via the ``FilterCollection`` which is
+3 -5
View File
@@ -11,9 +11,10 @@ request and can greatly improve performance.
"If you care about performance and don't use a bytecode
cache then you don't really care about performance. Please get one
and start using it."
*Stas Malyshev, Core Contributor to PHP and Zend Employee*
Metadata and Query caches
-------------------------
@@ -51,7 +52,7 @@ for more information on how this fetch mode works.
Temporarily change fetch mode in DQL
------------------------------------
See :ref:`dql-temporarily-change-fetch-mode`
See :ref:`Doctrine Query Language chapter <dql-temporarily-change-fetch-mode>`
Apply Best Practices
@@ -60,7 +61,4 @@ Apply Best Practices
A lot of the points mentioned in the Best Practices chapter will
also positively affect the performance of Doctrine.
Change Tracking policies
------------------------
See: :doc:`Change Tracking Policies <change-tracking-policies>`
+139 -89
View File
@@ -25,44 +25,36 @@ appear in the middle of an otherwise mapped inheritance hierarchy
For further support of inheritance, the single or
joined table inheritance features have to be used.
Example:
.. code-block:: php
<?php
/** @MappedSuperclass */
class Person
class MappedSuperclassBase
{
/** @Column(type="integer") */
protected $mapped1;
/** @Column(type="string") */
protected $mapped2;
/**
* @OneToOne(targetEntity="Toothbrush")
* @JoinColumn(name="toothbrush_id", referencedColumnName="id")
* @OneToOne(targetEntity="MappedSuperclassRelated1")
* @JoinColumn(name="related1_id", referencedColumnName="id")
*/
protected $toothbrush;
protected $mappedRelated1;
// ... more fields and methods
}
/** @Entity */
class Employee extends Person
class EntitySubClass extends MappedSuperclassBase
{
/** @Id @Column(type="integer") */
private $id;
/** @Column(type="string") */
private $name;
// ... more fields and methods
}
/** @Entity */
class Toothbrush
{
/** @Id @Column(type="integer") */
private $id;
// ... more fields and methods
}
@@ -81,7 +73,7 @@ defined on that class directly.
Single Table Inheritance
------------------------
`Single Table Inheritance <https://martinfowler.com/eaaCatalog/singleTableInheritance.html>`_
`Single Table Inheritance <http://martinfowler.com/eaaCatalog/singleTableInheritance.html>`_
is an inheritance mapping strategy where all classes of a hierarchy
are mapped to a single database table. In order to distinguish
which row represents which type in the hierarchy a so-called
@@ -89,49 +81,45 @@ discriminator column is used.
Example:
.. configuration-block::
.. code-block:: php
.. code-block:: php
<?php
namespace MyProject\Model;
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
class Person
{
// ...
}
/**
* @Entity
*/
class Employee extends Person
{
// ...
}
<?php
namespace MyProject\Model;
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
class Person
{
// ...
}
/**
* @Entity
*/
class Employee extends Person
{
// ...
}
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
type ``Person`` and "employee" identifies a row as being of type
``Employee``.
- All entity classes that is part of the mapped entity hierarchy
(including the topmost class) should be specified in the
@DiscriminatorMap. In the case above Person class included.
- The names of the classes in the discriminator map do not need to
be fully qualified if the classes are contained in the same
namespace as the entity class on which the discriminator map is
applied.
- If no discriminator map is provided, an exception will be thrown.
Design-time considerations
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -149,12 +137,12 @@ 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
association is an STI entity, it is preferable for performance reasons that it
be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
Inheritance: If the target-entity of a many-to-one or one-to-one
association is an STI entity, it is preferable for performance reasons that it
be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
Otherwise Doctrine *CANNOT* create proxy instances
of this entity and will *ALWAYS* load the entity eagerly.
@@ -164,14 +152,14 @@ SQL Schema considerations
For Single-Table-Inheritance to work in scenarios where you are
using either a legacy database schema or a self-written database
schema you have to make sure that all columns that are not in the
root entity but in any of the different sub-entities has to allow
root entity but in any of the different sub-entities has to allows
null values. Columns that have NOT NULL constraints have to be on
the root entity of the single-table inheritance hierarchy.
Class Table Inheritance
-----------------------
`Class Table Inheritance <https://martinfowler.com/eaaCatalog/classTableInheritance.html>`_
`Class Table Inheritance <http://martinfowler.com/eaaCatalog/classTableInheritance.html>`_
is an inheritance mapping strategy where each class in a hierarchy
is mapped to several tables: its own table and the tables of all
parent classes. The table of a child class is linked to the table
@@ -186,7 +174,7 @@ Example:
<?php
namespace MyProject\Model;
/**
* @Entity
* @InheritanceType("JOINED")
@@ -197,7 +185,7 @@ Example:
{
// ...
}
/** @Entity */
class Employee extends Person
{
@@ -206,6 +194,7 @@ Example:
Things to note:
- The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap
must be specified on the topmost class that is part of the mapped
entity hierarchy.
@@ -218,7 +207,6 @@ Things to note:
be fully qualified if the classes are contained in the same
namespace as the entity class on which the discriminator map is
applied.
- If no discriminator map is provided, an exception will be thrown.
.. note::
@@ -228,6 +216,7 @@ Things to note:
``ON DELETE CASCADE`` in all database implementations. A failure to
implement this yourself will lead to dead rows in the database.
Design-time considerations
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -254,9 +243,9 @@ themselves on access of any subtype fields, so accessing fields of
subtypes after such a query is not safe.
There is a general performance consideration with Class Table
Inheritance: If the target-entity of a many-to-one or one-to-one
association is a CTI entity, it is preferable for performance reasons that it
be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
Inheritance: If the target-entity of a many-to-one or one-to-one
association is a CTI entity, it is preferable for performance reasons that it
be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
Otherwise Doctrine *CANNOT* create proxy instances
of this entity and will *ALWAYS* load the entity eagerly.
@@ -271,7 +260,6 @@ or auto-increment details). Furthermore each child table has to
have a foreign key pointing from the id column to the root table id
column and cascading on delete.
.. _inheritence_mapping_overrides:
Overrides
---------
@@ -279,6 +267,7 @@ Used to override a mapping for an entity field or relationship.
May be applied to an entity that extends a mapped superclass
to override a relationship or field mapping defined by the mapped superclass.
Association Override
~~~~~~~~~~~~~~~~~~~~
Override a mapping for an entity relationship.
@@ -300,7 +289,7 @@ Example:
*/
class User
{
// other fields mapping
//other fields mapping
/**
* @ManyToMany(targetEntity="Group", inversedBy="users")
@@ -350,7 +339,8 @@ Example:
<many-to-many field="groups" target-entity="Group" inversed-by="users">
<cascade>
<cascade-persist/>
<cascade-refresh/>
<cascade-merge/>
<cascade-detach/>
</cascade>
<join-table name="users_groups">
<join-columns>
@@ -386,6 +376,51 @@ Example:
</association-overrides>
</entity>
</doctrine-mapping>
.. code-block:: yaml
# user mapping
MyProject\Model\User:
type: mappedSuperclass
# other fields mapping
manyToOne:
address:
targetEntity: Address
joinColumn:
name: address_id
referencedColumnName: id
cascade: [ persist, merge ]
manyToMany:
groups:
targetEntity: Group
joinTable:
name: users_groups
joinColumns:
user_id:
referencedColumnName: id
inverseJoinColumns:
group_id:
referencedColumnName: id
cascade: [ persist, merge, detach ]
# admin mapping
MyProject\Model\Admin:
type: entity
associationOverride:
address:
joinColumn:
adminaddress_id:
name: adminaddress_id
referencedColumnName: id
groups:
joinTable:
name: users_admingroups
joinColumns:
adminuser_id:
referencedColumnName: id
inverseJoinColumns:
admingroup_id:
referencedColumnName: id
Things to note:
@@ -393,8 +428,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
~~~~~~~~~~~~~~~~~~~~
@@ -432,7 +465,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",
@@ -440,7 +473,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
* )
* )
* })
@@ -461,7 +494,7 @@ Could be used by an entity that extends a mapped superclass to override a field
<many-to-one field="address" target-entity="Address">
<cascade>
<cascade-persist/>
<cascade-refresh/>
<cascade-merge/>
</cascade>
<join-column name="address_id" referenced-column-name="id"/>
</many-to-one>
@@ -482,28 +515,45 @@ Could be used by an entity that extends a mapped superclass to override a field
</attribute-overrides>
</entity>
</doctrine-mapping>
.. code-block:: yaml
# user mapping
MyProject\Model\User:
type: mappedSuperclass
id:
id:
type: integer
column: user_id
length: 150
generator:
strategy: AUTO
fields:
name:
type: string
column: user_name
length: 250
nullable: true
unique: false
#other fields mapping
# guest mapping
MyProject\Model\Guest:
type: entity
attributeOverride:
id:
column: guest_id
type: integer
length: 140
name:
column: guest_name
type: string
length: 240
nullable: false
unique: true
Things to note:
- The "attribute override" specifies the overrides base on the property name.
- The column type *CANNOT* be changed. If the column type is not equal you get a ``MappingException``
- The override can redefine all the columns except the type.
Query the Type
--------------
It may happen that the entities of a special type should be queried. Because there
is no direct access to the discriminator column, Doctrine provides the
``INSTANCE OF`` construct.
The following example shows how to use ``INSTANCE OF``. There is a three level hierarchy
with a base entity ``NaturalPerson`` which is extended by ``Staff`` which in turn
is extended by ``Technician``.
Querying for the staffs without getting any technicians can be achieved by this DQL:
.. code-block:: php
<?php
$query = $em->createQuery("SELECT staff FROM MyProject\Model\Staff staff WHERE staff NOT INSTANCE OF MyProject\Model\Technician");
$staffs = $query->getResult();
- The column type *CANNOT* be changed. if the column type is not equals you got a ``MappingException``
- The override can redefine all the column except the type.
+2 -1
View File
@@ -1,4 +1,5 @@
Installation
============
The installation chapter has moved to :doc:`Installation and Configuration <reference/configuration>`_.
The installation chapter has moved to `Installation and Configuration
<reference/configuration>`_.
@@ -39,7 +39,7 @@ possible either. See the following example:
name VARCHAR,
PRIMARY KEY(id)
);
CREATE TABLE product_attributes (
product_id INTEGER,
attribute_name VARCHAR,
@@ -63,7 +63,28 @@ Where the ``attribute_name`` column contains the key and
``$attributes``.
The feature request for persistence of primitive value arrays
`is described in the DDC-298 ticket <https://github.com/doctrine/orm/issues/3743>`_.
`is described in the DDC-298 ticket <http://www.doctrine-project.org/jira/browse/DDC-298>`_.
Value Objects
~~~~~~~~~~~~~
There is currently no native support value objects in Doctrine
other than for ``DateTime`` instances or if you serialize the
objects using ``serialize()/deserialize()`` which the DBAL Type
"object" supports.
The feature request for full value-object support
`is described in the DDC-93 ticket <http://www.doctrine-project.org/jira/browse/DDC-93>`_.
Cascade Merge with Bi-directional Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are two bugs now that concern the use of cascade merge in combination with bi-directional associations.
Make sure to study the behavior of cascade merge if you are using it:
- `DDC-875 <http://www.doctrine-project.org/jira/browse/DDC-875>`_ Merge can sometimes add the same entity twice into a collection
- `DDC-763 <http://www.doctrine-project.org/jira/browse/DDC-763>`_ Cascade merge on associated entities can insert too many rows through "Persistence by Reachability"
Custom Persisters
~~~~~~~~~~~~~~~~~
@@ -74,8 +95,10 @@ Currently there is no way to overwrite the persister implementation
for a given entity, however there are several use-cases that can
benefit from custom persister implementations:
- `Add Upsert Support <https://github.com/doctrine/orm/issues/5178>`_
- `Evaluate possible ways in which stored-procedures can be used <https://github.com/doctrine/orm/issues/4946>`_
- `Add Upsert Support <http://www.doctrine-project.org/jira/browse/DDC-668>`_
- `Evaluate possible ways in which stored-procedures can be used <http://www.doctrine-project.org/jira/browse/DDC-445>`_
- The previous Filter Rules Feature Request
Persist Keys of Collections
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -85,7 +108,7 @@ PHP Arrays are ordered hash-maps and so should be the
evaluate a feature that optionally persists and hydrates the keys
of a Collection instance.
`Ticket DDC-213 <https://github.com/doctrine/orm/issues/2817>`_
`Ticket DDC-213 <http://www.doctrine-project.org/jira/browse/DDC-213>`_
Mapping many tables to one entity
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -103,10 +126,11 @@ in the core library. We don't think behaviors add more value than
they cost pain and debugging hell. Please see the many different
blog posts we have written on this topics:
- `Doctrine2 "Behaviors" in a Nutshell <http://www.doctrine-project.org/2010/02/17/doctrine2-behaviours-nutshell.html>`_
- `A re-usable Versionable behavior for Doctrine2 <http://www.doctrine-project.org/2010/02/24/doctrine2-versionable.html>`_
- `Write your own ORM on top of Doctrine2 <http://www.doctrine-project.org/2010/07/19/your-own-orm-doctrine2.html>`_
- `Doctrine 2 Behavioral Extensions <http://www.doctrine-project.org/2010/11/18/doctrine2-behavioral-extensions.html>`_
- `Doctrine2 "Behaviors" in a Nutshell <http://www.doctrine-project.org/blog/doctrine2-behaviours-nutshell>`_
- `A re-usable Versionable behavior for Doctrine2 <http://www.doctrine-project.org/blog/doctrine2-versionable>`_
- `Write your own ORM on top of Doctrine2 <http://www.doctrine-project.org/blog/your-own-orm-doctrine2>`_
- `Doctrine 2 Behavioral Extensions <http://www.doctrine-project.org/blog/doctrine2-behavioral-extensions>`_
- `Doctrator <https://github.com/pablodip/doctrator`>_
Doctrine 2 has enough hooks and extension points so that **you** can
add whatever you want on top of it. None of this will ever become
@@ -121,8 +145,9 @@ included in the core of Doctrine 2. However there are already two
extensions out there that offer support for Nested Set with
Doctrine 2:
- `Doctrine2 Hierarchical-Structural Behavior <https://github.com/guilhermeblanco/Doctrine2-Hierarchical-Structural-Behavior>`_
- `Doctrine2 NestedSet <https://github.com/blt04/doctrine2-nestedset>`_
- `Doctrine2 Hierarchical-Structural Behavior <http://github.com/guilhermeblanco/Doctrine2-Hierarchical-Structural-Behavior>`_
- `Doctrine2 NestedSet <http://github.com/blt04/doctrine2-nestedset>`_
Known Issues
------------
@@ -133,7 +158,9 @@ backwards compatibility issues or where no simple fix exists (yet).
We don't plan to add every bug in the tracker there, just those
issues that can potentially cause nightmares or pain of any sort.
See bugs, improvement and feature requests on `Github issues <https://github.com/doctrine/orm/issues>`_.
See the Open Bugs on Jira for more details on `bugs, improvement and feature
requests
<http://www.doctrine-project.org/jira/secure/IssueNavigator.jspa?reset=true&mode=hide&pid=10032&resolution=-1&sorter/field=updated&sorter/order=DESC>`_.
Identifier Quoting and Legacy Databases
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -143,6 +170,7 @@ edge case problems Doctrine 2 does **NOT** do automatic identifier
quoting. This can lead to problems when trying to get
legacy-databases to work with Doctrine 2.
- You can quote column-names as described in the
:doc:`Basic-Mapping <basic-mapping>` section.
- You cannot quote join column names.
@@ -159,10 +187,3 @@ Microsoft SQL Server and Doctrine "datetime"
Doctrine assumes that you use ``DateTime2`` data-types. If your legacy database contains DateTime
datatypes then you have to add your own data-type (see Basic Mapping for an example).
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.
+31 -23
View File
@@ -11,8 +11,10 @@ Core Metadata Drivers
Doctrine provides a few different ways for you to specify your
metadata:
- **XML files** (XmlDriver)
- **Class DocBlock Annotations** (AnnotationDriver)
- **YAML files** (YamlDriver)
- **PHP Code in files or static functions** (PhpDriver)
Something important to note about the above drivers is they are all
@@ -33,7 +35,8 @@ 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
just need to configure it. All the drivers are in the
@@ -56,26 +59,26 @@ implements the ``Driver`` interface:
<?php
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
interface Driver
{
/**
* Loads the metadata for the specified class into the provided container.
*
*
* @param string $className
* @param ClassMetadata $metadata
* @param ClassMetadataInfo $metadata
*/
function loadMetadataForClass($className, ClassMetadata $metadata);
function loadMetadataForClass($className, ClassMetadataInfo $metadata);
/**
* Gets the names of all mapped classes known to this driver.
*
*
* @return array The names of all mapped classes known to this driver.
*/
function getAllClassNames();
function getAllClassNames();
/**
* Whether the class with the specified name should have its metadata loaded.
* This is only the case if it is either mapped as an Entity or a
@@ -99,22 +102,22 @@ the ``AbstractFileDriver`` implementation for you to extend from:
/**
* {@inheritdoc}
*/
protected $fileExtension = '.dcm.ext';
protected $_fileExtension = '.dcm.ext';
/**
* {@inheritdoc}
*/
public function loadMetadataForClass($className, ClassMetadata $metadata)
public function loadMetadataForClass($className, ClassMetadataInfo $metadata)
{
$data = $this->loadMappingFile($file);
// populate ClassMetadata instance from $data
$data = $this->_loadMappingFile($file);
// populate ClassMetadataInfo instance from $data
}
/**
* {@inheritdoc}
*/
protected function loadMappingFile($file)
protected function _loadMappingFile($file)
{
// parse contents of $file and return php data structure
}
@@ -130,6 +133,7 @@ the ``AbstractFileDriver`` implementation for you to extend from:
to name the file ``Entities.User.dcm.ext`` for it to be
recognized.
Now you can use your ``MyMetadataDriver`` implementation by setting
it with the ``setMetadataDriverImpl()`` method:
@@ -150,11 +154,14 @@ entity when needed.
You have all the methods you need to manually specify the mapping
information instead of using some mapping file to populate it from.
The ``ClassMetadata`` class is responsible for only data storage
and is not meant for runtime use. It does not require that the
class actually exists yet so it is useful for describing some
The base ``ClassMetadataInfo`` class is responsible for only data
storage and is not meant for runtime use. It does not require that
the class actually exists yet so it is useful for describing some
entity before it exists and using that information to generate for
example the entities themselves.
example the entities themselves. The class ``ClassMetadata``
extends ``ClassMetadataInfo`` and adds some functionality required
for runtime usage and requires that the PHP class is present and
can be autoloaded.
You can read more about the API of the ``ClassMetadata`` classes in
the PHP Mapping chapter.
@@ -184,3 +191,4 @@ iterate over them:
echo $fieldMapping['fieldName'] . "\n";
}
+31 -17
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
@@ -73,7 +78,7 @@ a naming strategy for database tables and columns.
* @param string $propertyName A property
* @return string A join column name
*/
function joinColumnName($propertyName, $className = null);
function joinColumnName($propertyName);
/**
* Return a join table name
@@ -96,11 +101,11 @@ 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.
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
You need to create a class which implements ``Doctrine\ORM\Mapping\NamingStrategy``.
.. code-block:: php
@@ -119,7 +124,7 @@ You need to create a class which implements ``Doctrine\ORM\Mapping\NamingStrateg
{
return 'id';
}
public function joinColumnName($propertyName, $className = null)
public function joinColumnName($propertyName)
{
return $propertyName . '_' . $this->referenceColumnName();
}
@@ -134,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);
+140 -18
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
@@ -71,7 +71,7 @@ with inheritance hierarchies.
use Doctrine\ORM\Query\ResultSetMappingBuilder;
$sql = "SELECT u.id, u.name, a.id AS address_id, a.street, a.city " .
$sql = "SELECT u.id, u.name, a.id AS address_id, a.street, a.city " .
"FROM users u INNER JOIN address a ON u.address_id = a.id";
$rsm = new ResultSetMappingBuilder($entityManager);
@@ -80,7 +80,7 @@ with inheritance hierarchies.
The builder extends the ``ResultSetMapping`` class and as such has all the functionality of it as well.
.. versionadded:: 2.4
..versionadded:: 2.4
Starting with Doctrine ORM 2.4 you can generate the ``SELECT`` clause
from a ``ResultSetMappingBuilder``. You can either cast the builder
@@ -92,12 +92,13 @@ a mapping from DQL alias (key) to SQL alias (value)
<?php
$selectClause = $rsm->generateSelectClause(array(
$selectClause = $builder->generateSelectClause(array(
'u' => 't1',
'g' => 't2'
));
$sql = "SELECT " . $selectClause . " FROM users t1 JOIN groups t2 ON t1.group_id = t2.id";
The ResultSetMapping
--------------------
@@ -105,6 +106,7 @@ Understanding the ``ResultSetMapping`` is the key to using a
``NativeQuery``. A Doctrine result can contain the following
components:
- Entity results. These represent root result elements.
- Joined entity results. These represent joined entities in
associations of root entity results.
@@ -130,6 +132,7 @@ components:
``ResultSetMapping`` that describes how the results should be
processed by the hydration routines.
We will now look at each of the result types that can appear in a
ResultSetMapping in detail.
@@ -264,7 +267,7 @@ detail:
<?php
/**
* Adds a meta column (foreign key or discriminator column) to the result set.
*
*
* @param string $alias
* @param string $columnAlias
* @param string $columnName
@@ -319,10 +322,10 @@ entity.
$rsm->addEntityResult('User', 'u');
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$query = $this->em->createNativeQuery('SELECT id, name FROM users WHERE name = ?', $rsm);
$query = $this->_em->createNativeQuery('SELECT id, name FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
The result would look like this:
@@ -355,10 +358,10 @@ thus owns the foreign key.
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$rsm->addMetaResult('u', 'address_id', 'address_id');
$query = $this->em->createNativeQuery('SELECT id, name, address_id FROM users WHERE name = ?', $rsm);
$query = $this->_em->createNativeQuery('SELECT id, name, address_id FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
Foreign keys are used by Doctrine for lazy-loading purposes when
@@ -384,12 +387,12 @@ associations that are lazy.
$rsm->addFieldResult('a', 'address_id', 'id');
$rsm->addFieldResult('a', 'street', 'street');
$rsm->addFieldResult('a', 'city', 'city');
$sql = 'SELECT u.id, u.name, a.id AS address_id, a.street, a.city FROM users u ' .
'INNER JOIN address a ON u.address_id = a.id WHERE u.name = ?';
$query = $this->em->createNativeQuery($sql, $rsm);
$query = $this->_em->createNativeQuery($sql, $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
In this case the nested entity ``Address`` is registered with the
@@ -419,10 +422,10 @@ to map the hierarchy (both use a discriminator column).
$rsm->addFieldResult('u', 'name', 'name');
$rsm->addMetaResult('u', 'discr', 'discr'); // discriminator column
$rsm->setDiscriminatorColumn('u', 'discr');
$query = $this->em->createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm);
$query = $this->_em->createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
Note that in the case of Class Table Inheritance, an example as
@@ -439,7 +442,8 @@ You can also map a native query using a named native query mapping.
To achieve that, you must describe the SQL resultset structure
using named native query (and sql resultset mappings if is a several resultset mappings).
Like named query, a named native query can be defined at class level or in an XML file.
Like named query, a named native query can be defined at class level or in a XML or YAML file.
A resultSetMapping parameter is defined in @NamedNativeQuery,
it represents the name of a defined @SqlResultSetMapping.
@@ -534,6 +538,47 @@ it represents the name of a defined @SqlResultSetMapping.
</sql-result-set-mappings>
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyProject\Model\User:
type: entity
namedNativeQueries:
fetchMultipleJoinsEntityResults:
name: fetchMultipleJoinsEntityResults
resultSetMapping: mappingMultipleJoinsEntityResults
query: SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM users u INNER JOIN addresses a ON u.id = a.user_id INNER JOIN phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username
sqlResultSetMappings:
mappingMultipleJoinsEntityResults:
name: mappingMultipleJoinsEntityResults
columnResult:
0:
name: numphones
entityResult:
0:
entityClass: __CLASS__
fieldResult:
0:
name: id
column: u_id
1:
name: name
column: u_name
2:
name: status
column: u_status
1:
entityClass: Address
fieldResult:
0:
name: id
column: a_id
1:
name: zip
column: a_zip
2:
name: country
column: a_country
Things to note:
- The resultset mapping declares the entities retrieved by this native query.
@@ -544,6 +589,7 @@ Things to note:
column name as the one declared on the class property.
- ``__CLASS__`` is an alias for the mapped class
In the above example,
the ``fetchJoinedAddress`` named query use the joinMapping result set mapping.
This mapping returns 2 entities, User and Address, each property is declared and associated to a column name,
@@ -609,6 +655,21 @@ Let's now see an implicit declaration of the property / column.
</sql-result-set-mappings>
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyProject\Model\Address:
type: entity
namedNativeQueries:
findAll:
resultSetMapping: mappingFindAll
query: SELECT * FROM addresses
sqlResultSetMappings:
mappingFindAll:
name: mappingFindAll
entityResult:
address:
entityClass: Address
In this example, we only describe the entity member of the result set mapping.
The property / column mappings is done using the entity mapping values.
@@ -618,6 +679,7 @@ a @FieldResult element should be used for each foreign key column.
The @FieldResult name is composed of the property name for the relationship,
followed by a dot ("."), followed by the name or the field or property of the primary key.
.. configuration-block::
.. code-block:: php
@@ -696,6 +758,41 @@ followed by a dot ("."), followed by the name or the field or property of the pr
</sql-result-set-mappings>
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyProject\Model\User:
type: entity
namedNativeQueries:
fetchJoinedAddress:
name: fetchJoinedAddress
resultSetMapping: mappingJoinedAddress
query: SELECT u.id, u.name, u.status, a.id AS a_id, a.country AS a_country, a.zip AS a_zip, a.city AS a_city FROM users u INNER JOIN addresses a ON u.id = a.user_id WHERE u.username = ?
sqlResultSetMappings:
mappingJoinedAddress:
entityResult:
0:
entityClass: __CLASS__
fieldResult:
0:
name: id
1:
name: name
2:
name: status
3:
name: address.id
column: a_id
4:
name: address.zip
column: a_zip
5:
name: address.city
column: a_city
6:
name: address.country
column: a_country
If you retrieve a single entity and if you use the default mapping,
you can use the resultClass attribute instead of resultSetMapping:
@@ -731,6 +828,16 @@ you can use the resultClass attribute instead of resultSetMapping:
</named-native-queries>
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyProject\Model\Address:
type: entity
namedNativeQueries:
findAll:
name: findAll
resultClass: Address
query: SELECT * FROM addresses
In some of your native queries, you'll have to return scalar values,
for example when building report queries.
@@ -781,3 +888,18 @@ You actually can even mix, entities and scalar returns in the same native query
</sql-result-set-mappings>
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyProject\Model\Address:
type: entity
namedNativeQueries:
count:
name: count
resultSetMapping: mappingCount
query: SELECT COUNT(*) AS count FROM addresses
sqlResultSetMappings:
mappingCount:
name: mappingCount
columnResult:
count:
name: count
+2
View File
@@ -23,6 +23,7 @@ of Doctrine2 to this problem is.
to a fully-loaded object by calling ``EntityManager#refresh()``
or a DQL query with the refresh flag.
What is the problem?
--------------------
@@ -86,3 +87,4 @@ Mainly for optimization purposes, but be careful of premature
optimization as partial objects lead to potentially more fragile
code.
+64 -39
View File
@@ -27,7 +27,7 @@ to write a mapping file for it using the above configured
<?php
namespace Entities;
class User
{
private $id;
@@ -42,30 +42,16 @@ named ``Entities.User.php`` inside of the
<?php
// /path/to/php/mapping/files/Entities.User.php
$metadata->mapField(array(
'id' => true,
'fieldName' => 'id',
'type' => 'integer'
));
$metadata->mapField(array(
'fieldName' => 'username',
'type' => 'string',
'options' => array(
'fixed' => true,
'comment' => "User's login name"
)
));
$metadata->mapField(array(
'fieldName' => 'login_count',
'type' => 'integer',
'nullable' => false,
'options' => array(
'unsigned' => true,
'default' => 0
)
'type' => 'string'
));
Now we can easily retrieve the populated ``ClassMetadata`` instance
@@ -101,13 +87,13 @@ Now you just need to define a static function named
<?php
namespace Entities;
use Doctrine\ORM\Mapping\ClassMetadata;
class User
{
// ...
public static function loadMetadata(ClassMetadata $metadata)
{
$metadata->mapField(array(
@@ -115,7 +101,7 @@ Now you just need to define a static function named
'fieldName' => 'id',
'type' => 'integer'
));
$metadata->mapField(array(
'fieldName' => 'username',
'type' => 'string'
@@ -180,29 +166,18 @@ It also has several methods that create builders (which are necessary for advanc
- ``createManyToMany($name, $targetEntity)`` returns an ``ManyToManyAssociationBuilder`` instance
- ``createOneToMany($name, $targetEntity)`` returns an ``OneToManyAssociationBuilder`` instance
ClassMetadata API
ClassMetadataInfo API
---------------------
The ``ClassMetadata`` class is the base data object for storing
The ``ClassMetadataInfo`` class is the base data object for storing
the mapping metadata for a single entity. It contains all the
getters and setters you need populate and retrieve information for
an entity.
Internal
~~~~~~~~
- ``getReflectionClass()``
- ``getReflectionProperties()``
- ``getReflectionProperty($name)``
- ``getSingleIdReflectionProperty()``
- ``getIdentifierValues($entity)``
- ``assignIdentifier($entity, $id)``
- ``setFieldValue($entity, $field, $value)``
- ``getFieldValue($entity, $field)``
General Setters
~~~~~~~~~~~~~~~
- ``setTableName($tableName)``
- ``setPrimaryTable(array $primaryTableDefinition)``
- ``setCustomRepositoryClass($repositoryClassName)``
@@ -215,6 +190,7 @@ General Setters
Inheritance Setters
~~~~~~~~~~~~~~~~~~~
- ``setInheritanceType($type)``
- ``setSubclasses(array $subclasses)``
- ``setParentClasses(array $classNames)``
@@ -224,18 +200,24 @@ Inheritance Setters
Field Mapping Setters
~~~~~~~~~~~~~~~~~~~~~
- ``addProperty(Property $property)``
- ``addAssociation(AssociationMetadata $property)``
- ``mapField(array $mapping)``
- ``mapOneToOne(array $mapping)``
- ``mapOneToMany(array $mapping)``
- ``mapManyToOne(array $mapping)``
- ``mapManyToMany(array $mapping)``
Lifecycle Callback Setters
~~~~~~~~~~~~~~~~~~~~~~~~~~
- ``addLifecycleCallback($callback, $event)``
- ``setLifecycleCallbacks(array $callbacks)``
Versioning Setters
~~~~~~~~~~~~~~~~~~
- ``setVersionMapping(array &$mapping)``
- ``setVersioned($bool)``
- ``setVersionField()``
@@ -243,15 +225,21 @@ Versioning Setters
General Getters
~~~~~~~~~~~~~~~
- ``getTableName()``
- ``getSchemaName()``
- ``getTemporaryIdTableName()``
Identifier Getters
~~~~~~~~~~~~~~~~~~
- ``getIdentifierColumnNames()``
- ``usesIdGenerator()``
- ``isIdentifier($fieldName)``
- ``isIdGeneratorIdentity()``
- ``isIdGeneratorSequence()``
- ``isIdGeneratorTable()``
- ``isIdentifierNatural()``
- ``getIdentifierFieldNames()``
- ``getSingleIdentifierFieldName()``
- ``getSingleIdentifierColumnName()``
@@ -259,18 +247,35 @@ Identifier Getters
Inheritance Getters
~~~~~~~~~~~~~~~~~~~
- ``isInheritanceTypeNone()``
- ``isInheritanceTypeJoined()``
- ``isInheritanceTypeSingleTable()``
- ``isInheritanceTypeTablePerClass()``
- ``isInheritedField($fieldName)``
- ``isInheritedAssociation($fieldName)``
Change Tracking Getters
~~~~~~~~~~~~~~~~~~~~~~~
- ``isChangeTrackingDeferredExplicit()``
- ``isChangeTrackingDeferredImplicit()``
- ``isChangeTrackingNotify()``
Field & Association Getters
~~~~~~~~~~~~~~~~~~~~~~~~~~~
- ``isUniqueField($fieldName)``
- ``isNullable($fieldName)``
- ``getColumnName($fieldName)``
- ``getFieldMapping($fieldName)``
- ``getAssociationMapping($fieldName)``
- ``getAssociationMappings()``
- ``getFieldName($columnName)``
- ``hasField($fieldName)``
- ``getColumnNames(array $fieldNames = null)``
- ``getTypeOfField($fieldName)``
- ``getTypeOfColumn($columnName)``
- ``hasAssociation($fieldName)``
@@ -280,6 +285,26 @@ Field & Association Getters
Lifecycle Callback Getters
~~~~~~~~~~~~~~~~~~~~~~~~~~
- ``hasLifecycleCallbacks($lifecycleEvent)``
- ``getLifecycleCallbacks($event)``
ClassMetadata API
-----------------
The ``ClassMetadata`` class extends ``ClassMetadataInfo`` and adds
the runtime functionality required by Doctrine. It adds a few extra
methods related to runtime reflection for working with the entities
themselves.
- ``getReflectionClass()``
- ``getReflectionProperties()``
- ``getReflectionProperty($name)``
- ``getSingleIdReflectionProperty()``
- ``getIdentifierValues($entity)``
- ``setIdentifierValues($entity, $id)``
- ``setFieldValue($entity, $field, $value)``
- ``getFieldValue($entity, $field)``
+45 -75
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
@@ -38,6 +38,7 @@ good example is to inspect what type of object the
There're currently 3 possible return values for ``getType()``:
- ``QueryBuilder::SELECT``, which returns value 0
- ``QueryBuilder::DELETE``, returning value 1
- ``QueryBuilder::UPDATE``, which returns value 2
@@ -65,6 +66,7 @@ performance. Any changes that may affect the generated DQL actually
modifies the state of ``QueryBuilder`` to a stage we call
STATE\_DIRTY. One ``QueryBuilder`` can be in two different states:
- ``QueryBuilder::STATE_CLEAN``, which means DQL haven't been
altered since last retrieval or nothing were added since its
instantiation
@@ -74,14 +76,15 @@ STATE\_DIRTY. One ``QueryBuilder`` can be in two different states:
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
@@ -94,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:
@@ -110,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``:
@@ -124,12 +127,6 @@ Here is a complete list of helper methods available in ``QueryBuilder``:
// 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');
// ->addSelect('p.area_code');
public function addSelect($select = null);
// Example - $qb->delete('User', 'u')
public function delete($delete = null, $alias = null);
@@ -142,23 +139,15 @@ Here is a complete list of helper methods available in ``QueryBuilder``:
public function set($key, $value);
// Example - $qb->from('Phonenumber', 'p')
// Example - $qb->from('Phonenumber', 'p', 'p.id')
public function from($from, $alias, $indexBy = null);
// Example - $qb->join('u.Group', 'g', Expr\Join::WITH, $qb->expr()->eq('u.status_id', '?1'))
// Example - $qb->join('u.Group', 'g', 'WITH', 'u.status = ?1')
// Example - $qb->join('u.Group', 'g', 'WITH', 'u.status = ?1', 'g.id')
public function join($join, $alias, $conditionType = null, $condition = null, $indexBy = null);
public function from($from, $alias = null);
// Example - $qb->innerJoin('u.Group', 'g', Expr\Join::WITH, $qb->expr()->eq('u.status_id', '?1'))
// Example - $qb->innerJoin('u.Group', 'g', 'WITH', 'u.status = ?1')
// Example - $qb->innerJoin('u.Group', 'g', 'WITH', 'u.status = ?1', 'g.id')
public function innerJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null);
public function innerJoin($join, $alias = null, $conditionType = null, $condition = null);
// Example - $qb->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, $qb->expr()->eq('p.area_code', 55))
// Example - $qb->leftJoin('u.Phonenumbers', 'p', 'WITH', 'p.area_code = 55')
// Example - $qb->leftJoin('u.Phonenumbers', 'p', 'WITH', 'p.area_code = 55', 'p.id')
public function leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null);
public function leftJoin($join, $alias = null, $conditionType = null, $condition = null);
// NOTE: ->where() overrides all previously set conditions
//
@@ -167,8 +156,6 @@ Here is a complete list of helper methods available in ``QueryBuilder``:
// Example - $qb->where('u.firstName = ?1 AND u.surname = ?2')
public function where($where);
// NOTE: ->andWhere() can be used directly, without any ->where() before
//
// Example - $qb->andWhere($qb->expr()->orX($qb->expr()->lte('u.age', 40), 'u.numChild = 0'))
public function andWhere($where);
@@ -219,9 +206,9 @@ allowed. Binding parameters can simply be achieved as follows:
// $qb instanceof QueryBuilder
$qb->select('u')
->from('User', 'u')
->from('User u')
->where('u.id = ?1')
->orderBy('u.name', 'ASC')
->orderBy('u.name', 'ASC');
->setParameter(1, 100); // Sets ?1 to 100, and thus we will fetch a user with u.id = 100
You are not forced to enumerate your placeholders as the
@@ -233,9 +220,9 @@ alternative syntax is available:
// $qb instanceof QueryBuilder
$qb->select('u')
->from('User', 'u')
->from('User u')
->where('u.id = :identifier')
->orderBy('u.name', 'ASC')
->orderBy('u.name', 'ASC');
->setParameter('identifier', 100); // Sets :identifier to 100, and thus we will fetch a user with u.id = 100
Note that numeric placeholders start with a ? followed by a number
@@ -244,8 +231,8 @@ while the named placeholders start with a : followed by a string.
Calling ``setParameter()`` automatically infers which type you are setting as
value. This works for integers, arrays of strings/integers, DateTime instances
and for managed entities. If you want to set a type explicitly you can call
the third argument to ``setParameter()`` explicitly. It accepts either a DBAL
Doctrine\DBAL\ParameterType::* or a DBAL Type name for conversion.
the third argument to ``setParameter()`` explicitly. It accepts either a PDO
type or a DBAL Type name for conversion.
If you've got several parameters to bind to your query, you can
also use setParameters() instead of setParameter() with the
@@ -314,7 +301,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:
@@ -349,8 +336,7 @@ set of useful methods to help build expressions:
<?php
// $qb instanceof QueryBuilder
// example8: QueryBuilder port of:
// "SELECT u FROM User u WHERE u.id = ? OR u.nickname LIKE ? ORDER BY u.name ASC" using Expr class
// example8: QueryBuilder port of: "SELECT u FROM User u WHERE u.id = ? OR u.nickname LIKE ? ORDER BY u.surname DESC" using Expr class
$qb->add('select', new Expr\Select(array('u')))
->add('from', new Expr\From('User', 'u'))
->add('where', $qb->expr()->orX(
@@ -376,6 +362,7 @@ complete list of supported helper methods available:
// Example - $qb->expr()->orX($cond1 [, $condN])->add(...)->...
public function orX($x = null); // Returns Expr\OrX instance
/** Comparison objects **/
// Example - $qb->expr()->eq('u.id', '?1') => u.id = ?1
@@ -402,6 +389,7 @@ complete list of supported helper methods available:
// Example - $qb->expr()->isNotNull('u.id') => u.id IS NOT NULL
public function isNotNull($x); // Returns string
/** Arithmetic objects **/
// Example - $qb->expr()->prod('u.id', '2') => u.id * 2
@@ -416,6 +404,7 @@ complete list of supported helper methods available:
// Example - $qb->expr()->quot('u.id', '2') => u.id / 2
public function quot($x, $y); // Returns Expr\Math instance
/** Pseudo-function objects **/
// Example - $qb->expr()->exists($qb2->getDql())
@@ -444,12 +433,10 @@ complete list of supported helper methods available:
// Example - $qb->expr()->like('u.firstname', $qb->expr()->literal('Gui%'))
public function like($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->notLike('u.firstname', $qb->expr()->literal('Gui%'))
public function notLike($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->between('u.id', '1', '10')
public function between($val, $x, $y); // Returns Expr\Func
/** Function objects **/
// Example - $qb->expr()->trim('u.firstname')
@@ -458,8 +445,8 @@ complete list of supported helper methods available:
// Example - $qb->expr()->concat('u.firstname', $qb->expr()->concat($qb->expr()->literal(' '), 'u.lastname'))
public function concat($x, $y); // Returns Expr\Func
// Example - $qb->expr()->substring('u.firstname', 0, 1)
public function substring($x, $from, $len); // Returns Expr\Func
// Example - $qb->expr()->substr('u.firstname', 0, 1)
public function substr($x, $from, $len); // Returns Expr\Func
// Example - $qb->expr()->lower('u.firstname')
public function lower($x); // Returns Expr\Func
@@ -492,38 +479,21 @@ complete list of supported helper methods available:
public function countDistinct($x); // Returns Expr\Func
}
Adding a Criteria to a Query
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can also add a :ref:`filtering-collections` to a QueryBuilder by
using ``addCriteria``:
.. code-block:: php
<?php
use Doctrine\Common\Collections\Criteria;
// ...
$criteria = Criteria::create()
->orderBy(['firstName' => Criteria::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
of DQL. It takes 3 parameters: ``$dqlPartName``, ``$dqlPart`` and
``$append`` (default=false)
- ``$dqlPartName``: Where the ``$dqlPart`` should be placed.
Possible values: select, from, where, groupBy, having, orderBy
- ``$dqlPart``: What should be placed in ``$dqlPartName``. Accepts
@@ -540,9 +510,7 @@ of DQL. It takes 3 parameters: ``$dqlPartName``, ``$dqlPart`` and
<?php
// $qb instanceof QueryBuilder
// example6: how to define:
// "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC"
// using QueryBuilder string support
// example6: how to define: "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC" using QueryBuilder string support
$qb->add('select', 'u')
->add('from', 'User u')
->add('where', 'u.id = ?1')
@@ -561,11 +529,13 @@ same query of example 6 written using
<?php
// $qb instanceof QueryBuilder
// example7: how to define:
// "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC"
// using QueryBuilder using Expr\* instances
// example7: how to define: "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC" using QueryBuilder using Expr\* instances
$qb->add('select', new Expr\Select(array('u')))
->add('from', new Expr\From('User', 'u'))
->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.
-664
View File
@@ -1,664 +0,0 @@
The Second Level Cache
======================
.. note::
The second level cache functionality is marked as experimental for now. It
is a very complex feature and we cannot guarantee yet that it works stable
in all cases.
The Second Level Cache is designed to reduce the amount of necessary database access.
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.
There are some flavors of caching available, but is better to cache read-only data.
Be aware that caches are not aware of changes made to the persistent store by another application.
They can, however, be configured to regularly expire cached data.
Caching Regions
---------------
Second level cache does not store instances of an entity, instead it caches only entity identifier and values.
Each entity class, collection association and query has its region, where values of each instance are stored.
Caching Regions are specific region into the cache provider that might store entities, collection or queries.
Each cache region resides in a specific cache namespace and has its own lifetime configuration.
Notice that when caching collection and queries only identifiers are stored.
The entity values will be stored in its own region
Something like below for an entity region :
.. code-block:: php
<?php
[
'region_name:entity_1_hash' => ['id'=> 1, 'name' => 'FooBar', 'associationName'=>null],
'region_name:entity_2_hash' => ['id'=> 2, 'name' => 'Foo', 'associationName'=>['id'=>11]],
'region_name:entity_3_hash' => ['id'=> 3, 'name' => 'Bar', 'associationName'=>['id'=>22]]
];
If the entity holds a collection that also needs to be cached.
An collection region could look something like :
.. code-block:: php
<?php
[
'region_name:entity_1_coll_assoc_name_hash' => ['ownerId'=> 1, 'list' => [1, 2, 3]],
'region_name:entity_2_coll_assoc_name_hash' => ['ownerId'=> 2, 'list' => [2, 3]],
'region_name:entity_3_coll_assoc_name_hash' => ['ownerId'=> 3, 'list' => [2, 4]]
];
A query region might be something like :
.. code-block:: php
<?php
[
'region_name:query_1_hash' => ['list' => [1, 2, 3]],
'region_name:query_2_hash' => ['list' => [2, 3]],
'region_name:query_3_hash' => ['list' => [2, 4]]
];
.. note::
The following data structures represents now the cache will looks like, this is not actual cached data.
.. _reference-second-level-cache-regions:
Cache Regions
-------------
``Doctrine\ORM\Cache\Region\DefaultRegion`` It's the default implementation.
A simplest cache region compatible with all doctrine-cache drivers but does not support locking.
``Doctrine\ORM\Cache\Region`` and ``Doctrine\ORM\Cache\ConcurrentRegion``
Defines contracts that should be implemented by a cache provider.
It allows you to provide your own cache implementation that might take advantage of specific cache driver.
If you want to support locking for ``READ_WRITE`` strategies you should implement ``ConcurrentRegion``; ``CacheRegion`` otherwise.
Cache region
~~~~~~~~~~~~
Defines a contract for accessing a particular region.
``Doctrine\ORM\Cache\Region``
Defines a contract for accessing a particular cache region.
`See API Doc <https://www.doctrine-project.org/api/orm/latest/Doctrine/ORM/Cache/Region.html>`_.
Concurrent cache region
~~~~~~~~~~~~~~~~~~~~~~~
A ``Doctrine\ORM\Cache\ConcurrentRegion`` is designed to store concurrently managed data region.
By default, Doctrine provides a very simple implementation based on file locks ``Doctrine\ORM\Cache\Region\FileLockRegion``.
If you want to use an ``READ_WRITE`` cache, you should consider providing your own cache region.
``Doctrine\ORM\Cache\ConcurrentRegion``
Defines contract for concurrently managed data region.
`See API Doc <https://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/ConcurrentRegion.html>`_.
Timestamp region
~~~~~~~~~~~~~~~~
``Doctrine\ORM\Cache\TimestampRegion``
Tracks the timestamps of the most recent updates to particular entity.
`See API Doc <http://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/TimestampRegion.html>`_.
.. _reference-second-level-cache-mode:
Caching mode
------------
* ``READ_ONLY`` (DEFAULT)
* Can do reads, inserts and deletes, cannot perform updates or employ any locks.
* Useful for data that is read frequently but never updated.
* Best performer.
* It is Simple.
* ``NONSTRICT_READ_WRITE``
* Read Write Cache doesnt employ any locks but can do reads, inserts, updates and deletes.
* Good if the application needs to update data rarely.
* ``READ_WRITE``
* Read Write cache employs locks before update/delete.
* Use if data needs to be updated.
* Slowest strategy.
* To use it a the cache region implementation must support locking.
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 |
+-----------------------+-------------------------------------------------------------------------------------------+
Configuration
-------------
Doctrine allows you to specify configurations and some points of extension for the second-level-cache
Enable Second Level Cache
~~~~~~~~~~~~~~~~~~~~~~~~~
To enable the second-level-cache, you should provide a cache factory.
``\Doctrine\ORM\Cache\DefaultCacheFactory`` is the default implementation.
.. code-block:: php
<?php
/** @var \Doctrine\ORM\Cache\RegionsConfiguration $cacheConfig */
/** @var \Doctrine\Common\Cache\Cache $cache */
/** @var \Doctrine\ORM\Configuration $config */
$factory = new \Doctrine\ORM\Cache\DefaultCacheFactory($cacheConfig, $cache);
// Enable second-level-cache
$config->setSecondLevelCacheEnabled();
// Cache factory
$config->getSecondLevelCacheConfiguration()
->setCacheFactory($factory);
Cache Factory
~~~~~~~~~~~~~
Cache Factory is the main point of extension.
It allows you to provide a specific implementation of the following components :
* ``QueryCache`` Store and retrieve query cache results.
* ``CachedEntityPersister`` Store and retrieve entity results.
* ``CachedCollectionPersister`` Store and retrieve query results.
* ``EntityHydrator`` Transform an entity into a cache entry and cache entry into entities
* ``CollectionHydrator`` Transform a collection into a cache entry and cache entry into collection
`See API Doc <http://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/DefaultCacheFactory.html>`_.
Region Lifetime
~~~~~~~~~~~~~~~
To specify a default lifetime for all regions or specify a different lifetime for a specific region.
.. code-block:: php
<?php
/** @var \Doctrine\ORM\Configuration $config */
/** @var \Doctrine\ORM\Cache\CacheConfiguration $cacheConfig */
/** @var \Doctrine\ORM\Cache\RegionsConfiguration $regionConfig */
$cacheConfig = $config->getSecondLevelCacheConfiguration();
$regionConfig = $cacheConfig->getRegionsConfiguration();
// Cache Region lifetime
$regionConfig->setLifetime('my_entity_region', 3600); // Time to live for a specific region; In seconds
$regionConfig->setDefaultLifetime(7200); // Default time to live; In seconds
Cache Log
~~~~~~~~~
By providing a cache logger you should be able to get information about all cache operations such as hits, misses and puts.
``\Doctrine\ORM\Cache\Logging\StatisticsCacheLogger`` is a built-in implementation that provides basic statistics.
.. code-block:: php
<?php
/** @var \Doctrine\ORM\Configuration $config */
$logger = new \Doctrine\ORM\Cache\Logging\StatisticsCacheLogger();
// Cache logger
$config->setSecondLevelCacheEnabled(true);
$config->getSecondLevelCacheConfiguration()
->setCacheLogger($logger);
// Collect cache statistics
// Get the number of entries successfully retrieved from a specific region.
$logger->getRegionHitCount('my_entity_region');
// Get the number of cached entries *not* found in a specific region.
$logger->getRegionMissCount('my_entity_region');
// Get the number of cacheable entries put in cache.
$logger->getRegionPutCount('my_entity_region');
// Get the total number of put in all regions.
$logger->getPutCount();
// Get the total number of entries successfully retrieved from all regions.
$logger->getHitCount();
// Get the total number of cached entries *not* found in all regions.
$logger->getMissCount();
If you want to get more information you should implement ``\Doctrine\ORM\Cache\Logging\CacheLogger``.
and collect all information you want.
`See API Doc <http://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/Logging/CacheLogger.html>`_.
Entity cache definition
-----------------------
* Entity cache configuration allows you to define the caching strategy and region for an entity.
* ``usage`` Specifies the caching strategy: ``READ_ONLY``, ``NONSTRICT_READ_WRITE``, ``READ_WRITE``. see :ref:`reference-second-level-cache-mode`
* ``region`` Optional value that specifies the name of the second level cache region.
.. configuration-block::
.. code-block:: php
<?php
/**
* @Entity
* @Cache(usage="READ_ONLY", region="my_entity_region")
*/
class Country
{
/**
* @Id
* @GeneratedValue
* @Column(type="integer")
*/
protected $id;
/**
* @Column(unique=true)
*/
protected $name;
// other properties and methods
}
.. 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">
<entity name="Country">
<cache usage="READ_ONLY" region="my_entity_region" />
<id name="id" type="integer" column="id">
<generator strategy="IDENTITY"/>
</id>
<field name="name" type="string" column="name"/>
</entity>
</doctrine-mapping>
Association cache definition
----------------------------
The most common use case is to cache entities. But we can also cache relationships.
It caches the primary keys of association and cache each element will be cached into its region.
.. configuration-block::
.. code-block:: php
<?php
/**
* @Entity
* @Cache("NONSTRICT_READ_WRITE")
*/
class State
{
/**
* @Id
* @GeneratedValue
* @Column(type="integer")
*/
protected $id;
/**
* @Column(unique=true)
*/
protected $name;
/**
* @Cache("NONSTRICT_READ_WRITE")
* @ManyToOne(targetEntity="Country")
* @JoinColumn(name="country_id", referencedColumnName="id")
*/
protected $country;
/**
* @Cache("NONSTRICT_READ_WRITE")
* @OneToMany(targetEntity="City", mappedBy="state")
*/
protected $cities;
// other properties and methods
}
.. 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">
<entity name="State">
<cache usage="NONSTRICT_READ_WRITE" />
<id name="id" type="integer" column="id">
<generator strategy="IDENTITY"/>
</id>
<field name="name" type="string" column="name"/>
<many-to-one field="country" target-entity="Country">
<cache usage="NONSTRICT_READ_WRITE" />
<join-columns>
<join-column name="country_id" referenced-column-name="id"/>
</join-columns>
</many-to-one>
<one-to-many field="cities" target-entity="City" mapped-by="state">
<cache usage="NONSTRICT_READ_WRITE"/>
</one-to-many>
</entity>
</doctrine-mapping>
> Note: for this to work, the target entity must also be marked as cacheable.
Cache usage
~~~~~~~~~~~
Basic entity cache
.. code-block:: php
<?php
$em->persist(new Country($name));
$em->flush(); // Hit database to insert the row and put into cache
$em->clear(); // Clear entity manager
$country1 = $em->find('Country', 1); // Retrieve item from cache
$country1->setName("New Name");
$em->flush(); // Hit database to update the row and update cache
$em->clear(); // Clear entity manager
$country2 = $em->find('Country', 1); // Retrieve item from cache
// Notice that $country1 and $country2 are not the same instance.
Association cache
.. code-block:: php
<?php
// Hit database to insert the row and put into cache
$em->persist(new State($name, $country));
$em->flush();
// Clear entity manager
$em->clear();
// Retrieve item from cache
$state = $em->find('State', 1);
// Hit database to update the row and update cache entry
$state->setName("New Name");
$em->persist($state);
$em->flush();
// Create a new collection item
$city = new City($name, $state);
$state->addCity($city);
// Hit database to insert new collection item,
// put entity and collection cache into cache.
$em->persist($city);
$em->persist($state);
$em->flush();
// Clear entity manager
$em->clear();
// Retrieve item from cache
$state = $em->find('State', 1);
// Retrieve association from cache
$country = $state->getCountry();
// Retrieve collection from cache
$cities = $state->getCities();
echo $country->getName();
echo $state->getName();
// Retrieve each collection item from cache
foreach ($cities as $city) {
echo $city->getName();
}
.. note::
Notice that all entities should be marked as cacheable.
Using the query cache
---------------------
The second level cache stores the entities, associations and collections.
The query cache stores the results of the query but as identifiers, entity values are actually stored in the 2nd level cache.
.. note::
Query cache should always be used in conjunction with the second-level-cache for those entities which should be cached.
.. code-block:: php
<?php
/** @var \Doctrine\ORM\EntityManager $em */
// Execute database query, store query cache and entity cache
$result1 = $em->createQuery('SELECT c FROM Country c ORDER BY c.name')
->setCacheable(true)
->getResult();
$em->clear()
// Check if query result is valid and load entities from cache
$result2 = $em->createQuery('SELECT c FROM Country c ORDER BY c.name')
->setCacheable(true)
->getResult();
Cache mode
~~~~~~~~~~
The Cache Mode controls how a particular query interacts with the second-level cache:
* ``Cache::MODE_GET`` - May read items from the cache, but will not add items.
* ``Cache::MODE_PUT`` - Will never read items from the cache, but will add items to the cache as it reads them from the database.
* ``Cache::MODE_NORMAL`` - May read items from the cache, and add items to the cache.
* ``Cache::MODE_REFRESH`` - The query will never read items from the cache, but will refresh items to the cache as it reads them from the database.
.. code-block:: php
<?php
/** @var \Doctrine\ORM\EntityManager $em */
// Will refresh the query cache and all entities the cache as it reads from the database.
$result1 = $em->createQuery('SELECT c FROM Country c ORDER BY c.name')
->setCacheMode(Cache::MODE_GET)
->setCacheable(true)
->getResult();
.. note::
The the default query cache mode is ```Cache::MODE_NORMAL```
DELETE / UPDATE queries
~~~~~~~~~~~~~~~~~~~~~~~
DQL UPDATE / DELETE statements are ported directly into a database and bypass the second-level cache,
Entities that are already cached will NOT be invalidated.
However the cached data could be evicted using the cache API or an special query hint.
Execute the ``UPDATE`` and invalidate ``all cache entries`` using ``Query::HINT_CACHE_EVICT``
.. code-block:: php
<?php
// Execute and invalidate
$this->em->createQuery("UPDATE Entity\Country u SET u.name = 'unknown' WHERE u.id = 1")
->setHint(Query::HINT_CACHE_EVICT, true)
->execute();
Execute the ``UPDATE`` and invalidate ``all cache entries`` using the cache API
.. code-block:: php
<?php
// Execute
$this->em->createQuery("UPDATE Entity\Country u SET u.name = 'unknown' WHERE u.id = 1")
->execute();
// Invoke Cache API
$em->getCache()->evictEntityRegion('Entity\Country');
Execute the ``UPDATE`` and invalidate ``a specific cache entry`` using the cache API
.. code-block:: php
<?php
// Execute
$this->em->createQuery("UPDATE Entity\Country u SET u.name = 'unknown' WHERE u.id = 1")
->execute();
// Invoke Cache API
$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,
When a query is loaded from cache, the timestamp region is checked for the last update for that persister.
Using the last update timestamps as part of the query key invalidate the cache key when an update occurs.
.. code-block:: php
<?php
// load from database and store cache query key hashing the query + parameters + last timestamp cache region..
$entities = $em->getRepository('Entity\Country')->findAll();
// load from query and entities from cache..
$entities = $em->getRepository('Entity\Country')->findAll();
// update the timestamp cache region for Country
$em->persist(new Country('zombieland'));
$em->flush();
$em->clear();
// Reload from database.
// At this point the query cache key if not logger valid, the select goes straight
$entities = $em->getRepository('Entity\Country')->findAll();
Cache API
---------
Caches are not aware of changes made by another application.
However, you can use the cache API to check / invalidate cache entries.
.. code-block:: php
<?php
/** @var \Doctrine\ORM\Cache $cache */
$cache = $em->getCache();
$cache->containsEntity('Entity\State', 1) // Check if the cache exists
$cache->evictEntity('Entity\State', 1); // Remove an entity from cache
$cache->evictEntityRegion('Entity\State'); // Remove all entities from cache
$cache->containsCollection('Entity\State', 'cities', 1); // Check if the cache exists
$cache->evictCollection('Entity\State', 'cities', 1); // Remove an entity collection from cache
$cache->evictCollectionRegion('Entity\State', 'cities'); // Remove all collections from cache
Limitations
-----------
Composite primary key
~~~~~~~~~~~~~~~~~~~~~
Composite primary key are supported by second level cache,
however when one of the keys is an association the cached entity should always be retrieved using the association identifier.
For performance reasons the cache API does not extract from composite primary key.
.. code-block:: php
<?php
/**
* @Entity
*/
class Reference
{
/**
* @Id
* @ManyToOne(targetEntity="Article", inversedBy="references")
* @JoinColumn(name="source_id", referencedColumnName="article_id")
*/
private $source;
/**
* @Id
* @ManyToOne(targetEntity="Article")
* @JoinColumn(name="target_id", referencedColumnName="article_id")
*/
private $target;
}
// Supported
/** @var Article $article */
$article = $em->find('Article', 1);
// Supported
/** @var Article $article */
$article = $em->find('Article', $article);
// Supported
$id = array('source' => 1, 'target' => 2);
$reference = $em->find('Reference', $id);
// NOT Supported
$id = array('source' => new Article(1), 'target' => new Article(2));
$reference = $em->find('Reference', $id);
Distributed environments
~~~~~~~~~~~~~~~~~~~~~~~~
Some cache driver are not meant to be used in a distributed environment.
Load-balancer for distributing workloads across multiple computing resources
should be used in conjunction with distributed caching system such as memcached, redis, riak ...
Caches should be used with care when using a load-balancer if you don't share the cache.
While using APC or any file based cache update occurred in a specific machine would not reflect to the cache in other machines.
Paginator
~~~~~~~~~
Count queries generated by ``Doctrine\ORM\Tools\Pagination\Paginator`` are not cached by second-level cache.
Although entities and query result are cached count queries will hit the database every time.
-150
View File
@@ -1,150 +0,0 @@
Security
========
The Doctrine library is operating very close to your database and as such needs
to handle and make assumptions about SQL injection vulnerabilities.
It is vital that you understand how Doctrine approaches security, because
we cannot protect you from SQL injection.
Please also read the documentation chapter on Security in Doctrine DBAL. This
page only handles Security issues in the ORM.
- `DBAL Security Page <http://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/security.html>`
If you find a Security bug in Doctrine, please report it on Jira and change the
Security Level to "Security Issues". It will be visible to Doctrine Core
developers and you only.
User input and Doctrine ORM
---------------------------
The ORM is much better at protecting against SQL injection than the DBAL alone.
You can consider the following APIs to be safe from SQL injection:
- ``\Doctrine\ORM\EntityManager#find()`` and ``getReference()``.
- All values on Objects inserted and updated through ``Doctrine\ORM\EntityManager#persist()``
- All find methods on ``Doctrine\ORM\EntityRepository``.
- User Input set to DQL Queries or QueryBuilder methods through
- ``setParameter()`` or variants
- ``setMaxResults()``
- ``setFirstResult()``
- 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:
- Expression API of ``Doctrine\ORM\QueryBuilder``
- Concatenating user input into DQL SELECT, UPDATE or DELETE statements or
Native SQL.
This means SQL injections can only occur with Doctrine ORM when working with
Query Objects of any kind. The safe rule is to always use prepared statement
parameters for user objects when using a Query object.
.. warning::
Insecure code follows, don't copy paste this.
The following example shows insecure DQL usage:
.. code-block:: php
<?php
// INSECURE
$dql = "SELECT u
FROM MyProject\Entity\User u
WHERE u.status = '" . $_GET['status'] . "'
ORDER BY " . $_GET['orderField'] . " ASC";
For Doctrine there is absolutely no way to find out which parts of ``$dql`` are
from user input and which are not, even if we have our own parsing process
this is technically impossible. The correct way is:
.. code-block:: php
<?php
$orderFieldWhitelist = array('email', 'username');
$orderField = "email";
if (in_array($_GET['orderField'], $orderFieldWhitelist)) {
$orderField = $_GET['orderField'];
}
$dql = "SELECT u
FROM MyProject\Entity\User u
WHERE u.status = ?1
ORDER BY u." . $orderField . " ASC";
$query = $entityManager->createQuery($dql);
$query->setParameter(1, $_GET['status']);
Preventing Mass Assignment Vulnerabilities
------------------------------------------
ORMs are very convenient for CRUD applications and Doctrine is no exception.
However CRUD apps are often vulnerable to mass assignment security problems
when implemented naively.
Doctrine is not vulnerable to this problem out of the box, but you can easily
make your entities vulnerable to mass assignment when you add methods of
the kind ``updateFromArray()`` or ``updateFromJson()`` to them. A vulnerable
entity might look like this:
.. code-block:: php
<?php
/**
* @Entity
*/
class InsecureEntity
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @Column */
private $email;
/** @Column(type="boolean") */
private $isAdmin;
public function fromArray(array $userInput)
{
foreach ($userInput as $key => $value) {
$this->$key = $value;
}
}
}
Now the possibility of mass-assignment exists on this entity and can
be exploited by attackers to set the "isAdmin" flag to true on any
object when you pass the whole request data to this method like:
.. code-block:: php
<?php
$entity = new InsecureEntity();
$entity->fromArray($_POST);
$entityManager->persist($entity);
$entityManager->flush();
You can spot this problem in this very simple example easily. However
in combination with frameworks and form libraries it might not be
so obvious when this issue arises. Be careful to avoid this
kind of mistake.
How to fix this problem? You should always have a whitelist
of allowed key to set via mass assignment functions.
.. code-block:: php
public function fromArray(array $userInput, $allowedFields = array())
{
foreach ($userInput as $key => $value) {
if (in_array($key, $allowedFields)) {
$this->$key = $value;
}
}
}
+178 -26
View File
@@ -22,6 +22,7 @@ about the use of generate entities for example, you can call:
$> php vendor/bin/doctrine orm:generate-entities --help
Configuration
~~~~~~~~~~~~~
@@ -32,8 +33,8 @@ already registers all the commands that currently ship with
Doctrine DBAL and ORM. If you want to use additional commands you
have to register them yourself.
All the commands of the Doctrine Console require access to the ``EntityManager``
or ``DBAL`` Connection. You have to inject them into the console application
All the commands of the Doctrine Console require access to the EntityManager
or DBAL Connection. You have to inject them into the console application
using so called Helper-Sets. This requires either the ``db``
or the ``em`` helpers to be defined in order to work correctly.
@@ -72,7 +73,7 @@ sample ``cli-config.php`` file looks as follows:
// Any way to access the EntityManager from your application
$em = GetMyEntityManager();
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()),
'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em)
@@ -83,7 +84,7 @@ script will ultimately use. The Doctrine Binary will automatically
find the first instance of HelperSet in the global variable
namespace and use this.
.. note::
.. note::
You have to adjust this snippet for your specific application or framework
and use their facilities to access the Doctrine EntityManager and
@@ -94,6 +95,7 @@ Command Overview
The following Commands are currently available:
- ``help`` Displays help for a command (?)
- ``list`` Lists commands
- ``dbal:import`` Import SQL file(s) directly to Database.
@@ -107,10 +109,16 @@ The following Commands are currently available:
cache drivers.
- ``orm:convert-d1-schema`` Converts Doctrine 1.X schema into a
Doctrine 2.X schema.
- ``orm:convert-mapping`` Convert mapping information between
supported formats.
- ``orm:ensure-production-settings`` Verify that Doctrine is
properly configured for a production environment.
- ``orm:generate-entities`` Generate entity classes and method
stubs from your mapping information.
- ``orm:generate-proxies`` Generates proxy classes for entity
classes.
- ``orm:generate-repositories`` Generate repository classes from
your mapping information.
- ``orm:run-dql`` Executes arbitrary DQL directly from the command
line.
- ``orm:schema-tool:create`` Processes the schema and either
@@ -125,8 +133,12 @@ The following Commands are currently available:
For these commands are also available aliases:
- ``orm:convert:d1-schema`` is alias for ``orm:convert-d1-schema``.
- ``orm:convert:mapping`` is alias for ``orm:convert-mapping``.
- ``orm:generate:entities`` is alias for ``orm:generate-entities``.
- ``orm:generate:proxies`` is alias for ``orm:generate-proxies``.
- ``orm:generate:repositories`` is alias for ``orm:generate-repositories``.
.. note::
@@ -152,6 +164,7 @@ Database Schema Generation
they are not related to the current project that is using Doctrine.
Please be careful!
To generate your database schema from your Doctrine mapping files
you can use the ``SchemaTool`` class or the ``schema-tool`` Console
Command.
@@ -160,7 +173,7 @@ When using the SchemaTool class directly, create your schema using
the ``createSchema()`` method. First create an instance of the
``SchemaTool`` and pass it an instance of the ``EntityManager``
that you want to use to create the schema. This method receives an
array of ``ClassMetadata`` instances.
array of ``ClassMetadataInfo`` instances.
.. code-block:: php
@@ -191,8 +204,8 @@ 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 ``ClassMetadata``
instances.
existing database schema to the passed array of
``ClassMetdataInfo`` instances.
.. code-block:: php
@@ -248,6 +261,162 @@ your cli-config.php properly.
(or mapping files), i.e.
``new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em, $mappingPaths);``
Entity Generation
-----------------
Generate entity classes and method stubs from your mapping information.
.. code-block:: php
$ php doctrine orm:generate-entities
$ php doctrine orm:generate-entities --update-entities
$ php doctrine orm:generate-entities --regenerate-entities
This command is not suited for constant usage. It is a little helper and does
not support all the mapping edge cases very well. You still have to put work
in your entities after using this command.
It is possible to use the EntityGenerator on code that you have already written. It will
not be lost. The EntityGenerator will only append new code to your
file and will not delete the old code. However this approach may still be prone
to error and we suggest you use code repositories such as GIT or SVN to make
backups of your code.
It makes sense to generate the entity code if you are using entities as Data
Access Objects only and don't put much additional logic on them. If you are
however putting much more logic on the entities you should refrain from using
the entity-generator and code your entities manually.
.. note::
Even if you specified Inheritance options in your
XML or YAML Mapping files the generator cannot generate the base and
child classes for you correctly, because it doesn't know which
class is supposed to extend which. You have to adjust the entity
code manually for inheritance to work!
Convert Mapping Information
---------------------------
Convert mapping information between supported formats.
This is an **execute one-time** command. It should not be necessary for
you to call this method multiple times, especially when using the ``--from-database``
flag.
Converting an existing database schema into mapping files only solves about 70-80%
of the necessary mapping information. Additionally the detection from an existing
database cannot detect inverse associations, inheritance types,
entities with foreign keys as primary keys and many of the
semantical operations on associations such as cascade.
.. note::
There is no need to convert YAML or XML mapping files to annotations
every time you make changes. All mapping drivers are first class citizens
in Doctrine 2 and can be used as runtime mapping for the ORM. See the
docs on XML and YAML Mapping for an example how to register this metadata
drivers as primary mapping source.
To convert some mapping information between the various supported
formats you can use the ``ClassMetadataExporter`` to get exporter
instances for the different formats:
.. code-block:: php
<?php
$cme = new \Doctrine\ORM\Tools\Export\ClassMetadataExporter();
Once you have a instance you can use it to get an exporter. For
example, the yml exporter:
.. code-block:: php
<?php
$exporter = $cme->getExporter('yml', '/path/to/export/yml');
Now you can export some ``ClassMetadata`` instances:
.. code-block:: php
<?php
$classes = array(
$em->getClassMetadata('Entities\User'),
$em->getClassMetadata('Entities\Profile')
);
$exporter->setMetadata($classes);
$exporter->export();
This functionality is also available from the command line to
convert your loaded mapping information to another format. The
``orm:convert-mapping`` command accepts two arguments, the type to
convert to and the path to generate it:
.. code-block:: php
$ php doctrine orm:convert-mapping xml /path/to/mapping-path-converted-to-xml
Reverse Engineering
-------------------
You can use the ``DatabaseDriver`` to reverse engineer a database
to an array of ``ClassMetadataInfo`` instances and generate YAML,
XML, etc. from them.
.. note::
Reverse Engineering is a **one-time** process that can get you started with a project.
Converting an existing database schema into mapping files only detects about 70-80%
of the necessary mapping information. Additionally the detection from an existing
database cannot detect inverse associations, inheritance types,
entities with foreign keys as primary keys and many of the
semantical operations on associations such as cascade.
First you need to retrieve the metadata instances with the
``DatabaseDriver``:
.. code-block:: php
<?php
$em->getConfiguration()->setMetadataDriverImpl(
new \Doctrine\ORM\Mapping\Driver\DatabaseDriver(
$em->getConnection()->getSchemaManager()
)
);
$cmf = new DisconnectedClassMetadataFactory();
$cmf->setEntityManager($em);
$metadata = $cmf->getAllMetadata();
Now you can get an exporter instance and export the loaded metadata
to yml:
.. code-block:: php
<?php
$exporter = $cme->getExporter('yml', '/path/to/export/yml');
$exporter->setMetadata($metadata);
$exporter->export();
You can also reverse engineer a database using the
``orm:convert-mapping`` command:
.. code-block:: php
$ php doctrine orm:convert-mapping --from-database yml /path/to/mapping-path-converted-to-yml
.. note::
Reverse Engineering is not always working perfectly
depending on special cases. It will only detect Many-To-One
relations (even if they are One-To-One) and will try to create
entities from Many-To-Many tables. It also has problems with naming
of foreign keys that have multiple column names. Any Reverse
Engineered Database-Schema needs considerable manual work to become
a useful domain model.
Runtime vs Development Mapping Validation
-----------------------------------------
@@ -293,6 +462,7 @@ number of elements with error messages.
prefix backslash. PHP does this with ``get_class()`` or Reflection
methods for backwards compatibility reasons.
Adding own commands
-------------------
@@ -306,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 ...
@@ -337,21 +507,3 @@ defined ones) is possible through the command:
new \MyProject\Tools\Console\Commands\AnotherCommand(),
new \MyProject\Tools\Console\Commands\OneMoreCommand(),
));
Re-use console application
--------------------------
You are also able to retrieve and re-use the default console application.
Just call ``ConsoleRunner::createApplication(...)`` with an appropriate
HelperSet, like it is described in the configuration section.
.. code-block:: php
<?php
// Retrieve default console application
$cli = ConsoleRunner::createApplication($helperSet);
// Runs console application
$cli->run();
@@ -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
~~~~~~~~~~~~~~~~~~~~~~
@@ -68,14 +62,15 @@ looks like this:
// $em instanceof EntityManager
$em->getConnection()->beginTransaction(); // suspend auto-commit
try {
// ... do some work
//... do some work
$user = new User;
$user->setName('George');
$em->persist($user);
$em->flush();
$em->getConnection()->commit();
} catch (Exception $e) {
$em->getConnection()->rollBack();
$em->getConnection()->rollback();
$em->close();
throw $e;
}
@@ -86,19 +81,21 @@ require an active transaction. Such methods will throw a
``TransactionRequiredException`` to inform you of that
requirement.
A more convenient alternative for explicit transaction demarcation is the use
of provided control abstractions in the form of
``Connection#transactional($func)`` and ``EntityManager#transactional($func)``.
When used, these control abstractions ensure that you never forget to rollback
the transaction, in addition to the obvious code reduction. An example that is
functionally equivalent to the previously shown code looks as follows:
A more convenient alternative for explicit transaction demarcation
is the use of provided control abstractions in the form of
``Connection#transactional($func)`` and
``EntityManager#transactional($func)``. When used, these control
abstractions ensure that you never forget to rollback the
transaction or close the ``EntityManager``, apart from the obvious
code reduction. An example that is functionally equivalent to the
previously shown code looks as follows:
.. code-block:: php
<?php
// $em instanceof EntityManager
$em->transactional(function($em) {
// ... do some work
//... do some work
$user = new User;
$user->setName('George');
$em->persist($user);
@@ -107,9 +104,8 @@ functionally equivalent to the previously shown code looks as follows:
The difference between ``Connection#transactional($func)`` and
``EntityManager#transactional($func)`` is that the latter
abstraction flushes the ``EntityManager`` prior to transaction
commit.
.. _transactions-and-concurrency_exception-handling:
commit and also closes the ``EntityManager`` properly when an
exception occurs (in addition to rolling back the transaction).
Exception Handling
~~~~~~~~~~~~~~~~~~
@@ -141,8 +137,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
---------------
@@ -151,8 +145,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
~~~~~~~~~~~~~~~~~~
@@ -179,50 +171,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>
<?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>
<?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
@@ -253,15 +225,15 @@ either when calling ``EntityManager#find()``:
<?php
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\OptimisticLockException;
$theEntityId = 1;
$expectedVersion = 184;
try {
$entity = $em->find('User', $theEntityId, LockMode::OPTIMISTIC, $expectedVersion);
// do the work
$em->flush();
} catch(OptimisticLockException $e) {
echo "Sorry, but someone else has already changed this entity. Please apply the changes again!";
@@ -274,16 +246,16 @@ Or you can use ``EntityManager#lock()`` to find out:
<?php
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\OptimisticLockException;
$theEntityId = 1;
$expectedVersion = 184;
$entity = $em->find('User', $theEntityId);
try {
// assert version
$em->lock($entity, LockMode::OPTIMISTIC, $expectedVersion);
} catch(OptimisticLockException $e) {
echo "Sorry, but someone else has already changed this entity. Please apply the changes again!";
}
@@ -322,7 +294,7 @@ See the example code, The form (GET Request):
<?php
$post = $em->find('BlogPost', 123456);
echo '<input type="hidden" name="id" value="' . $post->getId() . '" />';
echo '<input type="hidden" name="version" value="' . $post->getCurrentVersion() . '" />';
@@ -333,11 +305,9 @@ And the change headline action (POST Request):
<?php
$postId = (int)$_GET['id'];
$postVersion = (int)$_GET['version'];
$post = $em->find('BlogPost', $postId, \Doctrine\DBAL\LockMode::OPTIMISTIC, $postVersion);
.. _transactions-and-concurrency_pessimistic-locking:
Pessimistic Locking
~~~~~~~~~~~~~~~~~~~
@@ -356,6 +326,7 @@ transaction is running.
Doctrine 2 currently supports two pessimistic lock modes:
- Pessimistic Write
(``Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE``), locks the
underlying database rows for concurrent Read and Write Operations.
@@ -365,6 +336,7 @@ Doctrine 2 currently supports two pessimistic lock modes:
You can use pessimistic locks in three different scenarios:
1. Using
``EntityManager#find($className, $id, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
or
@@ -378,3 +350,4 @@ You can use pessimistic locks in three different scenarios:
or
``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``
@@ -15,12 +15,12 @@ Bidirectional Associations
The following rules apply to **bidirectional** associations:
- The inverse side has to have the ``mappedBy`` attribute of the OneToOne,
OneToMany, or ManyToMany mapping declaration. The ``mappedBy``
- 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
OneToOne, ManyToOne, or ManyToMany mapping declaration.
The ``inversedBy`` attribute contains the name of the association-field
- 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.
- ManyToOne is always the owning side of a bidirectional association.
- OneToMany is always the inverse side of a bidirectional association.
+10 -9
View File
@@ -36,15 +36,13 @@ will still end up with the same reference:
public function testIdentityMapReference()
{
/** @var EntityName|\ProxyManager\Proxy\GhostObjectInterface $objectA */
$objectA = $this->entityManager->getReference(EntityName::class, 1);
$objectA = $this->entityManager->getReference('EntityName', 1);
// check for proxyinterface
$this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $objectA);
self::assertInstanceOf(\ProxyManager\Proxy\GhostObjectInterface::class, $objectA);
self::assertFalse($objectA->isProxyInitialized());
$objectB = $this->entityManager->find('EntityName', 1);
$objectB = $this->entityManager->find(EntityName::class, 1);
self::assertSame($objectA, $objectB)
$this->assertSame($objectA, $objectB)
}
The identity map being indexed by primary keys only allows shortcuts when you
@@ -106,7 +104,7 @@ How Doctrine Detects Changes
Doctrine is a data-mapper that tries to achieve persistence-ignorance (PI).
This means you map php objects into a relational database that don't
necessarily know about the database at all. A natural question would now be,
"how does Doctrine even detect objects have changed?".
"how does Doctrine even detect objects have changed?".
For this Doctrine keeps a second map inside the UnitOfWork. Whenever you fetch
an object from the database Doctrine will keep a copy of all the properties and
@@ -131,10 +129,12 @@ optimize the performance of the Flush Operation:
- Temporarily mark entities as read only. If you have a very large UnitOfWork
but know that a large set of entities has not changed, just mark them as read
only with ``$entityManager->getUnitOfWork()->markReadOnly($entity)``.
- Flush only a single entity with ``$entityManager->flush($entity)``.
- Use :doc:`Change Tracking Policies <change-tracking-policies>` to use more
explicit strategies of notifying the UnitOfWork what objects/properties
changed.
Query Internals
---------------
@@ -157,7 +157,7 @@ wishes to be hydrated. Default result-types include:
- SQL to a single result variable
Hydration to entities and arrays is one of most complex parts of Doctrine
algorithm-wise. It can build results with for example:
algorithm-wise. It can built results with for example:
- Single table selects
- Joins with n:1 or 1:n cardinality, grouping belonging to the same parent.
@@ -198,3 +198,4 @@ ClassMetadataFactory
~~~~~~~~~~~~~~~~~~~~
tbr
+151 -161
View File
@@ -2,25 +2,27 @@ Working with Associations
=========================
Associations between entities are represented just like in regular
object-oriented PHP code using references to other objects or
collections of objects.
object-oriented PHP, with references to other objects or
collections of objects. When it comes to persistence, it is
important to understand three main things:
Changes to associations in your code are not synchronized to the
database directly, only when calling ``EntityManager#flush()``.
There are other concepts you should know about when working
with associations in Doctrine:
- The :doc:`concept of owning and inverse sides <unitofwork-associations>`
in bidirectional associations.
- If an entity is removed from a collection, the association is
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
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
- Collection-valued :ref:`persistent fields <architecture_persistent_fields>` have to be instances of the
``Doctrine\Common\Collections\Collection`` interface.
Changes to associations in your code are not synchronized to the
database directly, but upon calling ``EntityManager#flush()``.
To describe all the concepts of working with associations we
introduce a specific set of example entities that show all the
different flavors of association management in Doctrine.
Association Example Entities
----------------------------
@@ -37,30 +39,36 @@ information about its type and if it's the owning or inverse side.
{
/** @Id @GeneratedValue @Column(type="string") */
private $id;
/**
* Bidirectional - Many users have Many favorite comments (OWNING SIDE)
*
* @ManyToMany(targetEntity="Comment", inversedBy="userFavorites")
* @JoinTable(name="user_favorite_comments")
* @JoinTable(name="user_favorite_comments",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="favorite_comment_id", referencedColumnName="id")}
* )
*/
private $favorites;
/**
* Unidirectional - Many users have marked many comments as read
*
* @ManyToMany(targetEntity="Comment")
* @JoinTable(name="user_read_comments")
* @JoinTable(name="user_read_comments",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="comment_id", referencedColumnName="id")}
* )
*/
private $commentsRead;
/**
* Bidirectional - One-To-Many (INVERSE SIDE)
*
* @OneToMany(targetEntity="Comment", mappedBy="author")
*/
private $commentsAuthored;
/**
* Unidirectional - Many-To-One
*
@@ -68,20 +76,20 @@ information about its type and if it's the owning or inverse side.
*/
private $firstComment;
}
/** @Entity */
class Comment
{
/** @Id @GeneratedValue @Column(type="string") */
private $id;
/**
* Bidirectional - Many comments are favorited by many users (INVERSE SIDE)
*
* @ManyToMany(targetEntity="User", mappedBy="favorites")
*/
private $userFavorites;
/**
* Bidirectional - Many Comments are authored by one user (OWNING SIDE)
*
@@ -100,19 +108,19 @@ definitions omitted):
firstComment_id VARCHAR(255) DEFAULT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE Comment (
id VARCHAR(255) NOT NULL,
author_id VARCHAR(255) DEFAULT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE user_favorite_comments (
user_id VARCHAR(255) NOT NULL,
favorite_comment_id VARCHAR(255) NOT NULL,
PRIMARY KEY(user_id, favorite_comment_id)
) ENGINE = InnoDB;
CREATE TABLE user_read_comments (
user_id VARCHAR(255) NOT NULL,
comment_id VARCHAR(255) NOT NULL,
@@ -135,7 +143,7 @@ relations of the ``User``:
public function getReadComments() {
return $this->commentsRead;
}
public function setFirstComment(Comment $c) {
$this->firstComment = $c;
}
@@ -148,17 +156,17 @@ The interaction code would then look like in the following snippet
<?php
$user = $em->find('User', $userId);
// unidirectional many to many
$comment = $em->find('Comment', $readCommentId);
$user->getReadComments()->add($comment);
$em->flush();
// unidirectional many to one
$myFirstComment = new Comment();
$user->setFirstComment($myFirstComment);
$em->persist($myFirstComment);
$em->flush();
@@ -171,40 +179,40 @@ fields on both sides:
class User
{
// ..
public function getAuthoredComments() {
return $this->commentsAuthored;
}
public function getFavoriteComments() {
return $this->favorites;
}
}
class Comment
{
// ...
public function getUserFavorites() {
return $this->userFavorites;
}
public function setAuthor(User $author = null) {
$this->author = $author;
}
}
// Many-to-Many
$user->getFavorites()->add($favoriteComment);
$favoriteComment->getUserFavorites()->add($user);
$em->flush();
// Many-To-One / One-To-Many Bidirectional
$newComment = new Comment();
$user->getAuthoredComments()->add($newComment);
$newComment->setAuthor($user);
$em->persist($newComment);
$em->flush();
@@ -225,10 +233,10 @@ element. Here are some examples:
// Remove by Elements
$user->getComments()->removeElement($comment);
$comment->setAuthor(null);
$user->getFavorites()->removeElement($comment);
$comment->getUserFavorites()->removeElement($user);
// Remove by Key
$user->getComments()->remove($ithComment);
$comment->setAuthor(null);
@@ -238,14 +246,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
@@ -262,6 +270,7 @@ where n is the size of the map.
can often be used to improve performance by avoiding the loading of
the inverse collection.
You can also clear the contents of a whole collection using the
``Collections::clear()`` method. You should be aware that using
this method can lead to a straight and optimized database delete or
@@ -270,8 +279,8 @@ entities that have been re-added to the collection.
Say you clear a collection of tags by calling
``$post->getTags()->clear();`` and then call
``$post->getTags()->add($tag)``. This will not recognize the tag having
already been added previously and will consequently issue two separate database
``$post->getTags()->add($tag)``. This will not recognize the tag having
already been added previously and will consequently issue two separate database
calls.
Association Management Methods
@@ -290,12 +299,12 @@ example that encapsulate much of the association management code:
<?php
class User
{
// ...
//...
public function markCommentRead(Comment $comment) {
// Collections implement ArrayAccess
$this->commentsRead[] = $comment;
}
public function addComment(Comment $comment) {
if (count($this->commentsAuthored) == 0) {
$this->setFirstComment($comment);
@@ -303,30 +312,30 @@ example that encapsulate much of the association management code:
$this->comments[] = $comment;
$comment->setAuthor($this);
}
private function setFirstComment(Comment $c) {
$this->firstComment = $c;
}
public function addFavorite(Comment $comment) {
$this->favorites->add($comment);
$comment->addUserFavorite($this);
}
public function removeFavorite(Comment $comment) {
$this->favorites->removeElement($comment);
$comment->removeUserFavorite($this);
}
}
class Comment
{
// ..
public function addUserFavorite(User $user) {
$this->userFavorites[] = $user;
}
public function removeUserFavorite(User $user) {
$this->userFavorites->removeElement($user);
}
@@ -350,6 +359,7 @@ the details inside the classes can be challenging.
entity cannot circumvent the logic you implement on your entity for
association management. For example:
.. code-block:: php
<?php
@@ -371,7 +381,7 @@ as your preferences.
Synchronizing Bidirectional Collections
---------------------------------------
In the case of Many-To-Many associations you as the developer have the
In the case of Many-To-Many associations you as the developer have the
responsibility of keeping the collections on the owning and inverse side
in sync when you apply changes to them. Doctrine can only
guarantee a consistent state for the hydration, not for your client
@@ -385,33 +395,62 @@ can show the possible caveats you can encounter:
<?php
$user->getFavorites()->add($favoriteComment);
// not calling $favoriteComment->getUserFavorites()->add($user);
$user->getFavorites()->contains($favoriteComment); // TRUE
$favoriteComment->getUserFavorites()->contains($user); // FALSE
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``, ``refresh`` or ``all``.
Persisting, removing, detaching 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.
- all : Cascades persist, remove, merge 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
@@ -419,102 +458,58 @@ comment might look like in your controller (without ``cascade: persist``):
$user = new User();
$myFirstComment = new Comment();
$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
<?php
class User
{
// ...
//...
/**
* Bidirectional - One-To-Many (INVERSE SIDE)
*
* @OneToMany(targetEntity="Comment", mappedBy="author", cascade={"persist", "remove"})
*/
private $commentsAuthored;
// ...
//...
}
...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.
@@ -522,19 +517,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
--------------
@@ -606,17 +603,17 @@ Now two examples of what happens when you remove the references:
$em->flush();
In this case you have not only changed the ``Contact`` entity itself but
you have also removed the references for standing data and as well as one
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
In this case you have not only changed the ``Contact`` entity itself but
you have also removed the references for standing data and as well as one
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
@@ -633,7 +630,7 @@ large collections.
$criteria = Criteria::create()
->where(Criteria::expr()->eq("birthday", "1982-02-17"))
->orderBy(array("username" => Criteria::ASC))
->orderBy(array("username" => "ASC"))
->setFirstResult(0)
->setMaxResults(20)
;
@@ -711,12 +708,5 @@ methods:
* ``isNull($field)``
* ``in($field, array $values)``
* ``notIn($field, array $values)``
* ``contains($field, $value)``
* ``startsWith($field, $value)``
* ``endsWith($field, $value)``
.. note::
There is a limitation on the compatibility of Criteria comparisons.
You have to use scalar values only as the value in a comparison or
the behaviour between different backends is not the same.
+174 -82
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
-----------------------------
@@ -48,7 +41,7 @@ headline "Hello World" with the ID 1234:
<?php
$article = $entityManager->find('CMS\Article', 1234);
$article->setHeadline('Hello World dude!');
$article2 = $entityManager->find('CMS\Article', 1234);
echo $article2->getHeadline();
@@ -100,29 +93,28 @@ from newly opened EntityManager.
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @Column(type="string") */
private $headline;
/** @ManyToOne(targetEntity="User") */
private $author;
/** @OneToMany(targetEntity="Comment", mappedBy="article") */
private $comments;
public function __construct()
{
public function __construct {
$this->comments = new ArrayCollection();
}
public function getAuthor() { return $this->author; }
public function getComments() { return $this->comments; }
}
$article = $em->find('Article', 1);
This code only retrieves the ``Article`` instance with id 1 executing
a single SELECT statement against the articles table in the database.
a single SELECT statement against the user table in the database.
You can still access the associated properties author and comments
and the associated objects they contain.
@@ -139,22 +131,22 @@ your code. See the following code:
<?php
$article = $em->find('Article', 1);
// accessing a method of the user instance triggers the lazy-load
echo "Author: " . $article->getAuthor()->getName() . "\n";
// Lazy Loading Proxies pass instanceof tests:
if ($article->getAuthor() instanceof User) {
// a User Proxy is a generated "UserProxy" class
}
// accessing the comments as an iterator triggers the lazy-load
// retrieving ALL the comments of this article from the database
// using a single SELECT statement
foreach ($article->getComments() as $comment) {
foreach ($article->getComments() AS $comment) {
echo $comment->getText() . "\n\n";
}
// Article::$comments passes instanceof tests for the Collection interface
// But it will NOT pass for the ArrayCollection interface
if ($article->getComments() instanceof \Doctrine\Common\Collections\Collection) {
@@ -162,28 +154,25 @@ your code. See the following code:
}
A slice of the generated proxy classes code looks like the
following piece of code. A real proxy class override all non-identity
non-transient object state at instantiation time in order to
enable lazy-loading mechanisms:
following piece of code. A real proxy class override ALL public
methods along the lines of the ``getName()`` method shown below:
.. code-block:: php
<?php
class UserProxyHASH extends User implements GhostObjectInterface
class UserProxy extends User implements Proxy
{
// ... generated code
public static function staticProxyConstructor($initializer)
private function _load()
{
// ... generated code
// lazy loading code
}
private function callInitializerHASH($methodName, array $parameters)
public function getName()
{
// ... generated code
$this->_load();
return parent::getName();
}
// ... generated code
// .. other public methods of User
}
.. warning::
@@ -193,6 +182,7 @@ enable lazy-loading mechanisms:
to heavily. Make sure to use DQL to fetch-join all the parts of the
object-graph that you need as efficiently as possible.
Persisting entities
-------------------
@@ -215,6 +205,7 @@ be properly synchronized with the database when
database in the most efficient way and a single, short transaction,
taking care of maintaining referential integrity.
Example:
.. code-block:: php
@@ -235,16 +226,18 @@ Example:
generated identifier being not available after a failed flush
operation.
The semantics of the persist operation, applied on an entity X, are
as follows:
- If X is a new entity, it becomes managed. The entity X will be
entered into the database as a result of the flush operation.
- If X is a preexisting managed entity, it is ignored by the
persist operation. However, the persist operation is cascaded to
entities referenced by X, if the relationships from X to these
other entities are mapped with cascade=PERSIST or cascade=ALL (see
":ref:`transitive-persistence`").
"Transitive Persistence").
- If X is a removed entity, it becomes managed.
- If X is a detached entity, an exception will be thrown on
flush.
@@ -268,6 +261,7 @@ which means that its persistent state will be deleted once
for and appear in query and collection results. See
the section on :ref:`Database and UnitOfWork Out-Of-Sync <workingobjects_database_uow_outofsync>`
for more information.
Example:
@@ -280,15 +274,16 @@ Example:
The semantics of the remove operation, applied to an entity X are
as follows:
- If X is a new entity, it is ignored by the remove operation.
However, the remove operation is cascaded to entities referenced by
X, if the relationship from X to these other entities is mapped
with cascade=REMOVE or cascade=ALL (see ":ref:`transitive-persistence`").
with cascade=REMOVE or cascade=ALL (see "Transitive Persistence").
- If X is a managed entity, the remove operation causes it to
become removed. The remove operation is cascaded to entities
referenced by X, if the relationships from X to these other
entities is mapped with cascade=REMOVE or cascade=ALL (see
":ref:`transitive-persistence`").
"Transitive Persistence").
- If X is a detached entity, an InvalidArgumentException will be
thrown.
- If X is a removed entity, it is ignored by the remove operation.
@@ -308,10 +303,11 @@ foreign key semantics of onDelete="CASCADE".
Deleting an object with all its associated objects can be achieved
in multiple ways with very different performance impacts.
1. If an association is marked as ``CASCADE=REMOVE`` Doctrine 2
will fetch this association. If it is a Single association it will
will fetch this association. If its a Single association it will
pass this entity to
´EntityManager#remove()``. If the association is a collection, Doctrine will loop over all its elements and pass them to ``EntityManager#remove()\`.
´EntityManager#remove()``. If the association is a collection, Doctrine will loop over all its elements and pass them to``EntityManager#remove()\`.
In both cases the cascade remove semantics are applied recursively.
For large object graphs this removal strategy can be very costly.
2. Using a DQL ``DELETE`` statement allows you to delete multiple
@@ -329,39 +325,134 @@ in multiple ways with very different performance impacts.
Detaching entities
------------------
All entities are detached from an EntityManager and thus no longer
managed by it after invoking the ``EntityManager#clear()`` method.
Changes made to the detached entities, if any (including their removal),
will not be synchronized to the database after they have been
An entity is detached from an EntityManager and thus no longer
managed by invoking the ``EntityManager#detach($entity)`` method on
it or by cascading the detach operation to it. Changes made to the
detached entity, if any (including removal of the entity), will not
be synchronized to the database after the entity has been
detached.
Doctrine will not hold on to any references to detached entities.
Doctrine will not hold on to any references to a detached entity.
Example:
.. code-block:: php
<?php
$em->clear();
$em->detach($entity);
The semantics of the detach operation, applied to an entity X are
as follows:
- If X is a managed entity, the ``clear`` operation causes it to
become detached. Entities which previously referenced X
- If X is a managed entity, the detach operation causes it to
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
"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, it will become detached, and therefore
no longer scheduled to be removed. Entities which previously
referenced X will continue to reference X.
- 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
"Transitive Persistence"). Entities which previously referenced X
will continue to reference X.
There are several situations in which an entity is detached
automatically:
automatically without invoking the ``detach`` method:
- When ``EntityManager#clear()`` is invoked, all entities that are
currently managed by the EntityManager instance become detached.
- When serializing an entity. The entity retrieved upon subsequent
unserialization will be detached (this is the case for all entities
that are serialized and stored in some cache).
unserialization will be detached (This is the case for all entities
that are serialized and stored in some cache, i.e. when using the
Query Result Cache).
The ``detach`` operation is usually not as frequently needed and
used as ``persist`` and ``remove``.
Merging entities
----------------
Merging entities refers to the merging of (usually detached)
entities into the context of an EntityManager so that they become
managed again. To merge the state of an entity into an
EntityManager use the ``EntityManager#merge($entity)`` method. The
state of the passed entity will be merged into a managed copy of
this entity and this copy will subsequently be returned.
Example:
.. code-block:: php
<?php
$detachedEntity = unserialize($serializedEntity); // some detached entity
$entity = $em->merge($detachedEntity);
// $entity now refers to the fully managed copy returned by the merge operation.
// The EntityManager $em now manages the persistence of $entity as usual.
.. note::
When you want to serialize/unserialize entities you
have to make all entity properties protected, never private. The
reason for this is, if you serialize a class that was a proxy
instance before, the private variables won't be serialized and a
PHP Notice is thrown.
The semantics of the merge operation, applied to an entity X, are
as follows:
- If X is a detached entity, the state of X is copied onto a
pre-existing managed entity instance X' of the same identity.
- If X is a new entity instance, a new managed copy X' will be
created and the state of X is copied onto this managed instance.
- If X is a removed entity instance, an InvalidArgumentException
will be thrown.
- 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 "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
that if X is managed then X is the same object as X'.)
- If X is an entity merged to X', with a reference to another
entity Y, where cascade=MERGE or cascade=ALL is not specified, then
navigation of the same association from X' yields a reference to a
managed object Y' with the same persistent identity as Y.
The ``merge`` operation will throw an ``OptimisticLockException``
if the entity being merged uses optimistic locking through a
version field and the versions of the entity being merged and the
managed copy don't match. This usually means that the entity has
been modified while being detached.
The ``merge`` operation is usually not as frequently needed and
used as ``persist`` and ``remove``. The most common scenario for
the ``merge`` operation is to reattach entities to an EntityManager
that come from some cache (and are therefore detached) and you want
to modify and persist such an entity.
.. warning::
If you need to perform multiple merges of entities that share certain subparts
of their object-graphs and cascade merge, then you have to call ``EntityManager#clear()`` between the
successive calls to ``EntityManager#merge()``. Otherwise you might end up with
multiple copies of the "same" object in the database, however with different ids.
.. note::
If you load some detached entities from a cache and you do
not need to persist or delete them or otherwise make use of them
without the need for persistence services there is no need to use
``merge``. I.e. you can simply pass detached objects from a cache
directly to the view.
Synchronization with the Database
---------------------------------
@@ -404,6 +495,7 @@ Synchronizing New and Managed Entities
The flush operation applies to a managed entity with the following
semantics:
- The entity itself is synchronized to the database using a SQL
UPDATE statement, only if at least one persistent field has
changed.
@@ -412,12 +504,14 @@ semantics:
The flush operation applies to a new entity with the following
semantics:
- The entity itself is synchronized to the database using a SQL
INSERT statement.
For all (initialized) relationships of the new or managed entity
the following semantics apply to each associated entity X:
- If X is new and persist operations are configured to cascade on
the relationship, X will be persisted.
- If X is new and no persist operations are configured to cascade
@@ -449,6 +543,7 @@ The cost of flushing
How costly a flush operation is, mainly depends on two factors:
- The size of the EntityManager's current UnitOfWork.
- The configured change tracking policies
@@ -468,13 +563,14 @@ during development.
.. note::
Do not invoke ``flush`` after every change to an entity
or every single invocation of persist/remove/refresh/... This is an
or every single invocation of persist/remove/merge/... This is an
anti-pattern and unnecessarily reduces the performance of your
application. Instead, form units of work that operate on your
objects and call ``flush`` when you are done. While serving a
single HTTP request there should be usually no need for invoking
``flush`` more than 0-2 times.
Direct access to a Unit of Work
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -494,6 +590,7 @@ instance the EntityManager is currently using.
marked as INTERNAL by not using them and carefully read the API
documentation.
Entity State
~~~~~~~~~~~~
@@ -529,7 +626,7 @@ An entity is in DETACHED state if it has persistent state and
identity but is currently not associated with an
``EntityManager``.
An entity is in NEW state if it has no persistent state and identity
An entity is in NEW state if has no persistent state and identity
and is not associated with an ``EntityManager`` (for example those
just created via the "new" operator).
@@ -583,13 +680,13 @@ methods on a repository as follows:
<?php
// $em instanceof EntityManager
// All users that are 20 years old
$users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20));
// All users that are 20 years old and have a surname of 'Miller'
$users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20, 'surname' => 'Miller'));
// A single user by its nickname
$user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb'));
@@ -601,6 +698,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
@@ -625,35 +724,28 @@ examples are equivalent:
<?php
// A single user by its nickname
$user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb'));
// 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
~~~~~~~~~~~
.. versionadded:: 2.3
The Repository implements the ``Doctrine\Common\Collections\Selectable``
interface. It means you can build ``Doctrine\Common\Collections\Criteria``
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
~~~~~~~~~~~~~~~~
Whenever you query for an entity that has persistent associations
and these associations are mapped as EAGER, they will automatically
be loaded together with the entity being queried and are thus
be loaded together with the entity being queried and is thus
immediately available to your application.
By Lazy Loading
@@ -683,7 +775,7 @@ A DQL query is represented by an instance of the
<?php
// $em instanceof EntityManager
// All users with an age between 20 and 30 (inclusive).
$q = $em->createQuery("select u from MyDomain\Model\User u where u.age >= 20 and u.age <= 30");
$users = $q->getResult();
@@ -717,7 +809,7 @@ By default the EntityManager returns a default implementation of
``Doctrine\ORM\EntityRepository`` when you call
``EntityManager#getRepository($entityClass)``. You can overwrite
this behaviour by specifying the class name of your own Entity
Repository in the Annotation or XML metadata. In large
Repository in the Annotation, XML or YAML metadata. In large
applications that require lots of specialized DQL queries using a
custom repository is one recommended way of grouping these queries
in a central location.
@@ -726,23 +818,22 @@ in a central location.
<?php
namespace MyDomain\Model;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="MyDomain\Model\UserRepository")
* @entity(repositoryClass="MyDomain\Model\UserRepository")
*/
class User
{
}
class UserRepository extends EntityRepository
{
public function getAllAdminUsers()
{
return $this->em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"')
return $this->_em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"')
->getResult();
}
}
@@ -753,6 +844,7 @@ You can access your repository now by calling:
<?php
// $em instanceof EntityManager
$admins = $em->getRepository('MyDomain\Model\User')->getAllAdminUsers();
+55 -64
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,16 +21,17 @@ 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">
...
</doctrine-mapping>
The XML mapping document of a class is loaded on-demand the first
time it is requested and subsequently stored in the metadata cache.
In order to work, this requires certain conventions:
- Each entity/mapped superclass must get its own dedicated XML
mapping document.
- The name of the mapping document must consist of the fully
@@ -43,6 +44,8 @@ In order to work, this requires certain conventions:
convention and you are not forced to do this. You can change the
file extension easily enough.
-
.. code-block:: php
<?php
@@ -61,16 +64,6 @@ of the constructor, like this:
$driver = new \Doctrine\ORM\Mapping\Driver\XmlDriver(array('/path/to/files1', '/path/to/files2'));
$config->setMetadataDriverImpl($driver);
.. warning::
Note that Doctrine ORM does not modify any settings for ``libxml``,
therefore, external XML entities may or may not be enabled or
configured correctly.
XML mappings are not XXE/XEE attack vectors since they are not
related with user input, but it is recommended that you do not
use external XML entities in your mapping files to avoid running
into unexpected behaviour.
Simplified XML Driver
~~~~~~~~~~~~~~~~~~~~~
@@ -87,8 +80,8 @@ Configuration of this client works a little bit different:
<?php
$namespaces = array(
'/path/to/files1' => 'MyProject\Entities',
'/path/to/files2' => 'OtherProject\Entities'
'MyProject\Entities' => '/path/to/files1',
'OtherProject\Entities' => '/path/to/files2'
);
$driver = new \Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver($namespaces);
$driver->setGlobalBasename('global'); // global.orm.xml
@@ -106,38 +99,38 @@ 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">
<indexes>
<index name="name_idx" columns="name"/>
<index columns="user_email"/>
</indexes>
<unique-constraints>
<unique-constraint columns="name,user_email" name="search_idx" />
</unique-constraints>
<lifecycle-callbacks>
<lifecycle-callback type="prePersist" method="doStuffOnPrePersist"/>
<lifecycle-callback type="prePersist" method="doOtherStuffOnPrePersistToo"/>
<lifecycle-callback type="postPersist" method="doStuffOnPostPersist"/>
</lifecycle-callbacks>
<id name="id" type="integer" column="id">
<generator strategy="AUTO"/>
<sequence-generator sequence-name="tablename_seq" allocation-size="100" initial-value="1" />
</id>
<field name="name" column="name" type="string" length="50" nullable="true" unique="true" />
<field name="email" column="user_email" type="string" column-definition="CHAR(32) NOT NULL" />
<one-to-one field="address" target-entity="Address" inversed-by="user">
<cascade><cascade-remove /></cascade>
<join-column name="address_id" referenced-column-name="id" on-delete="CASCADE" on-update="CASCADE"/>
</one-to-one>
<one-to-many field="phonenumbers" target-entity="Phonenumber" mapped-by="user">
<cascade>
<cascade-persist/>
@@ -146,7 +139,7 @@ of several common elements:
<order-by-field name="number" direction="ASC" />
</order-by>
</one-to-many>
<many-to-many field="groups" target-entity="Group">
<cascade>
<cascade-all/>
@@ -160,9 +153,9 @@ of several common elements:
</inverse-join-columns>
</join-table>
</many-to-many>
</entity>
</doctrine-mapping>
Be aware that class-names specified in the XML files should be
@@ -186,17 +179,19 @@ specified as the ``<entity />`` element as a direct child of the
.. code-block:: xml
<doctrine-mapping>
<entity name="MyProject\User" table="cms_users" schema="schema_name" repository-class="MyProject\UserRepository">
<entity name="MyProject\User" table="cms_users" repository-class="MyProject\UserRepository">
<!-- definition here -->
</entity>
</doctrine-mapping>
Required attributes:
- name - The fully qualified class-name of the entity.
Optional attributes:
- **table** - The Table-Name to be used for this entity. Otherwise the
Unqualified Class-Name is used by default.
- **repository-class** - The fully qualified class-name of an
@@ -208,7 +203,6 @@ Optional attributes:
- **read-only** - (>= 2.1) Specifies that this entity is marked as read only and not
considered for change-tracking. Entities of this type can be persisted
and removed though.
- **schema** - (>= 2.5) The schema the table lies in, for platforms that support schemas
Defining Fields
~~~~~~~~~~~~~~~
@@ -222,27 +216,23 @@ entity. For the ID mapping you have to use the ``<id />`` element.
.. code-block:: xml
<entity name="MyProject\User">
<field name="name" type="string" length="50" />
<field name="username" type="string" unique="true" />
<field name="age" type="integer" nullable="true" />
<field name="isActive" column="is_active" type="boolean" />
<field name="weight" type="decimal" scale="5" precision="2" />
<field name="login_count" type="integer" nullable="false">
<options>
<option name="comment">The number of times the user has logged in.</option>
<option name="default">0</option>
</options>
</field>
</entity>
Required attributes:
- name - The name of the Property/Field on the given Entity PHP
class.
Optional attributes:
- type - The ``Doctrine\DBAL\Types\Type`` name, defaults to
"string"
- column - Name of the column in the database, defaults to the
@@ -257,32 +247,12 @@ Optional attributes:
works on fields with type integer or datetime.
- scale - Scale of a decimal type.
- precision - Precision of a decimal type.
- options - Array of additional options:
- default - The default value to set for the column if no value
is supplied.
- unsigned - Boolean value to determine if the column should
be capable of representing only non-negative integers
(applies only for integer column and might not be supported by
all vendors).
- fixed - Boolean value to determine if the specified length of
a string column should be fixed or varying (applies only for
string/binary column and might not be supported by all vendors).
- comment - The comment of the column in the schema (might not
be supported by all vendors).
- customSchemaOptions - Array of additional schema options
which are mostly vendor specific.
- column-definition - Optional alternative SQL representation for
this column. This definition begin after the field-name and has to
specify the complete column definition. Using this feature will
turn this field dirty for Schema-Tool update commands at all
times.
.. note::
For more detailed information on each attribute, please refer to
the DBAL ``Schema-Representation`` documentation.
Defining Identity and Generator Strategies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -300,6 +270,7 @@ subset of the ``<field />`` element attributes:
Required attributes:
- name - The name of the Property/Field on the given Entity PHP
class.
- type - The ``Doctrine\DBAL\Types\Type`` name, preferably
@@ -307,6 +278,7 @@ Required attributes:
Optional attributes:
- column - Name of the column in the database, defaults to the
field name.
@@ -314,12 +286,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
@@ -332,6 +304,7 @@ have to use the ``NONE`` strategy.
The following values are allowed for the ``<generator />`` strategy
attribute:
- AUTO - Automatic detection of the identifier strategy based on
the preferred solution of the database vendor.
- IDENTITY - Use of a IDENTIFY strategy such as Auto-Increment IDs
@@ -354,10 +327,12 @@ element to describe the sequence:
Required attributes for ``<sequence-generator />``:
- sequence-name - The name of the sequence
Optional attributes for ``<sequence-generator />``:
- allocation-size - By how much steps should the sequence be
incremented when a value is retrieved. Defaults to 1
- initial-value - What should the initial value of the sequence
@@ -370,6 +345,7 @@ Optional attributes for ``<sequence-generator />``:
element, if Doctrine chooses the sequence strategy for a
platform.
Defining a Mapped Superclass
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -389,6 +365,7 @@ can define it in XML using the ``<mapped-superclass />`` tag.
Required attributes:
- name - Class name of the mapped superclass.
You can nest any number of ``<field />`` and unidirectional
@@ -428,6 +405,7 @@ The allowed values for inheritance-type attribute are ``JOINED`` or
All inheritance related definitions have to be defined on the root
entity of the hierarchy.
Defining Lifecycle Callbacks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -437,7 +415,7 @@ using the ``<lifecycle-callbacks />`` element:
.. code-block:: xml
<entity name="Doctrine\Tests\ORM\Mapping\User" table="cms_users">
<lifecycle-callbacks>
<lifecycle-callback type="prePersist" method="onPrePersist" />
</lifecycle-callbacks>
@@ -460,6 +438,7 @@ For the inverse side the mapping is as simple as:
Required attributes for inverse One-To-One:
- field - Name of the property/field on the entity's PHP class.
- target-entity - Name of the entity associated entity class. If
this is not qualified the namespace of the current class is
@@ -477,6 +456,7 @@ For the owning side this mapping would look like:
Required attributes for owning One-to-One:
- field - Name of the property/field on the entity's PHP class.
- target-entity - Name of the entity associated entity class. If
this is not qualified the namespace of the current class is
@@ -484,6 +464,7 @@ Required attributes for owning One-to-One:
Optional attributes for owning One-to-One:
- inversed-by - If the association is bidirectional the
inversed-by attribute has to be specified with the name of the
field on the inverse entity that contains the back-reference.
@@ -526,6 +507,7 @@ like:
Required attributes:
- field - Name of the property/field on the entity's PHP class.
- target-entity - Name of the entity associated entity class. If
this is not qualified the namespace of the current class is
@@ -533,6 +515,7 @@ Required attributes:
Optional attributes:
- inversed-by - If the association is bidirectional the
inversed-by attribute has to be specified with the name of the
field on the inverse entity that contains the back-reference.
@@ -575,6 +558,7 @@ exists for bi-directional associations.
Required attributes:
- field - Name of the property/field on the entity's PHP class.
- target-entity - Name of the entity associated entity class. If
this is not qualified the namespace of the current class is
@@ -584,6 +568,7 @@ Required attributes:
Optional attributes:
- fetch - Either LAZY, EXTRA_LAZY or EAGER, defaults to LAZY.
- index-by: Index the collection by a field on the target entity.
@@ -602,6 +587,7 @@ definitions and rely on their implicit values.
Required attributes:
- field - Name of the property/field on the entity's PHP class.
- target-entity - Name of the entity associated entity class. If
this is not qualified the namespace of the current class is
@@ -609,6 +595,7 @@ Required attributes:
Optional attributes:
- mapped-by - Name of the field on the owning side that contains
the owning side association if the defined many-to-many association
is on the inverse side.
@@ -664,7 +651,9 @@ tags.
Besides ``<cascade-all />`` the following operations can be
specified by their respective tags:
- ``<cascade-persist />``
- ``<cascade-merge />``
- ``<cascade-remove />``
- ``<cascade-refresh />``
@@ -677,12 +666,14 @@ key names are called that are used for joining two entities.
Required attributes:
- name - The column name of the foreign key.
- referenced-column-name - The column name of the associated
entities primary key
Optional attributes:
- unique - If the join column should contain a UNIQUE constraint.
This makes sense for Many-To-Many join-columns only to simulate a
one-to-many unidirectional using a join-table.
@@ -717,12 +708,12 @@ table you can use the ``<indexes />`` and
.. code-block:: xml
<entity name="Doctrine\Tests\ORM\Mapping\User" table="cms_users">
<indexes>
<index name="name_idx" columns="name"/>
<index columns="user_email"/>
</indexes>
<unique-constraints>
<unique-constraint columns="name,user_email" name="search_idx" />
</unique-constraints>
@@ -742,7 +733,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" />
@@ -751,6 +742,6 @@ entity relationship. You can define this in XML with the "association-key" attri
<field name="value" type="string" />
<many-to-one field="article" target-entity="Article" inversed-by="attributes" />
</entity>
<entity>
</doctrine-mapping>
+117
View File
@@ -0,0 +1,117 @@
YAML Mapping
============
The YAML mapping driver enables you to provide the ORM metadata in
form of YAML documents.
The YAML mapping document of a class is loaded on-demand the first
time it is requested and subsequently stored in the metadata cache.
In order to work, this requires certain conventions:
- Each entity/mapped superclass must get its own dedicated YAML
mapping document.
- The name of the mapping document must consist of the fully
qualified name of the class, where namespace separators are
replaced by dots (.).
- All mapping documents should get the extension ".dcm.yml" to
identify it as a Doctrine mapping file. This is more of a
convention and you are not forced to do this. You can change the
file extension easily enough.
.. code-block:: php
<?php
$driver->setFileExtension('.yml');
It is recommended to put all YAML mapping documents in a single
folder but you can spread the documents over several folders if you
want to. In order to tell the YamlDriver where to look for your
mapping documents, supply an array of paths as the first argument
of the constructor, like this:
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\Driver\YamlDriver;
// $config instanceof Doctrine\ORM\Configuration
$driver = new YamlDriver(array('/path/to/files'));
$config->setMetadataDriverImpl($driver);
Simplified YAML Driver
~~~~~~~~~~~~~~~~~~~~~~
The Symfony project sponsored a driver that simplifies usage of the YAML Driver.
The changes between the original driver are:
- File Extension is .orm.yml
- Filenames are shortened, "MyProject\\Entities\\User" will become User.orm.yml
- You can add a global file and add multiple entities in this file.
Configuration of this client works a little bit different:
.. code-block:: php
<?php
$namespaces = array(
'/path/to/files1' => 'MyProject\Entities',
'/path/to/files2' => 'OtherProject\Entities'
);
$driver = new \Doctrine\ORM\Mapping\Driver\SimplifiedYamlDriver($namespaces);
$driver->setGlobalBasename('global'); // global.orm.yml
Example
-------
As a quick start, here is a small example document that makes use
of several common elements:
.. code-block:: yaml
# Doctrine.Tests.ORM.Mapping.User.dcm.yml
Doctrine\Tests\ORM\Mapping\User:
type: entity
table: cms_users
indexes:
name_index:
columns: [ name ]
id:
id:
type: integer
generator:
strategy: AUTO
fields:
name:
type: string
length: 50
oneToOne:
address:
targetEntity: Address
joinColumn:
name: address_id
referencedColumnName: id
oneToMany:
phonenumbers:
targetEntity: Phonenumber
mappedBy: user
cascade: ["persist", "merge"]
manyToMany:
groups:
targetEntity: Group
joinTable:
name: cms_users_groups
joinColumns:
user_id:
referencedColumnName: id
inverseJoinColumns:
group_id:
referencedColumnName: id
lifecycleCallbacks:
prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ]
postPersist: [ doStuffOnPostPersist ]
Be aware that class-names specified in the YAML files should be
fully qualified.
-82
View File
@@ -1,82 +0,0 @@
.. toc::
.. tocheader:: Tutorials
.. toctree::
:depth: 3
tutorials/getting-started
tutorials/getting-started-database
tutorials/getting-started-models
tutorials/working-with-indexed-associations
tutorials/extra-lazy-associations
tutorials/composite-primary-keys
tutorials/ordered-associations
tutorials/override-field-association-mappings-in-subclasses
tutorials/pagination
tutorials/embeddables
.. toc::
.. tocheader:: Reference
.. toctree::
:depth: 3
reference/architecture
reference/configuration
reference/faq
reference/basic-mapping
reference/association-mapping
reference/inheritance-mapping
reference/working-with-objects
reference/working-with-associations
reference/events
reference/unitofwork
reference/unitofwork-associations
reference/transactions-and-concurrency
reference/batch-processing
reference/dql-doctrine-query-language
reference/query-builder
reference/native-sql
reference/change-tracking-policies
reference/partial-objects
reference/xml-mapping
reference/annotations-reference
reference/php-mapping
reference/caching
reference/improving-performance
reference/tools
reference/metadata-drivers
reference/best-practices
reference/limitations-and-known-issues
tutorials/pagination
reference/filters
reference/namingstrategy
reference/advanced-configuration
reference/second-level-cache
reference/security
.. toc::
.. tocheader:: Cookbook
.. toctree::
:depth: 3
cookbook/aggregate-fields
cookbook/custom-mapping-types
cookbook/decorator-pattern
cookbook/dql-custom-walkers
cookbook/dql-user-defined-functions
cookbook/implementing-arrayaccess-for-domain-objects
cookbook/implementing-the-notify-changetracking-policy
cookbook/implementing-wakeup-or-clone
cookbook/resolve-target-entity-listener
cookbook/sql-table-prefixes
cookbook/strategy-cookbook-introduction
cookbook/validation-of-entities
cookbook/working-with-datetime
cookbook/mysql-enums
cookbook/advanced-field-value-conversion-using-custom-mapping-types
cookbook/entities-in-session
+80
View File
@@ -0,0 +1,80 @@
Welcome to Doctrine 2 ORM's documentation!
==========================================
Tutorials
---------
.. toctree::
:maxdepth: 1
tutorials/getting-started
tutorials/working-with-indexed-associations
tutorials/extra-lazy-associations
tutorials/composite-primary-keys
tutorials/ordered-associations
tutorials/in-ten-quick-steps
tutorials/override-field-association-mappings-in-subclasses
Reference Guide
---------------
.. toctree::
:maxdepth: 1
:numbered:
reference/introduction
reference/architecture
reference/configuration
reference/faq
reference/basic-mapping
reference/association-mapping
reference/inheritance-mapping
reference/working-with-objects
reference/working-with-associations
reference/events
reference/unitofwork
reference/unitofwork-associations
reference/transactions-and-concurrency
reference/batch-processing
reference/dql-doctrine-query-language
reference/query-builder
reference/native-sql
reference/change-tracking-policies
reference/partial-objects
reference/xml-mapping
reference/yaml-mapping
reference/annotations-reference
reference/php-mapping
reference/caching
reference/improving-performance
reference/tools
reference/metadata-drivers
reference/best-practices
reference/limitations-and-known-issues
tutorials/pagination.rst
reference/filters.rst
reference/namingstrategy.rst
Cookbook
--------
.. toctree::
:maxdepth: 1
cookbook/aggregate-fields
cookbook/decorator-pattern
cookbook/dql-custom-walkers
cookbook/dql-user-defined-functions
cookbook/implementing-arrayaccess-for-domain-objects
cookbook/implementing-the-notify-changetracking-policy
cookbook/implementing-wakeup-or-clone
cookbook/integrating-with-codeigniter
cookbook/sql-table-prefixes
cookbook/strategy-cookbook-introduction
cookbook/validation-of-entities
cookbook/working-with-datetime
cookbook/mysql-enums
cookbook/advanced-field-value-conversion-using-custom-mapping-types
cookbook/entities-in-session
+88 -50
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
@@ -28,20 +28,17 @@ and year of production as primary keys:
.. code-block:: php
<?php
namespace VehicleCatalogue\Model;
use Doctrine\ORM\Annotation as ORM;
/**
* @ORM\Entity
* @Entity
*/
class Car
{
/** @ORM\Id @ORM\Column(type="string") */
/** @Id @Column(type="string") */
private $name;
/** @ORM\Id @ORM\Column(type="integer") */
private $year;
/** @Id @Column(type="integer") */
private $year
public function __construct($name, $year)
{
@@ -66,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" />
@@ -74,6 +71,16 @@ and year of production as primary keys:
</entity>
</doctrine-mapping>
.. code-block:: yaml
VehicleCatalogue\Model\Car:
type: entity
id:
name:
type: string
year:
type: integer
Now you can use this entity:
.. code-block:: php
@@ -130,8 +137,9 @@ of one or many parent entities.
The semantics of mapping identity through foreign entities are easy:
- Only allowed on Many-To-One or One-To-One associations.
- Plug an ``@ORM\Id`` annotation onto every association.
- Plug an ``@Id`` annotation onto every association.
- Set an attribute ``association-key`` with the field name of the association in XML.
- Set a key ``associationKey:`` with the field name of the association in YAML.
Use-Case 1: Dynamic Attributes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -146,21 +154,19 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
namespace Application\Model;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Annotation as ORM;
/**
* @ORM\Entity
* @Entity
*/
class Article
{
/** @ORM\Id @ORM\Column(type="integer") @ORM\GeneratedValue */
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @ORM\Column(type="string") */
/** @Column(type="string") */
private $title;
/**
* @ORM\OneToMany(targetEntity="ArticleAttribute", mappedBy="article", cascade={"ALL"}, indexBy="attribute")
* @OneToMany(targetEntity="ArticleAttribute", mappedBy="article", cascade={"ALL"}, indexBy="attribute")
*/
private $attributes;
@@ -171,17 +177,17 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
}
/**
* @ORM\Entity
* @Entity
*/
class ArticleAttribute
{
/** @ORM\Id @ORM\ManyToOne(targetEntity="Article", inversedBy="attributes") */
/** @Id @ManyToOne(targetEntity="Article", inversedBy="attributes") */
private $article;
/** @ORM\Id @ORM\Column(type="string") */
/** @Id @Column(type="string") */
private $attribute;
/** @ORM\Column(type="string") */
/** @Column(type="string") */
private $value;
public function __construct($name, $value, $article)
@@ -197,12 +203,12 @@ 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" />
<id name="attribute" type="string" />
<field name="value" type="string" />
<many-to-one field="article" target-entity="Article" inversed-by="attributes" />
@@ -210,6 +216,24 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
</doctrine-mapping>
.. code-block:: yaml
Application\Model\ArticleAttribute:
type: entity
id:
article:
associationKey: true
attribute:
type: string
fields:
value:
type: string
manyToOne:
article:
targetEntity: Article
inversedBy: attributes
Use-Case 2: Simple Derived Identity
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -222,27 +246,44 @@ One good example for this is a user-address relationship:
.. code-block:: php
<?php
use Doctrine\ORM\Annotation as ORM;
/**
* @ORM\Entity
* @Entity
*/
class User
{
/** @ORM\Id @ORM\Column(type="integer") @ORM\GeneratedValue */
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
}
/**
* @ORM\Entity
* @Entity
*/
class Address
{
/** @ORM\Id @ORM\OneToOne(targetEntity="User") */
/** @Id @OneToOne(targetEntity="User") */
private $user;
}
.. code-block:: yaml
User:
type: entity
id:
id:
type: integer
generator:
strategy: AUTO
Address:
type: entity
id:
user:
associationKey: true
oneToOne:
user:
targetEntity: User
Use-Case 3: Join-Table with Metadata
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -254,27 +295,23 @@ of products purchased and maybe even the current price.
<?php
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Annotation as ORM;
/** @ORM\Entity */
/** @Entity */
class Order
{
/** @ORM\Id @ORM\Column(type="integer") @ORM\GeneratedValue */
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @ORM\ManyToOne(targetEntity="Customer") */
/** @ManyToOne(targetEntity="Customer") */
private $customer;
/** @ORM\OneToMany(targetEntity="OrderItem", mappedBy="order") */
/** @OneToMany(targetEntity="OrderItem", mappedBy="order") */
private $items;
/** @ORM\Column(type="boolean") */
private $paid = false;
/** @ORM\Column(type="boolean") */
/** @Column(type="boolean") */
private $payed = false;
/** @Column(type="boolean") */
private $shipped = false;
/** @ORM\Column(type="datetime") */
/** @Column(type="datetime") */
private $created;
public function __construct(Customer $customer)
@@ -285,16 +322,16 @@ of products purchased and maybe even the current price.
}
}
/** @ORM\Entity */
/** @Entity */
class Product
{
/** @ORM\Id @ORM\Column(type="integer") @ORM\GeneratedValue */
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @ORM\Column(type="string") */
/** @Column(type="string") */
private $name;
/** @ORM\Column(type="decimal") */
/** @Column(type="decimal") */
private $currentPrice;
public function getCurrentPrice()
@@ -303,19 +340,19 @@ of products purchased and maybe even the current price.
}
}
/** @ORM\Entity */
/** @Entity */
class OrderItem
{
/** @ORM\Id @ORM\ManyToOne(targetEntity="Order") */
/** @Id @ManyToOne(targetEntity="Order") */
private $order;
/** @ORM\Id @ORM\ManyToOne(targetEntity="Product") */
/** @Id @ManyToOne(targetEntity="Product") */
private $product;
/** @ORM\Column(type="integer") */
/** @Column(type="integer") */
private $amount = 1;
/** @ORM\Column(type="decimal") */
/** @Column(type="decimal") */
private $offeredPrice;
public function __construct(Order $order, Product $product, $amount = 1)
@@ -326,6 +363,7 @@ of products purchased and maybe even the current price.
}
}
Performance Considerations
~~~~~~~~~~~~~~~~~~~~~~~~~~
-150
View File
@@ -1,150 +0,0 @@
Separating Concerns using Embeddables
-------------------------------------
Embeddables are classes which are not entities themselves, 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.
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
the ``User`` class. We will model the ``Address`` class as an embeddable
instead of simply adding the respective columns to the ``User`` class.
.. configuration-block::
.. code-block:: php
<?php
use Doctrine\ORM\Annotation as ORM;
/** @ORM\Entity */
class User
{
/** @ORM\Embedded(class = "Address") */
private $address;
}
/** @ORM\Embeddable */
class Address
{
/** @ORM\Column(type = "string") */
private $street;
/** @ORM\Column(type = "string") */
private $postalCode;
/** @ORM\Column(type = "string") */
private $city;
/** @ORM\Column(type = "string") */
private $country;
}
.. code-block:: xml
<doctrine-mapping>
<entity name="User">
<embedded name="address" class="Address" />
</entity>
<embeddable name="Address">
<field name="street" type="string" />
<field name="postalCode" type="string" />
<field name="city" type="string" />
<field name="country" type="string" />
</embeddable>
</doctrine-mapping>
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
----------------
By default, Doctrine names your columns by prefixing them, using the value
object name.
Following the example above, your columns would be named as ``address_street``,
``address_postalCode``...
You can change this behaviour to meet your needs by changing the
``columnPrefix`` attribute in the ``@Embedded`` notation.
The following example shows you how to set your prefix to ``myPrefix_``:
.. configuration-block::
.. code-block:: php
<?php
use Doctrine\ORM\Annotation as ORM;
/** @ORM\Entity */
class User
{
/** @ORM\Embedded(class = "Address", columnPrefix = "myPrefix_") */
private $address;
}
.. code-block:: xml
<entity name="User">
<embedded name="address" class="Address" column-prefix="myPrefix_" />
</entity>
To have Doctrine drop the prefix and use the value object's property name
directly, set ``columnPrefix=false`` (``use-column-prefix="false"`` for XML):
.. configuration-block::
.. code-block:: php
<?php
use Doctrine\ORM\Annotation as ORM;
/** @ORM\Entity */
class User
{
/** @ORM\Embedded(class = "Address", columnPrefix = false) */
private $address;
}
.. code-block:: xml
<entity name="User">
<embedded name="address" class="Address" use-column-prefix="false" />
</entity>
DQL
---
You can also use mapped fields of embedded classes in DQL queries, just
as if they were declared in the ``User`` class:
.. code-block:: sql
SELECT u FROM User u WHERE u.address.city = :myCity
+17 -9
View File
@@ -15,12 +15,10 @@ the first time its accessed. If you mark an association as extra lazy the follow
can be called without triggering a full load of the collection:
- ``Collection#contains($entity)``
- ``Collection#containsKey($key)`` (available with Doctrine 2.5)
- ``Collection#count()``
- ``Collection#get($key)`` (available with Doctrine 2.4)
- ``Collection#slice($offset, $length = null)``
For each of the above methods the following semantics apply:
For each of this three methods the following semantics apply:
- For each call, if the Collection is not yet loaded, issue a straight SELECT statement against the database.
- For each call, if the collection is already loaded, fallback to the default functionality for lazy collections. No additional SELECT statements are executed.
@@ -34,6 +32,7 @@ Additionally even with Doctrine 2.0 the following methods do not trigger the col
With extra lazy collections you can now not only add entities to large collections but also paginate them
easily using a combination of ``count`` and ``slice``.
Enabling Extra-Lazy Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -45,18 +44,15 @@ switch to extra lazy as shown in these examples:
.. code-block:: php
<?php
namespace Doctrine\Tests\Models\CMS;
use Doctrine\ORM\Annotation as ORM;
/**
* @ORM\Entity
* @Entity
*/
class CmsGroup
{
/**
* @ORM\ManyToMany(targetEntity="CmsUser", mappedBy="groups", fetch="EXTRA_LAZY")
* @ManyToMany(targetEntity="CmsUser", mappedBy="groups", fetch="EXTRA_LAZY")
*/
public $users;
}
@@ -67,10 +63,22 @@ 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">
<!-- ... -->
<many-to-many field="users" target-entity="CmsUser" mapped-by="groups" fetch="EXTRA_LAZY" />
</entity>
</doctrine-mapping>
.. code-block:: yaml
Doctrine\Tests\Models\CMS\CmsGroup:
type: entity
# ...
manyToMany:
users:
targetEntity: CmsUser
mappedBy: groups
fetch: EXTRA_LAZY
@@ -7,7 +7,7 @@ Getting Started: Database First
start with developing Objects and then map them onto your database. When
you :doc:`Model First <getting-started-models>`, you are modelling your application using tools (for
example UML) and generate database schema and PHP code from this model.
When you have a Database First, you already have a database schema
When you have a :doc:`Database First <getting-started-database>`, you already have a database schema
and generate the corresponding PHP code from it.
.. note::
+1 -1
View File
@@ -5,7 +5,7 @@ Getting Started: Model First
When you :doc:`Code First <getting-started>`, you
start with developing Objects and then map them onto your database. When
you Model First, you are modelling your application using tools (for
you :doc:`Model First <getting-started-models>`, you are modelling your application using tools (for
example UML) and generate database schema and PHP code from this model.
When you have a :doc:`Database First <getting-started-database>`, then you already have a database schema
and generate the corresponding PHP code from it.
File diff suppressed because it is too large Load Diff
+30 -14
View File
@@ -5,29 +5,26 @@ There are use-cases when you'll want to sort collections when they are
retrieved from the database. In userland you do this as long as you
haven't initially saved an entity with its associations into the
database. To retrieve a sorted collection from the database you can
use the ``@ORM\OrderBy`` annotation with an collection that specifies
use the ``@OrderBy`` annotation with an collection that specifies
an DQL snippet that is appended to all queries with this
collection.
Additional to any ``@ORM\OneToMany`` or ``@ORM\ManyToMany`` annotation
you can specify the ``@ORM\OrderBy`` in the following way:
Additional to any ``@OneToMany`` or ``@ManyToMany`` annotation you
can specify the ``@OrderBy`` in the following way:
.. configuration-block::
.. code-block:: php
<?php
use Doctrine\ORM\Annotation as ORM;
/** @ORM\Entity **/
/** @Entity **/
class User
{
// ...
/**
* @ORM\ManyToMany(targetEntity="Group")
* @ORM\OrderBy({"name" = "ASC"})
* @ManyToMany(targetEntity="Group")
* @OrderBy({"name" = "ASC"})
**/
private $groups;
}
@@ -44,21 +41,39 @@ you can specify the ``@ORM\OrderBy`` in the following way:
</entity>
</doctrine-mapping>
.. code-block:: yaml
User:
type: entity
manyToMany:
groups:
orderBy: { 'name': 'ASC' }
targetEntity: Group
joinTable:
name: users_groups
joinColumns:
user_id:
referencedColumnName: id
inverseJoinColumns:
group_id:
referencedColumnName: id
The DQL Snippet in OrderBy is only allowed to consist of
unqualified, unquoted field names and of an optional ASC/DESC
positional statement. Multiple Fields are separated by a comma (,).
The referenced field names have to exist on the ``targetEntity``
class of the ``@ORM\ManyToMany`` or ``@ORM\OneToMany`` annotation.
class of the ``@ManyToMany`` or ``@OneToMany`` annotation.
The semantics of this feature can be described as follows:
The semantics of this feature can be described as follows.
- ``@ORM\OrderBy`` acts as an implicit ORDER BY clause for the given
- ``@OrderBy`` acts as an implicit ORDER BY clause for the given
fields, that is appended to all the explicitly given ORDER BY
items.
- All collections of the ordered type are always retrieved in an
ordered fashion.
- To keep the database impact low, these implicit ORDER BY items
are only added to a DQL Query if the collection is fetch joined in
are only added to an DQL Query if the collection is fetch joined in
the DQL query.
Given our previously defined example, the following would not add
@@ -92,3 +107,4 @@ You can reverse the order with an explicit DQL ORDER BY:
SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC, g.name ASC

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