Compare commits

..

149 Commits

Author SHA1 Message Date
Marco Pivetta
fc19c3b53d Release 2.4.4 2014-07-11 05:05:53 +02:00
Marco Pivetta
5c5abb6771 Merge branch 'hotfix/DDC-3208-backport-DDC-3160' into 2.4 2014-07-11 04:49:42 +02:00
Marco Pivetta
42226dadd1 DDC-3208 - hotfix for DDC-3160 backported to 2.4.x 2014-07-11 04:49:30 +02:00
Benjamin Eberlei
84f8ef5ca4 Bump version to 2.4.4 2014-06-10 13:49:09 +02:00
Benjamin Eberlei
8a13376d42 Release 2.4.3 2014-06-10 13:49:08 +02:00
Marco Pivetta
8b5632cb65 The proxy factory always expects non-null identifier values 2014-06-10 13:48:35 +02:00
Benjamin Eberlei
859465b691 Fix wrong version 2014-06-03 21:43:21 +02:00
Benjamin Eberlei
530c01b5e3 [DDC-3120] Fix bug with unserialize bc break in PHP 5.4.29 and PHP 5.5.13 2014-06-03 17:36:31 +02:00
Marco Pivetta
63c5758070 Merge pull request #784 from eventhorizonpl/fix_docs
fix documentation warnings p1
Conflicts:
	docs/en/reference/advanced-configuration.rst
2014-04-21 05:07:57 +00:00
Benjamin Eberlei
2a9a53ae9d Merge branch 'DDC-3076' into 2.4 2014-04-17 00:03:39 +02:00
Frank Liepert
0081319712 [DDC-3076] Add/Improve tests 2014-04-17 00:03:11 +02:00
Frank Liepert
78ceda7ecf [DDC-3076] Fix ObjectHydrator 2014-04-17 00:03:11 +02:00
Frank Liepert
1f4810e370 [DDC-3076] Add test 2014-04-17 00:03:11 +02:00
Frank
376a3ac3b6 Fix: handle invalid discriminator value 2014-04-17 00:03:11 +02:00
Frank
ab87dd6325 Add: invalidDiscriminatorValue method 2014-04-17 00:03:11 +02:00
Guilherme Blanco
2ae245db30 Fixes DDC-2984. Made DDC-742 more resilient to recurring failures. 2014-04-16 23:41:53 +02:00
Benjamin Eberlei
9d36e855c0 Disable SQLServer platform related test, because of a wrong merge into 2.4 2014-03-23 16:38:05 +01:00
Benjamin Eberlei
f7c87cddd8 Merge branch 'DDC-2997' into 2.4 2014-03-23 15:38:07 +01:00
Alexandru Patranescu
1566b8a057 allow passing EntityManagerInterface when creating a HelperSet 2014-03-23 15:37:07 +01:00
Benjamin Eberlei
333fa1090a Merge branch 'DDC-3018' into 2.4 2014-03-23 15:17:56 +01:00
Benjamin Eberlei
0262b083bb [DDC-3018] Fix string literals in new operator. 2014-03-23 15:17:47 +01:00
Benjamin Eberlei
60c9c27c08 Merge branch 'DDC-2996' into 2.4 2014-03-23 13:19:43 +01:00
Benjamin Eberlei
8b75e3563a [DDC-2996] Fix bug in UnitOfWork#recomputeSingleEntityChangeSet
When calling UnitOfWork#recomputeSingleEntityChangeSet on an entity
that didn't have a changeset before, the computation was ignored.
This method however is suggested to be used in "onFlush" and "preFlush"
events in the documentation.

Also fix a bug where recomputeSingleEntityChangeSet was used
before calculating a real changeset for an object.
2014-03-23 13:19:22 +01:00
Benjamin Eberlei
cbf16f1cf8 Merge branch 'DDC-3033' into 2.4 2014-03-23 12:41:23 +01:00
Benjamin Eberlei
b84c828ea1 [DDC-3033] Clarify restrictions in events. 2014-03-23 12:40:56 +01:00
Benjamin Eberlei
55b7e4cff2 [DDC-3033] Fix bug in UnitOfWork#recomputeSingleEntityChangeSet.
The fix for DDC-2624 had a side effect on recomputation of
changesets in preUpdate events. The method wasn't adjusted
to the changes in its sister method computeChangeSet() and
had wrong assumptions about the computation.

Especially:
1. Collections have to be skipped
2. Comparison was changed to strict equality only.
2014-03-23 12:38:51 +01:00
Thomas Lallement
e38af55100 Update DDC3033Test.php 2014-03-23 12:38:51 +01:00
Thomas Lallement
7338c2d1f8 Update DDC3033Test.php 2014-03-23 12:38:51 +01:00
Thomas Lallement
0ff3cdf150 Failing Test (since commit 53a5a48aed)
Hi,

It seems to be a regression since the commit 53a5a48aed

Doctrine\ORM\PersistentCollection can be populated in $changeSet if you set a PreUpdate and PostUpdate event.

Original issue: http://www.doctrine-project.org/jira/browse/DDC-3033
2014-03-23 12:38:51 +01:00
Benjamin Eberlei
24c5c54c1e Merge branch 'DDC-3041' into 2.4 2014-03-23 10:18:36 +01:00
Menno Holtkamp
6bb367f488 Added test to ensure boolean metadata is properly exported/serialized to XML 2014-03-23 10:16:43 +01:00
Menno Holtkamp
213cc5c695 Use boolean values for 'unique' attribute
As defined in: https://github.com/doctrine/doctrine2/blob/master/doctrine-mapping.xsd#L294

Same as 'nullable' attribute. 

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

The same bug of the yaml driver: see http://www.doctrine-project.org/jira/browse/DDC-2661
2014-01-02 23:34:17 +01:00
Eduardo
7c8350094e Options not respected for ID Fields in XML Mapping Driver
Same bug of the YAML driver, see: http://www.doctrine-project.org/jira/browse/DDC-2661
2014-01-02 23:34:17 +01:00
Benjamin Eberlei
c613410ba6 Merge branch 'DDC-2764' into 2.4 2014-01-02 23:16:56 +01:00
Sander Marechal
6bb7581dd7 Add rootAlias to Criteria where clauses 2014-01-02 23:16:35 +01:00
Sander Marechal
ab71dab7d1 Set rootAlias outside loop 2014-01-02 23:15:31 +01:00
Sander Marechal
2c114756bd [DDC-2764] Prefix criteria orderBy with rootAlias 2014-01-02 23:15:31 +01:00
Benjamin Eberlei
45496f040d Merge branch 'DDC-2775' into 2.4 2014-01-02 23:11:16 +01:00
Benjamin Eberlei
b40866c624 [DDC-2775] cleanup test. 2014-01-02 23:11:07 +01:00
Matthieu Napoli
a89cc7abea Inlined the model for the DCC2775 test case inside the test class 2014-01-02 23:07:53 +01:00
Matthieu Napoli
5ac111e5f8 Cleaned up tests for DDC-2775 2014-01-02 23:07:53 +01:00
Matthieu Napoli
c5f66e6e7f Fixed tests failing in pgsql because of used of a reserved keyword 2014-01-02 23:07:53 +01:00
Matthieu Napoli
b59f495875 Fixed tests for pgsql: was using reserved keyword as table name 2014-01-02 23:07:53 +01:00
Matthieu Napoli
3829b9c28b [DDC-2775] Bugfix 2014-01-02 23:07:53 +01:00
Matthieu Napoli
65bcdbf4c7 [DDC-2775] Tests reproducing DDC-2775 2014-01-02 23:07:53 +01:00
Benjamin Eberlei
95d000e51b Merge branch 'DDC-2692' into 2.4 2014-01-02 22:17:20 +01:00
Stefan Kleff
3657df3b01 Listener class prefix 2014-01-02 22:16:59 +01:00
Stefan Kleff
1661ffae9a removed unused use statements, fixed typo and group tag 2014-01-02 22:16:59 +01:00
Stefan Kleff
b424a5cf14 Added unit test 2014-01-02 22:16:59 +01:00
Stefan Kleff
2767a4eec4 Multiple invokation of listeners on PreFlush event
Only lifecycle callbacks and entity listeners should be triggered here. The preFlush listener event is already triggered at the beginning of commit()
2014-01-02 22:16:59 +01:00
Benjamin Eberlei
9486867279 Merge branch 'DDC-2645' into 2.4 2013-12-15 23:34:57 +01:00
Pouyan Savoli
6f2bb08972 [DDC-2645] Apply patch to fix issue 2013-12-15 23:34:34 +01:00
Aaron Muylaert
da2d3b406e Create failing test for DDC-2645.
Merge not dealing correctly with composite primary keys.
2013-12-15 23:34:34 +01:00
Benjamin Eberlei
c4b7d3fbea Bump version to 2.4.2 2013-11-12 13:40:15 +01:00
Benjamin Eberlei
84373d05a4 Release 2.4.1 2013-11-12 13:40:13 +01:00
Benjamin Eberlei
e82e7147fa Merge branch 'DDC-2715' into 2.4 2013-10-29 09:25:13 +01:00
jan brunnert
e23ed2250d Removed unnecessary is_object() check 2013-10-29 09:24:52 +01:00
jan brunnert
192bb6fd21 When the OptimisticLockingException is generated with the static function lockFailedVersionMismatch and the passed parameters are DateTime instances, the exception could not be thrown because the DateTime object is not implicitly converted to a string. 2013-10-29 09:24:52 +01:00
Benjamin Eberlei
0f3679f034 Merge branch 'DDC-2759' into 2.4 2013-10-26 11:17:34 +02:00
Benjamin Eberlei
1d2cd82706 [DDC-2759] Fix regression in ArrayHydrator introduced in DDC-1884 at SHA c7b4c9bf0f 2013-10-26 11:16:53 +02:00
Chris Collins
b983d86612 Added a failing test case for DDC-2759. 2013-10-26 11:16:53 +02:00
Benjamin Eberlei
b11f01643c Merge branch 'DDC-2668' into 2.4 2013-09-26 23:24:14 +02:00
Fabio B. Silva
b58fb8f5d4 [DDC-2668] Fix trim leading zero string 2013-09-26 23:23:49 +02:00
Benjamin Eberlei
925a22b71d Merge branch 'DDC-2608' into 2.4 2013-09-08 16:01:38 +02:00
Benjamin Eberlei
0f0d8abd67 [DDC-2608][DDC-2662] Fix SequenceGenerator requiring "sequenceName" and now throw exception. Fix a bug in quoting the sequenceName. 2013-09-08 16:00:14 +02:00
Benjamin Eberlei
470c15ce05 Merge branch 'DDC-2660' into 2.4 2013-09-08 14:39:54 +02:00
Benjamin Eberlei
3cc5fc0252 [DDC-2660] Fix error with NativeSQL, ResultSetMappingBuilder and Associations as Primary Key. 2013-09-08 14:39:25 +02:00
Benjamin Eberlei
fd0657089a Merge branch 'DDC-2661' into 2.4 2013-09-08 10:38:03 +02:00
Benjamin Eberlei
de3b237292 [DDC-2661] Fix bug in YamlDriver not passing options from id to mapField() 2013-09-08 10:37:42 +02:00
Benjamin Eberlei
1221cc3a2a More excludes 2013-09-07 18:27:20 +02:00
Benjamin Eberlei
9efbc1fa71 Bump version to 2.4.1 2013-09-07 18:19:57 +02:00
Benjamin Eberlei
57705e0d78 Release 2.4.0 2013-09-07 18:19:56 +02:00
Benjamin Eberlei
82bb6b78cd Travis should prefer dist. 2013-09-07 13:20:35 +02:00
Benjamin Eberlei
64c56b21aa Remove minimum stability from 2.4 composer.json 2013-09-07 13:08:14 +02:00
Benjamin Eberlei
b04e2e6364 Adjust composer.json to pending 2.4 stable release 2013-09-07 12:59:17 +02:00
Benjamin Eberlei
a70f9b7f49 Fix branch alias 2013-09-07 12:57:56 +02:00
Benjamin Eberlei
c88a7c1ffe New Build process
- Switch from Phing to Ant
- Remove PEAR packaging
- Add Composer archiving
2013-09-07 12:57:38 +02:00
Benjamin Eberlei
c206728c96 Merge branch 'DDC-2638' into 2.4 2013-09-07 09:04:34 +02:00
Attila Fulop
e8d420c641 Fix for entity generator discriminator column 2013-09-07 09:04:26 +02:00
Benjamin Eberlei
fdcab7eae8 Merge branch 'DDC-2640' into 2.4 2013-09-07 09:01:01 +02:00
Maks Feltrin
45d7d5234f DO NOT OVERRIDE CUSTOM TREE WALKERS IN getIterator() 2013-09-07 09:00:06 +02:00
Benjamin Eberlei
159ca79b81 Merge origin/2.4 into local branch 2013-09-07 08:55:15 +02:00
Benjamin Eberlei
2b148a27e0 Merge Oracle test fixes to 2.4 branch 2013-09-07 08:54:23 +02:00
Guilherme Blanco
0aef57f60c Fixing missing table aliases when using Many2Many persister. 2013-08-26 10:29:45 -04:00
Benjamin Eberlei
fef1e0286c Merge branch 'DDC-2235' into 2.4 2013-08-20 10:08:21 +02:00
Guilherme Blanco
4a38534150 Fixed DDC-2235. 2013-08-20 10:08:07 +02:00
Benjamin Eberlei
1de22adb16 Merge branch 'DDC-2506' into 2.4 2013-08-20 09:56:54 +02:00
Guilherme Blanco
62b4160887 Fixed DDC-2506 by manually updating code. Closes PR #708. 2013-08-20 09:56:39 +02:00
Benjamin Eberlei
dbb7c4d2bf Merge branch 'DDC-2607' into 2.4 2013-08-20 09:51:19 +02:00
Dustin Thomson
e8978ee365 Modified executeInserts method in JoinedSubclassPersister to only check for the presence of columns in a composite primary key 2013-08-20 09:51:01 +02:00
Benjamin Eberlei
c095b88804 Merge branch 'DDC-2578' into 2.4 2013-08-10 18:14:24 +02:00
Guilherme Blanco
efe4208ba6 Kept BC. 2013-08-10 18:14:07 +02:00
Guilherme Blanco
453a56670d Modified Hydrators to be per-query instances instead of a singleton-like approach. 2013-08-10 18:14:07 +02:00
Benjamin Eberlei
ec36e2c866 Merge branch 'DDC-2579' into 2.4 2013-08-10 17:54:37 +02:00
Fabio B. Silva
e250572cb4 fix DDC-2579 2013-08-10 17:53:50 +02:00
Benjamin Eberlei
758955e183 Merge branch 'DDC-2582' into 2.4 2013-08-10 17:48:20 +02:00
Guilherme Blanco
5b8d6a1486 CS fixes. 2013-08-10 17:48:03 +02:00
Guilherme Blanco
3f1003fee9 Fixed DDC-1884. 2013-08-10 17:48:03 +02:00
Benjamin Eberlei
7e241e89b8 Merge branch 'DDC-2548' into 2.4 2013-08-10 17:43:40 +02:00
Michaël Gallego
67c1e1d2b1 Allow to have non-distinct queries 2013-08-10 17:43:26 +02:00
Benjamin Eberlei
261eacdbfc Merge branch 'DDC-2565' into 2.4 2013-08-10 17:27:47 +02:00
Austin Morris
43df821691 convert PersistentCollection functional tests to unit tests 2013-08-10 17:27:30 +02:00
Austin Morris
11d09702da remove redundant require_once for TestInit.php 2013-08-10 17:27:30 +02:00
Austin Morris
f9f14139cf do not initialize coll on add() 2013-08-10 17:27:30 +02:00
Austin Morris
39f4d46d36 Initialize coll when using Collection methods inside PersistentCollection 2013-08-10 17:27:30 +02:00
Austin Morris
1dae8d318f PersistentCollection - initialize coll - create failing tests 2013-08-10 17:27:30 +02:00
Benjamin Eberlei
a361a7c1cb Merge branch 'DDC-2542' into 2.4 2013-08-10 17:02:34 +02:00
Roger Llopart Pla
6a73608baf Fixed name colision. 2013-08-10 17:02:10 +02:00
Roger Llopart Pla
f9955152b2 Added a test which verifies that the tree walkers are kept. 2013-08-10 17:02:10 +02:00
Roger Llopart Pla
5aad1df149 Added docblock. 2013-08-10 17:02:10 +02:00
Roger Llopart Pla
243832555b Appending the Paginator tree walker hint, instead of removing all the other hints. 2013-08-10 17:02:10 +02:00
Benjamin Eberlei
ae12fa6b5b Merge branch 'DDC-2577' into 2.4 2013-08-10 16:28:35 +02:00
Konstantin.Myakshin
edaf9b6813 Skip not mapped public properties in SchemaValidator 2013-08-10 16:28:27 +02:00
Benjamin Eberlei
b324a21abf Merge branch 'DDC-2587' into 2.4 2013-08-10 16:25:04 +02:00
J. Bruni
ff34aaaa2c Updated EntityGeneratorTest::testEntityTypeAlias 2013-08-10 16:24:43 +02:00
J. Bruni
9767a814a6 Updated EntityGeneratorTest::testEntityTypeAlias 2013-08-10 16:24:43 +02:00
J Bruni
e6007575e1 Corrected PHP type for "decimal" mapping type
"Basic Mapping" documentation says:
"decimal: Type that maps a SQL DECIMAL to a PHP string."
2013-08-10 16:24:43 +02:00
1181 changed files with 19363 additions and 79080 deletions

4
.coveralls.yml Normal file
View File

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

1
.gitattributes vendored
View File

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

4
.gitignore vendored
View File

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

View File

@@ -1,34 +0,0 @@
build:
nodes:
analysis:
environment:
php:
version: 7.1
cache:
disabled: false
directories:
- ~/.composer/cache
project_setup:
override: true
tests:
override:
- php-scrutinizer-run
before_commands:
- "composer install --no-dev --prefer-source"
tools:
external_code_coverage:
timeout: 3600
filter:
excluded_paths:
- docs
- tools
build_failure_conditions:
- 'elements.rating(<= C).new.exists' # No new classes/methods with a rating of C or worse allowed
- 'issues.severity(>= MAJOR).new.exists' # New issues of major or higher severity
- 'project.metric_change("scrutinizer.test_coverage", < 0)' # Code Coverage decreased from previous inspection
- 'patches.label("Unused Use Statements").new.exists' # No new unused imports patches allowed

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
Copyright (c) 2006-2015 Doctrine Project
Copyright (c) 2006-2012 Doctrine Project
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

25
README.markdown Normal file
View File

