mirror of
https://github.com/doctrine/orm.git
synced 2026-04-25 15:38:10 +02:00
Compare commits
111 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0363a5548d | |||
| 3764e49e6c | |||
| 6ee20204a5 | |||
| d9b0c87ded | |||
| 8594e5c4da | |||
| 5f821f3b98 | |||
| b566525099 | |||
| 215c4a03e1 | |||
| b3ccd6466b | |||
| b596bbb29f | |||
| c204e6c6a1 | |||
| 0bc94589e1 | |||
| f37856829f | |||
| 157c793810 | |||
| 72d838a804 | |||
| 58f8dc5d4c | |||
| 7d3ecd9481 | |||
| 1bb55703a7 | |||
| 56cbcec13d | |||
| 837c19bfc0 | |||
| 7b8f09ee4a | |||
| 488a4dc78a | |||
| 1364b6acc6 | |||
| 3dbe181762 | |||
| a3acaab65c | |||
| f183d25a33 | |||
| 7c8350094e | |||
| c613410ba6 | |||
| 6bb7581dd7 | |||
| ab71dab7d1 | |||
| 2c114756bd | |||
| 45496f040d | |||
| b40866c624 | |||
| a89cc7abea | |||
| 5ac111e5f8 | |||
| c5f66e6e7f | |||
| b59f495875 | |||
| 3829b9c28b | |||
| 65bcdbf4c7 | |||
| 95d000e51b | |||
| 3657df3b01 | |||
| 1661ffae9a | |||
| b424a5cf14 | |||
| 2767a4eec4 | |||
| 9486867279 | |||
| 6f2bb08972 | |||
| da2d3b406e | |||
| c4b7d3fbea | |||
| 84373d05a4 | |||
| e82e7147fa | |||
| e23ed2250d | |||
| 192bb6fd21 | |||
| 0f3679f034 | |||
| 1d2cd82706 | |||
| b983d86612 | |||
| b11f01643c | |||
| b58fb8f5d4 | |||
| 925a22b71d | |||
| 0f0d8abd67 | |||
| 470c15ce05 | |||
| 3cc5fc0252 | |||
| fd0657089a | |||
| de3b237292 | |||
| 1221cc3a2a | |||
| 9efbc1fa71 | |||
| 57705e0d78 | |||
| 82bb6b78cd | |||
| 64c56b21aa | |||
| b04e2e6364 | |||
| a70f9b7f49 | |||
| c88a7c1ffe | |||
| c206728c96 | |||
| e8d420c641 | |||
| fdcab7eae8 | |||
| 45d7d5234f | |||
| 159ca79b81 | |||
| 2b148a27e0 | |||
| 0aef57f60c | |||
| fef1e0286c | |||
| 4a38534150 | |||
| 1de22adb16 | |||
| 62b4160887 | |||
| dbb7c4d2bf | |||
| e8978ee365 | |||
| c095b88804 | |||
| efe4208ba6 | |||
| 453a56670d | |||
| ec36e2c866 | |||
| e250572cb4 | |||
| 758955e183 | |||
| 5b8d6a1486 | |||
| 3f1003fee9 | |||
| 7e241e89b8 | |||
| 67c1e1d2b1 | |||
| 261eacdbfc | |||
| 43df821691 | |||
| 11d09702da | |||
| f9f14139cf | |||
| 39f4d46d36 | |||
| 1dae8d318f | |||
| a361a7c1cb | |||
| 6a73608baf | |||
| f9955152b2 | |||
| 5aad1df149 | |||
| 243832555b | |||
| ae12fa6b5b | |||
| edaf9b6813 | |||
| b324a21abf | |||
| ff34aaaa2c | |||
| 9767a814a6 | |||
| e6007575e1 |
@@ -0,0 +1,4 @@
|
||||
# for php-coveralls
|
||||
service_name: travis-ci
|
||||
src_dir: lib
|
||||
coverage_clover: build/logs/clover.xml
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
patreon: phpdoctrine
|
||||
tidelift: packagist/doctrine/orm
|
||||
custom: https://www.doctrine-project.org/sponsorship.html
|
||||
@@ -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.
|
||||
-->
|
||||
@@ -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? -->
|
||||
|
||||
@@ -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. -->
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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,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
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Doctrine 2 ORM
|
||||
|
||||
Master: [](http://travis-ci.org/doctrine/doctrine2)
|
||||
2.3: [](http://travis-ci.org/doctrine/doctrine2)
|
||||
2.2: [](http://travis-ci.org/doctrine/doctrine2)
|
||||
2.1: [](http://travis-ci.org/doctrine/doctrine2)
|
||||
|
||||
Master: [](https://coveralls.io/r/doctrine/doctrine2?branch=master)
|
||||
|
||||
[](https://packagist.org/packages/doctrine/orm) [](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)
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
[](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
@@ -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
@@ -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
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
include('doctrine.php');
|
||||
|
||||
+18
-4
@@ -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
@@ -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);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Version class and file
|
||||
project.version_class = Doctrine\\ORM\\Version
|
||||
project.version_file = lib/Doctrine/ORM/Version.php
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
en/_exts/configurationblock.pyc
|
||||
build
|
||||
en/_build
|
||||
.idea
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "en/_theme"]
|
||||
path = en/_theme
|
||||
url = https://github.com/doctrine/doctrine-sphinx-theme.git
|
||||
-361
@@ -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/.
|
||||
@@ -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.
|
||||
Executable
+10
@@ -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
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
sudo apt-get install python25 python25-dev texlive-full rubber
|
||||
sudo easy_install pygments
|
||||
sudo easy_install sphinx
|
||||
@@ -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."
|
||||
@@ -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)
|
||||
Submodule
+1
Submodule docs/en/_theme added at 68795c5888
+201
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,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.
|
||||
|
||||
|
||||
@@ -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>`_.
|
||||
|
||||
|
||||
|
||||
@@ -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!
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
|
||||
@@ -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>`
|
||||
|
||||
@@ -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
@@ -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>`
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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***)
|
||||
-----------------------------------
|
||||
|
||||
|
||||
@@ -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 field 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 field 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:
|
||||
|
||||
|
||||
@@ -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
@@ -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());
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
@@ -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>`_.
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
-----------
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>`
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
|
||||
@@ -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)``
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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 doesn’t 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.
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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::
|
||||
|
||||
@@ -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
@@ -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
Reference in New Issue
Block a user