@@ -0,0 +1,25 @@
# Doctrine 2 ORM
Master: [![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png?branch=master)](http://travis-ci.org/doctrine/doctrine2)
2.3: [![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png?branch=2.3)](http://travis-ci.org/doctrine/doctrine2)
2.2: [![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png?branch=2.2)](http://travis-ci.org/doctrine/doctrine2)
2.1: [![Build Status](https://secure.travis-ci.org/doctrine/doctrine2.png?branch=2.1.x)](http://travis-ci.org/doctrine/doctrine2)
Master: [![Coverage Status](https://coveralls.io/repos/doctrine/doctrine2/badge.png?branch=master)](https://coveralls.io/r/doctrine/doctrine2?branch=master)
[![Latest Stable Version](https://poser.pugx.org/doctrine/orm/v/stable.png)](https://packagist.org/packages/doctrine/orm) [![Total Downloads](https://poser.pugx.org/doctrine/orm/downloads.png)](https://packagist.org/packages/doctrine/orm)
Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.2+ that provides transparent persistence
for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features
is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL),
inspired by Hibernates HQL. This provides developers with a powerful alternative to SQL that maintains flexibility
without requiring unnecessary code duplication.
## More resources:
* [Website](http://www.doctrine-project.org)
* [Documentation](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/index.html)
* [Issue Tracker](http://www.doctrine-project.org/jira/browse/DDC)
* [Downloads](http://github.com/doctrine/doctrine2/downloads)

View File

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

View File

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

View File

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

View File

@@ -31,7 +31,7 @@ $helperSet = null;
if (file_exists($configFile)) {
if ( ! is_readable($configFile)) {
trigger_error(
'Configuration file [' . $configFile . '] does not have read permission.', E_USER_ERROR
'Configuration file [' . $configFile . '] does not have read permission.', E_ERROR
);
}

18
bin/doctrine.php Normal file → Executable file
View File

@@ -20,19 +20,9 @@
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) {
@@ -53,7 +43,7 @@ if ( ! is_readable($configFile)) {
exit(1);
}
$commands = [];
$commands = array();
$helperSet = require $configFile;
@@ -66,4 +56,4 @@ if ( ! ($helperSet instanceof HelperSet)) {
}
}
ConsoleRunner::run($helperSet, $commands);
\Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet, $commands);

View File

@@ -3,55 +3,38 @@
"type": "library",
"description": "Object-Relational-Mapper for PHP",
"keywords": ["orm", "database"],
"homepage": "https://www.doctrine-project.org/projects/orm.html",
"homepage": "http://www.doctrine-project.org",
"license": "MIT",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"},
{"name": "Marco Pivetta", "email": "ocramius@gmail.com"}
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"}
],
"config": {
"sort-packages": true
},
"require": {
"php": "^7.1",
"php": ">=5.3.2",
"ext-pdo": "*",
"doctrine/annotations": "^1.8",
"doctrine/cache": "^1.9.1",
"doctrine/collections": "^1.5",
"doctrine/common": "^2.11",
"doctrine/dbal": "^2.9.3",
"doctrine/event-manager": "^1.1",
"doctrine/instantiator": "^1.3",
"doctrine/persistence": "^1.2",
"symfony/console": "^3.0|^4.0|^5.0"
"doctrine/collections": "~1.1",
"doctrine/dbal": "~2.4",
"symfony/console": "~2.0"
},
"require-dev": {
"doctrine/coding-standard": "^5.0",
"phpunit/phpunit": "^7.5",
"symfony/yaml": "^3.4|^4.0|^5.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": ["bin/doctrine", "bin/doctrine.php"],
"extra": {
"branch-alias": {
"dev-master": "2.7.x-dev"
"dev-master": "2.4.x-dev"
}
},
"archive": {
"exclude": ["!vendor", "tests", "*phpunit.xml", ".travis.yml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp"]
"exclude": ["!vendor", "tests", "*phpunit.xml", ".travis.yml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp", "*coveralls.yml"]
}
}

2744
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,363 +0,0 @@
The Doctrine2 documentation is licensed under [CC BY-NC-SA 3.0](http://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 http://creativecommons.org/.

View File

@@ -1,18 +1,8 @@
# Doctrine ORM Documentation
## How to Generate:
Using Ubuntu 14.04 LTS:
## How to Generate
1. Run ./bin/install-dependencies.sh
2. Run ./bin/generate-docs.sh
It will generate the documentation into the build directory of the checkout.
## Theme issues
If you get a "Theme error", check if the `en/_theme` subdirectory is empty,
in which case you will need to run:
1. git submodule init
2. git submodule update
It will generate the documentation into the build directory of the checkout.

View File

@@ -1,2 +1,4 @@
#!/bin/bash
sudo apt-get update && sudo apt-get install -y python2.7 python-sphinx python-pygments
sudo apt-get install python25 python25-dev texlive-full rubber
sudo easy_install pygments
sudo easy_install sphinx

View File

@@ -11,7 +11,7 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys, os, datetime
import sys, os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
@@ -38,7 +38,7 @@ master_doc = 'index'
# General information about the project.
project = u'Doctrine 2 ORM'
copyright = u'2010-%y, Doctrine Project Team'.format(datetime.date.today)
copyright = u'2010-12, Doctrine Project Team'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the

View File

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

View File

@@ -149,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;
@@ -322,7 +322,7 @@ The aggregate field ``Account::$balance`` is now -200, however the
SUM over all entries amounts yields -400. A violation of our max
credit rule.
You can use both optimistic or pessimistic locking to safe-guard
You can use both optimistic or pessimistic locking to save-guard
your aggregate fields against this kind of race-conditions. Reading
Eric Evans DDD carefully he mentions that the "Aggregate Root"
(Account in our example) needs a locking mechanism.
@@ -332,7 +332,7 @@ Optimistic locking is as easy as adding a version column:
.. code-block:: php
<?php
class Account
class Amount
{
/** @Column(type="integer") @Version */
private $version;

View File

@@ -1,101 +0,0 @@
Custom Mapping Types
====================
Doctrine allows you to create new mapping types. This can come in
handy when you're missing a specific mapping type or when you want
to replace the existing implementation of a mapping type.
In order to create a new mapping type you need to subclass
``Doctrine\DBAL\Types\Type`` and implement/override the methods as
you wish. Here is an example skeleton of such a custom type class:
.. code-block:: php
<?php
namespace My\Project\Types;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;
/**
* My custom datatype.
*/
class MyType extends Type
{
const MYTYPE = 'mytype'; // modify to match your type name
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
// return the SQL used to create your column type. To create a portable column type, use the $platform.
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
// This is executed when the value is read from the database. Make your conversions here, optionally using the $platform.
}
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
// This is executed when the value is written to the database. Make your conversions here, optionally using the $platform.
}
public function getName()
{
return self::MYTYPE; // modify to match your constant name
}
}
.. note::
The following assumptions are applied to mapping types by the ORM:
- The ``UnitOfWork`` never passes values to the database convert
method that did not change in the request.
- The ``UnitOfWork`` internally assumes that entity identifiers are
castable to string. Hence, when using custom types that map to PHP
objects as IDs, such objects must implement the ``__toString()`` magic
method.
When you have implemented the type you still need to let Doctrine
know about it. This can be achieved through the
``Doctrine\DBAL\Types\Type#addType($name, $className)``
method. See the following example:
.. code-block:: php
<?php
// in bootstrapping code
// ...
use Doctrine\DBAL\Types\Type;
// ...
// Register my type
Type::addType('mytype', 'My\Project\Types\MyType');
To convert the underlying database type of your
new "mytype" directly into an instance of ``MyType`` when performing
schema operations, the type has to be registered with the database
platform as well:
.. code-block:: php
<?php
$conn = $em->getConnection();
$conn->getDatabasePlatform()->registerDoctrineTypeMapping('db_mytype', 'mytype');
When registering the custom types in the configuration you specify a unique
name for the mapping type and map that to the corresponding fully qualified
class name. Now the new type can be used when mapping columns:
.. code-block:: php
<?php
class MyPersistentClass
{
/** @Column(type="mytype") */
private $field;
}

View File

@@ -112,7 +112,7 @@ The ``Paginate::count(Query $query)`` looks like:
}
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:
@@ -130,7 +130,7 @@ 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;

View File

@@ -21,7 +21,7 @@ the :doc:`Native Query <../reference/native-sql>` chapter.
The DQL Parser has hooks to register functions that can then be
used in your DQL queries and transformed into SQL, allowing to
extend Doctrines Query capabilities to the vendors strength. This
post explains the User-Defined Functions API (UDF) of the Dql
post explains the Used-Defined Functions API (UDF) of the Dql
Parser and shows some examples to give you some hints how you would
extend DQL.
@@ -53,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``.
@@ -132,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
@@ -164,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:
@@ -246,6 +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 <http://github.com/beberlei/DoctrineExtensions>`_.
`in my Github DoctrineExtensions repository <http://github.com/beberlei/DoctrineExtensions>`_.

View File

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

View File

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

View File

@@ -96,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)
@@ -118,11 +118,6 @@ For example for the previous enum type:
{
return self::ENUM_VISIBILITY;
}
public function requiresSQLCommentHint(AbstractPlatform $platform)
{
return true;
}
}
You can register this type with ``Type::addType('enumvisibility', 'MyProject\DBAL\EnumVisibilityType');``.
@@ -153,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)
@@ -177,11 +172,6 @@ You can generalize this approach easily to create a base class for enums:
{
return $this->name;
}
public function requiresSQLCommentHint(AbstractPlatform $platform)
{
return true;
}
}
With this base class you can define an enum as easily as:

View File

@@ -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,7 +62,6 @@ An Invoice entity
.. code-block:: php
<?php
// src/Acme/InvoiceModule/Entity/Invoice.php
namespace Acme\InvoiceModule\Entity;
@@ -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);

View File

@@ -39,16 +39,10 @@ appropriate autoloaders.
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
{
$classMetadata = $eventArgs->getClassMetadata();
if (!$classMetadata->isInheritanceTypeSingleTable() || $classMetadata->getName() === $classMetadata->rootEntityName) {
$classMetadata->setPrimaryTable([
'name' => $this->prefix . $classMetadata->getTableName()
]);
}
$classMetadata->setTableName($this->prefix . $classMetadata->getTableName());
foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) {
if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY && $mapping['isOwningSide']) {
$mappedTableName = $mapping['joinTable']['name'];
if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY) {
$mappedTableName = $classMetadata->associationMappings[$fieldName]['joinTable']['name'];
$classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix . $mappedTableName;
}
}

View File

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

View File

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

View File

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

View File

@@ -13,11 +13,11 @@ If this documentation is not helping to answer questions you have about
Doctrine ORM don't panic. You can get help from different sources:
- There is a :doc:`FAQ <reference/faq>` with answers to frequent questions.
- The `Doctrine Mailing List <https://groups.google.com/group/doctrine-user>`_
- Slack chat room `#orm <https://www.doctrine-project.org/slack>`_
- Report a bug on `GitHub <https://github.com/doctrine/orm/issues>`_.
- The `Doctrine Mailing List <http://groups.google.com/group/doctrine-user>`_
- Internet Relay Chat (IRC) in #doctrine on Freenode
- Report a bug on `JIRA <http://www.doctrine-project.org/jira>`_.
- On `Twitter <https://twitter.com/search/%23doctrine2>`_ with ``#doctrine2``
- On `StackOverflow <https://stackoverflow.com/questions/tagged/doctrine-orm>`_
- On `StackOverflow <http://stackoverflow.com/questions/tagged/doctrine2>`_
If you need more structure over the different topics you can browse the :doc:`table
of contents <toc>`.
@@ -65,37 +65,29 @@ Working with Objects
Advanced Topics
---------------
* :doc:`Architecture <reference/architecture>`
* :doc:`Advanced Configuration <reference/advanced-configuration>`
* :doc:`Limitations and known 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>`
* :doc:`Batch Processing <reference/batch-processing>`
* :doc:`Second Level Cache <reference/second-level-cache>`
* :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>`
* :doc:`Embeddables <tutorials/embeddables>`
Changelogs
----------
* `Upgrade <https://github.com/doctrine/doctrine2/blob/master/UPGRADE.md>`_
* :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
--------
@@ -103,7 +95,7 @@ Cookbook
* **Patterns**:
:doc:`Aggregate Fields <cookbook/aggregate-fields>` |
:doc:`Decorator Pattern <cookbook/decorator-pattern>` |
:doc:`Strategy Pattern <cookbook/strategy-cookbook-introduction>`
:doc:`Strategy Pattern <cookbook/strategy-cookbook-introduction>`
* **DQL Extension Points**:
:doc:`DQL Custom Walkers <cookbook/dql-custom-walkers>` |
@@ -118,6 +110,9 @@ Cookbook
:doc:`Entities in the Session <cookbook/entities-in-session>` |
:doc:`Keeping your Modules independent <cookbook/resolve-target-entity-listener>`
* **Integration into Frameworks/Libraries**
:doc:`CodeIgniter <cookbook/integrating-with-codeigniter>`
* **Hidden Gems**
:doc:`Prefixing Table Name <cookbook/sql-table-prefixes>`

View File

@@ -152,7 +152,6 @@ The recommended implementations for production are:
- ``Doctrine\Common\Cache\ApcCache``
- ``Doctrine\Common\Cache\ApcuCache``
- ``Doctrine\Common\Cache\MemcacheCache``
- ``Doctrine\Common\Cache\XcacheCache``
- ``Doctrine\Common\Cache\RedisCache``
@@ -184,7 +183,6 @@ The recommended implementations for production are:
- ``Doctrine\Common\Cache\ApcCache``
- ``Doctrine\Common\Cache\ApcuCache``
- ``Doctrine\Common\Cache\MemcacheCache``
- ``Doctrine\Common\Cache\XcacheCache``
- ``Doctrine\Common\Cache\RedisCache``
@@ -212,56 +210,17 @@ implementation that logs to the standard output using ``echo`` and
Auto-generating Proxy Classes (***OPTIONAL***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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:
.. code-block:: php
<?php
$config->setAutoGenerateProxyClasses($mode);
$config->setAutoGenerateProxyClasses($bool);
$config->getAutoGenerateProxyClasses();
Possible values for ``$mode`` are:
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_NEVER``
Never autogenerate a proxy. You will need to generate the proxies
manually, for this use the Doctrine Console like so:
.. code-block:: php
$ ./doctrine orm:generate-proxies
When you do this in a development environment,
be aware that you may get class/file not found errors if certain proxies
are not yet generated. You may also get 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.
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_ALWAYS``
Always generates a new proxy in every request and writes it to disk.
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``
Generate the proxy class when the proxy file does not exist.
This strategy causes a file exists call whenever any proxy is
used the first time in a request.
- ``Doctrine\Common\Proxy\AbstractProxyFactory::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.
In a production environment, it is highly recommended to use
AUTOGENERATE_NEVER to allow for optimal performances. The other
options are interesting in development environment.
Before v2.4, ``setAutoGenerateProxyClasses`` would accept a boolean
value. This is still possible, ``FALSE`` being equivalent to
AUTOGENERATE_NEVER and ``TRUE`` to AUTOGENERATE_ALWAYS.
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
---------------------------------------
@@ -292,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
-------------
@@ -363,28 +322,31 @@ transparently initialize itself on first access.
Generating Proxy classes
~~~~~~~~~~~~~~~~~~~~~~~~
In a production environment, it is highly recommended to use
``AUTOGENERATE_NEVER`` to allow for optimal performances.
However you will be required to generate the proxies manually
using the Doctrine Console:
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
The other options are interesting in development environment:
- ``AUTOGENERATE_ALWAYS`` will require you to create and configure
a proxy directory. Proxies will be generated and written to file
on each request, so any modification to your code will be acknowledged.
- ``AUTOGENERATE_FILE_NOT_EXISTS`` will not overwrite an existing
proxy file. If your code changes, you will need to regenerate the
proxies manually.
- ``AUTOGENERATE_EVAL`` will regenerate each proxy on each request,
but without writing them to disk.
Autoloading Proxies
-------------------
@@ -396,7 +358,7 @@ means that you have to register a special autoloader for these classes:
.. code-block:: php
<?php
use Doctrine\Common\Proxy\Autoloader;
use Doctrine\ORM\Proxy\Autoloader;
$proxyDir = "/path/to/proxies";
$proxyNamespace = "MyProxies";

View File

@@ -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
XML and YAML alternatives 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,43 +156,16 @@ Example:
*/
class User {}
.. _annref_customidgenerator:
@CustomIdGenerator
~~~~~~~~~~~~~~~~~~~~~
This annotations allows you to specify a user-provided class to generate identifiers. This annotation only works when both :ref:`@Id <annref_id>` and :ref:`@GeneratedValue(strategy="CUSTOM") <annref_generatedvalue>` are specified.
Required attributes:
- **class**: name of the class which should extend Doctrine\ORM\Id\AbstractIdGenerator
Example:
.. code-block:: php
<?php
/**
* @Id
* @Column(type="integer")
* @GeneratedValue(strategy="CUSTOM")
* @CustomIdGenerator(class="My\Namespace\MyIdGenerator")
*/
public $id;
.. _annref_discriminatorcolumn:
@DiscriminatorColumn
~~~~~~~~~~~~~~~~~~~~~
This annotation is an optional annotation for the topmost/super
This annotation is a required annotation for the topmost/super
class of an inheritance hierarchy. It specifies the details of the
column which saves the name of the class, which the entity is
actually instantiated as.
If this annotation is not specified, the discriminator column defaults
to a string column of length 255 called ``dtype``.
Required attributes:
@@ -292,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
@@ -312,74 +204,13 @@ 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:
@@ -451,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, UUID, CUSTOM and NONE.
If not specified, default value is AUTO.
Example:
@@ -476,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
@@ -505,7 +335,7 @@ 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.
@@ -515,14 +345,7 @@ 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
@@ -535,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
@@ -599,7 +409,7 @@ Examples:
{
// ...
}
/**
* @Entity
* @InheritanceType("JOINED")
@@ -619,7 +429,7 @@ 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.
@@ -635,15 +445,15 @@ 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
@@ -683,7 +493,7 @@ 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
@@ -745,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
@@ -762,9 +572,9 @@ Optional attributes:
- **mappedBy**: This option specifies the property name on the
targetEntity that is the owning side of this relation. It is a
targetEntity that is the owning side of this relation. Its a
required attribute for the inverse side of a relationship.
- **inversedBy**: The inversedBy attribute designates the field in the
- **inversedBy**: The inversedBy attribute designates the eld in the
entity that is the inverse side of the relationship.
- **cascade**: Cascade Option
- **fetch**: One of LAZY, EXTRA_LAZY or EAGER
@@ -792,7 +602,7 @@ Example:
* )
*/
private $groups;
/**
* Inverse Side
*
@@ -805,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.
@@ -918,7 +728,7 @@ 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.
@@ -938,7 +748,7 @@ Optional attributes:
- **orphanRemoval**: Boolean that specifies if orphans, inverse
OneToOne entities that are not connected to any owning instance,
should be removed by Doctrine. Defaults to false.
- **inversedBy**: The inversedBy attribute designates the field in the
- **inversedBy**: The inversedBy attribute designates the eld in the
entity that is the inverse side of the relationship.
Example:
@@ -1085,7 +895,7 @@ 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.
@@ -1098,10 +908,10 @@ 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:
@@ -1223,7 +1033,7 @@ 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:
@@ -1236,7 +1046,6 @@ Optional attributes:
- **indexes**: Array of @Index annotations
- **uniqueConstraints**: Array of @UniqueConstraint annotations.
- **schema**: (>= 2.5) Name of the schema the table lies in.
Example:
@@ -1248,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 { }
@@ -1270,14 +1078,7 @@ 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
@@ -1290,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:

View File

@@ -12,13 +12,13 @@ As the term ORM already hints at, Doctrine 2 aims to simplify the
translation between database rows and the PHP object model. The
primary use case for Doctrine are therefore applications that
utilize the Object-Oriented Programming Paradigm. For applications
that do not primarily work with objects Doctrine 2 is not suited very
that not primarily work with objects Doctrine 2 is not suited very
well.
Requirements
------------
Doctrine 2 requires a minimum of PHP 7.1. For greatly improved
Doctrine 2 requires a minimum of PHP 5.3.0. For greatly improved
performance it is also recommended that you use APC with PHP.
Doctrine 2 Packages
@@ -83,7 +83,7 @@ be any regular PHP class observing the following restrictions:
- An entity class must not implement ``__wakeup`` or
:doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
Also consider implementing
`Serializable <http://php.net/manual/en/class.serializable.php>`_
`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

View File

@@ -1,11 +1,12 @@
Association Mapping
===================
This chapter explains mapping associations between objects.
This chapter introduces association mappings which are used to explain
references between objects and are mapped to a relational database using
foreign keys.
Instead of working with foreign keys in your code, you will always work with
references to objects instead and Doctrine will convert those references
to foreign keys internally.
Instead of working with the foreign keys directly you will always work with
references to objects:
- A reference to a single object is represented by a foreign key.
- A collection of objects is represented by many foreign keys pointing to the object holding the collection
@@ -16,121 +17,37 @@ This chapter is split into three different sections.
- :ref:`association_mapping_defaults` are explained that simplify the use-case examples.
- :ref:`collections` are introduced that contain entities in associations.
One tip for working with relations is to read the relation from left to right, where the left word refers to the current Entity. For example:
- OneToMany - One instance of the current Entity has Many instances (references) to the refered Entity.
- ManyToOne - Many instances of the current Entity refer to One instance of the refered Entity.
- OneToOne - One instance of the current Entity refers to One instance of the refered Entity.
See below for all the possible relations.
An association is considered to be unidirectional if only one side of the association has
a property referring to the other side.
To gain a full understanding of associations you should also read about :doc:`owning and
inverse sides of associations <unitofwork-associations>`
Many-To-One, Unidirectional
---------------------------
A many-to-one association is the most common association between objects. Example: Many Users have One Address:
.. configuration-block::
.. code-block:: php
<?php
/** @Entity */
class User
{
// ...
/**
* @ManyToOne(targetEntity="Address")
* @JoinColumn(name="address_id", referencedColumnName="id")
*/
private $address;
}
/** @Entity */
class Address
{
// ...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="User">
<many-to-one field="address" target-entity="Address">
<join-column name="address_id" referenced-column-name="id" />
</many-to-one>
</entity>
</doctrine-mapping>
.. code-block:: yaml
User:
type: entity
manyToOne:
address:
targetEntity: Address
joinColumn:
name: address_id
referencedColumnName: id
.. note::
The above ``@JoinColumn`` is optional as it would default
to ``address_id`` and ``id`` anyways. You can omit it and let it
use the defaults.
Generated MySQL Schema:
.. code-block:: sql
CREATE TABLE User (
id INT AUTO_INCREMENT NOT NULL,
address_id INT DEFAULT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE Address (
id INT AUTO_INCREMENT NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE User ADD FOREIGN KEY (address_id) REFERENCES Address(id);
To master associations you should also learn about :doc:`owning and inverse sides of associations <unitofwork-associations>`
One-To-One, Unidirectional
--------------------------
Here is an example of a one-to-one association with a ``Product`` entity that
references one ``Shipment`` entity.
A unidirectional one-to-one association is very common. Here is an
example of a ``Product`` that has one ``Shipping`` object
associated to it. The ``Shipping`` side does not reference back to
the ``Product`` so it is unidirectional.
.. configuration-block::
.. code-block:: php
<?php
/** @Entity */
/** @Entity **/
class Product
{
// ...
/**
* One Product has One Shipment.
* @OneToOne(targetEntity="Shipment")
* @JoinColumn(name="shipment_id", referencedColumnName="id")
*/
private $shipment;
* @OneToOne(targetEntity="Shipping")
* @JoinColumn(name="shipping_id", referencedColumnName="id")
**/
private $shipping;
// ...
}
/** @Entity */
class Shipment
/** @Entity **/
class Shipping
{
// ...
}
@@ -139,8 +56,8 @@ references one ``Shipment`` entity.
<doctrine-mapping>
<entity class="Product">
<one-to-one field="shipment" target-entity="Shipment">
<join-column name="shipment_id" referenced-column-name="id" />
<one-to-one field="shipping" target-entity="Shipping">
<join-column name="shipping_id" referenced-column-name="id" />
</one-to-one>
</entity>
</doctrine-mapping>
@@ -150,10 +67,10 @@ references one ``Shipment`` entity.
Product:
type: entity
oneToOne:
shipment:
targetEntity: Shipment
shipping:
targetEntity: Shipping
joinColumn:
name: shipment_id
name: shipping_id
referencedColumnName: id
Note that the @JoinColumn is not really necessary in this example,
@@ -165,15 +82,15 @@ Generated MySQL Schema:
CREATE TABLE Product (
id INT AUTO_INCREMENT NOT NULL,
shipment_id INT DEFAULT NULL,
UNIQUE INDEX UNIQ_6FBC94267FE4B2B (shipment_id),
shipping_id INT DEFAULT NULL,
UNIQUE INDEX UNIQ_6FBC94267FE4B2B (shipping_id),
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE Shipment (
CREATE TABLE Shipping (
id INT AUTO_INCREMENT NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE Product ADD FOREIGN KEY (shipment_id) REFERENCES Shipment(id);
ALTER TABLE Product ADD FOREIGN KEY (shipping_id) REFERENCES Shipping(id);
One-To-One, Bidirectional
-------------------------
@@ -182,39 +99,33 @@ Here is a one-to-one relationship between a ``Customer`` and a
``Cart``. The ``Cart`` has a reference back to the ``Customer`` so
it is bidirectional.
Here we see the ``mappedBy`` and ``inversedBy`` annotations for the first time.
They are used to tell Doctrine which property on the other side refers to the
object.
.. configuration-block::
.. code-block:: php
<?php
/** @Entity */
/** @Entity **/
class Customer
{
// ...
/**
* One Customer has One Cart.
* @OneToOne(targetEntity="Cart", mappedBy="customer")
*/
**/
private $cart;
// ...
}
/** @Entity */
/** @Entity **/
class Cart
{
// ...
/**
* One Cart has One Customer.
* @OneToOne(targetEntity="Customer", inversedBy="cart")
* @JoinColumn(name="customer_id", referencedColumnName="id")
*/
**/
private $customer;
// ...
@@ -267,29 +178,27 @@ Generated MySQL Schema:
) ENGINE = InnoDB;
ALTER TABLE Cart ADD FOREIGN KEY (customer_id) REFERENCES Customer(id);
We had a choice of sides on which to place the ``inversedBy`` attribute. Because it
is on the ``Cart``, that is the owning side of the relation, and thus holds the
foreign key.
See how the foreign key is defined on the owning side of the
relation, the table ``Cart``.
One-To-One, Self-referencing
----------------------------
You can define a self-referencing one-to-one relationships like
You can easily have self referencing one-to-one relationships like
below.
.. code-block:: php
<?php
/** @Entity */
/** @Entity **/
class Student
{
// ...
/**
* One Student has One Mentor.
* @OneToOne(targetEntity="Student")
* @JoinColumn(name="mentor_id", referencedColumnName="id")
*/
**/
private $mentor;
// ...
@@ -309,105 +218,6 @@ With the generated MySQL Schema:
) ENGINE = InnoDB;
ALTER TABLE Student ADD FOREIGN KEY (mentor_id) REFERENCES Student(id);
One-To-Many, Bidirectional
--------------------------
A one-to-many association has to be bidirectional, unless you are using a
join table. This is because the "many" side in a one-to-many association holds
the foreign key, making it the owning side. Doctrine needs the "many" side
defined in order to understand the association.
This bidirectional mapping requires the ``mappedBy`` attribute on the
"one" side and the ``inversedBy`` attribute on the "many" side.
This means there is no difference between a bidirectional one-to-many and a
bidirectional many-to-one.
.. configuration-block::
.. code-block:: php
<?php
use Doctrine\Common\Collections\ArrayCollection;
/** @Entity */
class Product
{
// ...
/**
* One product has many features. This is the inverse side.
* @OneToMany(targetEntity="Feature", mappedBy="product")
*/
private $features;
// ...
public function __construct() {
$this->features = new ArrayCollection();
}
}
/** @Entity */
class Feature
{
// ...
/**
* Many features have one product. This is the owning side.
* @ManyToOne(targetEntity="Product", inversedBy="features")
* @JoinColumn(name="product_id", referencedColumnName="id")
*/
private $product;
// ...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="Product">
<one-to-many field="features" target-entity="Feature" mapped-by="product" />
</entity>
<entity name="Feature">
<many-to-one field="product" target-entity="Product" inversed-by="features">
<join-column name="product_id" referenced-column-name="id" />
</many-to-one>
</entity>
</doctrine-mapping>
.. code-block:: yaml
Product:
type: entity
oneToMany:
features:
targetEntity: Feature
mappedBy: product
Feature:
type: entity
manyToOne:
product:
targetEntity: Product
inversedBy: features
joinColumn:
name: product_id
referencedColumnName: id
Note that the @JoinColumn is not really necessary in this example,
as the defaults would be the same.
Generated MySQL Schema:
.. code-block:: sql
CREATE TABLE Product (
id INT AUTO_INCREMENT NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE Feature (
id INT AUTO_INCREMENT NOT NULL,
product_id INT DEFAULT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE Feature ADD FOREIGN KEY (product_id) REFERENCES Product(id);
One-To-Many, Unidirectional with Join Table
-------------------------------------------
@@ -416,6 +226,12 @@ join table. From Doctrine's point of view, it is simply mapped as a
unidirectional many-to-many whereby a unique constraint on one of
the join columns enforces the one-to-many cardinality.
.. note::
One-To-Many uni-directional relations with join-table only
work using the @ManyToMany annotation and a unique-constraint.
The following example sets up such a unidirectional one-to-many association:
.. configuration-block::
@@ -423,19 +239,18 @@ The following example sets up such a unidirectional one-to-many association:
.. code-block:: php
<?php
/** @Entity */
/** @Entity **/
class User
{
// ...
/**
* Many User have Many Phonenumbers.
* @ManyToMany(targetEntity="Phonenumber")
* @JoinTable(name="users_phonenumbers",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)}
* )
*/
**/
private $phonenumbers;
public function __construct()
@@ -446,7 +261,7 @@ The following example sets up such a unidirectional one-to-many association:
// ...
}
/** @Entity */
/** @Entity **/
class Phonenumber
{
// ...
@@ -511,6 +326,171 @@ Generates the following MySQL Schema:
ALTER TABLE users_phonenumbers ADD FOREIGN KEY (user_id) REFERENCES User(id);
ALTER TABLE users_phonenumbers ADD FOREIGN KEY (phonenumber_id) REFERENCES Phonenumber(id);
Many-To-One, Unidirectional
---------------------------
You can easily implement a many-to-one unidirectional association
with the following:
.. configuration-block::
.. code-block:: php
<?php
/** @Entity **/
class User
{
// ...
/**
* @ManyToOne(targetEntity="Address")
* @JoinColumn(name="address_id", referencedColumnName="id")
**/
private $address;
}
/** @Entity **/
class Address
{
// ...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="User">
<many-to-one field="address" target-entity="Address">
<join-column name="address_id" referenced-column-name="id" />
</many-to-one>
</entity>
</doctrine-mapping>
.. code-block:: yaml
User:
type: entity
manyToOne:
address:
targetEntity: Address
joinColumn:
name: address_id
referencedColumnName: id
.. note::
The above ``@JoinColumn`` is optional as it would default
to ``address_id`` and ``id`` anyways. You can omit it and let it
use the defaults.
Generated MySQL Schema:
.. code-block:: sql
CREATE TABLE User (
id INT AUTO_INCREMENT NOT NULL,
address_id INT DEFAULT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE Address (
id INT AUTO_INCREMENT NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE User ADD FOREIGN KEY (address_id) REFERENCES Address(id);
One-To-Many, Bidirectional
--------------------------
Bidirectional one-to-many associations are very common. The
following code shows an example with a Product and a Feature
class:
.. configuration-block::
.. code-block:: php
<?php
/** @Entity **/
class Product
{
// ...
/**
* @OneToMany(targetEntity="Feature", mappedBy="product")
**/
private $features;
// ...
public function __construct() {
$this->features = new \Doctrine\Common\Collections\ArrayCollection();
}
}
/** @Entity **/
class Feature
{
// ...
/**
* @ManyToOne(targetEntity="Product", inversedBy="features")
* @JoinColumn(name="product_id", referencedColumnName="id")
**/
private $product;
// ...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="Product">
<one-to-many field="features" target-entity="Feature" mapped-by="product" />
</entity>
<entity name="Feature">
<many-to-one field="product" target-entity="Product" inversed-by="features">
<join-column name="product_id" referenced-column-name="id" />
</many-to-one>
</entity>
</doctrine-mapping>
.. code-block:: yaml
Product:
type: entity
oneToMany:
features:
targetEntity: Feature
mappedBy: product
Feature:
type: entity
manyToOne:
product:
targetEntity: Product
inversedBy: features
joinColumn:
name: product_id
referencedColumnName: id
Note that the @JoinColumn is not really necessary in this example,
as the defaults would be the same.
Generated MySQL Schema:
.. code-block:: sql
CREATE TABLE Product (
id INT AUTO_INCREMENT NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE Feature (
id INT AUTO_INCREMENT NOT NULL,
product_id INT DEFAULT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE Feature ADD FOREIGN KEY (product_id) REFERENCES Product(id);
One-To-Many, Self-referencing
-----------------------------
@@ -525,21 +505,19 @@ database perspective is known as an adjacency list approach.
.. code-block:: php
<?php
/** @Entity */
/** @Entity **/
class Category
{
// ...
/**
* One Category has Many Categories.
* @OneToMany(targetEntity="Category", mappedBy="parent")
*/
**/
private $children;
/**
* Many Categories have One Category.
* @ManyToOne(targetEntity="Category", inversedBy="children")
* @JoinColumn(name="parent_id", referencedColumnName="id")
*/
**/
private $parent;
// ...
@@ -596,19 +574,18 @@ entities:
.. code-block:: php
<?php
/** @Entity */
/** @Entity **/
class User
{
// ...
/**
* Many Users have Many Groups.
* @ManyToMany(targetEntity="Group")
* @JoinTable(name="users_groups",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
* )
*/
**/
private $groups;
// ...
@@ -618,7 +595,7 @@ entities:
}
}
/** @Entity */
/** @Entity **/
class Group
{
// ...
@@ -697,16 +674,15 @@ one is bidirectional.
.. code-block:: php
<?php
/** @Entity */
/** @Entity **/
class User
{
// ...
/**
* Many Users have Many Groups.
* @ManyToMany(targetEntity="Group", inversedBy="users")
* @JoinTable(name="users_groups")
*/
**/
private $groups;
public function __construct() {
@@ -716,14 +692,13 @@ one is bidirectional.
// ...
}
/** @Entity */
/** @Entity **/
class Group
{
// ...
/**
* Many Groups have Many Users.
* @ManyToMany(targetEntity="User", mappedBy="groups")
*/
**/
private $users;
public function __construct() {
@@ -781,22 +756,22 @@ one is bidirectional.
The MySQL schema is exactly the same as for the Many-To-Many
uni-directional case above.
Owning and Inverse Side on a ManyToMany Association
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Picking Owning and Inverse Side
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For Many-To-Many associations you can chose which entity is the
owning and which the inverse side. There is a very simple semantic
rule to decide which side is more suitable to be the owning side
from a developers perspective. You only have to ask yourself which
entity is responsible for the connection management, and pick that
from a developers perspective. You only have to ask yourself, which
entity is responsible for the connection management and pick that
as the owning side.
Take an example of two entities ``Article`` and ``Tag``. Whenever
you want to connect an Article to a Tag and vice-versa, it is
mostly the Article that is responsible for this relation. Whenever
you add a new article, you want to connect it with existing or new
tags. Your "Create Article" form will probably support this notion
and allow specifying the tags directly. This is why you should pick
tags. Your create Article form will probably support this notion
and allow to specify the tags directly. This is why you should pick
the Article as owning side, as it makes the code more
understandable:
@@ -846,25 +821,23 @@ field named ``$friendsWithMe`` and ``$myFriends``.
.. code-block:: php
<?php
/** @Entity */
/** @Entity **/
class User
{
// ...
/**
* Many Users have Many Users.
* @ManyToMany(targetEntity="User", mappedBy="myFriends")
*/
**/
private $friendsWithMe;
/**
* Many Users have many Users.
* @ManyToMany(targetEntity="User", inversedBy="friendsWithMe")
* @JoinTable(name="friends",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="friend_user_id", referencedColumnName="id")}
* )
*/
**/
private $myFriends;
public function __construct() {
@@ -896,9 +869,11 @@ Generated MySQL Schema:
Mapping Defaults
----------------
The ``@JoinColumn`` and ``@JoinTable`` definitions are usually optional and have
sensible default values. The defaults for a join column in a
one-to-one/many-to-one association is as follows:
Before we introduce all the association mappings in detail, you
should note that the @JoinColumn and @JoinTable definitions are
usually optional and have sensible default values. The defaults for
a join column in a one-to-one/many-to-one association is as
follows:
::
@@ -912,14 +887,14 @@ As an example, consider this mapping:
.. code-block:: php
<?php
/** @OneToOne(targetEntity="Shipment") */
private $shipment;
/** @OneToOne(targetEntity="Shipping") **/
private $shipping;
.. code-block:: xml
<doctrine-mapping>
<entity class="Product">
<one-to-one field="shipment" target-entity="Shipment" />
<one-to-one field="shipping" target-entity="Shipping" />
</entity>
</doctrine-mapping>
@@ -928,8 +903,8 @@ As an example, consider this mapping:
Product:
type: entity
oneToOne:
shipment:
targetEntity: Shipment
shipping:
targetEntity: Shipping
This is essentially the same as the following, more verbose,
mapping:
@@ -940,18 +915,17 @@ mapping:
<?php
/**
* One Product has One Shipment.
* @OneToOne(targetEntity="Shipment")
* @JoinColumn(name="shipment_id", referencedColumnName="id")
*/
private $shipment;
* @OneToOne(targetEntity="Shipping")
* @JoinColumn(name="shipping_id", referencedColumnName="id")
**/
private $shipping;
.. code-block:: xml
<doctrine-mapping>
<entity class="Product">
<one-to-one field="shipment" target-entity="Shipment">
<join-column name="shipment_id" referenced-column-name="id" />
<one-to-one field="shipping" target-entity="Shipping">
<join-column name="shipping_id" referenced-column-name="id" />
</one-to-one>
</entity>
</doctrine-mapping>
@@ -961,10 +935,10 @@ mapping:
Product:
type: entity
oneToOne:
shipment:
targetEntity: Shipment
shipping:
targetEntity: Shipping
joinColumn:
name: shipment_id
name: shipping_id
referencedColumnName: id
The @JoinTable definition used for many-to-many mappings has
@@ -978,7 +952,7 @@ similar defaults. As an example, consider this mapping:
class User
{
//...
/** @ManyToMany(targetEntity="Group") */
/** @ManyToMany(targetEntity="Group") **/
private $groups;
//...
}
@@ -999,7 +973,8 @@ similar defaults. As an example, consider this mapping:
groups:
targetEntity: Group
This is essentially the same as the following, more verbose, mapping:
This is essentially the same as the following, more verbose,
mapping:
.. configuration-block::
@@ -1010,13 +985,12 @@ This is essentially the same as the following, more verbose, mapping:
{
//...
/**
* Many Users have Many Groups.
* @ManyToMany(targetEntity="Group")
* @JoinTable(name="User_Group",
* joinColumns={@JoinColumn(name="User_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="Group_id", referencedColumnName="id")}
* )
*/
**/
private $groups;
//...
}
@@ -1069,43 +1043,83 @@ minimum.
Collections
-----------
Unfortunately, PHP arrays, while being great for many things, are missing
features that make them suitable for lazy loading in the context of an ORM.
This is why in all the examples of many-valued associations in this manual we
will make use of a ``Collection`` interface and its
default implementation ``ArrayCollection`` that are both defined in the
``Doctrine\Common\Collections`` namespace. A collection implements
the PHP interfaces ``ArrayAccess``, ``Traversable`` and ``Countable``.
In all the examples of many-valued associations in this manual we
will make use of a ``Collection`` interface and a corresponding
default implementation ``ArrayCollection`` that are defined in the
``Doctrine\Common\Collections`` namespace. Why do we need that?
Doesn't that couple my domain model to Doctrine? Unfortunately, PHP
arrays, while being great for many things, do not make up for good
collections of business objects, especially not in the context of
an ORM. The reason is that plain PHP arrays can not be
transparently extended / instrumented in PHP code, which is
necessary for a lot of advanced ORM features. The classes /
interfaces that come closest to an OO collection are ArrayAccess
and ArrayObject but until instances of these types can be used in
all places where a plain array can be used (something that may
happen in PHP6) their usability is fairly limited. You "can"
type-hint on ``ArrayAccess`` instead of ``Collection``, since the
Collection interface extends ``ArrayAccess``, but this will
severely limit you in the way you can work with the collection,
because the ``ArrayAccess`` API is (intentionally) very primitive
and more importantly because you can not pass this collection to
all the useful PHP array functions, which makes it very hard to
work with.
.. note::
.. warning::
The Collection interface and ArrayCollection class,
like everything else in the Doctrine namespace, are neither part of
the ORM, nor the DBAL, it is a plain PHP class that has no outside
dependencies apart from dependencies on PHP itself (and the SPL).
Therefore using this class in your model and elsewhere
does not introduce a coupling to the ORM.
Therefore using this class in your domain classes and elsewhere
does not introduce a coupling to the persistence layer. The
Collection class, like everything else in the Common namespace, is
not part of the persistence layer. You could even copy that class
over to your project if you want to remove Doctrine from your
project and all your domain classes will work the same as before.
Initializing Collections
------------------------
You should always initialize the collections of your ``@OneToMany``
and ``@ManyToMany`` associations in the constructor of your entities:
You have to be careful when using entity fields that contain a
collection of related entities. Say we have a User entity that
contains a collection of groups:
.. code-block:: php
<?php
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
/** @Entity */
/** @Entity **/
class User
{
/**
* Many Users have Many Groups.
* @var Collection
* @ManyToMany(targetEntity="Group")
*/
/** @ManyToMany(targetEntity="Group") **/
private $groups;
public function getGroups()
{
return $this->groups;
}
}
With this code alone the ``$groups`` field only contains an
instance of ``Doctrine\Common\Collections\Collection`` if the user
is retrieved from Doctrine, however not after you instantiated a
fresh instance of the User. When your user entity is still new
``$groups`` will obviously be null.
This is why we recommend to initialize all collection fields to an
empty ``ArrayCollection`` in your entities constructor:
.. code-block:: php
<?php
use Doctrine\Common\Collections\ArrayCollection;
/** @Entity **/
class User
{
/** @ManyToMany(targetEntity="Group") **/
private $groups;
public function __construct()
@@ -1119,12 +1133,13 @@ and ``@ManyToMany`` associations in the constructor of your entities:
}
}
The following code will then work even if the Entity hasn't
Now the following code will work even if the Entity hasn't
been associated with an EntityManager yet:
.. code-block:: php
<?php
$group = new Group();
$group = $entityManager->find('Group', $groupId);
$user = new User();
$user->getGroups()->add($group);

View File

@@ -1,72 +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:`YAML <yaml-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 YAML and 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, 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. 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::
@@ -74,7 +79,7 @@ Marking our ``Message`` class as an entity for Doctrine is straightforward:
<?php
/** @Entity */
class Message
class MyPersistentClass
{
//...
}
@@ -82,20 +87,20 @@ Marking our ``Message`` class as an entity for Doctrine is straightforward:
.. code-block:: xml
<doctrine-mapping>
<entity name="Message">
<entity name="MyPersistentClass">
<!-- ... -->
</entity>
</doctrine-mapping>
.. code-block:: yaml
Message:
MyPersistentClass:
type: entity
# ...
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:
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::
@@ -104,9 +109,9 @@ You can change this by configuring information about the table:
<?php
/**
* @Entity
* @Table(name="message")
* @Table(name="my_persistent_class")
*/
class Message
class MyPersistentClass
{
//...
}
@@ -114,125 +119,42 @@ You can change this by configuring information about the table:
.. code-block:: xml
<doctrine-mapping>
<entity name="Message" table="message">
<entity name="MyPersistentClass" table="my_persistent_class">
<!-- ... -->
</entity>
</doctrine-mapping>
.. code-block:: yaml
Message:
MyPersistentClass:
type: entity
table: message
table: my_persistent_class
# ...
Now the class ``Message`` will be saved and fetched from the table ``message``.
Property Mapping
----------------
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>
.. code-block:: yaml
Message:
type: entity
fields:
id:
type: integer
text:
length: 140
postedAt:
type: datetime
column: posted_at
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 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 a SQL boolean or equivalent (TINYINT) to a PHP boolean.
- ``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.
@@ -258,19 +180,23 @@ built-in mapping types:
varchar but uses a specific type if the platform supports it.
- ``blob``: Type that maps a SQL BLOB to a PHP resource stream
A cookbook article shows how to define :doc:`your own custom mapping types
<../cookbook/custom-mapping-types>`.
.. note::
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.
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() <http://php.net/manual/en/function.date-default-timezone-set.php>`_
set by `date_default_timezone_set() <http://docs.php.net/manual/en/function.date-default-timezone-set.php>`_
or by the php.ini configuration ``date.timezone``. Working with
different timezones will cause troubles and unexpected behavior.
@@ -280,25 +206,242 @@ 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="MyPersistentClass">
<field name="id" type="integer" />
<field name="name" length="50" />
</entity>
</doctrine-mapping>
.. code-block:: yaml
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;
//...
}
@@ -306,17 +449,60 @@ annotation.
.. code-block:: xml
<doctrine-mapping>
<entity name="Message">
<id name="id" type="integer">
<generator strategy="AUTO" />
</id>
<!-- -->
<entity name="MyPersistentClass">
<id name="id" type="integer" />
<field name="name" length="50" />
</entity>
</doctrine-mapping>
.. code-block:: yaml
Message:
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:
@@ -324,12 +510,15 @@ annotation.
generator:
strategy: AUTO
fields:
# fields here
name:
length: 50
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.
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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -337,26 +526,24 @@ 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).
- ``UUID``: Tells Doctrine to use the built-in Universally Unique Identifier
generator. This strategy provides full portability.
- ``TABLE``: Tells Doctrine to use a separate table for ID
generation. This strategy provides full portability.
***This strategy is not yet implemented!***
@@ -364,8 +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
^^^^^^^^^^^^^^^^^^
@@ -379,31 +564,30 @@ 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
Message:
MyPersistentClass:
type: entity
id:
id:
@@ -411,7 +595,7 @@ besides specifying the sequence's name:
generator:
strategy: SEQUENCE
sequenceGenerator:
sequenceName: message_seq
sequenceName: tablename_seq
allocationSize: 100
initialValue: 1
@@ -451,22 +635,25 @@ need to access the sequence once to generate the identifiers for
Composite Keys
~~~~~~~~~~~~~~
With Doctrine 2 you can use composite primary keys, using ``@Id`` on more then
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
@@ -479,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());

View File

@@ -16,15 +16,6 @@ especially what the strategies presented here provide help with.
operations.
.. note::
Having an SQL logger enabled when processing batches can have a serious impact on performance and resource usage.
To avoid that you should disable it in the DBAL configuration:
.. code-block:: php
<?php
$em->getConnection()->getConfiguration()->setSQLLogger(null);
Bulk Inserts
------------
@@ -51,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
------------
@@ -84,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();
@@ -105,12 +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
------------
@@ -145,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) {
@@ -179,7 +162,7 @@ problems using the following approach:
<?php
$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 from Doctrine, so that it can be Garbage-Collected immediately

View File

@@ -6,6 +6,22 @@ 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
-------------------------------------------
@@ -54,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
----------------------------

View File

@@ -4,47 +4,44 @@ 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
- 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 ``CacheProvider`` class which defines a few
Each driver extends the ``AbstractCache`` class which defines a few
abstract protected methods that each of the drivers must
implement:
implement.
- doFetch($id)
- doContains($id)
- doSave($id, $data, $lifeTime = false)
- doDelete($id)
- \_doFetch($id)
- \_doContains($id)
- \_doSave($id, $data, $lifeTime = false)
- \_doDelete($id)
The public methods ``fetch()``, ``contains()`` etc. use the
above protected methods which are implemented by the drivers. The
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
~~~
@@ -63,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 <http://us2.php.net/apcu>`_. It will give
you a little background information about what it is and how you
can use it as well as how to install it.
Below is a simple example of how you could use the APCu cache driver
by itself.
.. code-block:: php
<?php
$cacheDriver = new \Doctrine\Common\Cache\ApcuCache();
$cacheDriver->save('cache_id', 'my_data');
Memcache
~~~~~~~~
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 <http://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.
@@ -103,31 +82,6 @@ driver by itself.
$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 <http://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
~~~~~~
@@ -150,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
and enabled in your php.ini. You can read about what is Redis
`from here <http://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 install Redis PHP extension.
Below is a simple example of how you could use the Redis cache
driver by itself.
@@ -172,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
@@ -184,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
@@ -193,7 +147,7 @@ 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
@@ -216,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.
@@ -234,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
@@ -245,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
^^^^^^^^^^^
@@ -270,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.
@@ -282,14 +237,12 @@ You can set the namespace a cache driver should use by using the
<?php
$cacheDriver->setNamespace('my_namespace_');
.. _integrating-with-the-orm:
Integrating with the ORM
------------------------
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
@@ -307,7 +260,7 @@ use on your ORM configuration.
<?php
$config = new \Doctrine\ORM\Configuration();
$config->setQueryCacheImpl(new \Doctrine\Common\Cache\ApcuCache());
$config->setQueryCacheImpl(new \Doctrine\Common\Cache\ApcCache());
Result Cache
~~~~~~~~~~~~
@@ -320,7 +273,7 @@ cache implementation.
.. code-block:: php
<?php
$config->setResultCacheImpl(new \Doctrine\Common\Cache\ApcuCache());
$config->setResultCacheImpl(new \Doctrine\Common\Cache\ApcCache());
Now when you're executing DQL queries you can configure them to use
the result cache.
@@ -337,7 +290,7 @@ result cache driver.
.. code-block:: php
<?php
$query->setResultCacheDriver(new \Doctrine\Common\Cache\ApcuCache());
$query->setResultCacheDriver(new \Doctrine\Common\Cache\ApcCache());
.. note::
@@ -389,7 +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.
@@ -397,79 +350,88 @@ 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>`_.

View File

@@ -1,7 +1,9 @@
Installation and Configuration
==============================
Doctrine can be installed with `Composer <https://getcomposer.org>`_.
Doctrine can be installed with `Composer <http://www.getcomposer.org>`_. For
older versions we still have `PEAR packages
<http://pear.doctrine-project.org>`_.
Define the following requirement in your ``composer.json`` file:
@@ -14,7 +16,8 @@ Define the following requirement in your ``composer.json`` file:
}
Then call ``composer install`` from your command line. If you don't know
how Composer works, check out their `Getting Started <https://getcomposer.org/doc/00-intro.md>`_ to set up.
how Composer works, check out their `Getting Started
<http://getcomposer.org/doc/00-intro.md>`_ to set up.
Class loading
-------------
@@ -44,7 +47,7 @@ access point to ORM functionality provided by Doctrine.
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;
$paths = array("/path/to/entity-files");
$paths = array("/path/to/entities-or-mapping-files");
$isDevMode = false;
// the connection configuration
@@ -63,7 +66,6 @@ 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);
@@ -72,25 +74,19 @@ Or if you prefer YAML:
.. code-block:: php
<?php
$paths = array("/path/to/yml-mappings");
$config = Setup::createYAMLMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);
.. note::
If you want to use yml mapping you should add yaml dependency to your `composer.json`:
::
"symfony/yaml": "*"
Inside the ``Setup`` methods several assumptions are made:
- If `$isDevMode` is true caching is done in memory with the ``ArrayCache``. Proxy objects are recreated on every request.
- 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::

View File

@@ -2,7 +2,7 @@ Doctrine Query Language
===========================
DQL stands for Doctrine Query Language and is an Object
Query Language derivative that is very similar to the Hibernate
Query Language derivate that is very similar to the Hibernate
Query Language (HQL) or the Java Persistence Query Language (JPQL).
In essence, DQL provides powerful querying capabilities over your
@@ -34,9 +34,9 @@ object model.
DQL SELECT statements are a very powerful way of retrieving parts
of your domain model that are not accessible via associations.
Additionally they allow you to retrieve entities and their associations
Additionally they allow to retrieve entities and their associations
in one single SQL select statement which can make a huge difference
in performance compared to using several queries.
in performance in contrast to using several queries.
DQL UPDATE and DELETE statements offer a way to execute bulk
changes on the entities of your domain model. This is often
@@ -49,6 +49,10 @@ SELECT queries
DQL SELECT clause
~~~~~~~~~~~~~~~~~
The select clause of a DQL query specifies what appears in the
query result. The composition of all the expressions in the select
clause also influences the nature of the query result.
Here is an example that selects all users with an age > 20:
.. code-block:: php
@@ -79,58 +83,14 @@ Lets examine the query:
The result of this query would be a list of User objects where all
users are older than 20.
Result format
~~~~~~~~~~~~~
The composition of the expressions in the SELECT clause also
influences the nature of the query result. There are three
cases:
**All objects**
.. code-block:: sql
SELECT u, p, n FROM Users u...
In this case, the result will be an array of User objects because of
the FROM clause, with children ``p`` and ``n`` hydrated because of
their inclusion in the SELECT clause.
**All scalars**
.. code-block:: sql
SELECT u.name, u.address FROM Users u...
In this case, the result will be an array of arrays. In the example
above, each element of the result array would be an array of the
scalar name and address values.
You can select scalars from any entity in the query.
**Mixed**
.. code-block:: sql
SELECT u, p.quantity FROM Users u...
Here, the result will again be an array of arrays, with each element
being an array made up of a User object and the scalar value
``p.quantity``.
Multiple FROM clauses are allowed, which would cause the result
array elements to cycle through the classes included in the
multiple FROM clauses.
.. note::
You cannot select other entities unless you also select the
root of the selection (which is the first entity in FROM).
For example, ``SELECT p,n FROM Users u...`` would be wrong because
``u`` is not part of the SELECT
Doctrine throws an exception if you violate this constraint.
The SELECT clause allows to specify both class identification
variables that signal the hydration of a complete entity class or
just fields of the entity using the syntax ``u.name``. Combinations
of both are also allowed and it is possible to wrap both fields and
identification values into aggregation and DQL functions. Numerical
fields can be part of computations using mathematical operations.
See the sub-section on `Functions, Operators, Aggregates`_ for
more information.
Joins
~~~~~
@@ -318,7 +278,7 @@ 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',
@@ -342,14 +302,6 @@ With 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
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
Using a LEFT JOIN to hydrate all user-ids and optionally associated
article-ids:
@@ -359,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
@@ -476,33 +427,12 @@ 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:
.. 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");
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 Blacklist b WITH u.email = b.email');
.. note::
The differences between WHERE, WITH and HAVING clauses may be
confusing.
- WHERE is applied to the results of an entire query
- WITH is applied to a join as an additional condition. For
arbitrary joins (SELECT f, b FROM Foo f, Bar b WITH f.id = b.id)
the WITH is required, even if it is 1 = 1
- HAVING is applied to the results of a query after
aggregation (GROUP BY)
$query = $em->createQuery('SELECT IDENTITY(c.location, 'latitude') AS latitude, IDENTITY(c.location, 'longitude') AS longitude FROM Checkpoint c WHERE c.user = ?1');
Partial Object Syntax
@@ -539,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:
@@ -570,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
~~~~~~~~~~~~~~
@@ -654,15 +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
~~~~~~~~~~~~~
@@ -691,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, value, unit) - Add the given time to a given date. (Supported units are SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR)
- DATE_SUB(date, value, unit) - Subtract the given time from a given date. (Supported units are SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR)
- DATE_ADD(date, days, unit) - Add the number of days to a given date. (Supported units are DAY, MONTH)
- DATE_SUB(date, days, unit) - Substract the number of days from a given date. (Supported units are DAY, MONTH)
- DATE_DIFF(date1, date2) - Calculate the difference in days between date1-date2.
Arithmetic operators
@@ -775,6 +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);
@@ -965,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
@@ -975,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
~~~~~~~~~~~~~~~~~~~~
@@ -993,12 +920,10 @@ the Query class. Here they are:
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.
- ``Query#getOneOrNullResult()``: Retrieve a single object. If the
result contains more than one object, a ``NonUniqueResultException``
is thrown. If no object is found null will be returned.
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
array) that is largely interchangeable with the object graph
generated by ``Query#getResult()`` for read-only purposes.
@@ -1061,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]
@@ -1161,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
^^^^^^^^^^^^^^^
@@ -1220,7 +1129,7 @@ Scalar Hydration:
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
@@ -1280,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
@@ -1442,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\ClassMetadata::FETCH_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:
@@ -1452,15 +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
----
@@ -1481,16 +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
- aliased_name (CMS:CmsUser) uses two identifiers, one for the namespace alias and one for the class inside it
- identifier (name, email, ...)
- string ('foo', 'bar''s house', '%ninja%', ...)
- char ('/', '\\', ' ', ...)
- integer (-1, 0, 1, 34, ...)
@@ -1524,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 | aliased_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 */
@@ -1543,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
~~~~~~~~~~~~~~~~
@@ -1599,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"
@@ -1608,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
@@ -1620,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
~~~~~~~~~~~~~~~~~~~~~~~
@@ -1687,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
@@ -1705,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
~~~~~~~~~~~~~~~~
@@ -1735,7 +1632,7 @@ QUANTIFIED/BETWEEN/COMPARISON/LIKE/NULL/EXISTS
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 ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!="
@@ -1770,6 +1667,6 @@ Functions
"TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" |
"LOWER" "(" StringPrimary ")" |
"UPPER" "(" StringPrimary ")" |
"IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")"
"IDENTITY" "(" SingleValuedAssociationPathExpression ")"

View File

@@ -20,12 +20,12 @@ manager.
$evm = new EventManager();
Now we can add some event listeners to the ``$evm``. Let's create a
``TestEvent`` class to play around with.
``EventTest`` class to play around with.
.. code-block:: php
<?php
class TestEvent
class EventTest
{
const preFoo = 'preFoo';
const postFoo = 'postFoo';
@@ -52,15 +52,15 @@ Now we can add some event listeners to the ``$evm``. Let's create a
}
// Create a new instance
$test = new TestEvent($evm);
$test = new EventTest($evm);
Events can be dispatched by using the ``dispatchEvent()`` method.
.. code-block:: php
<?php
$evm->dispatchEvent(TestEvent::preFoo);
$evm->dispatchEvent(TestEvent::postFoo);
$evm->dispatchEvent(EventTest::preFoo);
$evm->dispatchEvent(EventTest::postFoo);
You can easily remove a listener with the ``removeEventListener()``
method.
@@ -133,12 +133,13 @@ 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:
@@ -158,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
@@ -173,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/yaml). 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.
@@ -187,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.
@@ -216,6 +205,7 @@ 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.
@@ -307,7 +297,7 @@ can do it with the following.
name:
type: string(50)
lifecycleCallbacks:
prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersist ]
prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ]
postPersist: [ doStuffOnPostPersist ]
In YAML the ``key`` of the lifecycleCallbacks entry is the event that you
@@ -323,7 +313,7 @@ XML would look something like this:
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
/Users/robo/dev/php/Doctrine/doctrine-mapping.xsd">
<entity name="User">
@@ -359,17 +349,15 @@ 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
-----------------------------------
@@ -406,8 +394,8 @@ behaviors across different entity classes.
Note that they require much more detailed knowledge about the inner
workings of the EntityManager and UnitOfWork. Please read the
:ref:`reference-events-implementing-listeners` section carefully if you
are trying to write your own listener.
*Implementing Event Listeners* section carefully if you are trying
to write your own listener.
For event subscribers, there are no surprises. They declare the
lifecycle events in their ``getSubscribedEvents`` method and provide
@@ -434,7 +422,7 @@ A lifecycle event listener looks like the following:
}
}
A lifecycle event subscriber may look like this:
A lifecycle event subscriber may looks like this:
.. code-block:: php
@@ -548,9 +536,8 @@ preFlush
~~~~~~~~
``preFlush`` is called at ``EntityManager#flush()`` before
anything else. ``EntityManager#flush()`` should not be called inside
its listeners, since `preFlush` event is dispatched in it, which would
result in infinite loop.
anything else. ``EntityManager#flush()`` can be called safely
inside its listeners.
.. code-block:: php
@@ -595,23 +582,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) {
}
}
@@ -620,13 +607,13 @@ 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
@@ -654,8 +641,7 @@ preUpdate
PreUpdate is the most restrictive to use event, since it is called
right before an update statement is called for an entity inside the
``EntityManager#flush()`` method. Note that this event is not
triggered when the computed changeset is empty.
``EntityManager#flush()`` method.
Changes to associations of the updated entity are never allowed in
this event, since Doctrine cannot guarantee to correctly handle
@@ -741,7 +727,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
~~~~~~~~
@@ -754,9 +740,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::
@@ -814,8 +800,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::
@@ -889,16 +875,13 @@ you need to map the listener method using the event type mapping:
preRemove: [preRemoveHandler]
# ....
.. note::
The order of execution of multiple methods for the same event (e.g. multiple @PrePersist) is not guaranteed.
Entity listeners resolver
~~~~~~~~~~~~~~~~~~~~~~~~~~
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 :
@@ -953,9 +936,8 @@ 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
------------------------
@@ -967,12 +949,12 @@ process and manipulate the instance.
.. code-block:: php
<?php
$test = new TestEvent();
$test = new EventTest();
$metadataFactory = $em->getMetadataFactory();
$evm = $em->getEventManager();
$evm->addEventListener(Events::loadClassMetadata, $test);
class TestEvent
class EventTest
{
public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs)
{

View File

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

View File

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

View File

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

View File

@@ -81,67 +81,45 @@ discriminator column is used.
Example:
.. configuration-block::
.. code-block:: php
.. code-block:: php
<?php
namespace MyProject\Model;
<?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
{
// ...
}
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
class Person
{
// ...
}
/**
* @Entity
*/
class Employee extends Person
{
// ...
}
.. code-block:: yaml
MyProject\Model\Person:
type: entity
inheritanceType: SINGLE_TABLE
discriminatorColumn:
name: discr
type: string
discriminatorMap:
person: Person
employee: Employee
MyProject\Model\Employee:
type: entity
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, then the map is generated
automatically. The automatically generated discriminator map
contains the lowercase short name of each class as key.
Design-time considerations
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -159,7 +137,7 @@ This strategy is very efficient for querying across all types in
the hierarchy or for specific types. No table joins are required,
only a WHERE clause listing the type identifiers. In particular,
relationships involving types that employ this mapping strategy are
very performing.
very performant.
There is a general performance consideration with Single Table
Inheritance: If the target-entity of a many-to-one or one-to-one
@@ -174,7 +152,7 @@ SQL Schema considerations
For Single-Table-Inheritance to work in scenarios where you are
using either a legacy database schema or a self-written database
schema you have to make sure that all columns that are not in the
root entity but in any of the different sub-entities has to allow
root entity but in any of the different sub-entities has to allows
null values. Columns that have NOT NULL constraints have to be on
the root entity of the single-table inheritance hierarchy.
@@ -229,9 +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, then the map is generated
automatically. The automatically generated discriminator map
contains the lowercase short name of each class as key.
.. note::
@@ -285,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
---------
@@ -454,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
~~~~~~~~~~~~~~~~~~~~
@@ -493,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",
@@ -501,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
* )
* )
* })
@@ -583,24 +555,5 @@ Could be used by an entity that extends a mapped superclass to override a field
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.

View File

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

View File

@@ -63,7 +63,19 @@ 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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -71,8 +83,8 @@ Cascade Merge with Bi-directional Associations
There are two bugs now that concern the use of cascade merge in combination with bi-directional associations.
Make sure to study the behavior of cascade merge if you are using it:
- `DDC-875 <https://github.com/doctrine/orm/issues/5398>`_ Merge can sometimes add the same entity twice into a collection
- `DDC-763 <https://github.com/doctrine/orm/issues/5277>`_ Cascade merge on associated entities can insert too many rows through "Persistence by Reachability"
- `DDC-875 <http://www.doctrine-project.org/jira/browse/DDC-875>`_ Merge can sometimes add the same entity twice into a collection
- `DDC-763 <http://www.doctrine-project.org/jira/browse/DDC-763>`_ Cascade merge on associated entities can insert too many rows through "Persistence by Reachability"
Custom Persisters
~~~~~~~~~~~~~~~~~
@@ -83,8 +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
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -94,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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -112,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
@@ -143,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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -170,34 +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.
Entities, Proxies and Reflection
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Using methods for Reflection on entities can be prone to error, when the entity
is actually a proxy the following methods will not work correctly:
- ``new ReflectionClass``
- ``new ReflectionObject``
- ``get_class()``
- ``get_parent_class()``
This is why ``Doctrine\Common\Util\ClassUtils`` class exists that has similar
methods, which resolve the proxy problem beforehand.
.. code-block:: php
<?php
use Doctrine\Common\Util\ClassUtils;
$bookProxy = $entityManager->getReference('Acme\Book');
$reflection = ClassUtils::newReflectionClass($bookProxy);
$class = ClassUtils::getClass($bookProxy)¸

View File

@@ -35,7 +35,7 @@ an entity.
.. code-block:: php
<?php
$em->getConfiguration()->setMetadataCacheImpl(new ApcuCache());
$em->getConfiguration()->setMetadataCacheImpl(new ApcCache());
If you want to use one of the included core metadata drivers you

View File

@@ -3,43 +3,48 @@ Implementing a NamingStrategy
.. versionadded:: 2.3
Using a naming strategy you can provide rules for generating database identifiers,
column or table names when the column or table name is not given. This feature helps
reduce the verbosity of the mapping document, eliminating repetitive noise (eg: ``TABLE_``).
Using a naming strategy you can provide rules for automatically generating
database identifiers, columns and tables names
when the table/column name is not given.
This feature helps reduce the verbosity of the mapping document,
eliminating repetitive noise (eg: ``TABLE_``).
Configuring a naming strategy
-----------------------------
The default strategy used by Doctrine is quite minimal.
By default the ``Doctrine\ORM\Mapping\DefaultNamingStrategy``
uses the simple class name and the attribute names to generate tables and columns.
uses the simple class name and the attributes names to generate tables and columns
You can specify a different strategy by calling ``Doctrine\ORM\Configuration#setNamingStrategy()``:
You can specify a different strategy by calling ``Doctrine\ORM\Configuration#setNamingStrategy()`` :
.. code-block:: php
<?php
$namingStrategy = new MyNamingStrategy();
$configuration->setNamingStrategy($namingStrategy);
$configuration()->setNamingStrategy($namingStrategy);
Underscore naming strategy
---------------------------
``\Doctrine\ORM\Mapping\UnderscoreNamingStrategy`` is a built-in strategy.
``\Doctrine\ORM\Mapping\UnderscoreNamingStrategy`` is a built-in strategy
that might be a useful if you want to use a underlying convention.
.. code-block:: php
<?php
$namingStrategy = new \Doctrine\ORM\Mapping\UnderscoreNamingStrategy(CASE_UPPER);
$configuration->setNamingStrategy($namingStrategy);
$configuration()->setNamingStrategy($namingStrategy);
Then SomeEntityName will generate the table SOME_ENTITY_NAME when CASE_UPPER
or some_entity_name using CASE_LOWER is given.
For SomeEntityName the strategy will generate the table SOME_ENTITY_NAME with the
``CASE_UPPER`` option, or some_entity_name with the ``CASE_LOWER`` option.
Naming strategy interface
-------------------------
The interface ``Doctrine\ORM\Mapping\NamingStrategy`` allows you to specify
a naming strategy for database tables and columns.
a "naming standard" for database tables and columns.
.. code-block:: php
@@ -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,10 @@ a naming strategy for database tables and columns.
Implementing a naming strategy
-------------------------------
If you have database naming standards, like all table names should be prefixed
by the application prefix, all column names should be lower case, you can easily
achieve such standards by implementing a naming strategy.
You need to create a class which implements ``Doctrine\ORM\Mapping\NamingStrategy``.
If you have database naming standards like all tables names should be prefixed
by the application prefix, all column names should be upper case,
you can easily achieve such standards by implementing a naming strategy.
You need to implements NamingStrategy first. Following is an example
.. code-block:: php
@@ -120,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();
}
@@ -135,3 +139,12 @@ You need to create a class which implements ``Doctrine\ORM\Mapping\NamingStrateg
($referencedColumnName ?: $this->referenceColumnName()));
}
}
Configuring the namingstrategy is easy if.
Just set your naming strategy calling ``Doctrine\ORM\Configuration#setNamingStrategy()`` :.
.. code-block:: php
<?php
$namingStrategy = new MyAppNamingStrategy();
$configuration()->setNamingStrategy($namingStrategy);

View File

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

View File

@@ -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'
@@ -241,7 +227,6 @@ General Getters
- ``getTableName()``
- ``getSchemaName()``
- ``getTemporaryIdTableName()``
Identifier Getters

View File

@@ -7,20 +7,14 @@ conditionally constructing a DQL query in several steps.
It provides a set of classes and methods that is able to
programmatically build queries, and also provides a fluent API.
This means that you can change between one methodology to the other
as you want, or just pick a preferred one.
.. note::
The ``QueryBuilder`` is not an abstraction of DQL, but merely a tool to dynamically build it.
You should still use plain DQL when you can, as it is simpler and more readable.
More about this in the :doc:`FAQ <faq>`_.
as you want, and also pick one if you prefer.
Constructing a new QueryBuilder object
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The same way you build a normal Query, you build a ``QueryBuilder``
object. Here is an example of how to build a ``QueryBuilder``
object:
object, just providing the correct method name. Here is an example
how to build a ``QueryBuilder`` object:
.. code-block:: php
@@ -30,9 +24,9 @@ object:
// example1: creating a QueryBuilder instance
$qb = $em->createQueryBuilder();
An instance of QueryBuilder has several informative methods. One
good example is to inspect what type of object the
``QueryBuilder`` is.
Once you have created an instance of QueryBuilder, it provides a
set of useful informative functions that you can use. One good
example is to inspect what type of object the ``QueryBuilder`` is.
.. code-block:: php
@@ -86,11 +80,11 @@ Working with QueryBuilder
High level API methods
^^^^^^^^^^^^^^^^^^^^^^
The most straightforward way to build a dynamic query with the ``QueryBuilder`` is by taking
advantage of Helper methods. For all base code, there is a set of
useful methods to simplify a programmer's life. To illustrate how
to work with them, here is the same example 6 re-written using
``QueryBuilder`` helper methods:
To simplify even more the way you build a query in Doctrine, we can take
advantage of what we call Helper methods. For all base code, there
is a set of useful methods to simplify a programmer's life. To
illustrate how to work with them, here is the same example 6
re-written using ``QueryBuilder`` helper methods:
.. code-block:: php
@@ -103,9 +97,10 @@ to work with them, here is the same example 6 re-written using
->orderBy('u.name', 'ASC');
``QueryBuilder`` helper methods are considered the standard way to
use the ``QueryBuilder``. The ``$qb->expr()->*`` methods can help you
build conditional expressions dynamically. Here is a converted example 8 to
suggested way to build queries with dynamic conditions:
build DQL queries. Although it is supported, it should be avoided
to use string based queries and greatly encouraged to use
``$qb->expr()->*`` methods. Here is a converted example 8 to
suggested standard way to build queries:
.. code-block:: php
@@ -118,7 +113,7 @@ suggested way to build queries with dynamic conditions:
$qb->expr()->eq('u.id', '?1'),
$qb->expr()->like('u.nickname', '?2')
))
->orderBy('u.surname', 'ASC');
->orderBy('u.surname', 'ASC'));
Here is a complete list of helper methods available in ``QueryBuilder``:
@@ -132,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);
@@ -150,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
//
@@ -175,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);
@@ -227,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
@@ -241,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
@@ -322,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:
@@ -357,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(
@@ -455,9 +433,6 @@ 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
@@ -470,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
@@ -504,32 +479,14 @@ complete list of supported helper methods available:
public function countDistinct($x); // Returns Expr\Func
}
Adding a Criteria to a Query
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can also add a :ref:`filtering-collections` to a QueryBuilder by
using ``addCriteria``:
.. code-block:: php
<?php
use Doctrine\Common\Collections\Criteria;
// ...
$criteria = Criteria::create()
->orderBy(['firstName', 'ASC']);
// $qb instanceof QueryBuilder
$qb->addCriteria($criteria);
// then execute your query like normal
Low Level API
^^^^^^^^^^^^^
Now we will describe the low level method of creating queries.
It may be useful to work at this level for optimization purposes,
but most of the time it is preferred to work at a higher level of
abstraction.
Now we have describe the low level (thought of as the
hardcore method) of creating queries. It may be useful to work at
this level for optimization purposes, but most of the time it is
preferred to work at a higher level of abstraction.
All helper methods in ``QueryBuilder`` actually rely on a single
one: ``add()``. This method is responsible of building every piece
@@ -553,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')
@@ -574,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.

View File

@@ -1,731 +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/current/Doctrine/ORM/Cache/Region.html>`_.
Concurrent cache region
~~~~~~~~~~~~~~~~~~~~~~~
A ``Doctrine\ORM\Cache\ConcurrentRegion`` is designed to store concurrently managed data region.
By default, Doctrine provides a very simple implementation based on file locks ``Doctrine\ORM\Cache\Region\FileLockRegion``.
If you want to use an ``READ_WRITE`` cache, you should consider providing your own cache region.
``Doctrine\ORM\Cache\ConcurrentRegion``
Defines contract for concurrently managed data region.
`See API Doc <https://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/ConcurrentRegion.html>`_.
Timestamp region
~~~~~~~~~~~~~~~~
``Doctrine\ORM\Cache\TimestampRegion``
Tracks the timestamps of the most recent updates to particular entity.
`See API Doc <http://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/TimestampRegion.html>`_.
.. _reference-second-level-cache-mode:
Caching mode
------------
* ``READ_ONLY`` (DEFAULT)
* Can do reads, inserts and deletes, cannot perform updates or employ any locks.
* Useful for data that is read frequently but never updated.
* Best performer.
* It is Simple.
* ``NONSTRICT_READ_WRITE``
* Read Write Cache doesnt employ any locks but can do reads, inserts, updates and deletes.
* Good if the application needs to update data rarely.
* ``READ_WRITE``
* Read Write cache employs locks before update/delete.
* Use if data needs to be updated.
* Slowest strategy.
* To use it a the cache region implementation must support locking.
Built-in cached persisters
~~~~~~~~~~~~~~~~~~~~~~~~~~
Cached persisters are responsible to access cache regions.
+-----------------------+-------------------------------------------------------------------------------------------+
| Cache Usage | Persister |
+=======================+===========================================================================================+
| READ_ONLY | Doctrine\\ORM\\Cache\\Persister\\Entity\\ReadOnlyCachedEntityPersister |
+-----------------------+-------------------------------------------------------------------------------------------+
| READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\Entity\\ReadWriteCachedEntityPersister |
+-----------------------+-------------------------------------------------------------------------------------------+
| NONSTRICT_READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\Entity\\NonStrictReadWriteCachedEntityPersister |
+-----------------------+-------------------------------------------------------------------------------------------+
| READ_ONLY | Doctrine\\ORM\\Cache\\Persister\\Collection\\ReadOnlyCachedCollectionPersister |
+-----------------------+-------------------------------------------------------------------------------------------+
| READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\Collection\\ReadWriteCachedCollectionPersister |
+-----------------------+-------------------------------------------------------------------------------------------+
| NONSTRICT_READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\Collection\\NonStrictReadWriteCachedCollectionPersister |
+-----------------------+-------------------------------------------------------------------------------------------+
Configuration
-------------
Doctrine allows you to specify configurations and some points of extension for the second-level-cache
Enable Second Level Cache
~~~~~~~~~~~~~~~~~~~~~~~~~
To enable the second-level-cache, you should provide a cache factory
``\Doctrine\ORM\Cache\DefaultCacheFactory`` is the default implementation.
.. code-block:: php
<?php
/* @var $config \Doctrine\ORM\Cache\RegionsConfiguration */
/* @var $cache \Doctrine\Common\Cache\Cache */
$factory = new \Doctrine\ORM\Cache\DefaultCacheFactory($config, $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 $config \Doctrine\ORM\Configuration */
/* @var $cacheConfig \Doctrine\ORM\Cache\CacheConfiguration */
$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 $config \Doctrine\ORM\Configuration */
$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>
.. code-block:: yaml
Country:
type: entity
cache:
usage : READ_ONLY
region : my_entity_region
id:
id:
type: integer
id: true
generator:
strategy: IDENTITY
fields:
name:
type: string
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>
.. code-block:: yaml
State:
type: entity
cache:
usage : NONSTRICT_READ_WRITE
id:
id:
type: integer
id: true
generator:
strategy: IDENTITY
fields:
name:
type: string
manyToOne:
state:
targetEntity: Country
joinColumns:
country_id:
referencedColumnName: id
cache:
usage : NONSTRICT_READ_WRITE
oneToMany:
cities:
targetEntity:City
mappedBy: state
cache:
usage : NONSTRICT_READ_WRITE
> 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
$country->setName("New Name");
$em->persist($country);
$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 $em \Doctrine\ORM\EntityManager */
// 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 $em \Doctrine\ORM\EntityManager */
// 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 $cache \Doctrine\ORM\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.

View File

@@ -1,151 +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 possiblity of mass-asignment 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;
}
}
}

View File

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

View File

@@ -1,8 +1,6 @@
Transactions and Concurrency
============================
.. _transactions-and-concurrency_transaction-demarcation:
Transaction Demarcation
-----------------------
@@ -28,8 +26,6 @@ and control transaction demarcation yourself.
These are two ways to deal with transactions when using the
Doctrine ORM and are now described in more detail.
.. _transactions-and-concurrency_approach-implicitly:
Approach 1: Implicitly
~~~~~~~~~~~~~~~~~~~~~~
@@ -53,8 +49,6 @@ the DML operations by the Doctrine ORM and is sufficient if all the
data manipulation that is part of a unit of work happens through
the domain model and thus the ORM.
.. _transactions-and-concurrency_approach-explicitly:
Approach 2: Explicitly
~~~~~~~~~~~~~~~~~~~~~~
@@ -75,7 +69,8 @@ looks like this:
$em->flush();
$em->getConnection()->commit();
} catch (Exception $e) {
$em->getConnection()->rollBack();
$em->getConnection()->rollback();
$em->close();
throw $e;
}
@@ -86,12 +81,14 @@ 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
@@ -104,20 +101,11 @@ functionally equivalent to the previously shown code looks as follows:
$em->persist($user);
});
.. warning::
For historical reasons, ``EntityManager#transactional($func)`` will return
``true`` whenever the return value of ``$func`` is loosely false.
Some examples of this include ``array()``, ``"0"``, ``""``, ``0``, and
``null``.
The difference between ``Connection#transactional($func)`` and
``EntityManager#transactional($func)`` is that the latter
abstraction flushes the ``EntityManager`` prior to transaction
commit and rolls back the transaction when an
exception occurs.
.. _transactions-and-concurrency_exception-handling:
commit and also closes the ``EntityManager`` properly when an
exception occurs (in addition to rolling back the transaction).
Exception Handling
~~~~~~~~~~~~~~~~~~
@@ -149,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
---------------
@@ -159,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
~~~~~~~~~~~~~~~~~~
@@ -187,68 +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>
.. code-block:: yaml
User:
type: entity
fields:
version:
type: integer
version: true
<?php
class User
{
// ...
/** @Version @Column(type="integer") */
private $version;
// ...
}
Alternatively a datetime type can be used (which maps to a SQL
timestamp or datetime):
.. configuration-block::
.. code-block:: php
.. code-block:: php
<?php
class User
{
// ...
/** @Version @Column(type="datetime") */
private $version;
// ...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="User">
<field name="version" type="datetime" version="true" />
</entity>
</doctrine-mapping>
.. code-block:: yaml
User:
type: entity
fields:
version:
type: datetime
version: true
<?php
class User
{
// ...
/** @Version @Column(type="datetime") */
private $version;
// ...
}
Version numbers (not timestamps) should however be preferred as
they can not potentially conflict in a highly concurrent
@@ -362,8 +308,6 @@ And the change headline action (POST Request):
$post = $em->find('BlogPost', $postId, \Doctrine\DBAL\LockMode::OPTIMISTIC, $postVersion);
.. _transactions-and-concurrency_pessimistic-locking:
Pessimistic Locking
~~~~~~~~~~~~~~~~~~~

View File

@@ -15,10 +15,10 @@ Bidirectional Associations
The following rules apply to **bidirectional** associations:
- The inverse side has to have the ``mappedBy`` attribute of the OneToOne,
- The inverse side has to use the ``mappedBy`` attribute of the OneToOne,
OneToMany, or ManyToMany mapping declaration. The mappedBy
attribute contains the name of the association-field on the owning side.
- The owning side has to have the ``inversedBy`` attribute of the
- The owning side has to use the ``inversedBy`` attribute of the
OneToOne, ManyToOne, or ManyToMany mapping declaration.
The inversedBy attribute contains the name of the association-field
on the inverse-side.

View File

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

View File

@@ -2,25 +2,27 @@ Working with Associations
=========================
Associations between entities are represented just like in regular
object-oriented PHP code using references to other objects or
collections of objects.
object-oriented PHP, with references to other objects or
collections of objects. When it comes to persistence, it is
important to understand three main things:
Changes to associations in your code are not synchronized to the
database directly, only when calling ``EntityManager#flush()``.
There are other concepts you should know about when working
with associations in Doctrine:
- The :doc:`concept of owning and inverse sides <unitofwork-associations>`
in bidirectional associations.
- If an entity is removed from a collection, the association is
removed, not the entity itself. A collection of entities always
only represents the association to the containing entities, not the
entity itself.
- When a bidirectional association is updated, Doctrine only checks
on one of both sides for these changes. This is called the :doc:`owning side <unitofwork-associations>`
of the association.
- A property with a reference to many entities has to be instances of the
- Collection-valued :ref:`persistent fields <architecture_persistent_fields>` have to be instances of the
``Doctrine\Common\Collections\Collection`` interface.
Changes to associations in your code are not synchronized to the
database directly, but upon calling ``EntityManager#flush()``.
To describe all the concepts of working with associations we
introduce a specific set of example entities that show all the
different flavors of association management in Doctrine.
Association Example Entities
----------------------------
@@ -42,7 +44,10 @@ information about its type and if it's the owning or inverse side.
* 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;
@@ -50,7 +55,10 @@ information about its type and if it's the owning or inverse side.
* 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;
@@ -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
@@ -396,25 +404,53 @@ There are two approaches to handle this problem in your code:
1. Ignore updating the inverse side of bidirectional collections,
BUT never read from them in requests that changed their state. In
the next request Doctrine hydrates the consistent collection state
the next Request Doctrine hydrates the consistent collection state
again.
2. Always keep the bidirectional collections in sync through
association management methods. Reads of the Collections directly
after changes are consistent then.
.. _transitive-persistence:
Transitive persistence / Cascade Operations
-------------------------------------------
Doctrine 2 provides a mechanism for transitive persistence through cascading of certain operations.
Each association to another entity or a collection of
entities can be configured to automatically cascade the following operations to the associated entities:
``persist``, ``remove``, ``merge``, ``detach``, ``refresh`` or ``all``.
Persisting, removing, detaching 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
@@ -424,39 +460,36 @@ comment might look like in your controller (without ``cascade: persist``):
$user->addComment($myFirstComment);
$em->persist($user);
$em->persist($myFirstComment); // required, if `cascade: persist` is not set
$em->persist($myFirstComment);
$em->flush();
Note that the Comment entity is instantiated right here in the controller.
To avoid this, ``cascade: persist`` allows you to "hide" the Comment entity from the controller,
only accessing it through the User entity:
Even if you *persist* a new User that contains our new Comment this
code would fail if you removed the call to
``EntityManager#persist($myFirstComment)``. Doctrine 2 does not
cascade the persist operation to all nested entities that are new
as well.
More complicated is the deletion of all of a user's comments when he is
removed from the system:
.. code-block:: php
<?php
// User entity
class User
{
private $id;
private $comments;
public function __construct()
{
$this->id = User::new();
$this->comments = new ArrayCollection();
}
public function comment(string $text, DateTimeInterface $time) : void
{
$newComment = Comment::create($text, $time);
$newComment->setUser($this);
$this->comments->add($newComment);
}
// ...
$user = $em->find('User', $deleteUserId);
foreach ($user->getAuthoredComments() AS $comment) {
$em->remove($comment);
}
$em->remove($user);
$em->flush();
If you then set up the cascading to the ``User#commentsAuthored`` property...
Without the loop over all the authored comments Doctrine would use
an UPDATE statement only to set the foreign key to NULL and only
the User would be deleted from the database during the
flush()-Operation.
To have Doctrine handle both cases automatically we can change the
``User#commentsAuthored`` property to cascade both the "persist"
and the "remove" operation.
.. code-block:: php
@@ -473,51 +506,10 @@ If you then set up the cascading to the ``User#commentsAuthored`` property...
//...
}
...you can now create a user and an associated comment like this:
.. code-block:: php
<?php
$user = new User();
$user->comment('Lorem ipsum', new DateTime());
$em->persist($user);
$em->flush();
.. note::
The idea of ``cascade: persist`` is not to save you any lines of code in the controller.
If you instantiate the comment object in the controller (i.e. don't set up the user entity as shown above),
even with ``cascade: persist`` you still have to call ``$myFirstComment->setUser($user);``.
Thanks to ``cascade: remove``, you can easily delete a user and all linked comments without having to loop through them:
.. code-block:: php
<?php
$user = $em->find('User', $deleteUserId);
$em->remove($user);
$em->flush();
.. note::
Cascade operations are performed in memory. That means collections and related entities
are fetched into memory (even if they are marked as lazy) when
the cascade operation is about to be performed. This approach allows
entity lifecycle events to be performed for each of these operations.
However, pulling object graphs into memory on cascade can cause considerable performance
overhead, especially when the cascaded collections are large. Make sure
to weigh the benefits and downsides of each cascade operation that you define.
To rely on the database level cascade operations for the delete operation instead, you can
configure each join column with :doc:`the onDelete option <working-with-objects>`.
Even though automatic cascading is convenient, it should be used
with care. Do not blindly apply ``cascade=all`` to all associations as
Even though automatic cascading is convenient it should be used
with care. Do not blindly apply cascade=all to all associations as
it will unnecessarily degrade the performance of your application.
For each cascade operation that gets activated, Doctrine also
For each cascade operation that gets activated Doctrine also
applies that operation to the association, be it single or
collection valued.
@@ -525,20 +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
--------------
@@ -616,11 +609,11 @@ address reference. When flush is called not only are the references removed
but both the old standing data and the one address entity are also deleted
from the database.
.. _filtering-collections:
Filtering Collections
---------------------
.. filtering-collections:
Collections have a filtering API that allows to slice parts of data from
a collection. If the collection has not been loaded from the database yet,
the filtering API can work on the SQL level to make optimized access to
@@ -637,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)
;
@@ -715,14 +708,5 @@ methods:
* ``isNull($field)``
* ``in($field, array $values)``
* ``notIn($field, array $values)``
* ``contains($field, $value)``
* ``memberOf($value, $field)``
* ``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.

View File

@@ -25,13 +25,6 @@ Work that have not yet been persisted are lost.
Not calling ``EntityManager#flush()`` will lead to all changes
during that request being lost.
.. note::
Doctrine does NEVER touch the public API of methods in your entity
classes (like getters and setters) nor the constructor method.
Instead, it uses reflection to get/set data from/to your entity objects.
When Doctrine fetches data from DB and saves it back,
any code put in your get/set methods won't be implicitly taken into account.
Entities and the Identity Map
-----------------------------
@@ -110,8 +103,7 @@ from newly opened EntityManager.
/** @OneToMany(targetEntity="Comment", mappedBy="article") */
private $comments;
public function __construct()
{
public function __construct {
$this->comments = new ArrayCollection();
}
@@ -122,7 +114,7 @@ from newly opened EntityManager.
$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.
@@ -151,7 +143,7 @@ your code. See the following code:
// 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";
}
@@ -245,7 +237,7 @@ as follows:
persist operation. However, the persist operation is cascaded to
entities referenced by X, if the relationships from X to these
other entities are mapped with cascade=PERSIST or cascade=ALL (see
":ref:`transitive-persistence`").
"Transitive Persistence").
- If X is a removed entity, it becomes managed.
- If X is a detached entity, an exception will be thrown on
flush.
@@ -286,12 +278,12 @@ as follows:
- If X is a new entity, it is ignored by the remove operation.
However, the remove operation is cascaded to entities referenced by
X, if the relationship from X to these other entities is mapped
with cascade=REMOVE or cascade=ALL (see ":ref:`transitive-persistence`").
with cascade=REMOVE or cascade=ALL (see "Transitive Persistence").
- If X is a managed entity, the remove operation causes it to
become removed. The remove operation is cascaded to entities
referenced by X, if the relationships from X to these other
entities is mapped with cascade=REMOVE or cascade=ALL (see
":ref:`transitive-persistence`").
"Transitive Persistence").
- If X is a detached entity, an InvalidArgumentException will be
thrown.
- If X is a removed entity, it is ignored by the remove operation.
@@ -357,14 +349,14 @@ as follows:
become detached. The detach operation is cascaded to entities
referenced by X, if the relationships from X to these other
entities is mapped with cascade=DETACH or cascade=ALL (see
":ref:`transitive-persistence`"). Entities which previously referenced X
"Transitive Persistence"). Entities which previously referenced X
will continue to reference X.
- If X is a new or detached entity, it is ignored by the detach
operation.
- If X is a removed entity, the detach operation is cascaded to
entities referenced by X, if the relationships from X to these
other entities is mapped with cascade=DETACH or cascade=ALL (see
":ref:`transitive-persistence`"). Entities which previously referenced X
"Transitive Persistence"). Entities which previously referenced X
will continue to reference X.
There are several situations in which an entity is detached
@@ -423,7 +415,8 @@ as follows:
- If X is a managed entity, it is ignored by the merge operation,
however, the merge operation is cascaded to entities referenced by
relationships from X if these relationships have been mapped with
the cascade element value MERGE or ALL (see ":ref:`transitive-persistence`").
the cascade element value MERGE or ALL (see "Transitive
Persistence").
- For all entities Y referenced by relationships from X having the
cascade element value MERGE or ALL, Y is merged recursively as Y'.
For all such Y referenced by X, X' is set to reference Y'. (Note
@@ -705,6 +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
@@ -733,14 +728,6 @@ examples are equivalent:
// A single user by its nickname (__call magic)
$user = $em->getRepository('MyProject\Domain\User')->findOneByNickname('romanb');
Additionally, you can just count the result of the provided conditions when you don't really need the data:
.. code-block:: php
<?php
// Check there is no user with nickname
$availableNickname = 0 === $em->getRepository('MyProject\Domain\User')->count(['nickname' => 'nonexistent']);
By Criteria
~~~~~~~~~~~
@@ -750,7 +737,8 @@ The Repository implement the ``Doctrine\Common\Collections\Selectable``
interface. That means you can build ``Doctrine\Common\Collections\Criteria``
and pass them to the ``matching($criteria)`` method.
See section `Filtering collections` of chapter :doc:`Working with Associations <working-with-associations>`
See the :ref:`Working with Associations: Filtering collections
<filtering-collections>`.
By Eager Loading
~~~~~~~~~~~~~~~~
@@ -800,9 +788,7 @@ DQL and its syntax as well as the Doctrine class can be found in
:doc:`the dedicated chapter <dql-doctrine-query-language>`.
For programmatically building up queries based on conditions that
are only known at runtime, Doctrine provides the special
``Doctrine\ORM\QueryBuilder`` class. While this a powerful tool,
it also brings more complexity to your code compared to plain DQL,
so you should only use it when you need it. More information on
``Doctrine\ORM\QueryBuilder`` class. More information on
constructing queries with a QueryBuilder can be found
:doc:`in Query Builder chapter <query-builder>`.
@@ -834,10 +820,9 @@ in a central location.
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
{

View File

@@ -7,7 +7,7 @@ form of XML documents.
The XML driver is backed by an XML Schema document that describes
the structure of a mapping document. The most recent version of the
XML Schema document is available online at
`https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd <https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd>`_.
`http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd <http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd>`_.
In order to point to the latest version of the document of a
particular stable release branch, just append the release number,
i.e.: doctrine-mapping-2.0.xsd The most convenient way to work with
@@ -21,10 +21,10 @@ 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
@@ -44,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
@@ -62,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
~~~~~~~~~~~~~~~~~~~~~
@@ -88,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
@@ -107,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/>
@@ -147,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/>
@@ -161,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
@@ -187,7 +179,7 @@ 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>
@@ -211,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
~~~~~~~~~~~~~~~
@@ -225,18 +216,12 @@ 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:
@@ -262,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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -321,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
@@ -450,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>
@@ -743,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>
@@ -768,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" />
@@ -777,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>

View File

@@ -1,10 +1,6 @@
YAML Mapping
============
.. note::
The YAML driver is deprecated and will be removed in version 3.0.
It is strongly recommended to switch to one of the other mappings.
The YAML mapping driver enables you to provide the ORM metadata in
form of YAML documents.
@@ -76,10 +72,7 @@ of several common elements:
# Doctrine.Tests.ORM.Mapping.User.dcm.yml
Doctrine\Tests\ORM\Mapping\User:
type: entity
repositoryClass: Doctrine\Tests\ORM\Mapping\UserRepository
table: cms_users
schema: schema_name # The schema the table lies in, for platforms that support schemas (Optional, >= 2.5)
readOnly: true
indexes:
name_index:
columns: [ name ]
@@ -92,28 +85,12 @@ of several common elements:
name:
type: string
length: 50
email:
type: string
length: 32
column: user_email
unique: true
options:
fixed: true
comment: User's email address
loginCount:
type: integer
column: login_count
nullable: false
options:
unsigned: true
default: 0
oneToOne:
address:
targetEntity: Address
joinColumn:
name: address_id
referencedColumnName: id
onDelete: CASCADE
oneToMany:
phonenumbers:
targetEntity: Phonenumber
@@ -137,22 +114,4 @@ of several common elements:
Be aware that class-names specified in the YAML files should be
fully qualified.
Reference
~~~~~~~~~~~~~~~~~~~~~~
Unique Constraints
------------------
It is possible to define unique constraints by the following declaration:
.. code-block:: yaml
# ECommerceProduct.orm.yml
ECommerceProduct:
type: entity
fields:
# definition of some fields
uniqueConstraints:
search_idx:
columns: [ name, email ]

View File

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

View File

@@ -16,7 +16,6 @@ Tutorials
tutorials/ordered-associations
tutorials/override-field-association-mappings-in-subclasses
tutorials/pagination.rst
tutorials/embeddables.rst
Reference Guide
---------------
@@ -26,6 +25,7 @@ Reference Guide
:numbered:
reference/architecture
reference/installation
reference/configuration.rst
reference/faq
reference/basic-mapping
@@ -53,13 +53,10 @@ Reference Guide
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
reference/filters.rst
reference/namingstrategy.rst
reference/advanced-configuration.rst
Cookbook
--------
@@ -75,6 +72,7 @@ Cookbook
cookbook/implementing-arrayaccess-for-domain-objects
cookbook/implementing-the-notify-changetracking-policy
cookbook/implementing-wakeup-or-clone
cookbook/integrating-with-codeigniter
cookbook/resolve-target-entity-listener
cookbook/sql-table-prefixes
cookbook/strategy-cookbook-introduction

View File

@@ -13,7 +13,7 @@ This tutorial shows how the semantics of composite primary keys work and how the
General Considerations
~~~~~~~~~~~~~~~~~~~~~~
Every entity with a composite key cannot use an id generator other than "NONE". That means
Every entity with a composite key cannot use an id generator other than "ASSIGNED". That means
the ID fields have to have their values set before you call ``EntityManager#persist($entity)``.
Primitive Types only
@@ -38,7 +38,7 @@ and year of production as primary keys:
/** @Id @Column(type="string") */
private $name;
/** @Id @Column(type="integer") */
private $year;
private $year
public function __construct($name, $year)
{
@@ -63,7 +63,7 @@ and year of production as primary keys:
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="VehicleCatalogue\Model\Car">
<id field="name" type="string" />
@@ -203,7 +203,7 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Application\Model\ArticleAttribute">
<id name="article" association-key="true" />

View File

@@ -1,179 +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
/** @Entity */
class User
{
/** @Embedded(class = "Address") */
private $address;
}
/** @Embeddable */
class Address
{
/** @Column(type = "string") */
private $street;
/** @Column(type = "string") */
private $postalCode;
/** @Column(type = "string") */
private $city;
/** @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>
.. code-block:: yaml
User:
type: entity
embedded:
address:
class: Address
Address:
type: embeddable
fields:
street: { type: string }
postalCode: { type: string }
city: { type: string }
country: { type: string }
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
/** @Entity */
class User
{
/** @Embedded(class = "Address", columnPrefix = "myPrefix_") */
private $address;
}
.. code-block:: xml
<entity name="User">
<embedded name="address" class="Address" column-prefix="myPrefix_" />
</entity>
.. code-block:: yaml
User:
type: entity
embedded:
address:
class: Address
columnPrefix: myPrefix_
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
/** @Entity */
class User
{
/** @Embedded(class = "Address", columnPrefix = false) */
private $address;
}
.. code-block:: yaml
User:
type: entity
embedded:
address:
class: Address
columnPrefix: false
.. 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

View File

@@ -15,12 +15,10 @@ the first time its accessed. If you mark an association as extra lazy the follow
can be called without triggering a full load of the collection:
- ``Collection#contains($entity)``
- ``Collection#containsKey($key)`` (available with Doctrine 2.5)
- ``Collection#count()``
- ``Collection#get($key)`` (available with Doctrine 2.4)
- ``Collection#slice($offset, $length = null)``
For each of the above methods the following semantics apply:
For each of this three methods the following semantics apply:
- For each call, if the Collection is not yet loaded, issue a straight SELECT statement against the database.
- For each call, if the collection is already loaded, fallback to the default functionality for lazy collections. No additional SELECT statements are executed.
@@ -65,7 +63,7 @@ switch to extra lazy as shown in these examples:
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\CMS\CmsGroup">
<!-- ... -->

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -58,7 +58,6 @@ which has mapping metadata that is overridden by the annotation above:
.. code-block:: php
<?php
/**
* Trait class
*/
@@ -83,10 +82,9 @@ The case for just extending a class would be just the same but:
.. code-block:: php
<?php
class ExampleEntityWithOverride extends BaseEntityWithSomeMapping
{
// ...
}
Overriding is also supported via XML and YAML (:ref:`examples <inheritence_mapping_overrides>`).
Overriding is also supported via XML and YAML.

View File

@@ -3,7 +3,7 @@ Working with Indexed Associations
.. note::
This feature is available from version 2.1 of Doctrine.
This feature is scheduled for version 2.1 of Doctrine and not included in the 2.0.x series.
Doctrine 2 collections are modelled after PHPs native arrays. PHP arrays are an ordered hashmap, but in
the first version of Doctrine keys retrieved from the database were always numerical unless ``INDEX BY``
@@ -13,8 +13,8 @@ The feature works like an implicit ``INDEX BY`` for the selected association but
downsides also:
- You have to manage both the key and field if you want to change the index by field value.
- On each request the keys are regenerated from the field value, and not from the previous collection key.
- Values of the Index-By keys are never considered during persistence. They only exist for accessing purposes.
- On each request the keys are regenerated from the field value not from the previous collection key.
- Values of the Index-By keys are never considered during persistence, it only exists for accessing purposes.
- Fields that are used for the index by feature **HAVE** to be unique in the database. The behavior for multiple entities
with the same index-by field value is undefined.
@@ -107,7 +107,7 @@ The code and mappings for the Market entity looks like this:
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\StockExchange\Market">
<id name="id" type="integer">
@@ -164,6 +164,8 @@ here are the code and mappings for it:
private $id;
/**
* For real this column would have to be unique=true. But I want to test behavior of non-unique overrides.
*
* @Column(type="string", unique=true)
*/
private $symbol;
@@ -193,7 +195,7 @@ here are the code and mappings for it:
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\StockExchange\Stock">
<id name="id" type="integer">
@@ -225,7 +227,7 @@ here are the code and mappings for it:
Querying indexed associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Now that we defined the stocks collection to be indexed by symbol, we can take a look at some code
Now that we defined the stocks collection to be indexed by symbol we can take a look at some code,
that makes use of the indexing.
First we will populate our database with two example stocks traded on a single market:
@@ -261,7 +263,7 @@ now query for the market:
echo $stock->getSymbol(); // will print "AAPL"
The implementation of ``Market::addStock()``, in combination with ``indexBy``, allows us to access the collection
The implementation ``Market::addStock()`` in combination with ``indexBy`` allows to access the collection
consistently by the Stock symbol. It does not matter if Stock is managed by Doctrine or not.
The same applies to DQL queries: The ``indexBy`` configuration acts as implicit "INDEX BY" to a join association.
@@ -283,8 +285,8 @@ The same applies to DQL queries: The ``indexBy`` configuration acts as implicit
echo $stock->getSymbol(); // will print "AAPL"
If you want to use ``INDEX BY`` explicitly on an indexed association you are free to do so. Additionally,
indexed associations also work with the ``Collection::slice()`` functionality, even if the association's fetch mode is
If you want to use ``INDEX BY`` explicitly on an indexed association you are free to do so. Additionally
indexed associations also work with the ``Collection::slice()`` functionality, no matter if marked as
LAZY or EXTRA_LAZY.
Outlook into the Future
@@ -292,5 +294,5 @@ Outlook into the Future
For the inverse side of a many-to-many associations there will be a way to persist the keys and the order
as a third and fourth parameter into the join table. This feature is discussed in `DDC-213 <http://www.doctrine-project.org/jira/browse/DDC-213>`_
This feature cannot be implemented for one-to-many associations, because they are never the owning side.
This feature cannot be implemented for One-To-Many associations, because they are never the owning side.

View File

@@ -5,9 +5,9 @@
xmlns:orm="http://doctrine-project.org/schemas/orm/doctrine-mapping"
elementFormDefault="qualified">
<xs:annotation>
<xs:documentation><![CDATA[
This is the XML Schema for the object/relational
<xs:annotation>
<xs:documentation><![CDATA[
This is the XML Schema for the object/relational
mapping file used by the Doctrine ORM.
]]></xs:documentation>
</xs:annotation>
@@ -17,33 +17,32 @@
<xs:sequence>
<xs:element name="mapped-superclass" type="orm:mapped-superclass" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="entity" type="orm:entity" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="embeddable" type="orm:embeddable" minOccurs="0" maxOccurs="unbounded" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
</xs:element>
<xs:complexType name="emptyType">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="cascade-type">
<xs:sequence>
<xs:element name="cascade-all" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-persist" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-merge" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-remove" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-refresh" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-all" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-persist" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-merge" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-remove" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-refresh" type="orm:emptyType" minOccurs="0"/>
<xs:element name="cascade-detach" type="orm:emptyType" minOccurs="0"/>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:simpleType name="lifecycle-callback-type">
<xs:restriction base="xs:token">
<xs:enumeration value="prePersist"/>
@@ -56,15 +55,7 @@
<xs:enumeration value="preFlush"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="cache-usage-type">
<xs:restriction base="xs:token">
<xs:enumeration value="READ_ONLY"/>
<xs:enumeration value="READ_WRITE"/>
<xs:enumeration value="NONSTRICT_READ_WRITE"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="lifecycle-callback">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
@@ -73,7 +64,7 @@
<xs:attribute name="method" type="xs:NMTOKEN" use="required" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="lifecycle-callbacks">
<xs:sequence>
<xs:element name="lifecycle-callback" type="orm:lifecycle-callback" minOccurs="1" maxOccurs="unbounded" />
@@ -101,7 +92,7 @@
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="result-class" type="orm:fqcn" />
<xs:attribute name="result-class" type="xs:string" />
<xs:attribute name="result-set-mapping" type="xs:string" />
</xs:complexType>
@@ -117,7 +108,7 @@
<xs:element name="lifecycle-callback" type="orm:lifecycle-callback" minOccurs="0" maxOccurs="unbounded"/>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="class" type="orm:fqcn"/>
<xs:attribute name="class" type="xs:string"/>
</xs:complexType>
<xs:complexType name="entity-listeners">
@@ -139,7 +130,7 @@
<xs:sequence>
<xs:element name="field-result" type="orm:field-result" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="entity-class" type="orm:fqcn" use="required" />
<xs:attribute name="entity-class" type="xs:string" use="required" />
<xs:attribute name="discriminator-column" type="xs:string" use="optional" />
</xs:complexType>
@@ -161,14 +152,8 @@
</xs:sequence>
</xs:complexType>
<xs:complexType name="cache">
<xs:attribute name="usage" type="orm:cache-usage-type" />
<xs:attribute name="region" type="xs:string" />
</xs:complexType>
<xs:complexType name="entity">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="cache" type="orm:cache" minOccurs="0" maxOccurs="1"/>
<xs:sequence>
<xs:element name="options" type="orm:options" minOccurs="0" />
<xs:element name="indexes" type="orm:indexes" minOccurs="0"/>
<xs:element name="unique-constraints" type="orm:unique-constraints" minOccurs="0"/>
@@ -181,7 +166,6 @@
<xs:element name="sql-result-set-mappings" type="orm:sql-result-set-mappings" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="id" type="orm:id" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="field" type="orm:field" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="embedded" type="orm:embedded" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="one-to-one" type="orm:one-to-one" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="one-to-many" type="orm:one-to-many" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="many-to-one" type="orm:many-to-one" minOccurs="0" maxOccurs="unbounded" />
@@ -189,24 +173,17 @@
<xs:element name="association-overrides" type="orm:association-overrides" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="attribute-overrides" type="orm:attribute-overrides" minOccurs="0" maxOccurs="unbounded" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:choice>
</xs:sequence>
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="table" type="orm:tablename" />
<xs:attribute name="table" type="xs:NMTOKEN" />
<xs:attribute name="schema" type="xs:NMTOKEN" />
<xs:attribute name="repository-class" type="orm:fqcn"/>
<xs:attribute name="repository-class" type="xs:string"/>
<xs:attribute name="inheritance-type" type="orm:inheritance-type"/>
<xs:attribute name="change-tracking-policy" type="orm:change-tracking-policy" />
<xs:attribute name="read-only" type="xs:boolean" default="false" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:simpleType name="tablename" id="tablename">
<xs:restriction base="xs:token">
<xs:pattern value="[a-zA-Z_u01-uff.]+" id="tablename.pattern">
</xs:pattern>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="option" mixed="true">
<xs:sequence minOccurs="0" maxOccurs="unbounded">
<xs:element name="option" type="orm:option"/>
@@ -235,16 +212,6 @@
</xs:complexContent>
</xs:complexType>
<xs:complexType name="embeddable">
<xs:complexContent>
<xs:extension base="orm:entity">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:simpleType name="change-tracking-policy">
<xs:restriction base="xs:token">
<xs:enumeration value="DEFERRED_IMPLICIT"/>
@@ -252,7 +219,7 @@
<xs:enumeration value="NOTIFY"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="inheritance-type">
<xs:restriction base="xs:token">
<xs:enumeration value="SINGLE_TABLE"/>
@@ -260,33 +227,33 @@
<xs:enumeration value="TABLE_PER_CLASS"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="generator-strategy">
<xs:restriction base="xs:token">
<xs:simpleType name="generator-strategy">
<xs:restriction base="xs:token">
<xs:enumeration value="NONE"/>
<xs:enumeration value="TABLE"/>
<xs:enumeration value="SEQUENCE"/>
<xs:enumeration value="IDENTITY"/>
<xs:enumeration value="AUTO"/>
<xs:enumeration value="UUID"/>
<xs:enumeration value="TABLE"/>
<xs:enumeration value="SEQUENCE"/>
<xs:enumeration value="IDENTITY"/>
<xs:enumeration value="AUTO"/>
<xs:enumeration value="UUID"/>
<xs:enumeration value="CUSTOM" />
</xs:restriction>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="fk-action">
<xs:restriction base="xs:token">
<xs:enumeration value="CASCADE"/>
<xs:enumeration value="RESTRICT"/>
<xs:simpleType name="fk-action">
<xs:restriction base="xs:token">
<xs:enumeration value="CASCADE"/>
<xs:enumeration value="RESTRICT"/>
<xs:enumeration value="SET NULL"/>
</xs:restriction>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="fetch-type">
<xs:restriction base="xs:token">
<xs:enumeration value="EAGER"/>
<xs:simpleType name="fetch-type">
<xs:restriction base="xs:token">
<xs:enumeration value="EAGER"/>
<xs:enumeration value="LAZY"/>
<xs:enumeration value="EXTRA_LAZY"/>
</xs:restriction>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="field">
@@ -306,14 +273,7 @@
<xs:attribute name="scale" type="xs:integer" use="optional" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="embedded">
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="class" type="orm:fqcn" use="required" />
<xs:attribute name="column-prefix" type="xs:string" use="optional" />
<xs:attribute name="use-column-prefix" type="xs:boolean" default="true" use="optional" />
</xs:complexType>
<xs:complexType name="discriminator-column">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
@@ -325,17 +285,16 @@
<xs:attribute name="column-definition" type="xs:string" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="unique-constraint">
<xs:sequence>
<xs:element name="options" type="orm:options" minOccurs="0" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="optional"/>
<xs:attribute name="columns" type="xs:string" use="required"/>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="unique-constraints">
<xs:sequence>
<xs:element name="unique-constraint" type="orm:unique-constraint" minOccurs="1" maxOccurs="unbounded"/>
@@ -343,18 +302,16 @@
</xs:sequence>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="index">
<xs:sequence>
<xs:element name="options" type="orm:options" minOccurs="0" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="optional"/>
<xs:attribute name="columns" type="xs:string" use="required"/>
<xs:attribute name="flags" type="xs:string" use="optional"/>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="indexes">
<xs:sequence>
<xs:element name="index" type="orm:index" minOccurs="1" maxOccurs="unbounded"/>
@@ -362,16 +319,16 @@
</xs:sequence>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="discriminator-mapping">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="value" type="xs:NMTOKEN" use="required"/>
<xs:attribute name="class" type="orm:fqcn" use="required"/>
<xs:attribute name="class" type="xs:string" use="required"/>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="discriminator-map">
<xs:sequence>
<xs:element name="discriminator-mapping" type="orm:discriminator-mapping" minOccurs="1" maxOccurs="unbounded"/>
@@ -419,16 +376,9 @@
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="class" type="orm:fqcn" use="required" />
<xs:attribute name="class" type="xs:NMTOKEN" use="required" />
</xs:complexType>
<xs:simpleType name="fqcn" id="fqcn">
<xs:restriction base="xs:token">
<xs:pattern value="[a-zA-Z_u01-uff][a-zA-Z0-9_u01-uff]+" id="fqcn.pattern">
</xs:pattern>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="inverse-join-columns">
<xs:sequence>
<xs:element name="join-column" type="orm:join-column" minOccurs="1" maxOccurs="unbounded" />
@@ -446,6 +396,7 @@
<xs:attribute name="unique" type="xs:boolean" default="false" />
<xs:attribute name="nullable" type="xs:boolean" default="true" />
<xs:attribute name="on-delete" type="orm:fk-action" />
<xs:attribute name="on-update" type="orm:fk-action" />
<xs:attribute name="column-definition" type="xs:string" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
@@ -495,17 +446,16 @@
<xs:complexType name="many-to-many">
<xs:sequence>
<xs:element name="cache" type="orm:cache" minOccurs="0" maxOccurs="1"/>
<xs:element name="cascade" type="orm:cascade-type" minOccurs="0" />
<xs:element name="join-table" type="orm:join-table" minOccurs="0" />
<xs:element name="order-by" type="orm:order-by" minOccurs="0" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="target-entity" type="xs:string" use="required" />
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="mapped-by" type="xs:NMTOKEN" />
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
<xs:attribute name="index-by" type="xs:NMTOKEN" />
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
<xs:anyAttribute namespace="##other"/>
@@ -513,23 +463,21 @@
<xs:complexType name="one-to-many">
<xs:sequence>
<xs:element name="cache" type="orm:cache" minOccurs="0" maxOccurs="1"/>
<xs:element name="cascade" type="orm:cascade-type" minOccurs="0" />
<xs:element name="order-by" type="orm:order-by" minOccurs="0" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="target-entity" type="xs:string" use="required" />
<xs:attribute name="mapped-by" type="xs:NMTOKEN" use="required" />
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="index-by" type="xs:NMTOKEN" />
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="many-to-one">
<xs:sequence>
<xs:element name="cache" type="orm:cache" minOccurs="0" maxOccurs="1"/>
<xs:element name="cascade" type="orm:cascade-type" minOccurs="0" />
<xs:choice minOccurs="0" maxOccurs="1">
<xs:element name="join-column" type="orm:join-column"/>
@@ -538,16 +486,16 @@
</xs:choice>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="target-entity" type="xs:string" use="required" />
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="one-to-one">
<xs:sequence>
<xs:element name="cache" type="orm:cache" minOccurs="0" maxOccurs="1"/>
<xs:element name="cascade" type="orm:cascade-type" minOccurs="0" />
<xs:choice minOccurs="0" maxOccurs="1">
<xs:element name="join-column" type="orm:join-column"/>
@@ -560,8 +508,8 @@
<xs:attribute name="target-entity" type="xs:string" use="required" />
<xs:attribute name="mapped-by" type="xs:NMTOKEN" />
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
@@ -576,15 +524,9 @@
<xs:sequence>
<xs:element name="join-table" type="orm:join-table" minOccurs="0" />
<xs:element name="join-columns" type="orm:join-columns" minOccurs="0" />
<xs:element name="inversed-by" type="orm:inversed-by-override" minOccurs="0" maxOccurs="1" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
<xs:attribute name="fetch" type="orm:fetch-type" use="optional" />
</xs:complexType>
<xs:complexType name="inversed-by-override">
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
</xs:complexType>
<xs:complexType name="attribute-overrides">

View File

@@ -19,16 +19,15 @@
namespace Doctrine\ORM;
use Doctrine\Common\Persistence\Mapping\MappingException;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping\MappingException as ORMMappingException;
use Doctrine\ORM\Query\Parameter;
use Doctrine\ORM\Cache\QueryCacheKey;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Cache\QueryCacheProfile;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\ORMInvalidArgumentException;
/**
* Base contract for ORM queries. Base class for Query and NativeQuery.
*
@@ -72,7 +71,7 @@ abstract class AbstractQuery
/**
* The parameter map of this query.
*
* @var ArrayCollection|Parameter[]
* @var \Doctrine\Common\Collections\ArrayCollection
*/
protected $parameters;
@@ -86,7 +85,7 @@ abstract class AbstractQuery
/**
* The entity manager used by this query object.
*
* @var EntityManagerInterface
* @var \Doctrine\ORM\EntityManager
*/
protected $_em;
@@ -95,17 +94,17 @@ abstract class AbstractQuery
*
* @var array
*/
protected $_hints = [];
protected $_hints = array();
/**
* The hydration mode.
*
* @var string|int
* @var integer
*/
protected $_hydrationMode = self::HYDRATE_OBJECT;
/**
* @var \Doctrine\DBAL\Cache\QueryCacheProfile
* @param \Doctrine\DBAL\Cache\QueryCacheProfile
*/
protected $_queryCacheProfile;
@@ -117,157 +116,19 @@ abstract class AbstractQuery
protected $_expireResultCache = false;
/**
* @var \Doctrine\DBAL\Cache\QueryCacheProfile
* @param \Doctrine\DBAL\Cache\QueryCacheProfile
*/
protected $_hydrationCacheProfile;
/**
* Whether to use second level cache, if available.
*
* @var boolean
*/
protected $cacheable = false;
/**
* @var boolean
*/
protected $hasCache = false;
/**
* Second level cache region name.
*
* @var string|null
*/
protected $cacheRegion;
/**
* Second level query cache mode.
*
* @var integer|null
*/
protected $cacheMode;
/**
* @var \Doctrine\ORM\Cache\Logging\CacheLogger|null
*/
protected $cacheLogger;
/**
* @var integer
*/
protected $lifetime = 0;
/**
* Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
*
* @param \Doctrine\ORM\EntityManagerInterface $em
* @param \Doctrine\ORM\EntityManager $em
*/
public function __construct(EntityManagerInterface $em)
public function __construct(EntityManager $em)
{
$this->_em = $em;
$this->parameters = new ArrayCollection();
$this->_hints = $em->getConfiguration()->getDefaultQueryHints();
$this->hasCache = $this->_em->getConfiguration()->isSecondLevelCacheEnabled();
if ($this->hasCache) {
$this->cacheLogger = $em->getConfiguration()
->getSecondLevelCacheConfiguration()
->getCacheLogger();
}
}
/**
* Enable/disable second level query (result) caching for this query.
*
* @param boolean $cacheable
*
* @return static This query instance.
*/
public function setCacheable($cacheable)
{
$this->cacheable = (boolean) $cacheable;
return $this;
}
/**
* @return boolean TRUE if the query results are enable for second level cache, FALSE otherwise.
*/
public function isCacheable()
{
return $this->cacheable;
}
/**
* @param string $cacheRegion
*
* @return static This query instance.
*/
public function setCacheRegion($cacheRegion)
{
$this->cacheRegion = (string) $cacheRegion;
return $this;
}
/**
* Obtain the name of the second level query cache region in which query results will be stored
*
* @return string|null The cache region name; NULL indicates the default region.
*/
public function getCacheRegion()
{
return $this->cacheRegion;
}
/**
* @return boolean TRUE if the query cache and second level cache are enabled, FALSE otherwise.
*/
protected function isCacheEnabled()
{
return $this->cacheable && $this->hasCache;
}
/**
* @return integer
*/
public function getLifetime()
{
return $this->lifetime;
}
/**
* Sets the life-time for this query into second level cache.
*
* @param integer $lifetime
*
* @return \Doctrine\ORM\AbstractQuery This query instance.
*/
public function setLifetime($lifetime)
{
$this->lifetime = (integer) $lifetime;
return $this;
}
/**
* @return integer
*/
public function getCacheMode()
{
return $this->cacheMode;
}
/**
* @param integer $cacheMode
*
* @return \Doctrine\ORM\AbstractQuery This query instance.
*/
public function setCacheMode($cacheMode)
{
$this->cacheMode = (integer) $cacheMode;
return $this;
$this->_em = $em;
$this->parameters = new ArrayCollection();
}
/**
@@ -300,13 +161,13 @@ abstract class AbstractQuery
{
$this->parameters = new ArrayCollection();
$this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints();
$this->_hints = array();
}
/**
* Get all defined parameters.
*
* @return ArrayCollection The defined query parameters.
* @return \Doctrine\Common\Collections\ArrayCollection The defined query parameters.
*/
public function getParameters()
{
@@ -318,27 +179,27 @@ abstract class AbstractQuery
*
* @param mixed $key The key (index or name) of the bound parameter.
*
* @return Query\Parameter|null The value of the bound parameter, or NULL if not available.
* @return mixed The value of the bound parameter.
*/
public function getParameter($key)
{
$filteredParameters = $this->parameters->filter(
function (Query\Parameter $parameter) use ($key) : bool {
$parameterName = $parameter->getName();
return $key === $parameterName || (string) $key === (string) $parameterName;
function ($parameter) use ($key)
{
// Must not be identical because of string to integer conversion
return ($key == $parameter->getName());
}
);
return ! $filteredParameters->isEmpty() ? $filteredParameters->first() : null;
return count($filteredParameters) ? $filteredParameters->first() : null;
}
/**
* Sets a collection of query parameters.
*
* @param ArrayCollection|mixed[] $parameters
* @param \Doctrine\Common\Collections\ArrayCollection|array $parameters
*
* @return static This query instance.
* @return \Doctrine\ORM\AbstractQuery This query instance.
*/
public function setParameters($parameters)
{
@@ -347,7 +208,9 @@ abstract class AbstractQuery
$parameterCollection = new ArrayCollection();
foreach ($parameters as $key => $value) {
$parameterCollection->add(new Parameter($key, $value));
$parameter = new Query\Parameter($key, $value);
$parameterCollection->add($parameter);
}
$parameters = $parameterCollection;
@@ -367,19 +230,28 @@ abstract class AbstractQuery
* the type conversion of this type. This is usually not needed for
* strings and numeric types.
*
* @return static This query instance.
* @return \Doctrine\ORM\AbstractQuery This query instance.
*/
public function setParameter($key, $value, $type = null)
{
$existingParameter = $this->getParameter($key);
$filteredParameters = $this->parameters->filter(
function ($parameter) use ($key)
{
// Must not be identical because of string to integer conversion
return ($key == $parameter->getName());
}
);
if ($existingParameter !== null) {
$existingParameter->setValue($value, $type);
if (count($filteredParameters)) {
$parameter = $filteredParameters->first();
$parameter->setValue($value, $type);
return $this;
}
$this->parameters->add(new Parameter($key, $value, $type));
$parameter = new Query\Parameter($key, $value, $type);
$this->parameters->add($parameter);
return $this;
}
@@ -389,20 +261,12 @@ abstract class AbstractQuery
*
* @param mixed $value
*
* @return array|string
* @return array
*
* @throws \Doctrine\ORM\ORMInvalidArgumentException
* @throws ORMInvalidArgumentException
*/
public function processParameterValue($value)
{
if (is_scalar($value)) {
return $value;
}
if ($value instanceof Collection) {
$value = $value->toArray();
}
if (is_array($value)) {
foreach ($value as $key => $paramValue) {
$paramValue = $this->processParameterValue($paramValue);
@@ -412,24 +276,16 @@ abstract class AbstractQuery
return $value;
}
if ($value instanceof Mapping\ClassMetadata) {
return $value->name;
}
if (! is_object($value)) {
return $value;
}
try {
if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value))) {
$value = $this->_em->getUnitOfWork()->getSingleIdentifierValue($value);
if ($value === null) {
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
}
} catch (MappingException | ORMMappingException $e) {
// Silence any mapping exceptions. These can occur if the object in
// question is not a mapped entity, in which case we just don't do
// any preparation on the value.
}
if ($value instanceof Mapping\ClassMetadata) {
return $value->name;
}
return $value;
@@ -440,7 +296,7 @@ abstract class AbstractQuery
*
* @param \Doctrine\ORM\Query\ResultSetMapping $rsm
*
* @return static This query instance.
* @return \Doctrine\ORM\AbstractQuery
*/
public function setResultSetMapping(Query\ResultSetMapping $rsm)
{
@@ -450,16 +306,6 @@ abstract class AbstractQuery
return $this;
}
/**
* Gets the ResultSetMapping used for hydration.
*
* @return \Doctrine\ORM\Query\ResultSetMapping
*/
protected function getResultSetMapping()
{
return $this->_resultSetMapping;
}
/**
* Allows to translate entity namespaces to full qualified names.
*
@@ -469,8 +315,10 @@ abstract class AbstractQuery
*/
private function translateNamespaces(Query\ResultSetMapping $rsm)
{
$translate = function ($alias) {
return $this->_em->getClassMetadata($alias)->getName();
$entityManager = $this->_em;
$translate = function ($alias) use ($entityManager) {
return $entityManager->getClassMetadata($alias)->getName();
};
$rsm->aliasMap = array_map($translate, $rsm->aliasMap);
@@ -497,11 +345,11 @@ abstract class AbstractQuery
*
* @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
*
* @return static This query instance.
* @return \Doctrine\ORM\AbstractQuery
*/
public function setHydrationCacheProfile(QueryCacheProfile $profile = null)
{
if ($profile !== null && ! $profile->getResultCacheDriver()) {
if ( ! $profile->getResultCacheDriver()) {
$resultCacheDriver = $this->_em->getConfiguration()->getHydrationCacheImpl();
$profile = $profile->setResultCacheDriver($resultCacheDriver);
}
@@ -527,11 +375,11 @@ abstract class AbstractQuery
*
* @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
*
* @return static This query instance.
* @return \Doctrine\ORM\AbstractQuery
*/
public function setResultCacheProfile(QueryCacheProfile $profile = null)
{
if ($profile !== null && ! $profile->getResultCacheDriver()) {
if ( ! $profile->getResultCacheDriver()) {
$resultCacheDriver = $this->_em->getConfiguration()->getResultCacheImpl();
$profile = $profile->setResultCacheDriver($resultCacheDriver);
}
@@ -546,7 +394,7 @@ abstract class AbstractQuery
*
* @param \Doctrine\Common\Cache\Cache|null $resultCacheDriver Cache driver
*
* @return static This query instance.
* @return \Doctrine\ORM\AbstractQuery
*
* @throws ORMException
*/
@@ -583,45 +431,21 @@ abstract class AbstractQuery
* Set whether or not to cache the results of this query and if so, for
* how long and which ID to use for the cache entry.
*
* @deprecated 2.7 Use {@see enableResultCache} and {@see disableResultCache} instead.
* @param boolean $bool
* @param integer $lifetime
* @param string $resultCacheId
*
* @param bool $useCache
* @param int $lifetime
* @param string $resultCacheId
*
* @return static This query instance.
* @return \Doctrine\ORM\AbstractQuery This query instance.
*/
public function useResultCache($useCache, $lifetime = null, $resultCacheId = null)
public function useResultCache($bool, $lifetime = null, $resultCacheId = null)
{
return $useCache
? $this->enableResultCache($lifetime, $resultCacheId)
: $this->disableResultCache();
}
if ($bool) {
$this->setResultCacheLifetime($lifetime);
$this->setResultCacheId($resultCacheId);
/**
* Enables caching of the results of this query, for given or default amount of seconds
* and optionally specifies which ID to use for the cache entry.
*
* @param int|null $lifetime How long the cache entry is valid, in seconds.
* @param string|null $resultCacheId ID to use for the cache entry.
*
* @return static This query instance.
*/
public function enableResultCache(?int $lifetime = null, ?string $resultCacheId = null) : self
{
$this->setResultCacheLifetime($lifetime);
$this->setResultCacheId($resultCacheId);
return $this;
}
return $this;
}
/**
* Disables caching of the results of this query.
*
* @return static This query instance.
*/
public function disableResultCache() : self
{
$this->_queryCacheProfile = null;
return $this;
@@ -632,7 +456,7 @@ abstract class AbstractQuery
*
* @param integer $lifetime How long the cache entry is valid.
*
* @return static This query instance.
* @return \Doctrine\ORM\AbstractQuery This query instance.
*/
public function setResultCacheLifetime($lifetime)
{
@@ -662,7 +486,7 @@ abstract class AbstractQuery
*
* @param boolean $expire Whether or not to force resultset cache expiration.
*
* @return static This query instance.
* @return \Doctrine\ORM\AbstractQuery This query instance.
*/
public function expireResultCache($expire = true)
{
@@ -698,7 +522,7 @@ abstract class AbstractQuery
* @param string $assocName
* @param int $fetchMode
*
* @return static This query instance.
* @return AbstractQuery
*/
public function setFetchMode($class, $assocName, $fetchMode)
{
@@ -714,10 +538,10 @@ abstract class AbstractQuery
/**
* Defines the processing mode to be used during hydration / result set transformation.
*
* @param string|int $hydrationMode Doctrine processing mode to be used during hydration process.
* One of the Query::HYDRATE_* constants.
* @param integer $hydrationMode Doctrine processing mode to be used during hydration process.
* One of the Query::HYDRATE_* constants.
*
* @return static This query instance.
* @return \Doctrine\ORM\AbstractQuery This query instance.
*/
public function setHydrationMode($hydrationMode)
{
@@ -729,7 +553,7 @@ abstract class AbstractQuery
/**
* Gets the hydration mode currently used by the query.
*
* @return string|int
* @return integer
*/
public function getHydrationMode()
{
@@ -741,9 +565,9 @@ abstract class AbstractQuery
*
* Alias for execute(null, $hydrationMode = HYDRATE_OBJECT).
*
* @param string|int $hydrationMode
* @param int $hydrationMode
*
* @return mixed
* @return array
*/
public function getResult($hydrationMode = self::HYDRATE_OBJECT)
{
@@ -777,7 +601,7 @@ abstract class AbstractQuery
/**
* Get exactly one result or null.
*
* @param string|int $hydrationMode
* @param int $hydrationMode
*
* @return mixed
*
@@ -785,12 +609,7 @@ abstract class AbstractQuery
*/
public function getOneOrNullResult($hydrationMode = null)
{
try {
$result = $this->execute(null, $hydrationMode);
} catch (NoResultException $e) {
return null;
}
$result = $this->execute(null, $hydrationMode);
if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
return null;
@@ -815,12 +634,12 @@ abstract class AbstractQuery
* If the result is not unique, a NonUniqueResultException is thrown.
* If there is no result, a NoResultException is thrown.
*
* @param string|int $hydrationMode
* @param integer $hydrationMode
*
* @return mixed
*
* @throws NonUniqueResultException If the query result is not unique.
* @throws NoResultException If the query returned no result and hydration mode is not HYDRATE_SINGLE_SCALAR.
* @throws NoResultException If the query returned no result.
*/
public function getSingleResult($hydrationMode = null)
{
@@ -846,10 +665,9 @@ abstract class AbstractQuery
*
* Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
*
* @return mixed The scalar result.
* @return mixed
*
* @throws NoResultException If the query returned no result.
* @throws NonUniqueResultException If the query result is not unique.
* @throws QueryException If the query result is not unique.
*/
public function getSingleScalarResult()
{
@@ -862,7 +680,7 @@ abstract class AbstractQuery
* @param string $name The name of the hint.
* @param mixed $value The value of the hint.
*
* @return static This query instance.
* @return \Doctrine\ORM\AbstractQuery
*/
public function setHint($name, $value)
{
@@ -886,7 +704,7 @@ abstract class AbstractQuery
/**
* Check if the query has a hint
*
* @param string $name The name of the hint
* @param string $name The name of the hint
*
* @return bool False if the query does not have any hint
*/
@@ -910,7 +728,7 @@ abstract class AbstractQuery
* iterate over the result.
*
* @param ArrayCollection|array|null $parameters The query parameters.
* @param string|int|null $hydrationMode The hydration mode to use.
* @param integer|null $hydrationMode The hydration mode to use.
*
* @return \Doctrine\ORM\Internal\Hydration\IterableResult
*/
@@ -924,38 +742,22 @@ abstract class AbstractQuery
$this->setParameters($parameters);
}
$rsm = $this->getResultSetMapping();
$stmt = $this->_doExecute();
return $this->_em->newHydrator($this->_hydrationMode)->iterate($stmt, $rsm, $this->_hints);
return $this->_em->newHydrator($this->_hydrationMode)->iterate(
$stmt, $this->_resultSetMapping, $this->_hints
);
}
/**
* Executes the query.
*
* @param ArrayCollection|array|null $parameters Query parameters.
* @param string|int|null $hydrationMode Processing mode to be used during the hydration process.
* @param integer|null $hydrationMode Processing mode to be used during the hydration process.
*
* @return mixed
*/
public function execute($parameters = null, $hydrationMode = null)
{
if ($this->cacheable && $this->isCacheEnabled()) {
return $this->executeUsingQueryCache($parameters, $hydrationMode);
}
return $this->executeIgnoreQueryCache($parameters, $hydrationMode);
}
/**
* Execute query ignoring second level cache.
*
* @param ArrayCollection|array|null $parameters
* @param string|int|null $hydrationMode
*
* @return mixed
*/
private function executeIgnoreQueryCache($parameters = null, $hydrationMode = null)
{
if ($hydrationMode !== null) {
$this->setHydrationMode($hydrationMode);
@@ -979,7 +781,7 @@ abstract class AbstractQuery
}
if ( ! $result) {
$result = [];
$result = array();
}
$setCacheEntry = function($data) use ($cache, $result, $cacheKey, $realCacheKey, $queryCacheProfile) {
@@ -997,73 +799,15 @@ abstract class AbstractQuery
return $stmt;
}
$rsm = $this->getResultSetMapping();
$data = $this->_em->newHydrator($this->_hydrationMode)->hydrateAll($stmt, $rsm, $this->_hints);
$data = $this->_em->newHydrator($this->_hydrationMode)->hydrateAll(
$stmt, $this->_resultSetMapping, $this->_hints
);
$setCacheEntry($data);
return $data;
}
/**
* Load from second level cache or executes the query and put into cache.
*
* @param ArrayCollection|array|null $parameters
* @param string|int|null $hydrationMode
*
* @return mixed
*/
private function executeUsingQueryCache($parameters = null, $hydrationMode = null)
{
$rsm = $this->getResultSetMapping();
$queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion);
$queryKey = new QueryCacheKey(
$this->getHash(),
$this->lifetime,
$this->cacheMode ?: Cache::MODE_NORMAL,
$this->getTimestampKey()
);
$result = $queryCache->get($queryKey, $rsm, $this->_hints);
if ($result !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $queryKey);
}
return $result;
}
$result = $this->executeIgnoreQueryCache($parameters, $hydrationMode);
$cached = $queryCache->put($queryKey, $rsm, $result, $this->_hints);
if ($this->cacheLogger) {
$this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $queryKey);
if ($cached) {
$this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $queryKey);
}
}
return $result;
}
/**
* @return \Doctrine\ORM\Cache\TimestampCacheKey|null
*/
private function getTimestampKey()
{
$entityName = reset($this->_resultSetMapping->aliasMap);
if (empty($entityName)) {
return null;
}
$metadata = $this->_em->getClassMetadata($entityName);
return new Cache\TimestampCacheKey($metadata->rootEntityName);
}
/**
* Get the result cache id to use to store the result set cache entry.
* Will return the configured id if it exists otherwise a hash will be
@@ -1073,7 +817,7 @@ abstract class AbstractQuery
*/
protected function getHydrationCacheId()
{
$parameters = [];
$parameters = array();
foreach ($this->getParameters() as $parameter) {
$parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
@@ -1096,7 +840,7 @@ abstract class AbstractQuery
*
* @param string $id
*
* @return static This query instance.
* @return \Doctrine\ORM\AbstractQuery This query instance.
*/
public function setResultCacheId($id)
{
@@ -1135,31 +879,6 @@ abstract class AbstractQuery
{
$this->parameters = new ArrayCollection();
$this->_hints = [];
$this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints();
}
/**
* Generates a string of currently query to use for the cache second level cache.
*
* @return string
*/
protected function getHash()
{
$query = $this->getSQL();
$hints = $this->getHints();
$params = array_map(function(Parameter $parameter) {
// Small optimization
// Does not invoke processParameterValue for scalar values
if (is_scalar($value = $parameter->getValue())) {
return $value;
}
return $this->processParameterValue($value);
}, $this->parameters->getValues());
ksort($hints);
return sha1($query . '-' . serialize($params) . '-' . serialize($hints));
$this->_hints = array();
}
}

View File

@@ -1,178 +0,0 @@
<?php
/*
* 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>.
*/
namespace Doctrine\ORM;
/**
* Provides an API for querying/managing the second level cache regions.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface Cache
{
const DEFAULT_QUERY_REGION_NAME = 'query_cache_region';
const DEFAULT_TIMESTAMP_REGION_NAME = 'timestamp_cache_region';
/**
* May read items from the cache, but will not add items.
*/
const MODE_GET = 1;
/**
* Will never read items from the cache,
* but will add items to the cache as it reads them from the database.
*/
const MODE_PUT = 2;
/**
* May read items from the cache, and add items to the cache.
*/
const MODE_NORMAL = 3;
/**
* The query will never read items from the cache,
* but will refresh items to the cache as it reads them from the database.
*/
const MODE_REFRESH = 4;
/**
* @param string $className The entity class.
*
* @return \Doctrine\ORM\Cache\Region|null
*/
public function getEntityCacheRegion($className);
/**
* @param string $className The entity class.
* @param string $association The field name that represents the association.
*
* @return \Doctrine\ORM\Cache\Region|null
*/
public function getCollectionCacheRegion($className, $association);
/**
* Determine whether the cache contains data for the given entity "instance".
*
* @param string $className The entity class.
* @param mixed $identifier The entity identifier
*
* @return boolean true if the underlying cache contains corresponding data; false otherwise.
*/
public function containsEntity($className, $identifier);
/**
* Evicts the entity data for a particular entity "instance".
*
* @param string $className The entity class.
* @param mixed $identifier The entity identifier.
*
* @return void
*/
public function evictEntity($className, $identifier);
/**
* Evicts all entity data from the given region.
*
* @param string $className The entity metadata.
*
* @return void
*/
public function evictEntityRegion($className);
/**
* Evict data from all entity regions.
*
* @return void
*/
public function evictEntityRegions();
/**
* Determine whether the cache contains data for the given collection.
*
* @param string $className The entity class.
* @param string $association The field name that represents the association.
* @param mixed $ownerIdentifier The identifier of the owning entity.
*
* @return boolean true if the underlying cache contains corresponding data; false otherwise.
*/
public function containsCollection($className, $association, $ownerIdentifier);
/**
* Evicts the cache data for the given identified collection instance.
*
* @param string $className The entity class.
* @param string $association The field name that represents the association.
* @param mixed $ownerIdentifier The identifier of the owning entity.
*
* @return void
*/
public function evictCollection($className, $association, $ownerIdentifier);
/**
* Evicts all entity data from the given region.
*
* @param string $className The entity class.
* @param string $association The field name that represents the association.
*
* @return void
*/
public function evictCollectionRegion($className, $association);
/**
* Evict data from all collection regions.
*
* @return void
*/
public function evictCollectionRegions();
/**
* Determine whether the cache contains data for the given query.
*
* @param string $regionName The cache name given to the query.
*
* @return boolean true if the underlying cache contains corresponding data; false otherwise.
*/
public function containsQuery($regionName);
/**
* Evicts all cached query results under the given name, or default query cache if the region name is NULL.
*
* @param string|null $regionName The cache name associated to the queries being cached.
*/
public function evictQueryRegion($regionName = null);
/**
* Evict data from all query regions.
*
* @return void
*/
public function evictQueryRegions();
/**
* Get query cache by region name or create a new one if none exist.
*
* @param string|null $regionName Query cache region name, or default query cache if the region name is NULL.
*
* @return \Doctrine\ORM\Cache\QueryCache The Query Cache associated with the region name.
*/
public function getQueryCache($regionName = null);
}

View File

@@ -1,68 +0,0 @@
<?php
/*
* 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>.
*/
namespace Doctrine\ORM\Cache;
/**
* Association cache entry
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class AssociationCacheEntry implements CacheEntry
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var array The entity identifier
*/
public $identifier;
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var string The entity class name
*/
public $class;
/**
* @param string $class The entity class.
* @param array $identifier The entity identifier.
*/
public function __construct($class, array $identifier)
{
$this->class = $class;
$this->identifier = $identifier;
}
/**
* Creates a new AssociationCacheEntry
*
* This method allow Doctrine\Common\Cache\PhpFileCache compatibility
*
* @param array $values array containing property values
*
* @return AssociationCacheEntry
*/
public static function __set_state(array $values)
{
return new self($values['class'], $values['identifier']);
}
}

View File

@@ -1,128 +0,0 @@
<?php
/*
* 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>.
*/
namespace Doctrine\ORM\Cache;
use Doctrine\ORM\Cache\Logging\CacheLogger;
/**
* Configuration container for second-level cache.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class CacheConfiguration
{
/**
* @var \Doctrine\ORM\Cache\CacheFactory|null
*/
private $cacheFactory;
/**
* @var \Doctrine\ORM\Cache\RegionsConfiguration|null
*/
private $regionsConfig;
/**
* @var \Doctrine\ORM\Cache\Logging\CacheLogger|null
*/
private $cacheLogger;
/**
* @var \Doctrine\ORM\Cache\QueryCacheValidator|null
*/
private $queryValidator;
/**
* @return \Doctrine\ORM\Cache\CacheFactory|null
*/
public function getCacheFactory()
{
return $this->cacheFactory;
}
/**
* @param \Doctrine\ORM\Cache\CacheFactory $factory
*
* @return void
*/
public function setCacheFactory(CacheFactory $factory)
{
$this->cacheFactory = $factory;
}
/**
* @return \Doctrine\ORM\Cache\Logging\CacheLogger|null
*/
public function getCacheLogger()
{
return $this->cacheLogger;
}
/**
* @param \Doctrine\ORM\Cache\Logging\CacheLogger $logger
*/
public function setCacheLogger(CacheLogger $logger)
{
$this->cacheLogger = $logger;
}
/**
* @return \Doctrine\ORM\Cache\RegionsConfiguration
*/
public function getRegionsConfiguration()
{
if ($this->regionsConfig === null) {
$this->regionsConfig = new RegionsConfiguration();
}
return $this->regionsConfig;
}
/**
* @param \Doctrine\ORM\Cache\RegionsConfiguration $regionsConfig
*/
public function setRegionsConfiguration(RegionsConfiguration $regionsConfig)
{
$this->regionsConfig = $regionsConfig;
}
/**
* @return \Doctrine\ORM\Cache\QueryCacheValidator
*/
public function getQueryValidator()
{
if ($this->queryValidator === null) {
$this->queryValidator = new TimestampQueryCacheValidator(
$this->cacheFactory->getTimestampRegion()
);
}
return $this->queryValidator;
}
/**
* @param \Doctrine\ORM\Cache\QueryCacheValidator $validator
*/
public function setQueryValidator(QueryCacheValidator $validator)
{
$this->queryValidator = $validator;
}
}

View File

@@ -1,36 +0,0 @@
<?php
/*
* 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>.
*/
namespace Doctrine\ORM\Cache;
/**
* Cache entry interface
*
* <b>IMPORTANT NOTE:</b>
*
* Fields of classes that implement CacheEntry are public for performance reason.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface CacheEntry
{
}

View File

@@ -1,73 +0,0 @@
<?php
/*
* 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>.
*/
namespace Doctrine\ORM\Cache;
use Doctrine\ORM\ORMException;
/**
* Exception for cache.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class CacheException extends ORMException
{
/**
* @param string $sourceEntity
* @param string $fieldName
*
* @return \Doctrine\ORM\Cache\CacheException
*/
public static function updateReadOnlyCollection($sourceEntity, $fieldName)
{
return new self(sprintf('Cannot update a readonly collection "%s#%s"', $sourceEntity, $fieldName));
}
/**
* @param string $entityName
*
* @return \Doctrine\ORM\Cache\CacheException
*/
public static function updateReadOnlyEntity($entityName)
{
return new self(sprintf('Cannot update a readonly entity "%s"', $entityName));
}
/**
* @param string $entityName
*
* @return \Doctrine\ORM\Cache\CacheException
*/
public static function nonCacheableEntity($entityName)
{
return new self(sprintf('Entity "%s" not configured as part of the second-level cache.', $entityName));
}
/**
* @param string $entityName
* @param string $field
*
* @return CacheException
*/
public static function nonCacheableEntityAssociation($entityName, $field)
{
return new self(sprintf('Entity association field "%s#%s" not configured as part of the second-level cache.', $entityName, $field));
}
}

View File

@@ -1,112 +0,0 @@
<?php
/*
* 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>.
*/
namespace Doctrine\ORM\Cache;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
/**
* Contract for building second level cache regions components.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface CacheFactory
{
/**
* Build an entity persister for the given entity metadata.
*
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
* @param \Doctrine\ORM\Persisters\Entity\EntityPersister $persister The entity persister that will be cached.
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
*
* @return \Doctrine\ORM\Cache\Persister\Entity\CachedEntityPersister
*/
public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPersister $persister, ClassMetadata $metadata);
/**
* Build a collection persister for the given relation mapping.
*
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
* @param \Doctrine\ORM\Persisters\Collection\CollectionPersister $persister The collection persister that will be cached.
* @param array $mapping The association mapping.
*
* @return \Doctrine\ORM\Cache\Persister\Collection\CachedCollectionPersister
*/
public function buildCachedCollectionPersister(EntityManagerInterface $em, CollectionPersister $persister, array $mapping);
/**
* Build a query cache based on the given region name
*
* @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager.
* @param string $regionName The region name.
*
* @return \Doctrine\ORM\Cache\QueryCache The built query cache.
*/
public function buildQueryCache(EntityManagerInterface $em, $regionName = null);
/**
* Build an entity hydrator
*
* @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager.
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
*
* @return \Doctrine\ORM\Cache\EntityHydrator The built entity hydrator.
*/
public function buildEntityHydrator(EntityManagerInterface $em, ClassMetadata $metadata);
/**
* Build a collection hydrator
*
* @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager.
* @param array $mapping The association mapping.
*
* @return \Doctrine\ORM\Cache\CollectionHydrator The built collection hydrator.
*/
public function buildCollectionHydrator(EntityManagerInterface $em, array $mapping);
/**
* Build a cache region
*
* @param array $cache The cache configuration.
*
* @return \Doctrine\ORM\Cache\Region The cache region.
*/
public function getRegion(array $cache);
/**
* Build timestamp cache region
*
* @return \Doctrine\ORM\Cache\TimestampRegion The timestamp region.
*/
public function getTimestampRegion();
/**
* Build \Doctrine\ORM\Cache
*
* @param EntityManagerInterface $entityManager
*
* @return \Doctrine\ORM\Cache
*/
public function createCache(EntityManagerInterface $entityManager);
}

View File

@@ -1,38 +0,0 @@
<?php
/*
* 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>.
*/
namespace Doctrine\ORM\Cache;
/**
* Defines entity / collection / query key to be stored in the cache region.
* Allows multiple roles to be stored in the same cache region.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
abstract class CacheKey
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var string Unique identifier
*/
public $hash;
}

View File

@@ -1,59 +0,0 @@
<?php
/*
* 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>.
*/
namespace Doctrine\ORM\Cache;
/**
* Collection cache entry
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class CollectionCacheEntry implements CacheEntry
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var CacheKey[] The list of entity identifiers hold by the collection
*/
public $identifiers;
/**
* @param CacheKey[] $identifiers List of entity identifiers hold by the collection
*/
public function __construct(array $identifiers)
{
$this->identifiers = $identifiers;
}
/**
* Creates a new CollectionCacheEntry
*
* This method allows for Doctrine\Common\Cache\PhpFileCache compatibility
*
* @param array $values array containing property values
*
* @return CollectionCacheEntry
*/
public static function __set_state(array $values)
{
return new self($values['identifiers']);
}
}

View File

@@ -1,66 +0,0 @@
<?php
/*
* 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>.
*/
namespace Doctrine\ORM\Cache;
/**
* Defines entity collection roles to be stored in the cache region.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class CollectionCacheKey extends CacheKey
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var array The owner entity identifier
*/
public $ownerIdentifier;
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var string The owner entity class
*/
public $entityClass;
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var string The association name
*/
public $association;
/**
* @param string $entityClass The entity class.
* @param string $association The field name that represents the association.
* @param array $ownerIdentifier The identifier of the owning entity.
*/
public function __construct($entityClass, $association, array $ownerIdentifier)
{
ksort($ownerIdentifier);
$this->ownerIdentifier = $ownerIdentifier;
$this->entityClass = (string) $entityClass;
$this->association = (string) $association;
$this->hash = str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association;
}
}

View File

@@ -1,52 +0,0 @@
<?php
/*
* 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>.
*/
namespace Doctrine\ORM\Cache;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Mapping\ClassMetadata;
/**
* Hydrator cache entry for collections
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface CollectionHydrator
{
/**
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cached collection key.
* @param array|\Doctrine\Common\Collections\Collection $collection The collection.
*
* @return \Doctrine\ORM\Cache\CollectionCacheEntry
*/
public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, $collection);
/**
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The owning entity metadata.
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cached collection key.
* @param \Doctrine\ORM\Cache\CollectionCacheEntry $entry The cached collection entry.
* @param \Doctrine\ORM\PersistentCollection $collection The collection to load the cache into.
*
* @return array
*/
public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, CollectionCacheEntry $entry, PersistentCollection $collection);
}

View File

@@ -1,57 +0,0 @@
<?php
/*
* 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>.
*/
namespace Doctrine\ORM\Cache;
/**
* Defines contract for concurrently managed data region.
* It should be able to lock an specific cache entry in an atomic operation.
*
* When a entry is locked another process should not be able to read or write the entry.
* All evict operation should not consider locks, even though an entry is locked evict should be able to delete the entry and its lock.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface ConcurrentRegion extends Region
{
/**
* Attempts to read lock the mapping for the given key.
*
* @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to lock.
*
* @return \Doctrine\ORM\Cache\Lock A lock instance or NULL if the lock already exists.
*
* @throws \Doctrine\ORM\Cache\LockException Indicates a problem accessing the region.
*/
public function lock(CacheKey $key);
/**
* Attempts to read unlock the mapping for the given key.
*
* @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to unlock.
* @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained from {@link readLock}
*
* @return void
*
* @throws \Doctrine\ORM\Cache\LockException Indicates a problem accessing the region.
*/
public function unlock(CacheKey $key, Lock $lock);
}

View File

@@ -1,342 +0,0 @@
<?php
/*
* 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>.
*/
namespace Doctrine\ORM\Cache;
use Doctrine\ORM\Cache;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Cache\Persister\CachedPersister;
use Doctrine\ORM\ORMInvalidArgumentException;
/**
* Provides an API for querying/managing the second level cache regions.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class DefaultCache implements Cache
{
/**
* @var \Doctrine\ORM\EntityManagerInterface
*/
private $em;
/**
* @var \Doctrine\ORM\UnitOfWork
*/
private $uow;
/**
* @var \Doctrine\ORM\Cache\CacheFactory
*/
private $cacheFactory;
/**
* @var \Doctrine\ORM\Cache\QueryCache[]
*/
private $queryCaches = [];
/**
* @var \Doctrine\ORM\Cache\QueryCache
*/
private $defaultQueryCache;
/**
* {@inheritdoc}
*/
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
$this->uow = $em->getUnitOfWork();
$this->cacheFactory = $em->getConfiguration()
->getSecondLevelCacheConfiguration()
->getCacheFactory();
}
/**
* {@inheritdoc}
*/
public function getEntityCacheRegion($className)
{
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getEntityPersister($metadata->rootEntityName);
if ( ! ($persister instanceof CachedPersister)) {
return null;
}
return $persister->getCacheRegion();
}
/**
* {@inheritdoc}
*/
public function getCollectionCacheRegion($className, $association)
{
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association));
if ( ! ($persister instanceof CachedPersister)) {
return null;
}
return $persister->getCacheRegion();
}
/**
* {@inheritdoc}
*/
public function containsEntity($className, $identifier)
{
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getEntityPersister($metadata->rootEntityName);
if ( ! ($persister instanceof CachedPersister)) {
return false;
}
return $persister->getCacheRegion()->contains($this->buildEntityCacheKey($metadata, $identifier));
}
/**
* {@inheritdoc}
*/
public function evictEntity($className, $identifier)
{
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getEntityPersister($metadata->rootEntityName);
if ( ! ($persister instanceof CachedPersister)) {
return;
}
$persister->getCacheRegion()->evict($this->buildEntityCacheKey($metadata, $identifier));
}
/**
* {@inheritdoc}
*/
public function evictEntityRegion($className)
{
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getEntityPersister($metadata->rootEntityName);
if ( ! ($persister instanceof CachedPersister)) {
return;
}
$persister->getCacheRegion()->evictAll();
}
/**
* {@inheritdoc}
*/
public function evictEntityRegions()
{
$metadatas = $this->em->getMetadataFactory()->getAllMetadata();
foreach ($metadatas as $metadata) {
$persister = $this->uow->getEntityPersister($metadata->rootEntityName);
if ( ! ($persister instanceof CachedPersister)) {
continue;
}
$persister->getCacheRegion()->evictAll();
}
}
/**
* {@inheritdoc}
*/
public function containsCollection($className, $association, $ownerIdentifier)
{
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association));
if ( ! ($persister instanceof CachedPersister)) {
return false;
}
return $persister->getCacheRegion()->contains($this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier));
}
/**
* {@inheritdoc}
*/
public function evictCollection($className, $association, $ownerIdentifier)
{
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association));
if ( ! ($persister instanceof CachedPersister)) {
return;
}
$persister->getCacheRegion()->evict($this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier));
}
/**
* {@inheritdoc}
*/
public function evictCollectionRegion($className, $association)
{
$metadata = $this->em->getClassMetadata($className);
$persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association));
if ( ! ($persister instanceof CachedPersister)) {
return;
}
$persister->getCacheRegion()->evictAll();
}
/**
* {@inheritdoc}
*/
public function evictCollectionRegions()
{
$metadatas = $this->em->getMetadataFactory()->getAllMetadata();
foreach ($metadatas as $metadata) {
foreach ($metadata->associationMappings as $association) {
if ( ! $association['type'] & ClassMetadata::TO_MANY) {
continue;
}
$persister = $this->uow->getCollectionPersister($association);
if ( ! ($persister instanceof CachedPersister)) {
continue;
}
$persister->getCacheRegion()->evictAll();
}
}
}
/**
* {@inheritdoc}
*/
public function containsQuery($regionName)
{
return isset($this->queryCaches[$regionName]);
}
/**
* {@inheritdoc}
*/
public function evictQueryRegion($regionName = null)
{
if ($regionName === null && $this->defaultQueryCache !== null) {
$this->defaultQueryCache->clear();
return;
}
if (isset($this->queryCaches[$regionName])) {
$this->queryCaches[$regionName]->clear();
}
}
/**
* {@inheritdoc}
*/
public function evictQueryRegions()
{
$this->getQueryCache()->clear();
foreach ($this->queryCaches as $queryCache) {
$queryCache->clear();
}
}
/**
* {@inheritdoc}
*/
public function getQueryCache($regionName = null)
{
if ($regionName === null) {
return $this->defaultQueryCache ?:
$this->defaultQueryCache = $this->cacheFactory->buildQueryCache($this->em);
}
if ( ! isset($this->queryCaches[$regionName])) {
$this->queryCaches[$regionName] = $this->cacheFactory->buildQueryCache($this->em, $regionName);
}
return $this->queryCaches[$regionName];
}
/**
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param mixed $identifier The entity identifier.
*
* @return \Doctrine\ORM\Cache\EntityCacheKey
*/
private function buildEntityCacheKey(ClassMetadata $metadata, $identifier)
{
if ( ! is_array($identifier)) {
$identifier = $this->toIdentifierArray($metadata, $identifier);
}
return new EntityCacheKey($metadata->rootEntityName, $identifier);
}
/**
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param string $association The field name that represents the association.
* @param mixed $ownerIdentifier The identifier of the owning entity.
*
* @return \Doctrine\ORM\Cache\CollectionCacheKey
*/
private function buildCollectionCacheKey(ClassMetadata $metadata, $association, $ownerIdentifier)
{
if ( ! is_array($ownerIdentifier)) {
$ownerIdentifier = $this->toIdentifierArray($metadata, $ownerIdentifier);
}
return new CollectionCacheKey($metadata->rootEntityName, $association, $ownerIdentifier);
}
/**
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param mixed $identifier The entity identifier.
*
* @return array
*/
private function toIdentifierArray(ClassMetadata $metadata, $identifier)
{
if (is_object($identifier) && $this->em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($identifier))) {
$identifier = $this->uow->getSingleIdentifierValue($identifier);
if ($identifier === null) {
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
}
}
return [$metadata->identifier[0] => $identifier];
}
}

View File

@@ -1,275 +0,0 @@
<?php
/*
* 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>.
*/
namespace Doctrine\ORM\Cache;
use Doctrine\Common\Cache\Cache as CacheAdapter;
use Doctrine\Common\Cache\CacheProvider;
use Doctrine\Common\Cache\MultiGetCache;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Cache\Persister\Collection\NonStrictReadWriteCachedCollectionPersister;
use Doctrine\ORM\Cache\Persister\Collection\ReadOnlyCachedCollectionPersister;
use Doctrine\ORM\Cache\Persister\Collection\ReadWriteCachedCollectionPersister;
use Doctrine\ORM\Cache\Persister\Entity\NonStrictReadWriteCachedEntityPersister;
use Doctrine\ORM\Cache\Persister\Entity\ReadOnlyCachedEntityPersister;
use Doctrine\ORM\Cache\Persister\Entity\ReadWriteCachedEntityPersister;
use Doctrine\ORM\Cache\Region\DefaultMultiGetRegion;
use Doctrine\ORM\Cache\Region\DefaultRegion;
use Doctrine\ORM\Cache\Region\FileLockRegion;
use Doctrine\ORM\Cache\Region\UpdateTimestampCache;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
/**
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class DefaultCacheFactory implements CacheFactory
{
/**
* @var CacheAdapter
*/
private $cache;
/**
* @var \Doctrine\ORM\Cache\RegionsConfiguration
*/
private $regionsConfig;
/**
* @var \Doctrine\ORM\Cache\TimestampRegion|null
*/
private $timestampRegion;
/**
* @var \Doctrine\ORM\Cache\Region[]
*/
private $regions = [];
/**
* @var string|null
*/
private $fileLockRegionDirectory;
/**
* @param RegionsConfiguration $cacheConfig
* @param CacheAdapter $cache
*/
public function __construct(RegionsConfiguration $cacheConfig, CacheAdapter $cache)
{
$this->cache = $cache;
$this->regionsConfig = $cacheConfig;
}
/**
* @param string $fileLockRegionDirectory
*/
public function setFileLockRegionDirectory($fileLockRegionDirectory)
{
$this->fileLockRegionDirectory = (string) $fileLockRegionDirectory;
}
/**
* @return string
*/
public function getFileLockRegionDirectory()
{
return $this->fileLockRegionDirectory;
}
/**
* @param \Doctrine\ORM\Cache\Region $region
*/
public function setRegion(Region $region)
{
$this->regions[$region->getName()] = $region;
}
/**
* @param \Doctrine\ORM\Cache\TimestampRegion $region
*/
public function setTimestampRegion(TimestampRegion $region)
{
$this->timestampRegion = $region;
}
/**
* {@inheritdoc}
*/
public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPersister $persister, ClassMetadata $metadata)
{
$region = $this->getRegion($metadata->cache);
$usage = $metadata->cache['usage'];
if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) {
return new ReadOnlyCachedEntityPersister($persister, $region, $em, $metadata);
}
if ($usage === ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE) {
return new NonStrictReadWriteCachedEntityPersister($persister, $region, $em, $metadata);
}
if ($usage === ClassMetadata::CACHE_USAGE_READ_WRITE) {
return new ReadWriteCachedEntityPersister($persister, $region, $em, $metadata);
}
throw new \InvalidArgumentException(sprintf("Unrecognized access strategy type [%s]", $usage));
}
/**
* {@inheritdoc}
*/
public function buildCachedCollectionPersister(EntityManagerInterface $em, CollectionPersister $persister, array $mapping)
{
$usage = $mapping['cache']['usage'];
$region = $this->getRegion($mapping['cache']);
if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) {
return new ReadOnlyCachedCollectionPersister($persister, $region, $em, $mapping);
}
if ($usage === ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE) {
return new NonStrictReadWriteCachedCollectionPersister($persister, $region, $em, $mapping);
}
if ($usage === ClassMetadata::CACHE_USAGE_READ_WRITE) {
return new ReadWriteCachedCollectionPersister($persister, $region, $em, $mapping);
}
throw new \InvalidArgumentException(sprintf("Unrecognized access strategy type [%s]", $usage));
}
/**
* {@inheritdoc}
*/
public function buildQueryCache(EntityManagerInterface $em, $regionName = null)
{
return new DefaultQueryCache(
$em,
$this->getRegion(
[
'region' => $regionName ?: Cache::DEFAULT_QUERY_REGION_NAME,
'usage' => ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE
]
)
);
}
/**
* {@inheritdoc}
*/
public function buildCollectionHydrator(EntityManagerInterface $em, array $mapping)
{
return new DefaultCollectionHydrator($em);
}
/**
* {@inheritdoc}
*/
public function buildEntityHydrator(EntityManagerInterface $em, ClassMetadata $metadata)
{
return new DefaultEntityHydrator($em);
}
/**
* {@inheritdoc}
*/
public function getRegion(array $cache)
{
if (isset($this->regions[$cache['region']])) {
return $this->regions[$cache['region']];
}
$name = $cache['region'];
$cacheAdapter = $this->createRegionCache($name);
$lifetime = $this->regionsConfig->getLifetime($cache['region']);
$region = ($cacheAdapter instanceof MultiGetCache)
? new DefaultMultiGetRegion($name, $cacheAdapter, $lifetime)
: new DefaultRegion($name, $cacheAdapter, $lifetime);
if ($cache['usage'] === ClassMetadata::CACHE_USAGE_READ_WRITE) {
if (
'' === $this->fileLockRegionDirectory ||
null === $this->fileLockRegionDirectory
) {
throw new \LogicException(
'If you want to use a "READ_WRITE" cache an implementation of "Doctrine\ORM\Cache\ConcurrentRegion" is required, ' .
'The default implementation provided by doctrine is "Doctrine\ORM\Cache\Region\FileLockRegion" if you want to use it please provide a valid directory, DefaultCacheFactory#setFileLockRegionDirectory(). '
);
}
$directory = $this->fileLockRegionDirectory . DIRECTORY_SEPARATOR . $cache['region'];
$region = new FileLockRegion($region, $directory, $this->regionsConfig->getLockLifetime($cache['region']));
}
return $this->regions[$cache['region']] = $region;
}
/**
* @param string $name
*
* @return CacheAdapter
*/
private function createRegionCache($name)
{
$cacheAdapter = clone $this->cache;
if (!$cacheAdapter instanceof CacheProvider) {
return $cacheAdapter;
}
$namespace = $cacheAdapter->getNamespace();
if ('' !== $namespace) {
$namespace .= ':';
}
$cacheAdapter->setNamespace($namespace . $name);
return $cacheAdapter;
}
/**
* {@inheritdoc}
*/
public function getTimestampRegion()
{
if ($this->timestampRegion === null) {
$name = Cache::DEFAULT_TIMESTAMP_REGION_NAME;
$lifetime = $this->regionsConfig->getLifetime($name);
$this->timestampRegion = new UpdateTimestampCache($name, clone $this->cache, $lifetime);
}
return $this->timestampRegion;
}
/**
* {@inheritdoc}
*/
public function createCache(EntityManagerInterface $em)
{
return new DefaultCache($em);
}
}

View File

@@ -1,104 +0,0 @@
<?php
/*
* 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>.
*/
namespace Doctrine\ORM\Cache;
use Doctrine\ORM\Query;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\EntityManagerInterface;
/**
* Default hydrator cache for collections
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class DefaultCollectionHydrator implements CollectionHydrator
{
/**
* @var \Doctrine\ORM\EntityManagerInterface
*/
private $em;
/**
* @var \Doctrine\ORM\UnitOfWork
*/
private $uow;
/**
* @var array
*/
private static $hints = [Query::HINT_CACHE_ENABLED => true];
/**
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
*/
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
$this->uow = $em->getUnitOfWork();
}
/**
* {@inheritdoc}
*/
public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, $collection)
{
$data = [];
foreach ($collection as $index => $entity) {
$data[$index] = new EntityCacheKey($metadata->rootEntityName, $this->uow->getEntityIdentifier($entity));
}
return new CollectionCacheEntry($data);
}
/**
* {@inheritdoc}
*/
public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, CollectionCacheEntry $entry, PersistentCollection $collection)
{
$assoc = $metadata->associationMappings[$key->association];
/* @var $targetPersister \Doctrine\ORM\Cache\Persister\CachedPersister */
$targetPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
$targetRegion = $targetPersister->getCacheRegion();
$list = [];
$entityEntries = $targetRegion->getMultiple($entry);
if ($entityEntries === null) {
return null;
}
/* @var $entityEntries \Doctrine\ORM\Cache\EntityCacheEntry[] */
foreach ($entityEntries as $index => $entityEntry) {
$list[$index] = $this->uow->createEntity($entityEntry->class, $entityEntry->resolveAssociationEntries($this->em), self::$hints);
}
array_walk($list, function($entity, $index) use ($collection) {
$collection->hydrateSet($index, $entity);
});
$this->uow->hydrationComplete();
return $list;
}
}

View File

@@ -1,205 +0,0 @@
<?php
/*
* 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>.
*/
namespace Doctrine\ORM\Cache;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\ORM\Query;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Utility\IdentifierFlattener;
/**
* Default hydrator cache for entities
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class DefaultEntityHydrator implements EntityHydrator
{
/**
* @var \Doctrine\ORM\EntityManagerInterface
*/
private $em;
/**
* @var \Doctrine\ORM\UnitOfWork
*/
private $uow;
/**
* The IdentifierFlattener used for manipulating identifiers
*
* @var \Doctrine\ORM\Utility\IdentifierFlattener
*/
private $identifierFlattener;
/**
* @var array
*/
private static $hints = [Query::HINT_CACHE_ENABLED => true];
/**
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
*/
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
$this->uow = $em->getUnitOfWork();
$this->identifierFlattener = new IdentifierFlattener($em->getUnitOfWork(), $em->getMetadataFactory());
}
/**
* {@inheritdoc}
*/
public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $entity)
{
$data = $this->uow->getOriginalEntityData($entity);
$data = array_merge($data, $metadata->getIdentifierValues($entity)); // why update has no identifier values ?
if ($metadata->isVersioned) {
$data[$metadata->versionField] = $metadata->getFieldValue($entity, $metadata->versionField);
}
foreach ($metadata->associationMappings as $name => $assoc) {
if ( ! isset($data[$name])) {
continue;
}
if ( ! ($assoc['type'] & ClassMetadata::TO_ONE)) {
unset($data[$name]);
continue;
}
if ( ! isset($assoc['cache'])) {
$targetClassMetadata = $this->em->getClassMetadata($assoc['targetEntity']);
$owningAssociation = ( ! $assoc['isOwningSide'])
? $targetClassMetadata->associationMappings[$assoc['mappedBy']]
: $assoc;
$associationIds = $this->identifierFlattener->flattenIdentifier(
$targetClassMetadata,
$targetClassMetadata->getIdentifierValues($data[$name])
);
unset($data[$name]);
foreach ($associationIds as $fieldName => $fieldValue) {
if (isset($targetClassMetadata->fieldMappings[$fieldName])) {
$fieldMapping = $targetClassMetadata->fieldMappings[$fieldName];
$data[$owningAssociation['targetToSourceKeyColumns'][$fieldMapping['columnName']]] = $fieldValue;
continue;
}
$targetAssoc = $targetClassMetadata->associationMappings[$fieldName];
foreach($assoc['targetToSourceKeyColumns'] as $referencedColumn => $localColumn) {
if (isset($targetAssoc['sourceToTargetKeyColumns'][$referencedColumn])) {
$data[$localColumn] = $fieldValue;
}
}
}
continue;
}
if ( ! isset($assoc['id'])) {
$targetClass = ClassUtils::getClass($data[$name]);
$targetId = $this->uow->getEntityIdentifier($data[$name]);
$data[$name] = new AssociationCacheEntry($targetClass, $targetId);
continue;
}
// handle association identifier
$targetId = is_object($data[$name]) && $this->uow->isInIdentityMap($data[$name])
? $this->uow->getEntityIdentifier($data[$name])
: $data[$name];
// @TODO - fix it !
// handle UnitOfWork#createEntity hash generation
if ( ! is_array($targetId)) {
$data[reset($assoc['joinColumnFieldNames'])] = $targetId;
$targetEntity = $this->em->getClassMetadata($assoc['targetEntity']);
$targetId = [$targetEntity->identifier[0] => $targetId];
}
$data[$name] = new AssociationCacheEntry($assoc['targetEntity'], $targetId);
}
return new EntityCacheEntry($metadata->name, $data);
}
/**
* {@inheritdoc}
*/
public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, EntityCacheEntry $entry, $entity = null)
{
$data = $entry->data;
$hints = self::$hints;
if ($entity !== null) {
$hints[Query::HINT_REFRESH] = true;
$hints[Query::HINT_REFRESH_ENTITY] = $entity;
}
foreach ($metadata->associationMappings as $name => $assoc) {
if ( ! isset($assoc['cache']) || ! isset($data[$name])) {
continue;
}
$assocClass = $data[$name]->class;
$assocId = $data[$name]->identifier;
$isEagerLoad = ($assoc['fetch'] === ClassMetadata::FETCH_EAGER || ($assoc['type'] === ClassMetadata::ONE_TO_ONE && ! $assoc['isOwningSide']));
if ( ! $isEagerLoad) {
$data[$name] = $this->em->getReference($assocClass, $assocId);
continue;
}
$assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']);
$assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocId);
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
$assocRegion = $assocPersister->getCacheRegion();
$assocEntry = $assocRegion->get($assocKey);
if ($assocEntry === null) {
return null;
}
$data[$name] = $this->uow->createEntity($assocEntry->class, $assocEntry->resolveAssociationEntries($this->em), $hints);
}
if ($entity !== null) {
$this->uow->registerManaged($entity, $key->identifier, $data);
}
$result = $this->uow->createEntity($entry->class, $data, $hints);
$this->uow->hydrationComplete();
return $result;
}
}

View File

@@ -1,478 +0,0 @@
<?php
/*
* 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>.
*/
namespace Doctrine\ORM\Cache;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Cache\Persister\CachedPersister;
use Doctrine\ORM\Cache\Persister\Entity\CachedEntityPersister;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
use Doctrine\Common\Proxy\Proxy;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Query;
use function assert;
/**
* Default query cache implementation.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class DefaultQueryCache implements QueryCache
{
/**
* @var \Doctrine\ORM\EntityManagerInterface
*/
private $em;
/**
* @var \Doctrine\ORM\UnitOfWork
*/
private $uow;
/**
* @var \Doctrine\ORM\Cache\Region
*/
private $region;
/**
* @var \Doctrine\ORM\Cache\QueryCacheValidator
*/
private $validator;
/**
* @var \Doctrine\ORM\Cache\Logging\CacheLogger
*/
protected $cacheLogger;
/**
* @var array
*/
private static $hints = [Query::HINT_CACHE_ENABLED => true];
/**
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
* @param \Doctrine\ORM\Cache\Region $region The query region.
*/
public function __construct(EntityManagerInterface $em, Region $region)
{
$cacheConfig = $em->getConfiguration()->getSecondLevelCacheConfiguration();
$this->em = $em;
$this->region = $region;
$this->uow = $em->getUnitOfWork();
$this->cacheLogger = $cacheConfig->getCacheLogger();
$this->validator = $cacheConfig->getQueryValidator();
}
/**
* {@inheritdoc}
*/
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = [])
{
if ( ! ($key->cacheMode & Cache::MODE_GET)) {
return null;
}
$cacheEntry = $this->region->get($key);
if ( ! $cacheEntry instanceof QueryCacheEntry) {
return null;
}
if ( ! $this->validator->isValid($key, $cacheEntry)) {
$this->region->evict($key);
return null;
}
$result = [];
$entityName = reset($rsm->aliasMap);
$hasRelation = ! empty($rsm->relationMap);
$persister = $this->uow->getEntityPersister($entityName);
assert($persister instanceof CachedEntityPersister);
$region = $persister->getCacheRegion();
$regionName = $region->getName();
$cm = $this->em->getClassMetadata($entityName);
$generateKeys = static function (array $entry) use ($cm) : EntityCacheKey {
return new EntityCacheKey($cm->rootEntityName, $entry['identifier']);
};
$cacheKeys = new CollectionCacheEntry(array_map($generateKeys, $cacheEntry->result));
$entries = $region->getMultiple($cacheKeys) ?? [];
// @TODO - move to cache hydration component
foreach ($cacheEntry->result as $index => $entry) {
$entityEntry = $entries[$index] ?? null;
if (! $entityEntry instanceof EntityCacheEntry) {
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheMiss($regionName, $cacheKeys->identifiers[$index]);
}
return null;
}
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheHit($regionName, $cacheKeys->identifiers[$index]);
}
if ( ! $hasRelation) {
$result[$index] = $this->uow->createEntity($entityEntry->class, $entityEntry->resolveAssociationEntries($this->em), self::$hints);
continue;
}
$data = $entityEntry->data;
foreach ($entry['associations'] as $name => $assoc) {
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
assert($assocPersister instanceof CachedEntityPersister);
$assocRegion = $assocPersister->getCacheRegion();
$assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']);
if ($assoc['type'] & ClassMetadata::TO_ONE) {
if (($assocEntry = $assocRegion->get($assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assoc['identifier']))) === null) {
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey);
}
$this->uow->hydrationComplete();
return null;
}
$data[$name] = $this->uow->createEntity($assocEntry->class, $assocEntry->resolveAssociationEntries($this->em), self::$hints);
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKey);
}
continue;
}
if ( ! isset($assoc['list']) || empty($assoc['list'])) {
continue;
}
$generateKeys = function ($id) use ($assocMetadata): EntityCacheKey {
return new EntityCacheKey($assocMetadata->rootEntityName, $id);
};
$collection = new PersistentCollection($this->em, $assocMetadata, new ArrayCollection());
$assocKeys = new CollectionCacheEntry(array_map($generateKeys, $assoc['list']));
$assocEntries = $assocRegion->getMultiple($assocKeys);
foreach ($assoc['list'] as $assocIndex => $assocId) {
$assocEntry = is_array($assocEntries) && array_key_exists($assocIndex, $assocEntries) ? $assocEntries[$assocIndex] : null;
if ($assocEntry === null) {
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKeys->identifiers[$assocIndex]);
}
$this->uow->hydrationComplete();
return null;
}
$element = $this->uow->createEntity($assocEntry->class, $assocEntry->resolveAssociationEntries($this->em), self::$hints);
$collection->hydrateSet($assocIndex, $element);
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKeys->identifiers[$assocIndex]);
}
}
$data[$name] = $collection;
$collection->setInitialized(true);
}
foreach ($data as $fieldName => $unCachedAssociationData) {
// In some scenarios, such as EAGER+ASSOCIATION+ID+CACHE, the
// cache key information in `$cacheEntry` will not contain details
// for fields that are associations.
//
// This means that `$data` keys for some associations that may
// actually not be cached will not be converted to actual association
// data, yet they contain L2 cache AssociationCacheEntry objects.
//
// We need to unwrap those associations into proxy references,
// since we don't have actual data for them except for identifiers.
if ($unCachedAssociationData instanceof AssociationCacheEntry) {
$data[$fieldName] = $this->em->getReference(
$unCachedAssociationData->class,
$unCachedAssociationData->identifier
);
}
}
$result[$index] = $this->uow->createEntity($entityEntry->class, $data, self::$hints);
}
$this->uow->hydrationComplete();
return $result;
}
/**
* {@inheritdoc}
*/
public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = [])
{
if ($rsm->scalarMappings) {
throw new CacheException("Second level cache does not support scalar results.");
}
if (count($rsm->entityMappings) > 1) {
throw new CacheException("Second level cache does not support multiple root entities.");
}
if ( ! $rsm->isSelect) {
throw new CacheException("Second-level cache query supports only select statements.");
}
if (isset($hints[Query::HINT_FORCE_PARTIAL_LOAD]) && $hints[Query::HINT_FORCE_PARTIAL_LOAD]) {
throw new CacheException("Second level cache does not support partial entities.");
}
if ( ! ($key->cacheMode & Cache::MODE_PUT)) {
return false;
}
$data = [];
$entityName = reset($rsm->aliasMap);
$rootAlias = key($rsm->aliasMap);
$persister = $this->uow->getEntityPersister($entityName);
if (! $persister instanceof CachedEntityPersister) {
throw CacheException::nonCacheableEntity($entityName);
}
$region = $persister->getCacheRegion();
foreach ($result as $index => $entity) {
$identifier = $this->uow->getEntityIdentifier($entity);
$entityKey = new EntityCacheKey($entityName, $identifier);
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $region->contains($entityKey)) {
// Cancel put result if entity put fail
if ( ! $persister->storeEntityCache($entity, $entityKey)) {
return false;
}
}
$data[$index]['identifier'] = $identifier;
$data[$index]['associations'] = [];
// @TODO - move to cache hydration components
foreach ($rsm->relationMap as $alias => $name) {
$parentAlias = $rsm->parentAliasMap[$alias];
$parentClass = $rsm->aliasMap[$parentAlias];
$metadata = $this->em->getClassMetadata($parentClass);
$assoc = $metadata->associationMappings[$name];
$assocValue = $this->getAssociationValue($rsm, $alias, $entity);
if ($assocValue === null) {
continue;
}
// root entity association
if ($rootAlias === $parentAlias) {
// Cancel put result if association put fail
if ( ($assocInfo = $this->storeAssociationCache($key, $assoc, $assocValue)) === null) {
return false;
}
$data[$index]['associations'][$name] = $assocInfo;
continue;
}
// store single nested association
if ( ! is_array($assocValue)) {
// Cancel put result if association put fail
if ($this->storeAssociationCache($key, $assoc, $assocValue) === null) {
return false;
}
continue;
}
// store array of nested association
foreach ($assocValue as $aVal) {
// Cancel put result if association put fail
if ($this->storeAssociationCache($key, $assoc, $aVal) === null) {
return false;
}
}
}
}
return $this->region->put($key, new QueryCacheEntry($data));
}
/**
* @param \Doctrine\ORM\Cache\QueryCacheKey $key
* @param array $assoc
* @param mixed $assocValue
*
* @return array|null
*/
private function storeAssociationCache(QueryCacheKey $key, array $assoc, $assocValue)
{
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
$assocMetadata = $assocPersister->getClassMetadata();
$assocRegion = $assocPersister->getCacheRegion();
// Handle *-to-one associations
if ($assoc['type'] & ClassMetadata::TO_ONE) {
$assocIdentifier = $this->uow->getEntityIdentifier($assocValue);
$entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier);
if ( ! $assocValue instanceof Proxy && ($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) {
// Entity put fail
if ( ! $assocPersister->storeEntityCache($assocValue, $entityKey)) {
return null;
}
}
return [
'targetEntity' => $assocMetadata->rootEntityName,
'identifier' => $assocIdentifier,
'type' => $assoc['type']
];
}
// Handle *-to-many associations
$list = [];
foreach ($assocValue as $assocItemIndex => $assocItem) {
$assocIdentifier = $this->uow->getEntityIdentifier($assocItem);
$entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier);
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) {
// Entity put fail
if ( ! $assocPersister->storeEntityCache($assocItem, $entityKey)) {
return null;
}
}
$list[$assocItemIndex] = $assocIdentifier;
}
return [
'targetEntity' => $assocMetadata->rootEntityName,
'type' => $assoc['type'],
'list' => $list,
];
}
/**
* @param \Doctrine\ORM\Query\ResultSetMapping $rsm
* @param string $assocAlias
* @param object $entity
*
* @return array|object
*/
private function getAssociationValue(ResultSetMapping $rsm, $assocAlias, $entity)
{
$path = [];
$alias = $assocAlias;
while (isset($rsm->parentAliasMap[$alias])) {
$parent = $rsm->parentAliasMap[$alias];
$field = $rsm->relationMap[$alias];
$class = $rsm->aliasMap[$parent];
array_unshift($path, [
'field' => $field,
'class' => $class
]
);
$alias = $parent;
}
return $this->getAssociationPathValue($entity, $path);
}
/**
* @param mixed $value
* @param array $path
*
* @return array|object|null
*/
private function getAssociationPathValue($value, array $path)
{
$mapping = array_shift($path);
$metadata = $this->em->getClassMetadata($mapping['class']);
$assoc = $metadata->associationMappings[$mapping['field']];
$value = $metadata->getFieldValue($value, $mapping['field']);
if ($value === null) {
return null;
}
if (empty($path)) {
return $value;
}
// Handle *-to-one associations
if ($assoc['type'] & ClassMetadata::TO_ONE) {
return $this->getAssociationPathValue($value, $path);
}
$values = [];
foreach ($value as $item) {
$values[] = $this->getAssociationPathValue($item, $path);
}
return $values;
}
/**
* {@inheritdoc}
*/
public function clear()
{
return $this->region->evictAll();
}
/**
* {@inheritdoc}
*/
public function getRegion()
{
return $this->region;
}
}

View File

@@ -1,88 +0,0 @@
<?php
/*
* 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>.
*/
namespace Doctrine\ORM\Cache;
use Doctrine\ORM\EntityManagerInterface;
/**
* Entity cache entry
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class EntityCacheEntry implements CacheEntry
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var array The entity map data
*/
public $data;
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var string The entity class name
*/
public $class;
/**
* @param string $class The entity class.
* @param array $data The entity data.
*/
public function __construct($class, array $data)
{
$this->class = $class;
$this->data = $data;
}
/**
* Creates a new EntityCacheEntry
*
* This method allow Doctrine\Common\Cache\PhpFileCache compatibility
*
* @param array $values array containing property values
*
* @return EntityCacheEntry
*/
public static function __set_state(array $values)
{
return new self($values['class'], $values['data']);
}
/**
* Retrieves the entity data resolving cache entries
*
* @param \Doctrine\ORM\EntityManagerInterface $em
*
* @return array
*/
public function resolveAssociationEntries(EntityManagerInterface $em)
{
return array_map(function($value) use ($em) {
if ( ! ($value instanceof AssociationCacheEntry)) {
return $value;
}
return $em->getReference($value->class, $value->identifier);
}, $this->data);
}
}

View File

@@ -1,57 +0,0 @@
<?php
/*
* 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>.
*/
namespace Doctrine\ORM\Cache;
/**
* Defines entity classes roles to be stored in the cache region.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class EntityCacheKey extends CacheKey
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var array The entity identifier
*/
public $identifier;
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var string The entity class name
*/
public $entityClass;
/**
* @param string $entityClass The entity class name. In a inheritance hierarchy it should always be the root entity class.
* @param array $identifier The entity identifier
*/
public function __construct($entityClass, array $identifier)
{
ksort($identifier);
$this->identifier = $identifier;
$this->entityClass = $entityClass;
$this->hash = str_replace('\\', '.', strtolower($entityClass) . '_' . implode(' ', $identifier));
}
}

View File

@@ -1,49 +0,0 @@
<?php
/*
* 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>.
*/
namespace Doctrine\ORM\Cache;
use Doctrine\ORM\Mapping\ClassMetadata;
/**
* Hydrator cache entry for entities
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface EntityHydrator
{
/**
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param \Doctrine\ORM\Cache\EntityCacheKey $key The entity cache key.
* @param object $entity The entity.
*
* @return \Doctrine\ORM\Cache\EntityCacheEntry
*/
public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $entity);
/**
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param \Doctrine\ORM\Cache\EntityCacheKey $key The entity cache key.
* @param \Doctrine\ORM\Cache\EntityCacheEntry $entry The entity cache entry.
* @param object $entity The entity to load the cache into. If not specified, a new entity is created.
*/
public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, EntityCacheEntry $entry, $entity = null);
}

View File

@@ -1,58 +0,0 @@
<?php
/*
* 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>.
*/
namespace Doctrine\ORM\Cache;
/**
* Cache Lock
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class Lock
{
/**
* @var string
*/
public $value;
/**
* @var integer
*/
public $time;
/**
* @param string $value
* @param integer $time
*/
public function __construct($value, $time = null)
{
$this->value = $value;
$this->time = $time ? : time();
}
/**
* @return \Doctrine\ORM\Cache\Lock
*/
public static function createLockRead()
{
return new self(uniqid(time(), true));
}
}

View File

@@ -1,106 +0,0 @@
<?php
/*
* 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>.
*/
namespace Doctrine\ORM\Cache\Logging;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Cache\QueryCacheKey;
/**
* Interface for logging.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface CacheLogger
{
/**
* Log an entity put into second level cache.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\EntityCacheKey $key The cache key of the entity.
*/
public function entityCachePut($regionName, EntityCacheKey $key);
/**
* Log an entity get from second level cache resulted in a hit.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\EntityCacheKey $key The cache key of the entity.
*/
public function entityCacheHit($regionName, EntityCacheKey $key);
/**
* Log an entity get from second level cache resulted in a miss.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\EntityCacheKey $key The cache key of the entity.
*/
public function entityCacheMiss($regionName, EntityCacheKey $key);
/**
* Log an entity put into second level cache.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cache key of the collection.
*/
public function collectionCachePut($regionName, CollectionCacheKey $key);
/**
* Log an entity get from second level cache resulted in a hit.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cache key of the collection.
*/
public function collectionCacheHit($regionName, CollectionCacheKey $key);
/**
* Log an entity get from second level cache resulted in a miss.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cache key of the collection.
*/
public function collectionCacheMiss($regionName, CollectionCacheKey $key);
/**
* Log a query put into the query cache.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\QueryCacheKey $key The cache key of the query.
*/
public function queryCachePut($regionName, QueryCacheKey $key);
/**
* Log a query get from the query cache resulted in a hit.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\QueryCacheKey $key The cache key of the query.
*/
public function queryCacheHit($regionName, QueryCacheKey $key);
/**
* Log a query get from the query cache resulted in a miss.
*
* @param string $regionName The name of the cache region.
* @param \Doctrine\ORM\Cache\QueryCacheKey $key The cache key of the query.
*/
public function queryCacheMiss($regionName, QueryCacheKey $key);
}

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