Compare commits

...

1363 Commits

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

The same bug of the yaml driver: see http://www.doctrine-project.org/jira/browse/DDC-2661
2014-01-02 23:34:17 +01:00
Eduardo
7c8350094e Options not respected for ID Fields in XML Mapping Driver
Same bug of the YAML driver, see: http://www.doctrine-project.org/jira/browse/DDC-2661
2014-01-02 23:34:17 +01:00
Benjamin Eberlei
c613410ba6 Merge branch 'DDC-2764' into 2.4 2014-01-02 23:16:56 +01:00
Sander Marechal
6bb7581dd7 Add rootAlias to Criteria where clauses 2014-01-02 23:16:35 +01:00
Sander Marechal
ab71dab7d1 Set rootAlias outside loop 2014-01-02 23:15:31 +01:00
Sander Marechal
2c114756bd [DDC-2764] Prefix criteria orderBy with rootAlias 2014-01-02 23:15:31 +01:00
Benjamin Eberlei
45496f040d Merge branch 'DDC-2775' into 2.4 2014-01-02 23:11:16 +01:00
Benjamin Eberlei
b40866c624 [DDC-2775] cleanup test. 2014-01-02 23:11:07 +01:00
Matthieu Napoli
a89cc7abea Inlined the model for the DCC2775 test case inside the test class 2014-01-02 23:07:53 +01:00
Matthieu Napoli
5ac111e5f8 Cleaned up tests for DDC-2775 2014-01-02 23:07:53 +01:00
Matthieu Napoli
c5f66e6e7f Fixed tests failing in pgsql because of used of a reserved keyword 2014-01-02 23:07:53 +01:00
Matthieu Napoli
b59f495875 Fixed tests for pgsql: was using reserved keyword as table name 2014-01-02 23:07:53 +01:00
Matthieu Napoli
3829b9c28b [DDC-2775] Bugfix 2014-01-02 23:07:53 +01:00
Matthieu Napoli
65bcdbf4c7 [DDC-2775] Tests reproducing DDC-2775 2014-01-02 23:07:53 +01:00
Benjamin Eberlei
95d000e51b Merge branch 'DDC-2692' into 2.4 2014-01-02 22:17:20 +01:00
Stefan Kleff
3657df3b01 Listener class prefix 2014-01-02 22:16:59 +01:00
Stefan Kleff
1661ffae9a removed unused use statements, fixed typo and group tag 2014-01-02 22:16:59 +01:00
Stefan Kleff
b424a5cf14 Added unit test 2014-01-02 22:16:59 +01:00
Stefan Kleff
2767a4eec4 Multiple invokation of listeners on PreFlush event
Only lifecycle callbacks and entity listeners should be triggered here. The preFlush listener event is already triggered at the beginning of commit()
2014-01-02 22:16:59 +01:00
Benjamin Eberlei
9486867279 Merge branch 'DDC-2645' into 2.4 2013-12-15 23:34:57 +01:00
Pouyan Savoli
6f2bb08972 [DDC-2645] Apply patch to fix issue 2013-12-15 23:34:34 +01:00
Aaron Muylaert
da2d3b406e Create failing test for DDC-2645.
Merge not dealing correctly with composite primary keys.
2013-12-15 23:34:34 +01:00
Benjamin Eberlei
c4b7d3fbea Bump version to 2.4.2 2013-11-12 13:40:15 +01:00
Benjamin Eberlei
84373d05a4 Release 2.4.1 2013-11-12 13:40:13 +01:00
Benjamin Eberlei
e82e7147fa Merge branch 'DDC-2715' into 2.4 2013-10-29 09:25:13 +01:00
jan brunnert
e23ed2250d Removed unnecessary is_object() check 2013-10-29 09:24:52 +01:00
jan brunnert
192bb6fd21 When the OptimisticLockingException is generated with the static function lockFailedVersionMismatch and the passed parameters are DateTime instances, the exception could not be thrown because the DateTime object is not implicitly converted to a string. 2013-10-29 09:24:52 +01:00
Benjamin Eberlei
0f3679f034 Merge branch 'DDC-2759' into 2.4 2013-10-26 11:17:34 +02:00
Benjamin Eberlei
1d2cd82706 [DDC-2759] Fix regression in ArrayHydrator introduced in DDC-1884 at SHA c7b4c9bf0f 2013-10-26 11:16:53 +02:00
Chris Collins
b983d86612 Added a failing test case for DDC-2759. 2013-10-26 11:16:53 +02:00
Benjamin Eberlei
b11f01643c Merge branch 'DDC-2668' into 2.4 2013-09-26 23:24:14 +02:00
Fabio B. Silva
b58fb8f5d4 [DDC-2668] Fix trim leading zero string 2013-09-26 23:23:49 +02:00
Benjamin Eberlei
925a22b71d Merge branch 'DDC-2608' into 2.4 2013-09-08 16:01:38 +02:00
Benjamin Eberlei
0f0d8abd67 [DDC-2608][DDC-2662] Fix SequenceGenerator requiring "sequenceName" and now throw exception. Fix a bug in quoting the sequenceName. 2013-09-08 16:00:14 +02:00
Benjamin Eberlei
470c15ce05 Merge branch 'DDC-2660' into 2.4 2013-09-08 14:39:54 +02:00
Benjamin Eberlei
3cc5fc0252 [DDC-2660] Fix error with NativeSQL, ResultSetMappingBuilder and Associations as Primary Key. 2013-09-08 14:39:25 +02:00
Benjamin Eberlei
fd0657089a Merge branch 'DDC-2661' into 2.4 2013-09-08 10:38:03 +02:00
Benjamin Eberlei
de3b237292 [DDC-2661] Fix bug in YamlDriver not passing options from id to mapField() 2013-09-08 10:37:42 +02:00
Benjamin Eberlei
1221cc3a2a More excludes 2013-09-07 18:27:20 +02:00
Benjamin Eberlei
9efbc1fa71 Bump version to 2.4.1 2013-09-07 18:19:57 +02:00
Benjamin Eberlei
57705e0d78 Release 2.4.0 2013-09-07 18:19:56 +02:00
Benjamin Eberlei
82bb6b78cd Travis should prefer dist. 2013-09-07 13:20:35 +02:00
Benjamin Eberlei
64c56b21aa Remove minimum stability from 2.4 composer.json 2013-09-07 13:08:14 +02:00
Benjamin Eberlei
b04e2e6364 Adjust composer.json to pending 2.4 stable release 2013-09-07 12:59:17 +02:00
Benjamin Eberlei
a70f9b7f49 Fix branch alias 2013-09-07 12:57:56 +02:00
Benjamin Eberlei
c88a7c1ffe New Build process
- Switch from Phing to Ant
- Remove PEAR packaging
- Add Composer archiving
2013-09-07 12:57:38 +02:00
Benjamin Eberlei
c206728c96 Merge branch 'DDC-2638' into 2.4 2013-09-07 09:04:34 +02:00
Attila Fulop
e8d420c641 Fix for entity generator discriminator column 2013-09-07 09:04:26 +02:00
Benjamin Eberlei
fdcab7eae8 Merge branch 'DDC-2640' into 2.4 2013-09-07 09:01:01 +02:00
Maks Feltrin
45d7d5234f DO NOT OVERRIDE CUSTOM TREE WALKERS IN getIterator() 2013-09-07 09:00:06 +02:00
Benjamin Eberlei
159ca79b81 Merge origin/2.4 into local branch 2013-09-07 08:55:15 +02:00
Benjamin Eberlei
2b148a27e0 Merge Oracle test fixes to 2.4 branch 2013-09-07 08:54:23 +02:00
Guilherme Blanco
0aef57f60c Fixing missing table aliases when using Many2Many persister. 2013-08-26 10:29:45 -04:00
Benjamin Eberlei
fef1e0286c Merge branch 'DDC-2235' into 2.4 2013-08-20 10:08:21 +02:00
Guilherme Blanco
4a38534150 Fixed DDC-2235. 2013-08-20 10:08:07 +02:00
Benjamin Eberlei
1de22adb16 Merge branch 'DDC-2506' into 2.4 2013-08-20 09:56:54 +02:00
Guilherme Blanco
62b4160887 Fixed DDC-2506 by manually updating code. Closes PR #708. 2013-08-20 09:56:39 +02:00
Benjamin Eberlei
dbb7c4d2bf Merge branch 'DDC-2607' into 2.4 2013-08-20 09:51:19 +02:00
Dustin Thomson
e8978ee365 Modified executeInserts method in JoinedSubclassPersister to only check for the presence of columns in a composite primary key 2013-08-20 09:51:01 +02:00
Benjamin Eberlei
c095b88804 Merge branch 'DDC-2578' into 2.4 2013-08-10 18:14:24 +02:00
Guilherme Blanco
efe4208ba6 Kept BC. 2013-08-10 18:14:07 +02:00
Guilherme Blanco
453a56670d Modified Hydrators to be per-query instances instead of a singleton-like approach. 2013-08-10 18:14:07 +02:00
Benjamin Eberlei
ec36e2c866 Merge branch 'DDC-2579' into 2.4 2013-08-10 17:54:37 +02:00
Fabio B. Silva
e250572cb4 fix DDC-2579 2013-08-10 17:53:50 +02:00
Benjamin Eberlei
758955e183 Merge branch 'DDC-2582' into 2.4 2013-08-10 17:48:20 +02:00
Guilherme Blanco
5b8d6a1486 CS fixes. 2013-08-10 17:48:03 +02:00
Guilherme Blanco
3f1003fee9 Fixed DDC-1884. 2013-08-10 17:48:03 +02:00
Benjamin Eberlei
7e241e89b8 Merge branch 'DDC-2548' into 2.4 2013-08-10 17:43:40 +02:00
Michaël Gallego
67c1e1d2b1 Allow to have non-distinct queries 2013-08-10 17:43:26 +02:00
Benjamin Eberlei
261eacdbfc Merge branch 'DDC-2565' into 2.4 2013-08-10 17:27:47 +02:00
Austin Morris
43df821691 convert PersistentCollection functional tests to unit tests 2013-08-10 17:27:30 +02:00
Austin Morris
11d09702da remove redundant require_once for TestInit.php 2013-08-10 17:27:30 +02:00
Austin Morris
f9f14139cf do not initialize coll on add() 2013-08-10 17:27:30 +02:00
Austin Morris
39f4d46d36 Initialize coll when using Collection methods inside PersistentCollection 2013-08-10 17:27:30 +02:00
Austin Morris
1dae8d318f PersistentCollection - initialize coll - create failing tests 2013-08-10 17:27:30 +02:00
Benjamin Eberlei
a361a7c1cb Merge branch 'DDC-2542' into 2.4 2013-08-10 17:02:34 +02:00
Roger Llopart Pla
6a73608baf Fixed name colision. 2013-08-10 17:02:10 +02:00
Roger Llopart Pla
f9955152b2 Added a test which verifies that the tree walkers are kept. 2013-08-10 17:02:10 +02:00
Roger Llopart Pla
5aad1df149 Added docblock. 2013-08-10 17:02:10 +02:00
Roger Llopart Pla
243832555b Appending the Paginator tree walker hint, instead of removing all the other hints. 2013-08-10 17:02:10 +02:00
Benjamin Eberlei
ae12fa6b5b Merge branch 'DDC-2577' into 2.4 2013-08-10 16:28:35 +02:00
Konstantin.Myakshin
edaf9b6813 Skip not mapped public properties in SchemaValidator 2013-08-10 16:28:27 +02:00
Benjamin Eberlei
b324a21abf Merge branch 'DDC-2587' into 2.4 2013-08-10 16:25:04 +02:00
J. Bruni
ff34aaaa2c Updated EntityGeneratorTest::testEntityTypeAlias 2013-08-10 16:24:43 +02:00
J. Bruni
9767a814a6 Updated EntityGeneratorTest::testEntityTypeAlias 2013-08-10 16:24:43 +02:00
J Bruni
e6007575e1 Corrected PHP type for "decimal" mapping type
"Basic Mapping" documentation says:
"decimal: Type that maps a SQL DECIMAL to a PHP string."
2013-08-10 16:24:43 +02:00
Benjamin Eberlei
29d6da0fa0 Merge pull request #703 from shulcsm/patch-1
Clear visitedCollections
2013-06-30 01:38:02 -07:00
Benjamin Eberlei
69fe5c48f4 Merge pull request #710 from sandermarechal/extra-lazy-get-fix
Fix extra lazy get
2013-06-30 01:37:26 -07:00
Benjamin Eberlei
8e1111c8d3 Merge pull request #711 from FabioBatSilva/coveralls
Coveralls code coverage
2013-06-27 23:52:15 -07:00
Fabio B. Silva
e4bccdc7b3 coveralls code coverage 2013-06-27 20:18:21 -04:00
Sander Marechal
06ed21e883 Remove extra-lazy-get for ManyToMany relation 2013-06-27 14:19:39 +02:00
Sander Marechal
5635fa60a4 Check owning entitiy on extra lazy get with OneToMany relation 2013-06-27 14:17:41 +02:00
Benjamin Eberlei
4d93a4950b Merge branch 'DDC-2530' 2013-06-25 19:34:22 +02:00
Benjamin Eberlei
a91050e7f4 [DDC-2350] Eager Collections are not marked as initialized, leading to multiple queries being executed. 2013-06-25 19:34:12 +02:00
Guilherme Blanco
20e5d98b7b Merge pull request #640 from denkiryokuhatsuden/patch-1
[Paginator]Add hidden field ordering for postgresql
2013-06-24 19:23:36 -07:00
Benjamin Eberlei
2f6e914d64 Dont allow failures in 5.5 anymore 2013-06-24 08:42:17 +02:00
Guilherme Blanco
457036aacb Merge pull request #702 from FabioBatSilva/DDC-2459
[DDC-2459]ANSI compliant quote strategy
2013-06-23 14:26:49 -07:00
Guilherme Blanco
2ce72f38a2 Merge pull request #705 from FabioBatSilva/DDC-2519
[DDC-2519] Partial association identifier
2013-06-23 14:25:58 -07:00
Fabio B. Silva
1cff8b4d98 Fix DDC-2519 2013-06-21 16:07:09 -04:00
Fabio B. Silva
a165f63c8c ANSI compliant quote strategy 2013-06-21 16:05:59 -04:00
Benjamin Eberlei
eaf8fd3c34 Merge pull request #706 from sandermarechal/extra-lazy-get
[DDC-1398] Extra-lazy get for indexed associations
2013-06-20 05:26:52 -07:00
Sander Marechal
70427871ce Extra test for indexBy identifier versus indexBy other fields 2013-06-20 14:20:00 +02:00
Sander Marechal
2879162015 No need to lookup metadata 2013-06-20 14:00:58 +02:00
Sander Marechal
3b92cfac5a Use find() if the indexBy field is the identifier 2013-06-20 13:45:38 +02:00
Sander Marechal
53c9ffda30 Get rid of variable 2013-06-20 10:20:16 +02:00
Sander Marechal
647c5e2cad Test actual data returned by get() 2013-06-20 10:18:08 +02:00
Sander Marechal
3555007f08 Return NULL for non-existent keys
The load() function already returns just one entity or NULL, so
the current() is not needed and the result can be returned directly.
2013-06-20 10:09:52 +02:00
Sander Marechal
523697d0b6 [DDC-1398] Extra-lazy get for indexed associations
If an association is EXTRA_LAZY and has an indexBy, then
you can call get() without loading the entire collection.
2013-06-20 09:29:56 +02:00
Benjamin Eberlei
1382d766b0 Merge pull request #704 from liuggio/patch-1
added badges stable release and total downloads
2013-06-19 09:02:33 -07:00
Giulio De Donato
c743bb938b added badges 2013-06-19 16:40:57 +02:00
shulcsm
3340234785 Clear visitedCollections
Visited collections are cleared only in commit(). Commit clears up only if it actually has something to do. Processing large amounts of records without changing them cause visitedCollections to grow without any way of clearing.
2013-06-19 16:34:44 +03:00
Marco Pivetta
a39ceb3159 Merge pull request #701 from rbroen/patch-2
list_bugs.php needs to call to getters for protected vars
2013-06-17 02:41:29 -07:00
Robert Broen
6ff5043ce8 list_bugs.php needs to call to getters for protected vars
list_bugs.php needs to call to getters for protected vars. This was changed in the "getting-started" code repository, but not in the "getting-started" tutorial.
2013-06-17 11:36:09 +02:00
Marco Pivetta
1a958f70fd Merge pull request #700 from rbroen/patch-2
Update getting-started.rst
2013-06-17 01:39:43 -07:00
Robert Broen
184e8eb26c Update getting-started.rst
The tutorial assumes Doctrine 2.4
2013-06-17 10:31:06 +02:00
Guilherme Blanco
7903a2b513 Merge pull request #695 from doctrine/RepositoryFactory
Implemented support for RepositoryFactory.
2013-06-14 09:19:43 -07:00
Guilherme Blanco
52b3fc1fc3 Updated since php doc tag. 2013-06-14 12:07:28 -04:00
Guilherme Blanco
09d67b10b0 Merge pull request #697 from michaelperrin/patch-1
Fix phpDoc syntax in ClassMetadataInfo.php
2013-06-14 06:57:14 -07:00
Michaël Perrin
37d7df6ac4 Fix phpDoc syntax in ClassMetadataInfo.php 2013-06-14 11:00:17 +03:00
Guilherme Blanco
3488049c18 Reduced granularity of DefaultRepositoryFactory reference to ObjectRepository instances, in cases where consumers are completely rewrote EntityRepository. 2013-06-13 23:59:08 -04:00
Guilherme Blanco
a66fc03441 Reducing dependency on RepositoryFactory by providing EntityManager as a getRepository argument. 2013-06-13 23:53:53 -04:00
Guilherme Blanco
37e7e841c3 Fixed wrong interface. 2013-06-13 23:31:18 -04:00
Guilherme Blanco
f2f1d8986c Merge pull request #694 from FabioBatSilva/DDC-2478
[DDC-2478] Support null association comparison
2013-06-13 20:12:48 -07:00
Guilherme Blanco
7eb744126b Implemented support for RepositoryFactory. 2013-06-13 21:47:40 -04:00
Fabio B. Silva
f16c8e3efe Fix DDC-2478 2013-06-13 16:44:02 -04:00
Guilherme Blanco
6ef48561ba Merge pull request #688 from sellingsource/master
Implement QuoteStrategy on SqlWalker walkRangeVariableDeclaration
2013-06-12 12:03:24 -07:00
Guilherme Blanco
0a90279a99 Merge pull request #693 from doctrine/hotfix/DDC-2214
Adding failing test for DDC-2214
2013-06-12 12:00:31 -07:00
Marco Pivetta
a1355d0bb9 Adding failing test for DDC-2214
Parameters being bound to an SQL query should have the same type as
the identifier of the objects being bound to the placeholders of a
DQL query - this is currently broken with proxies, as this test
demonstrates.
2013-06-12 20:51:45 +02:00
Guilherme Blanco
6937061b23 Merge pull request #690 from FabioBatSilva/DDC-2494
[DDC-2494] Apply type conversion to meta columns
2013-06-12 11:47:26 -07:00
Fabio B. Silva
c1e688fc81 drop useless support for associations 2013-06-12 10:30:51 -04:00
Fabio B. Silva
d961028b14 small optimization 2013-06-12 10:30:51 -04:00
Fabio B. Silva
d685f592fe Fix DDC-2494 2013-06-12 10:30:51 -04:00
Guilherme Blanco
b15758bb42 DDC-2476 Commented check under PostgreSQL. 2013-06-12 02:29:08 -04:00
Guilherme Blanco
3d86c82a7f DDC-2476 Better approach for reverse engineer. Some refactoring done to driver. 2013-06-12 02:00:36 -04:00
Guilherme Blanco
0d834d0bd4 DDC-2489 Added missing semicolon when dump-sql on schema update. 2013-06-12 00:31:25 -04:00
Guilherme Blanco
0248f743ba Merge pull request #691 from 51systems/master
IDENTITY function fix for Joined Inheritance
2013-06-11 21:04:27 -07:00
Dustin Thomson
ed7a4bdcf3 Fix typo in test name 2013-06-11 18:38:05 -06:00
Dustin Thomson
529064aff2 Modified identity function to work with joined inheritance tables.
Added regression tests
2013-06-11 18:09:49 -06:00
Guilherme Blanco
4e99c5c127 DDC-1858 Added coverage to ticket. Functionality already implemented. 2013-06-11 01:21:47 -04:00
Guilherme Blanco
462173ad71 Merge pull request #689 from FabioBatSilva/DDC-1995
[WIP][DDC-1995 ] Support metadata class as parameter for instance of expression
2013-06-07 14:42:59 -07:00
Fabio B. Silva
710d0d1109 Fix DDC-1995 2013-06-07 17:24:05 -04:00
John Brown
4ef043fc3b updating sql walker to use quote strategy in joins 2013-06-07 08:56:58 -07:00
John Brown
afb9c829e2 updating sql walker to use quote strategy in joins 2013-06-07 08:49:49 -07:00
John Brown
9bea612d74 Adding simple test to ensure quoting of table names still functions. Note: the funtionality of this change won't be noticiable unless a custom quote strategy is implemented 2013-06-06 15:51:23 -07:00
John Brown
77b905eaa8 Implement QuoteStrategy on SqlWalker walkRangeVariableDeclaration
Based on:
http://www.doctrine-project.org/jira/browse/DDC-1845
cb72219b11
2013-06-06 15:08:22 -07:00
Marco Pivetta
5c7b98b2a9 Merge pull request #687 from mnapoli/patch-3
Fixed rendering
2013-06-06 01:51:49 -07:00
Matthieu Napoli
424793c263 Fixed rendering
Fixed some broken rendering on http://docs.doctrine-project.org/en/latest/reference/yaml-mapping.html
2013-06-06 11:36:02 +03:00
Guilherme Blanco
753d63c2d4 Merge pull request #686 from FabioBatSilva/DDC-2475
[DDC-2475] Replace OrderBy mapping when OrderByClause is given
2013-06-04 22:03:53 -07:00
Fabio B. Silva
27511374ec Fix DDC-2475 2013-06-04 23:50:43 -04:00
Marco Pivetta
3d6436c2f3 Merge pull request #683 from greg0ire/error_message_improvement
Explicitely state what the problem is
2013-06-03 03:37:38 -07:00
Grégoire Paris
a986fe013e Explicitely state what the problem is
People like me think the problem is that there is no association
mapping, when the problem in fact could be that there also is a field
mapping on the property.
This message makes it clearer why we are getting an error message.
2013-06-03 12:07:08 +02:00
Marco Pivetta
4e8b787d07 Merge pull request #679 from ajgarlag/getting-started
Fix getting started doc to work with current version
2013-05-28 00:36:17 -07:00
Antonio J. García Lagar
c64c149ebf Fix getting started doc to work with current version 2013-05-28 08:57:17 +02:00
Benjamin Eberlei
f269ecc3ac Bump dev version to 2.4.0 2013-05-27 21:47:16 +02:00
Benjamin Eberlei
6bc18402e2 Release 2.4.0-RC1 2013-05-27 21:47:16 +02:00
Marco Pivetta
3d0ac62059 Merge pull request #678 from jbafford/duplicate-words-fix
Fix an instance of doubled words in the docs
2013-05-27 08:51:38 -07:00
John Bafford
0e29fe871a Fix an instance of doubled words 2013-05-27 11:20:26 -04:00
Alexander
e5de0dad7e Merge remote-tracking branch 'asm89/ddc-2417'
* asm89/ddc-2417:
  [DDC-2471] Fix EQ/NEQ null handling of criteria
2013-05-26 09:10:18 +02:00
Alexander
66a842c143 [DDC-2471] Fix EQ/NEQ null handling of criteria 2013-05-26 09:04:19 +02:00
Benjamin Eberlei
6548947df5 Merge pull request #666 from FabioBatSilva/DDC-2429
[DDC-2429] Fix xsd definition
2013-05-25 23:01:13 -07:00
Benjamin Eberlei
20b5ab26e7 Merge pull request #669 from hason/many_to_many
Fixed generating column names for self referencing entity.
2013-05-25 22:16:48 -07:00
Benjamin Eberlei
a36f84eeb5 Merge pull request #672 from hell0w0rd/simplification_example
Simplification example
2013-05-25 22:00:59 -07:00
Benjamin Eberlei
e8b3598751 Merge pull request #673 from hell0w0rd/namespace_base_commands_names
Namespace based command names
2013-05-25 21:59:32 -07:00
Gusakov Nikita
1f4a65f3e0 add debug variable and check for loader 2013-05-20 17:08:20 +04:00
Gusakov Nikita
384dfa87ab add docs 2013-05-18 04:00:28 +04:00
Gusakov Nikita
6bb3184dbf fix bc 2013-05-18 02:40:18 +04:00
Gusakov Nikita
4c1869dca0 ready 2013-05-18 02:09:07 +04:00
Gusakov Nikita
ec57306efe remove comment about cli-config 2013-05-18 01:44:47 +04:00
Gusakov Nikita
dca0881d94 forget add changes 2013-05-18 01:42:55 +04:00
Gusakov Nikita
7430320bac ready 2013-05-18 01:40:05 +04:00
Benjamin Eberlei
c9d9b68fa9 Merge pull request #671 from FabioBatSilva/DDC-2435
[DDC-2435] Fix column name with numbers and non alphanumeric characters.
2013-05-17 09:34:00 -07:00
Fabio B. Silva
f92214997f [DDC-2435] Fix column name with numbers and non alphanumeric characters. 2013-05-17 13:02:46 -03:00
Benjamin Eberlei
65886fdfea Merge pull request #670 from FabioBatSilva/DDC-2451
[DDC-2451] Fix entity listeners serialization
2013-05-17 08:09:28 -07:00
Fabio B. Silva
1e95110b08 Add missing mapping files 2013-05-17 11:59:28 -03:00
Fabio B. Silva
1d7c72cc06 [DDC-2451] Fix test assert 2013-05-17 11:55:36 -03:00
Fabio B. Silva
4d6cef1ff6 [DDC-2451] Fix entity listeners serialization 2013-05-17 11:42:11 -03:00
Martin Hasoň
bef5b585cb Fixed generating join column names for self referencing entity. 2013-05-17 16:28:45 +02:00
Fabio B. Silva
b147c472be [DDC-2429] Fix xsd definition 2013-05-13 11:04:36 -03:00
Benjamin Eberlei
eb1a162cbc Fix regression in DDC-2430. 2013-05-10 23:31:27 +02:00
Benjamin Eberlei
bf9673203c Merge pull request #639 from goetas/indexby-metadata
Added abillity to use metacolumn as indexBy
2013-05-09 23:53:26 -07:00
Benjamin Eberlei
fa75856d5f Merge pull request #663 from doctrine/hotfix/DDC-2432
Hotfix for DDC-2432
2013-05-09 14:37:55 -07:00
Marco Pivetta
22c9f6ebec applying required fixes for DDC-2432 2013-05-09 21:14:58 +02:00
Marco Pivetta
07c207081e Adding failing test to demonstrate DDC-2432
Loading proxies with invalid identifiers will currently mark them as initialized regardless of the failure
2013-05-09 21:11:10 +02:00
Benjamin Eberlei
762f43c3dc Merge branch 'DDC-2280' 2013-05-09 18:15:48 +02:00
Benjamin Eberlei
b53f4fd4cc [DDC-2280] length attribute in <id> was not converted. 2013-05-09 18:15:41 +02:00
Benjamin Eberlei
665efad039 Merge branch 'master' of github.com:doctrine/doctrine2 2013-05-09 16:55:38 +02:00
Benjamin Eberlei
540af2fd2a Merge pull request #662 from jakzal/code-block-fix
Fixed a code block.
2013-05-09 07:27:35 -07:00
Benjamin Eberlei
1a0adecf29 [DDC-2335] Add note about filtering schema by regexp expression to relevant commands help output. 2013-05-09 16:24:19 +02:00
Jakub Zalas
97622b57bd Fixed a code block.
Sphinx does not like the way code was indented. Building the documentation raises the following error:
en/cookbook/resolve-target-entity-listener.rst:121: ERROR: Unexpected indentation.
2013-05-09 15:19:58 +01:00
Benjamin Eberlei
b9a0a19607 Merge pull request #661 from HarmenM/patch-1
Update annotations-reference.rst
2013-05-09 05:10:26 -07:00
HarmenM
f8efd85ae6 Update annotations-reference.rst
Added missing @JoinColumns in the index
2013-05-09 14:49:38 +03:00
Benjamin Eberlei
dc674f809f Mention BC BREAK in PersistentCollection#matching() more prominently. 2013-05-09 13:40:14 +02:00
Benjamin Eberlei
30f90a6f49 Merge branch 'DDC-2430' 2013-05-09 13:24:45 +02:00
Benjamin Eberlei
6d5afb18bc [DDC-2430] Prevent Criteria queries using the ID of an assocation on PersistentCollections, as the in-memory ArrayCollection does not work with this kind of query. Attention this is a BC-BREAK, that is necessary to fix potentially very hard to debug bugs. Therefore it is not merged back into 2.3 2013-05-09 13:24:36 +02:00
Benjamin Eberlei
d3cd10d926 Merge branch 'DDC-2387' 2013-05-09 12:10:45 +02:00
Benjamin Eberlei
7220c3c125 [DDC-2387] Fix DatabaseDriver not working with combinations of composite/association keys. 2013-05-09 12:10:37 +02:00
Benjamin Eberlei
86277def7e Merge branch 'DDC-2437' 2013-05-09 11:03:21 +02:00
Benjamin Eberlei
abe8ef6778 Merge branch 'DDC-2423' 2013-05-09 10:55:20 +02:00
Benjamin Eberlei
e3b8ce7737 [DDC-2423] Fixed bug with EntityGenerator not generating fetch="" attribute in association annotations. 2013-05-09 10:55:12 +02:00
Benjamin Eberlei
acbafd081b Add documentation to EntityManager about instantiation, decoration over inheritance, and some generic introduction. 2013-05-09 10:23:12 +02:00
Vladislav Vlastovskiy
3997d0df87 Added test complex inner join with indexBy 2013-05-09 03:32:28 +04:00
Vladislav Vlastovskiy
33888f1b08 Swapped places indexBy and condition in accordance with EBNF 2013-05-09 03:30:48 +04:00
Benjamin Eberlei
c8bcdb4b61 Merge pull request #524 from lstrojny/feature/entity-manager-decorator
EntityManagerDecorator base class as an extension point for EntityManager
2013-05-04 06:02:39 -07:00
Benjamin Eberlei
8e8560b276 Merge pull request #537 from Powerhamster/joined-composite-keys
fixed problems with joined inheritance and composite keys
2013-05-04 05:59:02 -07:00
Benjamin Eberlei
937473329f Merge branch 'DDC-2267' 2013-05-04 13:38:42 +02:00
Benjamin Eberlei
5e19e1bed3 [DDC-2267] Allow EntityManager#flush($entity) to be called on entities scheduled for removal. 2013-05-04 13:38:30 +02:00
Benjamin Eberlei
3c74491720 Merge branch 'DDC-2426' 2013-05-04 12:58:25 +02:00
Benjamin Eberlei
9adc45767d [DDC-2426] Missing length attribute in doctrine-mapping.xsd for <id> tag. 2013-05-04 12:58:06 +02:00
Benjamin Eberlei
59fff4ddef Fix Docs build 2013-05-03 13:32:42 +02:00
Benjamin Eberlei
6d02c7e1a5 [DDC-2136] Fix exporting to YAML and XML with assocation keys. 2013-05-01 23:10:13 +02:00
Benjamin Eberlei
d33e0a3488 Merge branch 'DDC-1998' 2013-05-01 20:33:49 +02:00
Benjamin Eberlei
0864ab8ada [DDC-1998] Pass types to Connection#delete() to allow custom conversions to happen. 2013-05-01 20:30:45 +02:00
Benjamin Eberlei
7d53cb2aeb Merge branch 'DDC-1984' 2013-05-01 19:39:30 +02:00
Benjamin Eberlei
131164b7f6 [DDC-1984] Throw exception if passing null into UnitOfWork#lock() - which can happen when EntityManager#find() tries to lock entity that was just deleted by another process. 2013-05-01 19:39:21 +02:00
Benjamin Eberlei
6c889eee64 Merge branch 'DDC-2106' 2013-05-01 18:46:54 +02:00
Benjamin Eberlei
640a8e58c7 [DDC-2106] Fix entity as parameter value when its managed but not yet with identifier. 2013-05-01 18:46:41 +02:00
Benjamin Eberlei
6505c96ec4 Simplify condition of previous commit (5cdc73e) 2013-05-01 10:58:44 +02:00
Benjamin Eberlei
760aaa67c4 Merge pull request #655 from FabioBatSilva/DDC-2409
[DDC-2409] Fix merge association STATE_NEW
2013-05-01 01:49:25 -07:00
Benjamin Eberlei
325387a6f2 Merge branch 'DBAL-483' 2013-05-01 10:42:35 +02:00
Benjamin Eberlei
1f08acb576 [DBAL-483] Pass default values to DBAL mapping layer correctly to fix default comparision bug. 2013-05-01 10:42:28 +02:00
Asmir Mustafic
7abf46af70 cascade remove persist on links 2013-04-29 11:03:55 +02:00
Asmir Mustafic
2ca24375e4 no lang 2013-04-29 10:32:40 +02:00
Asmir Mustafic
34adb16ee8 indexby ddc117 tests 2013-04-29 10:29:58 +02:00
Fabio B. Silva
5cdc73e13b Fix DDC-2409 2013-04-28 16:54:44 -03:00
Benjamin Eberlei
d513e0f084 Merge pull request #653 from FabioBatSilva/DDC-2415
[DDC-2415] Fix CustomIdGenerator inheritance
2013-04-27 00:40:57 -07:00
Fabio B. Silva
7c2da2d5b8 Fix DDC-2415 2013-04-26 16:11:04 -03:00
Marco Pivetta
cc24ac496d Merge pull request #652 from dannykopping/patch-1
Fixed entities path
2013-04-26 02:11:52 -07:00
Danny Kopping
408eef1356 Fixed entities path
In the tutorial, the user is told to create a new file in the '/src' folder, and the 'entities' folder is never referenced. Updating the SQLite schema according to the tutorial fails with the 'No Metadata Classes to process.' message. Changing the folder to '/src' fixes this, ostensibly.
2013-04-26 12:05:50 +03:00
Marco Pivetta
9791b2eb00 Merge pull request #651 from EuKov/patch-1
Fixed typo in SQLFilter (use statement ClassMetadata)
2013-04-23 10:50:01 -07:00
EuKov
99ec4dc72c Fixed typo in SQLFilter (use statement ClassMetadata) 2013-04-23 20:46:19 +03:00
Marco Pivetta
3aae50cb59 Merge pull request #649 from calumbrodie/patch-1
Update coding standards in change tracking cookbook entry
2013-04-21 12:07:31 -07:00
Calum Brodie
a0a133b02c Removed unneeded escape character
Removed backslash
2013-04-21 20:55:54 +02:00
Calum Brodie
c2967b35ff Update coding standards
Removing underscores from property/method names and change use statements to PSR-2
2013-04-21 20:43:48 +02:00
Benjamin Eberlei
92b41e017a [DDC-2407] Fix missing support for UUID and CUSTOM id generators in Exporter 2013-04-20 10:25:36 +02:00
Benjamin Eberlei
52b2e066c5 Merge pull request #611 from stefankleff/fix-eagerloading
Fixed typo in hints. Caused slow loading of eager entities.
2013-04-14 00:43:42 -07:00
Guilherme Blanco
e835175865 Merge pull request #646 from raykolbe/master
Oracle Pagination bug when ordering is present
2013-04-10 10:49:26 -07:00
Raymond Kolbe
27e23faa5a Accompanying tests for PR #646 2013-04-10 13:07:09 -03:00
Raymond Kolbe
4bafcc5b31 Fix Oracle subquery ordering
See http://www.doctrine-project.org/jira/browse/DDC-1800 and http://www.doctrine-project.org/jira/browse/DDC-1958#comment-19969
2013-04-09 17:30:54 -03:00
Raymond Kolbe
b8b7afe576 Fix Oracle subquery ordering lost bug
See http://www.doctrine-project.org/jira/browse/DDC-1800 for a full description.
2013-04-09 17:00:06 -03:00
Benjamin Eberlei
142c20aad1 Work on the Tutorial 2013-04-09 00:00:16 +02:00
Stefan Kleff
e561f47cb2 Added constant 2013-04-08 11:27:22 +02:00
Benjamin Eberlei
d1f8e18d02 Enhance documentation on NEW() keyword. (ref DDC-1574) 2013-04-07 14:05:42 +02:00
Benjamin Eberlei
34374db56e Enhance documentation on IDENTITY() for composite keys (ref DDC-2202) 2013-04-07 14:02:47 +02:00
Benjamin Eberlei
b2e29eaf97 Rework NativeSQL doc chapter and document SELECT clause generation (ref DDC-2055). 2013-04-07 14:01:27 +02:00
Fabio B. Silva
2ad6565632 Fix parenthesis example 2013-04-06 14:53:36 -03:00
Benjamin Eberlei
cef20890dc Merge pull request #616 from FabioBatSilva/DDC-2252
[DDC-2252] Fix delete many-to-many composite key
2013-04-06 10:37:16 -07:00
Fabio B. Silva
8109db02b5 Document Parenthesis BC break. 2013-04-06 13:02:43 -03:00
Benjamin Eberlei
3fef8d7285 Merge pull request #621 from dbu/event-subscribers
[doc] adding some more doc and examples for lifecycle event listeners and subscribers
2013-04-06 08:04:24 -07:00
Benjamin Eberlei
fe238d03c8 Merge pull request #618 from FabioBatSilva/DDC-2188
[DDC-2188] Fix arithmetic priority
2013-04-06 07:56:36 -07:00
Benjamin Eberlei
3c4a9c8efa Merge pull request #644 from pborreli/typos
Fixed typos
2013-04-06 07:49:04 -07:00
Pascal Borreli
30b050b44c Fixed typos 2013-04-06 14:31:27 +00:00
Benjamin Eberlei
64b2ecfefc [DDC-2224] Rewrite instanceof feature with parameter needle ClassMetadata breaks caching of queries. 2013-04-04 20:22:48 +02:00
Marco Pivetta
edca8c88ea Merge pull request #641 from bksunday/patch-1
Added yml example in ordered-associations.rst
2013-04-03 09:36:19 -07:00
Anthony
1278b79c79 Added yml example in ordered-associations.rst
And modified it to be in a configuration-block instead of separate code-block
2013-04-03 13:31:07 -03:00
denkiryokuhatsuden
7af84e79e5 Fixed postgresql hidden scalar sort 2013-04-03 17:22:31 +09:00
denkiryokuhatsuden
e54c11e3bb Add test for postgresql hidden scalar sorting 2013-04-03 17:21:51 +09:00
denkiryokuhatsuden
786d904328 Revert "Add hidden field ordering for postgresql"
This reverts commit 3e8796f781.
2013-04-03 17:14:31 +09:00
denkiryokuhatsuden
3e8796f781 Add hidden field ordering for postgresql
In postgresql environment, when some hidden fields are used in orderBy clause,
they're not property added because $rsm->scalarMappings don't have information about them.
2013-04-02 18:54:55 +09:00
Asmir Mustafic
3196b0c05a missing new line 2013-04-02 10:14:24 +02:00
Asmir Mustafic
6fc18e330d indexby metadata column 2013-04-02 10:04:15 +02:00
Guilherme Blanco
9b0f252aff Merge pull request #637 from choomz/patch-1
Update association-mapping.rst
2013-03-30 06:48:06 -07:00
Valentin Ferriere
2e8272e18f Update association-mapping.rst 2013-03-29 16:39:55 +01:00
Guilherme Blanco
484d03a5bc Merge pull request #636 from dustinmoorman/rst-file-documentation-fixes
Fixed typos in documentation.
2013-03-27 21:26:24 -07:00
Dustin Moorman
51bcda51c5 Fixed typos in documentation. 2013-03-27 22:43:58 -05:00
Guilherme Blanco
a4db7c8b42 Merge pull request #631 from aaronmu/master
Fix typo in one of the orderBy examples.
2013-03-26 09:18:01 -07:00
Aaron Muylaert
3e8dd1e45c Update query-builder.rst
Fix typo in one of the orderBy examples.
2013-03-26 16:47:41 +01:00
Benjamin Eberlei
5ea9cf418a Merge pull request #627 from chuanma/master
update document on Doctrine cache provider
2013-03-24 13:22:20 -07:00
Benjamin Eberlei
b7e09ecf98 Merge pull request #630 from Ocramius/hotfix/DDC-2359
Hotfix for DDC-2359
2013-03-24 12:23:34 -07:00
Benjamin Eberlei
7f26e9ac27 Bump dev version to 2.4.0 2013-03-24 19:58:19 +01:00
Benjamin Eberlei
827f1f84cb Release 2.4.0-BETA1 2013-03-24 19:58:19 +01:00
Marco Pivetta
7afe5af73a Fix for DDC-2359 2013-03-24 19:42:50 +01:00
Marco Pivetta
57020322cb Adding failing test for DDC-2359
Doctrine\ORM\Mapping\ClassMetadataFactory#wakeupReflection is called twice
2013-03-24 19:40:53 +01:00
Guilherme Blanco
1f89d8ce2f Merge pull request #629 from dustinmoorman/typo-fixes-in-basic-mapping-rst
Fixed typos in Doctrine Mapping Types section.
2013-03-24 09:28:16 -07:00
Guilherme Blanco
99a15377be Merge pull request #628 from franmomu/patch-2
[Docs] Fix field name in inversedby parameter
2013-03-24 09:27:48 -07:00
Dustin Moorman
d4061ff41b Fixed typos in Doctrine Mapping Types section. 2013-03-24 03:16:00 -05:00
Fran Moreno
10c48bad7b [Docs] Fix field name in inversedby parameter 2013-03-24 02:07:05 +01:00
Chuan Ma
94ceb0e410 Document: delete the doc for a number of cache provider methods that don't exist 2013-03-22 17:53:47 -04:00
Guilherme Blanco
24c1b00963 Merge pull request #622 from hrubi/master
Import EntityManager from proper namespace
2013-03-21 09:31:17 -07:00
Jan Hruban
3866472459 Import EntityManager in ConsoleRunner 2013-03-21 15:49:00 +01:00
David Buchmann
204c1afe9a cleanup event subscriber note 2013-03-21 10:43:01 +01:00
David Buchmann
220f367658 adding some more doc and examples for lifecycle event listeners and subscribers 2013-03-20 00:46:11 +01:00
Benjamin Eberlei
60b8bc63a1 Merge pull request #619 from FabioBatSilva/DDC-2090
[DDC-2090] Fix MultiTableUpdateExecutor with query cache
2013-03-17 13:35:34 -07:00
Fabio B. Silva
39ea24675d Fix DDC-2090 2013-03-17 16:59:33 -03:00
Benjamin Eberlei
acae1aeaaa Fix link in docs 2013-03-17 19:43:09 +01:00
Benjamin Eberlei
c399dcfe58 Add simpler section for 2.4 CLI integration 2013-03-17 19:18:49 +01:00
Benjamin Eberlei
ea19a0063f Huge refactoring of the Getting Started Tutorial to allow for an earlier success with the simple Product entity. 2013-03-17 13:43:16 +01:00
Benjamin Eberlei
4ef8e8c7aa Move all classes to src/ in tutorial rather than in entities/ 2013-03-17 12:45:49 +01:00
Benjamin Eberlei
bd964411e8 Remove models/database first tutorials for now 2013-03-17 12:39:04 +01:00
Benjamin Eberlei
5941d0267d Worked on the Getting Started Guide 2013-03-17 12:26:34 +01:00
Benjamin Eberlei
a7d764f6c0 Moved tools to Advanced topic, included simple setting up tools in Installation. Reworked CLI tool to print a template when no cli-config.php is defined. 2013-03-17 12:16:42 +01:00
Benjamin Eberlei
bdfe6098a4 Reworked parts of the tutorial 2013-03-16 23:36:36 +01:00
Benjamin Eberlei
e0d706219b Start reorganizing documentation with focus on simplicity. 2013-03-16 20:36:09 +01:00
Fabio B. Silva
685c96a1b9 Fix arithmetic priority 2013-03-16 14:33:19 -03:00
Fabio B. Silva
1effd38043 Fix DDC-2252 2013-03-16 01:15:54 -03:00
Benjamin Eberlei
6ee9e2284a Merge branch 'DDC-1666' 2013-03-14 23:41:40 +01:00
Benjamin Eberlei
559303430a [DDC-1666] Fix bug where orphan removal on one-to-one associations lead to unique constraint errors when replacing an entity with a new one. 2013-03-14 23:41:31 +01:00
Benjamin Eberlei
d0419782bd [DDC-2300] Fix version xml mapping and serialization of ClassMetadata. 2013-03-14 23:20:23 +01:00
Benjamin Eberlei
4982e2b6b0 Merge pull request #593 from norzechowicz/hydrator-fix
Fix SimpleObjectHydrator behavior when column not exists in fieldMappings, relationMappings and metaMappings
2013-03-14 14:58:52 -07:00
Benjamin Eberlei
b53fffe252 Fix YAML EntityListener definition in tests 2013-03-14 21:18:54 +01:00
Benjamin Eberlei
622ddd8d05 Refix test 2013-03-14 20:12:20 +01:00
Benjamin Eberlei
e128728105 Fix bugs in tests 2013-03-14 20:09:41 +01:00
Benjamin Eberlei
cf2cd549c8 Fix change in signature for hydrator statement mock. 2013-03-14 19:33:21 +01:00
Stefan Kleff
057e86eb27 Added test based on e468ced00b 2013-03-13 12:29:17 +01:00
Benjamin Eberlei
eca468b9d1 [DDC-2340] Fix bug with dirty collection matching + ordering. 2013-03-12 22:49:30 +01:00
Benjamin Eberlei
dba63c5a61 Merge pull request #579 from BenMorel/cleanup
Unit tests: cleanup of outdated / unused / commented out code
2013-03-12 12:00:47 -07:00
Rajesh Sharma
4841a068be [DDC-2304] accept more than 2 parameters in CONCAT function
- Tested and parser validates at least 2 parameters given
- test added for CONCAT function and indentation fixed
- calling getConcatExpression using call_user_func_array as number of arguments is not known removing dependency to patch DBAL
- maintaining backward compatibility
2013-03-12 19:59:45 +01:00
Benjamin Eberlei
fc86a31c10 Merge pull request #591 from Aitboudad/master
Remove dead code
2013-03-12 11:52:23 -07:00
Benjamin Eberlei
ba4705176e Merge pull request #601 from jankramer/add-contains-comparison
Add 'contains' comparison
2013-03-12 11:38:18 -07:00
Benjamin Eberlei
610e1a96f0 Merge pull request #595 from mnapoli/patch-1
Fixed broken code block in documentation
2013-03-12 11:31:52 -07:00
Benjamin Eberlei
0c4f48766a Merge pull request #602 from alex88/patch-1
Added $isIdentifierColumn documentation
2013-03-12 11:30:36 -07:00
Jean-Guilhem Rouel
8b8d1a5aaa Don't add empty expression to another one 2013-03-12 19:17:11 +01:00
Benjamin Eberlei
ce0dd1c4f4 Merge pull request #610 from afoeder/patch-1
[BUGFIX] Correct link to Functions, Operators, Aggregates
2013-03-12 11:13:11 -07:00
Benjamin Eberlei
3ba0562006 Merge branch 'GH-572' 2013-03-12 19:04:27 +01:00
Norbert Orzechowicz
6a69b4700c [DDC-2282] Fix pagination problem with SQL Server.
ORDER BY removed from all count queries when on SQL Server
Fixed SQLServer ORDER BY problem in paginator CountOutputWalker
Added test to check query with ORDER BY and SQLServerPlatform
2013-03-12 19:03:27 +01:00
Benjamin Eberlei
b6c3fc5b1a Mention flush cannot be called inside postFlush 2013-03-12 18:50:45 +01:00
Stefan Kleff
d937d1fc82 Fixed typo in hints. Caused slow loading of eager entities. 2013-03-12 16:17:14 +01:00
Adrian Föder
ca268c9da6 [BUGFIX] Correct link to Functions, Operators, Aggregates
This fixes the in-page link to the Functions, Operators, Aggregates
section.
2013-03-12 15:56:54 +01:00
Guilherme Blanco
905c0b9d91 Merge pull request #609 from haroldb/patch-1
Fixed typo.
2013-03-11 15:42:09 -07:00
Harold Barker
363df46006 Fixed typo. 2013-03-11 22:11:50 +00:00
Guilherme Blanco
d808c5d895 Merge pull request #608 from pborreli/patch-1
Fixed typo
2013-03-10 17:55:41 -07:00
Pascal Borreli
87e06993d6 Fixed typo 2013-03-11 00:31:50 +00:00
Guilherme Blanco
6d85779f4d Merge pull request #607 from pborreli/typos
Fixed typos
2013-03-10 17:25:31 -07:00
Pascal Borreli
a2cd0f5804 Fixed typos 2013-03-11 00:08:58 +00:00
Jan Kramer
9d5e7eb6e9 Added composer.lock to .gitignore 2013-03-07 14:12:10 +01:00
Jan Kramer
760623346c Added 'contains' comparison 2013-03-07 13:32:56 +01:00
Lars Strojny
acc8b61cd1 Adding EntityManagerDecorator base class as an extension point for EntityManager 2013-03-06 23:30:47 +01:00
Alessandro Tagliapietra
9ce4f9806e Added $isIdentifierColumn documentation
I've added the documentation of the argument $isIdentifierColumn since in case of foreign composite keys it doesn't
2013-03-06 10:48:47 +01:00
Benjamin Eberlei
399584db4c Merge pull request #599 from alexcarol/fix/docs
Removed unnecessary "<?php" from the docs
2013-03-04 14:39:01 -08:00
Benjamin Eberlei
a07c9dfbbd Merge pull request #596 from mnapoli/patch-2
Missing link to a cookbook in the docs
2013-03-04 14:18:33 -08:00
Alex Carol
e809f9c266 Removed unnecessary <?php from the docs 2013-03-03 23:14:58 +01:00
Guilherme Blanco
b30d6dfd8e Merge pull request #597 from MDrollette/fix-proxy-interface
use the extended proxy interface in the same namespace
2013-03-03 09:14:30 -08:00
Matthieu Napoli
2eff096ddd Typo 2013-03-02 18:42:38 +01:00
Matthieu Napoli
159daa9985 Reverted incorrect typo fix -_- 2013-03-02 18:40:58 +01:00
MDrollette
1846f5845c alias the proxy class on import to avoid "already exists" error 2013-03-01 11:58:51 -06:00
Matthieu Napoli
32dd7f1a0e Missing link to a cookbook in the docs
The cookbook existed in the docs but there was no link to it in the docs.
2013-03-01 15:16:59 +01:00
Matthieu Napoli
20b46fe17f Fixed broken code block in documentation 2013-03-01 15:11:21 +01:00
Guilherme Blanco
2372a85d9f Merge pull request #594 from v3labs/master
Use inflector in EntityGenerator
2013-02-28 16:02:20 -08:00
Vladislav Veselinov
30fd22a260 Use inflector for add/remove methods 2013-02-28 21:05:41 +02:00
Norbert Orzechowicz
f9519479fc Fix SimpleObjectHydrator behavior when column not exists in fieldMappings, relationMappings and metaMappings 2013-02-28 10:10:28 +01:00
Benjamin Eberlei
e5779a0756 Fix versionadded for IDENTITY() 2013-02-27 12:47:02 +01:00
Benjamin Eberlei
61e4413541 Update theme 2013-02-27 12:40:39 +01:00
Benjamin Eberlei
4f46a08d65 Dont mention 2.2 version in headers anymore. 2013-02-27 12:37:26 +01:00
Benjamin Eberlei
f001c31342 Add versionadded tags to many ffeatures in the docs. 2013-02-27 12:34:10 +01:00
Benjamin Eberlei
6b85d5b5ac Clarified versionadded on EntityListeners feature. 2013-02-27 12:19:08 +01:00
Guilherme Blanco
d5dd7d6f8a Merge pull request #589 from Ocramius/hotfix/DDC-2230
Hotfix for DDC-2230
2013-02-25 21:59:04 -08:00
Guilherme Blanco
32c220497c Merge pull request #585 from Ocramius/hotfix/DDC-2306
Hotfix/DDC-2306
2013-02-25 21:55:44 -08:00
Abdellatif Ait boudad
5206566707 Remove dead code 2013-02-26 00:02:34 +00:00
Marco Pivetta
350fa4f15b Applying fix for failing test for DDC-2230 2013-02-23 01:45:40 +01:00
Marco Pivetta
be24439e2f Adding failing test for DDC-2230
Proxies that implement the Doctrine\Common\PropertyChangedListener are getting eagerly
initialized because the UnitOfWork injects itself into them after they are created.
The test currently fails for what described above, and also verifies if the UoW
is correctly injected in the proxy during lazy loading.
2013-02-23 01:44:58 +01:00
Guilherme Blanco
97ff197198 Merge pull request #587 from stof/patch-1
Fixed the license and the added version for NewObjectExpression
2013-02-22 09:10:36 -08:00
Christophe Coevoet
16b407f535 Fixed the license and the added version 2013-02-22 17:57:50 +01:00
Benjamin Eberlei
8cfbe0c032 Merge pull request #586 from jsjohns/patch-1
Fix EntityManager doc
2013-02-21 14:56:59 -08:00
Joshua Johnson
b55d78e119 Fix EntityManager doc 2013-02-21 17:27:55 -05:00
Benjamin Eberlei
04b216426a Merge branch 'DDC-2310' 2013-02-21 19:02:37 +01:00
Benjamin Eberlei
8fce78fbfb [DDC-2310] Fix regression introduced in SQL Server lock handling. 2013-02-21 19:02:21 +01:00
Marco Pivetta
a5ece5063a Fixing DDC-2306 2013-02-21 02:24:48 +01:00
Marco Pivetta
d8dd5129e7 Failing test for DDC-2306
As of DDC-1734, proxies should have null identifier when the UnitOFWork
refreshes entities and replaces them (marking them as un-managed).
The problem here is that refreshes on entities with same identifier
but different type are still considered same, and the UnitOfWork discards
the proxy instance instead of ignoring it.
2013-02-21 02:21:06 +01:00
Benjamin Eberlei
2980d76adb Merge pull request #581 from Ocramius/hotfix/submodules-removal
Removing submodules
2013-02-15 09:41:38 -08:00
Marco Pivetta
3f16ec0d22 Removing submodules as of doctrine/doctrine2#570 2013-02-15 18:33:29 +01:00
Benjamin Eberlei
805bb5ff9f Remove mentions of PEAR installation method, remove code. 2013-02-15 01:04:29 +01:00
Benjamin Eberlei
6b928600ba Add vendor to .gitignore, add composer.lock and some weird submodule change 2013-02-15 00:49:48 +01:00
Benjamin Eberlei
19da1933a6 Fix dropping the theme folder. 2013-02-15 00:34:48 +01:00
Benjamin Eberlei
afee16e56b Merge pull request #406 from Ocramius/DCOM-96
[DCOM-96] Moving proxy generation and autoloading to common
2013-02-14 02:04:32 -08:00
Marco Pivetta
a58d4ae462 Cleaning up logic of the proxy factory by moving closure generation to own private methods 2013-02-14 10:52:13 +01:00
Marco Pivetta
271f5cf033 Adding fix and tests for DDC-1734 2013-02-14 09:57:12 +01:00
Marco Pivetta
8272ffd23f Proxy generation as of doctrine/common#168 - DCOM-96 2013-02-14 09:57:12 +01:00
Benjamin Eberlei
35fda90473 Merge pull request #570 from Ocramius/cleanup/submodules-removal
Deprecation of PEAR/GIT/TAR autoloading
2013-02-14 00:55:08 -08:00
Benjamin Eberlei
ce594fb152 [DDC-2256] Cleanup patch, move dependency on EntityManager out of ResultSetMapping and let AbstractQuery perform the translation. 2013-02-13 09:05:35 +01:00
Benjamin Eberlei
3d9cb9460a Fix test failing when memcache extension is installed, but memcache server isnt. 2013-02-13 08:46:37 +01:00
Fabio B. Silva
719031f2ef Merge pull request #569 from Ocramius/hotfix/pre-flush-event-args-params
Hotfix/pre flush event args params
2013-02-12 15:39:40 -08:00
Guilherme Blanco
39374b7235 Merge pull request #578 from BenMorel/docfix
Fix for wrong return types in documentation.
2013-02-12 09:05:13 -08:00
Benjamin Morel
35562d3a4d Unit tests: cleaned up outdated / unused / commented out code. 2013-02-12 15:51:24 +00:00
Benjamin Morel
cba1c8295c Fixed wrong return types in documentation. 2013-02-12 11:49:44 +00:00
Benjamin Eberlei
d08c010ae1 Merge pull request #576 from acasademont/patch-1
Update docs/en/reference/batch-processing.rst
2013-02-11 07:50:57 -08:00
Benjamin Eberlei
e86419cfa5 Merge pull request #575 from naitsirch/master
Added YAML configuration example for "Simple Derived Identity" in Docs
2013-02-11 07:50:08 -08:00
Alexander
14bc7f75be Add php 5.5 to the build matrix (travis) 2013-02-09 22:44:14 +01:00
Alexander
1a163cd48d [DDC-2019] Add support for expression in QueryBuilder#addOrderBy() 2013-02-09 22:40:34 +01:00
Alexander
9bf501dd25 Revert "allowed to pass filter objects to the configurator"
This reverts commit a9b4debe37. See the
discussion on the original PR:

https://github.com/doctrine/doctrine2/pull/434

Conflicts:
	lib/Doctrine/ORM/Configuration.php
2013-02-09 20:28:52 +01:00
Albert Casademont
4c90d0cedc Update docs/en/reference/batch-processing.rst
If you have only $batchSize - 1 rows (amongst other cases), the entities are never flushed, you need a final flush outside the loop.

In the bulk insert you should also need a final flush if the number of entities inserted is not a multiple of the $batchSize.
2013-02-08 16:32:03 +01:00
Christian Stoller
07616094d2 Added YAML configuration example for "Simple Derived Identity" in "Composite and Foreign Keys as Primary Key" Tutorial 2013-02-08 11:21:21 +01:00
Guilherme Blanco
3b0a242ab3 Merge pull request #573 from EvanK/patch-1
[Documentation] Noted prePersist event only triggers on first persist
2013-02-07 18:27:05 -08:00
Evan Kaufman
e2ac064914 Noted prePersist event only triggers on first persist
While probably obvious to a core doctrine developer, a user of doctrine would not necessarily know this without doing some digging
2013-02-07 12:59:14 -06:00
Marco Pivetta
673323fc67 Adding warnings about deprecation of GIT, TAR and PEAR autoloading 2013-02-04 23:30:02 +01:00
Marco Pivetta
a928ce48da Using composer autoloader for the test suite 2013-02-04 21:30:02 +01:00
Marco Pivetta
f281dbbf54 Fixing incorrect constructor params for PreFlushEventArgs 2013-02-04 20:46:51 +01:00
Marco Pivetta
3ebed101fd Strong typehinting to avoid incorrect constructor params 2013-02-04 20:45:58 +01:00
Guilherme Blanco
f0674ea034 Merge pull request #564 from BenMorel/f834c37f8a465ca3e23ee9ae62ef0bc4a525454c
Fix a wrong return type
2013-02-02 20:06:44 -08:00
Guilherme Blanco
114827a4b3 Merge pull request #565 from BenMorel/unusedvar
Removed an unused local variable.
2013-02-02 20:05:42 -08:00
Guilherme Blanco
d3cbdfcafa Merge pull request #566 from BenMorel/toolreturntype
Added missing return statement to AbstractCommand.
2013-02-02 20:04:30 -08:00
Guilherme Blanco
ef1ed588b5 Merge pull request #567 from BenMorel/uselessmethods
Removed outdated methods in DatabasePlatformMock
2013-02-02 20:03:26 -08:00
Benjamin Morel
ec1b47a3e8 Removed outdated methods in DatabasePlatformMock. 2013-02-03 01:51:05 +00:00
Benjamin Morel
2bfbe03e37 Fixed return statements in SchemaTool. 2013-02-03 01:42:27 +00:00
Benjamin Morel
4b58c6fc41 Removed an unused local variable. 2013-02-03 01:05:43 +00:00
Benjamin Morel
f834c37f8a Fixed a wrong return type. 2013-02-03 00:48:05 +00:00
Benjamin Eberlei
71a68a5c6f Merge pull request #423 from FabioBatSilva/DDC-1955
DDC-1955 - @EntityListeners
2013-02-02 11:47:46 -08:00
Guilherme Blanco
dea37ed9e8 Merge pull request #554 from beregond/hydrator-fix
Fixed ObjectHydrator when namespace alias is given.
2013-02-02 11:18:00 -08:00
Guilherme Blanco
abc3ba0c7e Merge pull request #561 from ftdebugger/master
fix typo in the documentation
2013-02-02 11:15:47 -08:00
Guilherme Blanco
4651d92d63 Merge pull request #562 from vrana/master
Fix error in QueryBuilder example
2013-02-02 11:15:30 -08:00
Guilherme Blanco
1627fc9596 Merge pull request #563 from FabioBatSilva/DDC-2268
DDC-2268 - Regression test
2013-02-02 11:15:14 -08:00
Fabio B. Silva
452e6912b1 DDC-2268 - regression test 2013-02-02 16:57:17 -02:00
Fabio B. Silva
c5aecd43c8 Fix yaml example 2013-02-02 16:19:35 -02:00
Fabio B. Silva
7764ed9a8b Docs for Entity Listener and Entity Listener Resolver 2013-02-02 15:48:40 -02:00
Jakub Vrana
819e5896bf Fix error in QueryBuilder example 2013-01-30 11:11:09 -08:00
Evgeny Shpilevsky
d65ba04c5c Fix typo in docs 2013-01-30 17:59:38 +03:00
Fabio B. Silva
76c4be1b74 Update docs/en/reference/events.rst
Docs for lifecycle-callback event arg
2013-01-29 12:19:47 -02:00
Fabio B. Silva
7177306536 remove extra comma 2013-01-29 12:14:53 -02:00
Fabio B. Silva
ec2d5af2c7 added missing file 2013-01-29 12:14:53 -02:00
Fabio B. Silva
e6d9d1de47 support naming convention for listeners without mapping. 2013-01-29 12:14:53 -02:00
Fabio B. Silva
46fea51622 use '!==' instead of '!=' 2013-01-29 12:14:53 -02:00
Fabio B. Silva
e9c89cafb9 Fix DocBlock 2013-01-29 12:14:53 -02:00
Fabio B. Silva
3f9a4c82b0 Fix typo 2013-01-29 12:14:53 -02:00
Fabio B. Silva
6d7b3863b5 Use bitmask of subscribed event systems. 2013-01-29 12:14:53 -02:00
Fabio B. Silva
7b0f59ed7c split override test 2013-01-29 12:14:53 -02:00
Fabio B. Silva
0d0fc320b4 Fix DocBlock 2013-01-29 12:14:53 -02:00
Fabio B. Silva
0d0f91a807 move listeners invocation from ClassMetadataInfo to ListenerInvoker 2013-01-29 12:14:53 -02:00
Fabio B. Silva
c60e3e4ba4 remove @LifecycleCallback 2013-01-29 12:14:52 -02:00
Fabio B. Silva
ffc8d032c7 Fix typo 2013-01-29 12:14:52 -02:00
Fabio B. Silva
195b639344 change xml driver to use <lifecycle-callback\> 2013-01-29 12:14:52 -02:00
Fabio B. Silva
6b7e588da5 fix CS 2013-01-29 12:14:52 -02:00
Fabio B. Silva
4be25cb330 small refactoring 2013-01-29 12:14:52 -02:00
Fabio B. Silva
8495eca1a4 rename test 2013-01-29 12:14:52 -02:00
Fabio B. Silva
a01d6583d3 implements a entity listener resolver 2013-01-29 12:14:52 -02:00
Fabio B. Silva
27745bb87b Fix some CS 2013-01-29 12:14:52 -02:00
Fabio B. Silva
a265511368 rename subscribers to listeners 2013-01-29 12:14:52 -02:00
Fabio B. Silva
46474bf457 added doctrine-mapping.xsd 2013-01-29 12:14:52 -02:00
Fabio B. Silva
69bfc71b6a test event listeners lifecycle callback 2013-01-29 12:14:52 -02:00
Fabio B. Silva
256cecbefa evaluate as lifecycle callback if the listener class is not given. 2013-01-29 12:14:52 -02:00
Fabio B. Silva
fd6f592430 support @LifecycleCallback in @EntityListeners 2013-01-29 12:14:52 -02:00
Fabio B. Silva
7021f002f2 php driver 2013-01-29 12:14:52 -02:00
Fabio B. Silva
415c2a95f2 php static driver 2013-01-29 12:14:52 -02:00
Fabio B. Silva
f0b04375de yaml driver 2013-01-29 12:14:52 -02:00
Fabio B. Silva
917aa70c97 test invalid class/method 2013-01-29 12:14:52 -02:00
Fabio B. Silva
7e54ae3702 xml driver 2013-01-29 12:14:52 -02:00
Fabio B. Silva
6be7a03b72 fix previous test 2013-01-29 12:14:52 -02:00
Fabio B. Silva
4cfe2294e3 test lifecycle callbacks event args 2013-01-29 12:14:52 -02:00
Fabio B. Silva
c6adcda567 give event to lifecycle callbacks 2013-01-29 12:14:52 -02:00
Fabio B. Silva
dbd0697c2c test @PostLoad 2013-01-29 12:14:51 -02:00
Fabio B. Silva
315f7ba43b call listeners in UoW 2013-01-29 12:14:51 -02:00
Fabio B. Silva
ccc0a2a94f test entity listener calls 2013-01-29 12:14:51 -02:00
Fabio B. Silva
c5d59ab4c7 test entity listener metadata 2013-01-29 12:14:51 -02:00
Fabio B. Silva
3c223a59c4 move call listeners tests to AbstractMappingDriverTest 2013-01-29 12:14:51 -02:00
Fabio B. Silva
0f081d7c45 support short class name 2013-01-29 12:14:51 -02:00
Fabio B. Silva
368cf73f89 entity listeners mapping 2013-01-29 12:14:51 -02:00
Guilherme Blanco
3d1956d260 Merge pull request #559 from Stroitel/fix-paginator
[Paginator] Added support for order by scalar
2013-01-27 08:58:14 -08:00
aleks
206c251090 Add test with mixed sort 2013-01-27 11:40:40 +02:00
aleks
dc190a297d Fix typo 2013-01-27 11:09:26 +02:00
Guilherme Blanco
8eee325db4 Merge pull request #558 from pkruithof/master
Added missing mapping types
2013-01-26 15:32:22 -08:00
aleks
6662096ed3 Fix typo 2013-01-26 23:27:38 +02:00
aleks
5e6bc0847f Added support for order by scalar 2013-01-26 21:31:45 +02:00
aleks
61634950f3 Add test for order by scalar 2013-01-26 21:21:09 +02:00
Peter Kruithof
46f4b00f8d Added missing mapping types 2013-01-26 16:18:45 +01:00
Szczepan Cieślik
92ada246b5 [DDC-2256] Code improvements. 2013-01-26 09:09:21 +01:00
Szczepan Cieślik
935594578a [DDC-2256] Code refactorization. 2013-01-25 13:15:14 +01:00
Szczepan Cieślik
d7f82221d1 [DDC-2256] Moved aliases translation to ResultSetMapping, fixed tests. 2013-01-25 12:06:13 +01:00
Szczepan Cieślik
1949ff8602 [DDC-2256] Added test for hydrator. 2013-01-24 23:10:50 +01:00
Szczepan Cieślik
23e0bb7345 Fixed ObjectHydrator when namespace alias is given. 2013-01-24 15:50:05 +01:00
Benjamin Eberlei
f5f583d1cc Fix .gitmodules 2013-01-24 00:17:32 +01:00
Benjamin Eberlei
cbcc693e36 Add 'docs/' from commit '8fcf2d45019bf38a1df728353a1e417343c69cfb'
git-subtree-dir: docs
git-subtree-mainline: 271bd37ad3
git-subtree-split: 8fcf2d4501
2013-01-24 00:02:03 +01:00
Benjamin Eberlei
8fcf2d4501 Merge pull request #143 from relo-san/patch-1
Update en/reference/query-builder.rst
2013-01-23 14:51:49 -08:00
Benjamin Eberlei
94526ab602 Merge pull request #145 from HoffmannP/master
key for yaml-files was changed, know Idea what it might affect as well
2013-01-23 14:51:12 -08:00
Benjamin Eberlei
a512c7f89a Merge pull request #152 from pkruithof/patch-1
Documented `guid` mapping type
2013-01-23 14:49:27 -08:00
Peter Kruithof
f5ba83cae5 Documented guid mapping type
refs #456a756febb258b52092fa2640c77bb8400114fa
2013-01-21 15:03:46 +01:00
Benjamin Eberlei
271bd37ad3 Merge branch 'DDC-2243' 2013-01-20 20:32:54 +01:00
Benjamin Eberlei
eedf85cbdb [DDC-2243] Fix bug where a bigint identifier would be casted to an integer, causing inconsistency with the string handling. 2013-01-20 20:31:22 +01:00
Benjamin Eberlei
3c157eafd5 Merge branch 'DDC-2246' 2013-01-20 20:11:20 +01:00
Benjamin Eberlei
5298c03fce [DDC-2246] Fix bug with UnitOfWork#getEntityState() and entities with foreign identifier. 2013-01-20 20:11:08 +01:00
Benjamin Eberlei
916424af49 Merge pull request #551 from FabioBatSilva/DDC-2234
[DDC-2234] FUNCTION() IS NULL comparison
2013-01-20 03:30:01 -08:00
Benjamin Eberlei
d0810c7c19 Remove README.markdown from .gitattributes 2013-01-19 20:04:43 +01:00
Benjamin Eberlei
3a4331db89 Merge pull request #543 from carlosbuenosvinos/master
Make doctrine a Light-weight distribution package in Composer
2013-01-19 11:04:16 -08:00
Fabio B. Silva
93fba518a6 keep braces 2013-01-19 13:38:44 -02:00
Fabio B. Silva
1d42a5385b test NOT EXISTS expression 2013-01-18 23:47:31 -02:00
Fabio B. Silva
4dcd5a1286 Fix DDC-2234 2013-01-18 23:18:50 -02:00
Jonathan H. Wage
c4b0bc5adc Merge pull request #150 from shiroyuki/patch-1
Correct the usage for "nullable"
2013-01-16 13:00:42 -08:00
Juti Noppornpitak
ec23961c7d Update en/cookbook/decorator-pattern.rst 2013-01-16 15:48:15 -05:00
Jonathan H. Wage
27ac88fe28 Update theme again. 2013-01-14 20:12:20 -06:00
Jonathan H. Wage
b369158dc6 Upgrade theme. 2013-01-14 20:08:25 -06:00
Jonathan H. Wage
16533299b0 Update en/reference/installation.rst
Remove reference to download page as it does not exist anymore. We are no longer maintaining packages for download in any proprietary Doctrine repository.
2013-01-14 18:47:18 -06:00
Guilherme Blanco
b30b06852b Merge pull request #549 from FabioBatSilva/DDC-1376
[DDC-1376] Support for order by association when using findBy
2013-01-13 17:16:50 -08:00
Fabio B. Silva
6074755b91 Fix DDC-1376 2013-01-13 22:39:59 -02:00
Jonathan H. Wage
8230b270e3 Merge pull request #148 from FabioBatSilva/composite-identity
Docs for IDENTITY() composite primary key
2013-01-13 10:14:43 -08:00
Jonathan H. Wage
66774f8fe0 Merge pull request #149 from FabioBatSilva/new-operator
Docs for “NEW” Operator Syntax
2013-01-13 10:14:32 -08:00
Fabio B. Silva
6a23e6be96 Docs for “NEW” Operator Syntax 2013-01-13 15:56:11 -02:00
Fabio B. Silva
939ca1b85f Docs for IDENTITY() composite primary key 2013-01-13 15:08:25 -02:00
Benjamin Eberlei
47043a54a5 Update en/reference/filters.rst 2013-01-13 09:37:45 +01:00
Benjamin Eberlei
6f572a61c7 Merge branch 'DDC-2231' 2013-01-12 10:29:20 +01:00
Benjamin Eberlei
3ccbbcb0b5 DDC-2231 - Simplify test 2013-01-12 10:29:11 +01:00
Guilherme Blanco
71efe2109a Merge pull request #548 from nemekzg/DDC-2203
Fix for DDC-2203
2013-01-10 11:13:18 -08:00
nemekzg
cfd1b07ffe Fix for DDC-2203 2013-01-10 16:52:19 +01:00
Stefan Kleff
6032e3efd8 Added test 2013-01-10 16:10:29 +01:00
Stefan Kleff
dc925cc9c5 fixed indentation
Restored old way of injection to just inject it during a refresh
Added injection for initialized proxies
2013-01-10 15:19:44 +01:00
Stefan Kleff
151192ae37 The EntityManager was not injected in uninitialized proxys which are ObjectManagerAware.
I ran into that problem while I had two objects in the identitymap while hydrating a collection: one was new a "real" entity and the other one was an uninitialized proxy. For "real" entities the em is injected in line 2427, for new entities it is injected in 2436->2364, but for proxies this is missing. According to the comment "inject ObjectManager into just loaded proxies." the code in line 2427 should do this, but in fact it is just used if it is a "real" entity or an already initialized proxy. Moving the injection to outside of the condition in line 2411 (if the entity is an unitialized proxy) solves this.
2013-01-10 14:54:52 +01:00
Benjamin Eberlei
0b2d3d4f5d DDC-2173 - Correct issue is about "postFlush" not "preFlush" and add test 2013-01-06 19:16:12 +01:00
Benjamin Eberlei
c20cfed6ae Merge branch 'DDC-2173' 2013-01-06 19:12:02 +01:00
Benjamin Eberlei
512a001e8c DDC-2173 - Add Test for new OnFlush or PreFlush behavior and update UPGRADE.md 2013-01-06 19:11:52 +01:00
Francesc Rosàs
1e669132c2 No huge if clause 2013-01-06 19:05:46 +01:00
Francesc Rosàs
9322ca7052 Ensure onFlush and postFlush events are always raised 2013-01-06 19:05:46 +01:00
Benjamin Eberlei
ce290bc99b DDC-1698 - Allow autoload registration from a Configuration instance 2013-01-06 11:07:19 +01:00
Benjamin Eberlei
7dfe0cae08 DDC-1698 - Prepend autoloader to stack and fix CS 2013-01-06 11:03:58 +01:00
Benjamin Eberlei
4210969087 DDC-2192 - Prevent using append flag in case of where and having to avoid user confusion, because this is not allowed. 2013-01-06 10:33:57 +01:00
Benjamin Eberlei
32f4be83b1 Fix composer to only allow DBAL 2.4 DEV and larger 2013-01-06 10:06:03 +01:00
Guilherme Blanco
fbd1e7bc45 Merge pull request #147 from aaronmu/master
Fix syntax error in one of the orderBy examples.
2013-01-04 06:22:22 -08:00
Aaron Muylaert
b0a24c8baa Update en/reference/query-builder.rst
Fix syntax error in one of the orderBy examples.
2013-01-04 11:29:34 +01:00
Carlos Buenosvinos
1b5d4316fe Update .gitattributes
Fix TYPO
2012-12-29 21:08:21 +01:00
Carlos Buenosvinos
2eb4849a69 XSD, license and upgrade should be distributed 2012-12-28 16:55:49 +01:00
Carlos Buenosvinos
9354e70fd3 Make doctrine a light-weight package based in http://getcomposer.org/doc/02-libraries.md#light-weight-distribution-packages 2012-12-28 16:46:41 +01:00
Carlos Buenosvinos
0577f73ef5 Make doctrine light-weight for composer 2012-12-28 16:41:38 +01:00
Benjamin Eberlei
90b6d5e293 Merge branch 'master' of github.com:doctrine/doctrine2 2012-12-24 10:40:52 +01:00
Benjamin Eberlei
59ffbd5f8d Merge branch 'DDC-2175' 2012-12-24 10:40:10 +01:00
Benjamin Eberlei
e2c1ff1a48 [DDC-2175] Fix bug in JoinedSubclassPersister 2012-12-24 10:39:23 +01:00
Benjamin Eberlei
904effcf4e Merge pull request #538 from FabioBatSilva/identity-composite-key
IDENTITY() Support composite primary key
2012-12-24 01:19:05 -08:00
Benjamin Eberlei
7cf26950cc Merge branch 'DDC-2187' 2012-12-24 10:15:47 +01:00
Klein Florian
8443eee628 use base events 2012-12-24 10:15:25 +01:00
Benjamin Eberlei
e319e34783 Merge pull request #528 from BenMorel/master
Documentation fixes
2012-12-23 12:22:09 -08:00
Benjamin Eberlei
d8b94b0527 Some text refactorings in tutorial 2012-12-23 13:05:54 +01:00
Fabio B. Silva
3d99711ac8 fix PHPDoc 2012-12-22 10:36:23 -02:00
Fabio B. Silva
99ab58febd Fix CS 2012-12-22 10:36:23 -02:00
Fabio B. Silva
2e90cd9924 Identity function support composite primary key 2012-12-22 10:36:23 -02:00
Benjamin Eberlei
6f5948746e Merge pull request #540 from FabioBatSilva/DDC-2208
[DDC-2208] Fix DDC-2208
2012-12-22 03:53:14 -08:00
Benjamin Eberlei
015771f10b Merge pull request #541 from PSchwisow/master
Fix DDC-1690
2012-12-22 03:38:03 -08:00
Patrick Schwisow
0b21046fce [DDC-1690] Added an empty line as requested. 2012-12-21 15:35:32 -06:00
Patrick Schwisow
bc6921504a [DDC-1690] Created unit test 2012-12-21 14:32:10 -06:00
Fabio B. Silva
b6b493f450 test parentheses 2012-12-21 10:00:40 -02:00
Fabio B. Silva
eda43c77bb Fix DDC-2208 2012-12-21 10:00:40 -02:00
Guilherme Blanco
a8340cc980 Merge pull request #146 from noginn/patch-1
Added missing "new" keyword for event listener
2012-12-21 03:46:51 -08:00
Guilherme Blanco
8b5e4a9a52 Merge pull request #542 from FabioBatSilva/DDC-2205
[DDC-2205] Fix DDC-2205
2012-12-21 03:45:07 -08:00
Fabio B. Silva
2104ae9935 fix DDC-2205 2012-12-20 23:06:30 -02:00
Patrick Schwisow
5627993827 Fix DDC-1690
Added the lines suggested by the original reporter.
2012-12-20 15:31:46 -06:00
Tom Graham
e17fc6c474 Added missing "new" keyword for event listener 2012-12-20 20:00:50 +00:00
Thomas Rothe
86c33d78d0 inheritance with composite keys
added tests for single table inheritance
2012-12-17 11:01:20 +01:00
Thomas Rothe
fb055ca75d fixed problems with joined inheritance and composite keys
SchemaTool now creates all Id columns not just only the first one.
Insert statement for child entity now contains parameter for additional key columns only once.
2012-12-16 18:20:10 +01:00
Benjamin Eberlei
4bbfe0ce8a Merge pull request #536 from KonstantinKuklin/master
add missed branch
2012-12-16 04:05:57 -08:00
Benjamin Eberlei
b6fd203355 Merge branch 'DDC-2199' 2012-12-16 12:58:01 +01:00
Benjamin Eberlei
7c337748b6 DDC-2199 / DDC-2192 - Versioned fields didnt work in XML/YAML mapping 2012-12-16 12:57:53 +01:00
Benjamin Morel
774bb3fec4 Fixed missed documentation issues in Doctrine\ORM 2012-12-14 20:12:56 +00:00
Benjamin Morel
aadce3c747 Fixed documentation for Doctrine\Tests 2012-12-14 18:55:49 +00:00
Benjamin Morel
c405f6d3f6 Fixed documentation for Doctrine\Tests\Mocks 2012-12-14 18:55:28 +00:00
Benjamin Morel
76f2ba50eb Fixed documentation for Doctrine\Tests\DbalTypes 2012-12-14 18:55:16 +00:00
Konstantin Kuklin
8e2c060fc7 add missed branch 2012-12-14 18:04:19 +04:00
Benjamin Morel
ad967e8e22 Fixed documentation for Doctrine\ORM\Tools\Pagination 2012-12-14 13:13:22 +00:00
Benjamin Morel
2524c878b6 Fixed documentation for Doctrine\ORM\Tools\Export 2012-12-14 13:02:12 +00:00
Benjamin Morel
0122d8d36c Fixed documentation for Doctrine\ORM\Tools\Event 2012-12-14 12:49:59 +00:00
Benjamin Morel
71e78014e5 Fixed documentation for Doctrine\ORM\Tools\Console 2012-12-14 12:49:05 +00:00
Benjamin Morel
e72445b836 Fixed documentation for Doctrine\ORM\Tools 2012-12-13 18:55:44 +00:00
Benjamin Morel
7869ec714d Fixed unused 'use' statements.
Fixed missed documentation issues in Doctrine\ORM
2012-12-13 18:19:21 +00:00
Peter Hoffmann
4c1759eecb Keyname was obviously changed in (latest) 2.3.1, cost me some hours to figure out, NOT cool! 2012-12-13 17:53:44 +01:00
Benjamin Morel
ba16789843 Fixed documentation for Doctrine\ORM\Query 2012-12-13 16:36:14 +00:00
Benjamin Morel
28966e2087 Fixed documentation for Doctrine\ORM\Query\Expr and Doctrine\ORM\Query\Filter 2012-12-13 12:05:34 +00:00
Benjamin Morel
d4357801a2 Fixed documentation for Doctrine\ORM\Query\Exec 2012-12-13 11:57:46 +00:00
Benjamin Morel
98ac6b5fec Fixed documentation for Doctrine\ORM\Query\AST 2012-12-13 11:52:19 +00:00
Benjamin Morel
f743da0e02 Fixed documentation for Doctrine\ORM\Query\AST\Functions 2012-12-13 10:42:25 +00:00
Benjamin Morel
42e83a2716 Fixed documentation for ORM\Proxy 2012-12-13 10:28:55 +00:00
Mykola Zyk
ca1c6498ec Update en/reference/query-builder.rst
Adding note for $append parameter of QueryBuilder::add() method.
2012-12-07 19:44:47 +02:00
Benjamin Eberlei
df1336dd26 Merge pull request #529 from francisbesset/options_join_columns
[DDC-2182] Options join columns
2012-12-04 04:29:01 -08:00
Francis Besset
657a54da84 Passed column options to the join column 2012-12-04 12:26:40 +01:00
Francis Besset
df2bfbb636 Fixed trailing spaces on SchemaTool 2012-12-04 12:23:55 +01:00
Benjamin Morel
43b301f22b Fixed English mistakes 2012-12-03 11:02:29 +00:00
Benjamin Morel
159ece8943 Fixed documentation for ORM\Persisters 2012-12-03 10:15:43 +00:00
Benjamin Morel
46e6ada4f9 Fixed documentation for ORM\Mapping 2012-12-03 09:36:08 +00:00
Guilherme Blanco
4b4126dfa6 Merge pull request #142 from pborreli/typos
Fixed typos
2012-12-02 12:41:07 -08:00
Benjamin Morel
4714a53c32 Fixed documentation for ORM\Internal 2012-12-02 19:46:34 +00:00
Pascal Borreli
8717088ed1 Fixed typos 2012-12-02 17:58:15 +00:00
Benjamin Morel
fad22d1e60 Fixed documentation for ORM\Event and ORM\Id 2012-12-02 12:37:56 +00:00
Francis Besset
56b230a1f0 Fixed typo 2012-12-01 19:41:51 +01:00
Benjamin Morel
dacdd6cd89 Documentation (docblock) fixes. 2012-12-01 16:28:06 +00:00
Guilherme Blanco
4bb6ff637c Merge pull request #527 from BenMorel/master
Fix documentation for Doctrine\ORM\Query\AST\Node::dispatch()
2012-11-30 12:43:49 -08:00
Benjamin Morel
26d6f5ce4e Fixed coding standard issue, as per @stof's request. 2012-11-30 19:10:53 +00:00
Benjamin Morel
5edc287848 Fixed documentation for Doctrine\ORM\Query\AST\Node::dispatch(). 2012-11-30 11:41:03 +00:00
Benjamin Eberlei
95334e9764 Clarify BC break in DDC-2156 2012-11-27 22:25:58 +01:00
Guilherme Blanco
29f0b678cf Merge pull request #526 from FabioBatSilva/DDC-2172
[DDC-2172] Fix EntityGenerator get literal type
2012-11-27 12:14:12 -08:00
Guilherme Blanco
f4c0fd1744 Added support to subselects in update item. 2012-11-27 14:36:56 -05:00
Fabio B. Silva
bac92f4d3e Fix CS 2012-11-27 16:58:48 -02:00
Fabio B. Silva
d1dc72b65a refactoring tests 2012-11-27 14:38:18 -02:00
Fabio B. Silva
41b907606f Fix DDC-2172 2012-11-27 14:29:02 -02:00
Benjamin Eberlei
46e6753649 Merge pull request #523 from jankramer/DDC-2074
[DDC-2074] Bugfix regarding clearing cloned PersistentCollections
2012-11-25 11:13:28 -08:00
Jan Kramer
5b3f54429a [DDC-2074] Fixed bug regarding clearing PC's without owner
When calling clear on a PC that has no owner (e.g. because it was
cloned), it can't be deleted as there is no metadata available.
In these cases, it shouldn't be scheduled for deletion.
2012-11-25 19:27:39 +01:00
Jan Kramer
9b78100378 [DDC-2074] Added test for PersistentCollection#clear. 2012-11-25 19:27:24 +01:00
Benjamin Eberlei
05e5ae8bfa Merge pull request #413 from FabioBatSilva/refactory-persisters
code refactorings on persister
2012-11-25 03:50:52 -08:00
Benjamin Eberlei
173333c861 Merge pull request #517 from nemekzg/DDC-1765
Fix for DDC-1765
2012-11-25 03:38:39 -08:00
Benjamin Eberlei
072d8a8a13 Merge pull request #515 from tranver/master
Fixes sandbox cli: The helper "em" is not defined.
2012-11-25 03:38:03 -08:00
Benjamin Eberlei
8457a9e77e Merge pull request #518 from Fran6co/fix-double-on
regression fix for left joins (double ON)
2012-11-25 03:27:17 -08:00
Benjamin Eberlei
390fbd8493 Merge pull request #87 from FabioBatSilva/patch-6
docs for association/attribute override
2012-11-24 02:34:56 -08:00
Benjamin Eberlei
1f2050f20d Merge pull request #101 from merk/rtel
Initial ResolveTargetEntityListener cookbook entry
2012-11-24 02:34:36 -08:00
Benjamin Eberlei
7f0ff6a079 Merge pull request #104 from shieldo/missing_joincolumn_in_yaml
added missing JoinColumn node for xml and yaml for many-to-one unidirect...
2012-11-24 02:34:17 -08:00
Benjamin Eberlei
bc9c7d1c07 Merge pull request #117 from cordoval/feature/addTutorialOverrideMappingAnnotations
[DDC-1872] add tutorial for override annotations mappings
2012-11-24 02:33:58 -08:00
Benjamin Eberlei
9f86cda681 Merge pull request #119 from Spabby/master
Cleaner to remove the note, and modify the code to put the doctrine_bootstrap.php include first?
2012-11-24 02:25:17 -08:00
Benjamin Eberlei
52bc9a82ef Merge pull request #121 from frosas/document-pre-and-post-flush-events
Remove "before the lists of scheduled changes are cleared"
2012-11-24 02:24:57 -08:00
Benjamin Eberlei
d84462923c Merge pull request #122 from kdambekalns/patch-1
Update en/reference/working-with-associations.rst
2012-11-24 02:24:10 -08:00
Benjamin Eberlei
106620e6dd Merge pull request #123 from bamarni/patch-4
simplified interface check
2012-11-24 02:23:55 -08:00
Benjamin Eberlei
0cfc7dd402 Merge pull request #124 from bamarni/patch-5
fixed typo
2012-11-24 02:23:41 -08:00
Benjamin Eberlei
ced44e1916 Merge pull request #125 from Ocramius/patch-4
Update en/reference/tools.rst
2012-11-24 02:22:30 -08:00
Benjamin Eberlei
9873c9fc04 Merge pull request #127 from lennerd/fix-typo-1
Fixed typo for array mapping type
2012-11-24 02:22:12 -08:00
Benjamin Eberlei
07e6a3ec68 Merge pull request #129 from md2perpe/patch-1
Update en/tutorials/getting-started.rst
2012-11-24 02:22:01 -08:00
Benjamin Eberlei
24228711fd Merge pull request #133 from Ocramius/patch-5
Removing removed API description from docs
2012-11-24 02:21:53 -08:00
Benjamin Eberlei
4a01d2904c Merge pull request #138 from fulopattila122/master
The yaml mapping part probably shows the path/Namespace key-value pair in the wrong order
2012-11-24 02:21:43 -08:00
Guilherme Blanco
697e7b1ca5 Merge pull request #471 from chives/DDC-2052
Extended TreeWalker interface with getQueryComponents() and setQueryComponent() which are used by the Parser class
2012-11-22 07:39:59 -08:00
Francisco Facioni
c84099508f added outer left join 2012-11-22 09:59:44 -03:00
Francisco Facioni
9c59ed5891 regression fix for left joins (double ON) 2012-11-20 12:30:52 -03:00
Guilherme Blanco
f7e9a91b5c Merge pull request #141 from hifi7/patch-1
Fix wrong date format in example PrePersist method
2012-11-20 07:01:03 -08:00
Lukasz Cybula
afdb92ff9b Added note about new methods in UPGRADE.md 2012-11-20 12:42:05 +01:00
Lukasz Cybula
08a3423ce2 CS fixes 2012-11-20 12:42:05 +01:00
Lukasz Cybula
7b1d84cbdb Moved CustomTreeWalkersJoinTest to proper namespace and fixed licence 2012-11-20 12:42:05 +01:00
Lukasz Cybula
2c99ecf586 Extended TreeWalker interface with getQueryComponenets() and setQueryComponent() which are used by the Parser class 2012-11-20 12:42:05 +01:00
hifi7
f221faff28 Fix wrong date format in example PrePersist method 2012-11-20 05:37:43 +01:00
Benjamin Eberlei
f25b098029 [DDC-2055] Add Test for EntityRepository#createResultSetMappingBuilder 2012-11-19 17:45:14 +01:00
Benjamin Eberlei
941670aa9d [DDC-2055] Add EntityRepository#createResultSetMappingBuilder() 2012-11-19 17:09:18 +01:00
Benjamin Eberlei
17bb564534 [DDC-2055] Some adjustments to ResultSetMappingBuildder patch 2012-11-19 16:54:56 +01:00
nemekzg
727647902c Fix for DDC-1765 2012-11-17 16:18:18 +01:00
Benjamin Eberlei
9ea40edb48 Discuss branching strategy 2012-11-17 10:48:46 +01:00
Benjamin Eberlei
3666d1485a Merge pull request #516 from lstrojny/bug/phpunit-composer
Allow running tests with composer
2012-11-17 01:25:16 -08:00
Benjamin Eberlei
2a69d2d1f9 Update Common dependency 2012-11-17 09:52:11 +01:00
Lars Strojny
bb3eeffe78 Allow running tests with composer 2012-11-17 03:41:32 +01:00
Chris Schuhmann
1d04902326 Fix sandbox cli
In afd8ea9 $helpers became an instance of HelperSet and caused an "The helper "em" is not defined." exception.
2012-11-17 00:12:29 +01:00
Guilherme Blanco
30bddbd254 Merge pull request #139 from jorns/master
Update the i in index to uppercase
2012-11-16 11:26:02 -08:00
jorns
072a65bd26 Updated @Index lower i to Upper i
This fixes the error:
[Semantical Error] The annotation "@Doctrine\ORM\Mapping\index"
2012-11-16 11:21:06 +01:00
Benjamin Eberlei
0782b8a682 Expanded first paragraphs 2012-11-16 00:08:25 +01:00
Benjamin Eberlei
da6236f830 More on testing 2012-11-16 00:01:37 +01:00
Benjamin Eberlei
6821e633fd Add CONTRIBUTING.md 2012-11-15 23:57:34 +01:00
Benjamin Eberlei
d0428df9bd Merge pull request #513 from FabioBatSilva/annot-enum
Enumeration support
2012-11-15 10:52:14 -08:00
Fabio B. Silva
77827303d2 Fix CS 2012-11-13 14:54:37 -02:00
Fabio B. Silva
c1de4c5fda update doctrine-common 2012-11-13 14:13:49 -02:00
Fabio B. Silva
a07c63dde6 added support for @Enum 2012-11-13 14:13:48 -02:00
Benjamin Eberlei
0d58e6627a Merge branch 'DDC-2109' 2012-11-12 15:48:19 +01:00
Benjamin Eberlei
f453d6c85b [DDC-2109] Fix bug with ResolveTargetEntityListener and ManyToMany associations. 2012-11-12 15:48:10 +01:00
Benjamin Eberlei
918ea1cdd8 Merge pull request #449 from mvrhov/DDC-1958
Pager fix for DDC-1958
2012-11-12 06:02:32 -08:00
Benjamin Eberlei
935842845b Merge branch 'DDC-2138' 2012-11-12 15:01:30 +01:00
Benjamin Eberlei
624ef309f0 Remove unnecssary code 2012-11-12 15:01:20 +01:00
Benjamin Eberlei
5e2a433828 Inlined Test and Entities into DDC2138Test 2012-11-12 14:59:48 +01:00
Stefano Rodriguez
482da95352 The schema tool now doesn't add a foreign constraint when subclassess of a STI use the same field to map relations with entities of different classes 2012-11-12 14:59:48 +01:00
Stefano Rodriguez
b1c69ebab9 adedd failing test for PR #440 2012-11-12 14:19:31 +01:00
Miha Vrhovnik
8fe9fa0dc7 extracted pgsql sql generation into a helper method 2012-11-12 13:48:55 +01:00
Miha Vrhovnik
c7a75f477f The distinct query should replicate the fields in order by clause and the order by clause itself from inner query
This fixes DDC-1958
2012-11-12 13:18:46 +01:00
Benjamin Eberlei
4e04daaed4 Merge pull request #466 from ethanresnick/patch-1
Use `protected` so EntityGenerator can be extended
2012-11-12 04:05:23 -08:00
HarmenM
b6b75d3a27 Modified the WhereInWalkerTest to be compatible with a single InputParameter. 2012-11-12 12:30:03 +01:00
HarmenM
8a29d91d15 Update lib/Doctrine/ORM/Tools/Pagination/Paginator.php
Replaced the foreach loop adding all IDs as single parameters with a single parameter which injects the IDs as an array.
2012-11-12 12:30:03 +01:00
HarmenM
88d0933a6e Update lib/Doctrine/ORM/Tools/Pagination/WhereInWalker.php
replaced the for-loop which adds the InputParameters for a single InputParameter for use with an array instead of a set of scalars.
2012-11-12 12:30:03 +01:00
Benjamin Eberlei
a0e8ca128c Merge pull request #482 from Halleck45/cli-customizable
user can set its own commands in the cli-config.php file
2012-11-12 03:13:34 -08:00
Benjamin Eberlei
79315cb7de Merge pull request #510 from ruian/patch-2
CS on QueryBuilder
2012-11-12 02:52:54 -08:00
Benjamin Eberlei
ccfd2adf5b Merge pull request #511 from Ocramius/hotfix/console-runner-version
Fixing reference to Doctrine\ORM\Version
2012-11-12 02:42:30 -08:00
Marco Pivetta
7520a745c2 Fixing reference to Doctrine\ORM\Version 2012-11-12 11:31:19 +01:00
Julien 'ruian' Galenski
055d7f261c CS on queryBuilder 2012-11-12 10:23:50 +01:00
Attila Fulop
3cee83be2b Swapped order of path/Entities at Simplified YAML Driver
According to my experience, the path has to be the key, and Namespace prefix the value in the array.
2012-11-11 21:19:18 +02:00
Benjamin Eberlei
fa3f1e088d Add test for boolean parameter type infering 2012-11-09 22:03:40 +01:00
Guilherme Blanco
2edccbfbc8 Merge pull request #137 from Squazic/patch-1
Grammar fix
2012-11-09 09:48:59 -08:00
Squazic
56eec8979b Grammar fix 2012-11-09 11:55:34 -05:00
Guilherme Blanco
182ed07a41 Merge pull request #509 from smottt/master
SchemaTool ignoring 'fixed' option
2012-11-07 13:58:56 -08:00
Guilherme Blanco
af39687f45 Merge pull request #135 from nanocom/patch-1
Fixed typo
2012-11-07 13:58:05 -08:00
Arnaud Kleinpeter
9f898b0b90 Update en/tutorials/getting-started.rst 2012-11-07 22:31:40 +01:00
Metod
cfee331006 SchemaTool ignoring 'fixed' option fixed 2012-11-07 16:31:31 +01:00
Guilherme Blanco
09d53f0e58 Merge pull request #508 from mnapoli/DDC-2073
Fix and test for DDC-2073
2012-11-06 14:52:34 -08:00
Matthieu Napoli
c2d9197900 Fix and test for DDC-2073 2012-11-06 15:12:19 +01:00
Matthieu Napoli
e15cf324c3 Fix and test for DDC-2073 2012-11-06 15:05:53 +01:00
Fabio B. Silva
122569eee0 Fix CS 2012-11-05 22:52:43 -02:00
Fabio B. Silva
0fa89647d2 Fix DocBlock 2012-11-05 22:52:42 -02:00
Fabio B. Silva
ecaa3fd03e Third round of refactorings on BasicEntityPersister 2012-11-05 22:52:42 -02:00
Fabio B. Silva
c8899c2b3b second round of CS fixes 2012-11-05 22:52:42 -02:00
Fabio B. Silva
a295525501 Fix some CS 2012-11-05 22:52:41 -02:00
Fabio B. Silva
827153624c Second round of refactorings on BasicEntityPersister 2012-11-05 22:52:41 -02:00
Fabio B. Silva
04e1838c92 change 'use' statements 2012-11-05 22:52:41 -02:00
Fabio B. Silva
14a2b61671 code refactoring on SingleTablePersister 2012-11-05 22:52:40 -02:00
Fabio B. Silva
9a041c8fdb code refactoring on OneToManyPersister 2012-11-05 22:52:40 -02:00
Fabio B. Silva
2b1aaebe18 code refactoring on ManyToManyPersister 2012-11-05 22:52:40 -02:00
Fabio B. Silva
07492bda9d fix JoinedSubclassPersister#delete when supports foreign key 2012-11-05 22:52:39 -02:00
Fabio B. Silva
3156c1549d code refactoring on JoinedSubclassPersister 2012-11-05 22:52:39 -02:00
Fabio B. Silva
308b54a8f3 code refactoring on BasicEntityPersister 2012-11-05 22:52:39 -02:00
Fabio B. Silva
7e348b7815 small refacory on AbstractEntityInheritancePersister 2012-11-05 22:50:24 -02:00
Fabio B. Silva
e6f08f0b92 remove '_' prefix at AbstractCollectionPersister 2012-11-05 22:50:23 -02:00
Fabio B. Silva
b998a522b0 remove '_' prefix at BasicEntityPersister 2012-11-05 22:50:23 -02:00
Guilherme Blanco
d6d5c341e2 Merge pull request #486 from FabioBatSilva/DDC-2084
Fix DDC-2084
2012-11-05 16:38:35 -08:00
Guilherme Blanco
262c3eea6b Merge pull request #506 from FabioBatSilva/DDC-2121
Fix DDC-2121
2012-11-05 16:27:34 -08:00
Fabio B. Silva
62f43e6ea2 remove require_once 2012-11-05 22:23:44 -02:00
Fabio B. Silva
c4e6a04676 remove duplicate return statement 2012-11-05 22:23:44 -02:00
Fabio B. Silva
a09a5b9b7b Fix DDC-2084 2012-11-05 22:23:44 -02:00
Guilherme Blanco
57e5fa9873 Merge pull request #496 from arse/master
Testing for key existance in basicEntityPersister / getIndividualValue
2012-11-05 16:20:26 -08:00
Guilherme Blanco
a44579303c Merge pull request #493 from nmpolo/codegenerationfixes
Do not add trailing whitespace to blank lines
2012-11-05 16:19:39 -08:00
Guilherme Blanco
863d14a61a Merge pull request #503 from sebastianbauer/master
added unsigned mapping to SchemaTool options
2012-11-05 16:16:25 -08:00
Guilherme Blanco
7a895209e3 Merge pull request #502 from gwis/master
Fix for invalid 'double-ON' SQL generation with entity inheritance type JOINED.
2012-11-05 16:15:45 -08:00
Guilherme Blanco
283ed55824 Merge pull request #504 from nemekzg/DDC-1241
Proposed fix for DDC-1241
2012-11-05 16:14:48 -08:00
Guilherme Blanco
6949a95782 Merge pull request #505 from BenMorel/fix-errors
Fix errors in JoinClassPathExpression and SqlWalker
2012-11-05 16:12:31 -08:00
Fabio B. Silva
2f7e970c5f Fix DDC-2121 2012-11-05 21:53:07 -02:00
Benjamin Morel
88b29a4e59 Fixed errors:
- Typo in variable name in JoinClassPathExpression;
 - Undefined class AST\ArithmeticPrimary (x2);
 - QueryException::invalidPathExpression() expects a PathExpression, not a string.
2012-11-05 14:45:57 -08:00
nemekzg
9705ee89d9 Proposed fix for DDC-1241 2012-11-05 19:55:54 +01:00
Sebastian Bauer
a27be2fab6 added unsigned mapping to SchemaTool options 2012-11-05 19:49:16 +01:00
Gordon Stratton
9e916a2893 Fix for invalid 'double-ON' SQL generation with entity inheritance type JOINED.
In SqlWalker::walkJoin(), SqlWalker::walkRangeVariableDeclaration() can be
called which may produce an 'ON' clause if the entity inheritance type is
JOINED. As walkJoin() may then produce another ON clause, this results in
invalid SQL (e.g. '... ON foo = bar ON (baz = quux) ...' when the inheritance
type is JOINED.

This adds a test and a fix for the problem, by checking for an inheritance type
of JOINED in walkJoin() and using AND instead of ON in the appropriate place.

It seems like this part of the code is begging to be refactored. This is my
first foray into Doctrine internals and can't see a way to do this without
stomping all over the rest of the code, but this section seems ripe for cleanup
by somebody who is familiar.
2012-11-05 01:19:25 -08:00
Guilherme Blanco
ff80e99cc9 Merge pull request #501 from jeremymarc/patch-1
Allow 0 id for Entity
2012-11-04 19:22:57 -08:00
Jeremy Marc
26dd533662 Compare to null instead of using isset 2012-11-04 19:04:13 -08:00
Jeremy Marc
84477440b6 Allow 0 id for Entity
When using a 0 id, it's throwing InvalidArgumentException (Binding entities to query parameters only allowed for entities that have an identifier.)
2012-11-04 17:41:08 -08:00
Nick Masters
e402a0c078 Spaces around ! sign 2012-11-04 15:22:32 +00:00
Nick Masters
3a8ea7260c Merge remote-tracking branch 'origin/master' into codegenerationfixes 2012-11-04 15:14:54 +00:00
Guilherme Blanco
fc40c437cb Merge pull request #489 from stof/cs_fixes
Fixed coding standards in the Tools namespace
2012-11-03 10:04:10 -07:00
Christophe Coevoet
1b01a074dc Fixed the testsuite 2012-11-03 17:07:56 +01:00
Christophe Coevoet
1d3fe87215 Removed an unused private method in the SchemaValidator 2012-11-03 16:37:34 +01:00
Christophe Coevoet
5a6c398ea0 Fixed coding standards in the Tools namespace 2012-11-03 16:37:31 +01:00
TR
73e6164096 Update lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
coding standards change
2012-11-03 09:47:08 +00:00
Guilherme Blanco
7d7287a1ba Merge pull request #434 from bamarni/filter-objects
allowed to pass filter objects to the configurator
2012-11-02 17:52:23 -07:00
Guilherme Blanco
ec1950d3ca Merge pull request #448 from stefanklug/master-parserFix
Fixed Parser problem for SELECT (((3))) as ....
2012-11-02 17:46:43 -07:00
Guilherme Blanco
515847bece Merge pull request #498 from lanthaler/improve-generated-entity-doc
Improve DocBlock annotations of generated entities
2012-11-02 17:27:11 -07:00
Guilherme Blanco
409516e86c Merge pull request #499 from md2perpe/master
Speling: "invidiual" -> "individual"
2012-11-02 11:13:19 -07:00
Per Persson
b5ac85d19a Update lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
Speling
2012-11-02 18:37:46 +01:00
Markus Lanthaler
1b7ca67fdb Improve DocBlock annotations of generated entities
Currently, the DocBlock annotations for member variables contain the variable name as description which is redundant and should be removed. Furthermore the class is annotated with the FQN instead of just the name. This makes automatically generated documentation quite ugly.
2012-11-02 17:15:44 +01:00
Guilherme Blanco
129d6efd85 Merge pull request #497 from vclayton/DDC-2113
[DDC-2113] Surround WHERE clause with parens if using SQLFilter
2012-11-02 07:27:28 -07:00
Vaughn Clayton
ae30ce4596 [DDC-2113] Surround WHERE clause with parens if using SQLFilter 2012-11-02 08:48:57 -04:00
TR
185a0fb19c refactoring getIndividualValue for valid key value
refactoring getIndividualValue
2012-11-02 00:33:55 +00:00
TR
a65996f74c notice is thrown up if no identifier values found
wrapping the setting of value with an array_key_exists to prevent a notice from being thrown
2012-11-01 23:59:57 +00:00
Nick Masters
0a2ba38e58 Add new line to end of file 2012-10-30 22:42:01 +00:00
Nick Masters
0f92944edd Do not add trailing whitespace to blank lines 2012-10-30 22:22:04 +00:00
Guilherme Blanco
98c5b34f2b Merge pull request #492 from hason/notlike
Added NOT LIKE expression
2012-10-27 11:01:17 -07:00
Martin Hasoň
7f33143502 Added NOT LIKE expression 2012-10-25 12:58:19 +02:00
Guilherme Blanco
f5c1b38e2d Merge pull request #491 from eventhorizonpl/optimise_use
remove unused use statements
2012-10-23 17:13:44 -07:00
Michal Piotrowski
a55b46b4bf remove unused classes 2012-10-23 18:19:28 +02:00
Marco Pivetta
95b8e27bc5 Removing removed API description from docs 2012-10-23 15:21:55 +03:00
Guilherme Blanco
e9c7deae4f Merge pull request #490 from eventhorizonpl/fix_test
fix StatementMock bindParam parameters
2012-10-22 18:56:35 -07:00
Michal Piotrowski
f6cf8f2f0c fix StatementMock bindParam parameters 2012-10-23 00:09:38 +02:00
Guilherme Blanco
9d0b254407 Merge pull request #481 from beejeebus/master
check for false as a return value from get_parent_class(), not null
2012-10-21 20:54:22 -07:00
Guilherme Blanco
38d8c7f0d9 Merge pull request #467 from docteurklein/fix-bool-type-binding
add bool detection when inferring type
2012-10-21 18:39:29 -07:00
Guilherme Blanco
a16a935bff Merge pull request #472 from twinh/master
Fixed empty namespace in generated code when repository class do not have namespace
2012-10-21 18:38:53 -07:00
Guilherme Blanco
cd7ef6e7a7 Merge pull request #484 from jappie/master
Prevented "Undefined index" notice when updating
2012-10-21 18:35:38 -07:00
Guilherme Blanco
814f2f9e03 Merge pull request #487 from FabioBatSilva/DDC-2069
Fix DDC-2069
2012-10-20 20:00:54 -07:00
Guilherme Blanco
2c0feb2a46 Merge pull request #488 from FabioBatSilva/DDC-2079
Fix DDC-2079
2012-10-20 19:59:58 -07:00
Fabio B. Silva
b03388293f Fix typo 2012-10-20 15:44:09 -03:00
Fabio B. Silva
fb467a1196 Fix DDC-2079 2012-10-20 15:37:13 -03:00
Fabio B. Silva
86fddfed9a Fix DDC-2069 2012-10-20 00:28:38 -03:00
Jasper N. Brouwer
1a17b1670b Added testcase for DDC-2086 2012-10-19 09:15:07 +02:00
Johannes
f4cdded06c Merge pull request #131 from adrienbrault/patch-2
Add UUID,CUSTOM generatedValue values to annot ref
2012-10-18 09:24:14 -07:00
Adrien Brault
e3bbd058f2 Add UUID,CUSTOM generatedValue values to annot ref 2012-10-18 18:34:22 +03:00
Jasper N. Brouwer
0cfc37d757 Prevented "Undefined index" notice when updating
While executing updates on an entity scheduled for update without
a change-set, an "Undefined index" notice is raised.
2012-10-17 21:50:09 +02:00
Johannes
54941d1f09 Merge pull request #130 from drak/patch-1
Add example for index definition
2012-10-17 12:06:44 -07:00
Drak
05b170fe99 Add example for index definition 2012-10-17 20:41:15 +02:00
Halleck45
d3c58d83a5 user can set its own commands in the cli-config.php file 2012-10-16 17:03:18 +02:00
justin.randell
ca82a4720b check for false as a return value from get_parent_class(), not null 2012-10-16 15:04:25 +11:00
Per Persson
5657e199bd Update en/tutorials/getting-started.rst
Spelling: UDPATE -> UPDATE
2012-10-15 17:28:12 +03:00
Benjamin Eberlei
56e96793c0 Merge branch 'DDC-2067' 2012-10-12 21:47:39 +02:00
Benjamin Eberlei
deb6327b56 [DDC-2067] Refactor and fix bug in boolean evaluation inside XML Driver. 2012-10-12 21:47:09 +02:00
Benjamin Eberlei
b7b49203aa Merge pull request #477 from jakoch/master
fixed typo/bug and CS on use statements
2012-10-12 11:29:00 -07:00
jakoch
d4a6c488ca fixed use statements 2012-10-12 13:53:20 +02:00
jakoch
ec5ad7136f fix typo 2012-10-12 13:37:17 +02:00
Guilherme Blanco
a4b85c49c9 Merge pull request #474 from okovalov/master
Fixed bug with comment option not being added to column.
2012-10-11 22:22:59 -07:00
Oleksandr Kovalov
90bbb35655 Fixed bug with comment option not being added to column. 2012-10-11 15:57:51 +00:00
twinh
8cc24f4cf2 removed blanks 2012-10-11 06:04:03 -07:00
twinh
66efd65e64 fixed empty namespace in generated code when repository class do not have namespace 2012-10-11 06:01:55 -07:00
Klein Florian
7f8af83b5b add bool detection when inferring type 2012-10-10 11:20:41 +02:00
Ethan
c604adc804 Use protected so generator can be extended
This is definitely something I'd like to be able to extend, and I imagine others might too.
2012-10-09 08:00:51 -03:00
Guilherme Blanco
65fabc20c9 Merge pull request #465 from yohang/master
Fixed a typo in ConcatFunction
2012-10-08 06:43:53 -07:00
Stefan Klug
bf54c22cd9 removed unneded variable 2012-10-08 14:01:04 +02:00
Stefan Klug
1e1f34f9cb cleanup ScalarExpression
_isFunction doesn't exclude subselects anymore
2012-10-08 13:59:54 +02:00
Stefan Klug
6ccf7a7ac7 fixed Parser which incorrectly treated ((( as function 2012-10-08 13:57:34 +02:00
Stefan Klug
d344407636 added test case 2012-10-08 13:49:31 +02:00
yohang
adc3d21385 Fixed typo on ConcatFunction 2012-10-08 12:44:36 +02:00
Benjamin Eberlei
235ad8e553 [DDC-2052] Add SqlWalker::setQueryComponent() to allow modification of the query component in a custom output walker 2012-10-06 11:16:16 +02:00
Benjamin Eberlei
a67a6aa685 Merge pull request #414 from cordoval/DDC-1872
[DDC-1872] Overriding Mapping Annotations
2012-10-06 01:35:47 -07:00
Benjamin Eberlei
a5e043e6e6 Merge pull request #444 from goetas/xmlfix
Fixed some typo error in XML Exporter
2012-10-05 14:16:59 -07:00
Benjamin Eberlei
f7220ae416 Merge pull request #455 from radmar/master
Fixed unique-constraint name in XML Exporter
2012-10-05 11:23:55 -07:00
Benjamin Eberlei
44c0ca4d3c [DDC-2059] Fix column and foreign key interfering with each other during reverse engineering. 2012-10-05 20:03:51 +02:00
Benjamin Eberlei
fd28624120 Merge pull request #456 from Slamdunk/patch-1
Optimize autoload prefix in composer.json
2012-10-05 10:05:14 -07:00
Benjamin Eberlei
a0440b63bb Merge pull request #462 from doctrine/DDC-2055
[DDC-2055] Generate SELECT clause from ResultSetMappingBuilder
2012-10-05 09:46:37 -07:00
Benjamin Eberlei
0c8be37ca9 Merge pull request #458 from barelon/master
Use cascade=all if all cascade options set
2012-10-05 09:45:44 -07:00
Benjamin Eberlei
13762f20c9 Merge pull request #422 from FabioBatSilva/DDC-1574
DDC-1574 - "new" operator
2012-10-05 07:31:18 -07:00
Benjamin Eberlei
a47359e3f5 [DDC-2055] Fix CS 2012-10-04 20:18:10 +02:00
Benjamin Eberlei
91caff1d89 Merge pull request #459 from FabioBatSilva/DDC-2012
Fix DDC-2012
2012-10-03 03:46:04 -07:00
Benjamin Eberlei
913377e31b [DDC-2055] Add support to generate entity result parts of the SELECT clause from a ResultSetMappingBuilder instance. Add support for column incrementing. 2012-10-03 12:35:14 +02:00
Benjamin Eberlei
079beb957e Merge pull request #461 from franmomu/patch-1
[SchemaValidator] Fix typo
2012-10-03 01:46:44 -07:00
Fran Moreno
45eef4a03c [SchemaValidator] Fix typo 2012-10-03 10:51:14 +03:00
Guilherme Blanco
3ecce5251b Merge pull request #128 from egulias/master
[Association-Mapping] - Missing many-to-many xml target-entity tag
2012-10-02 07:30:44 -07:00
Eduardo Gulias Davis
742590d1d7 [Association-Mapping] - Mising target-entity tag in xml format
In the many to many bidirectional xml format the "target-entity" tag is missing and generates a MappingException when not included.
2012-10-02 12:15:03 +03:00
barelon
919cf8558b Remove trailing whitespace 2012-10-02 00:16:29 +03:00
barelon
3b27216c51 add empty lines around if block 2012-10-02 00:14:24 +03:00
Fabio B. Silva
5cb4466f7c Fix test case 2012-09-30 15:47:00 -03:00
Fabio B. Silva
4510f5a5b8 Fix DDC-2012 2012-09-30 15:40:19 -03:00
barelon
cd37ec47d5 Set 'cascade' => 'all' if all cascade options set 2012-09-30 01:16:21 +03:00
barelon
c97eff94f5 Output cascade={"all"} if all cascade options set 2012-09-30 01:12:06 +03:00
Fabio B. Silva
dd984c7319 remove extra line 2012-09-29 16:19:03 -03:00
Fabio B. Silva
5f89fa4190 fix CS 2012-09-29 16:19:03 -03:00
Fabio B. Silva
7c754e495e support namespace alias 2012-09-29 16:19:03 -03:00
Fabio B. Silva
1bd6e841bf Fix some CS 2012-09-29 16:19:02 -03:00
Fabio B. Silva
de93983dff assume entity namespace when not given 2012-09-29 16:19:02 -03:00
Fabio B. Silva
3aa8d3fdac test constructor exceptions 2012-09-29 16:19:01 -03:00
Fabio B. Silva
91efe10855 fix some cs on ObjectHydrator 2012-09-29 16:19:01 -03:00
Fabio B. Silva
4dca27962e support multiple operators 2012-09-29 16:19:01 -03:00
Fabio B. Silva
6844116b94 test case expression 2012-09-29 16:19:00 -03:00
Fabio B. Silva
f0403a5394 test literal values 2012-09-29 16:19:00 -03:00
Fabio B. Silva
e5e45a3a5c test sql generation 2012-09-29 16:18:59 -03:00
Fabio B. Silva
ddb2651691 fix tests on postgres 2012-09-29 16:18:59 -03:00
Fabio B. Silva
af2f556fd3 small refactory 2012-09-29 16:18:59 -03:00
Fabio B. Silva
b19e4a6440 support arithmetic expression and aggregate functions 2012-09-29 16:18:58 -03:00
Fabio B. Silva
88f04b5ebd parse nested new operators 2012-09-29 16:18:58 -03:00
Fabio B. Silva
2b403b7dad basic support refactory 2012-09-29 16:18:58 -03:00
Fabio B. Silva
b29d47a682 cache new object mappings 2012-09-29 16:18:58 -03:00
Fabio B. Silva
0fbb78e61a basic support, need some code refactory and improvements 2012-09-29 16:18:57 -03:00
Fabio B. Silva
ed89695a8c collect new object parameters 2012-09-29 16:18:57 -03:00
Fabio B. Silva
0e60c50c5e small code refactoring 2012-09-29 16:18:56 -03:00
Fabio B. Silva
0c1a8cd43f sql generation 2012-09-29 16:18:56 -03:00
Fabio B. Silva
ee7b5da64a start work 2012-09-29 16:18:56 -03:00
Filippo Tessarotto
95971a6180 Optimize autoload prefix in composer.json
By having more specific autoload prefixes it is possible to reduce the number of stat calls made.
2012-09-28 09:58:38 +03:00
Marcin Radziwoński
380f4fbac7 Fixed unique-constraint name in XML Exporter 2012-09-26 14:27:16 +02:00
Guilherme Blanco
831f0acdc5 Merge pull request #450 from KonstantinKuklin/master
fix some phpdoc
2012-09-20 18:10:22 -07:00
Konstantin Kuklin
34d8843fd6 improve phpdoc 2012-09-21 03:20:06 +04:00
Lennart Hildebrandt
a61afedcf9 Fixed typo for mapping type "array" 2012-09-20 13:11:36 +02:00
Lennart Hildebrandt
06ad3389e0 Added .idea for PhpStorm contributers 2012-09-20 13:11:09 +02:00
Guilherme Blanco
fa29d36d09 Merge pull request #126 from barelon/patch-1
Fix some typos in annotations reference
2012-09-18 08:56:50 -07:00
barelon
c463121da8 Fix some typos in annotations reference
- Changed capitalization of the `@GeneratedValue` and `@Version` examples which incorrectly used lower case

- Fixed parameter names in `@NamedNativeQuery` and `@SqlResultSetMapping`

- Changed the Docblock style for the @MappedSuperclass example to the style used by all examples
2012-09-18 11:28:24 +03:00
Benjamin Eberlei
a9517b1b17 Merge pull request #436 from Powerhamster/comment-fixes
Comment fixes
2012-09-17 03:45:34 -07:00
Benjamin Eberlei
a256c43871 Merge pull request #442 from FabioBatSilva/fix-overrides-annot
Remove unused code
2012-09-17 03:33:19 -07:00
Asmir Mustafic
bc277c6e28 spaces 2012-09-13 11:04:23 +02:00
Asmir Mustafic
f86dcfc288 typo fix 2012-09-13 09:40:14 +02:00
Guilherme Blanco
68f543b8bb Merge pull request #443 from cas87/patch-1
Allow 'nullable' attribute to be used during XML export
2012-09-12 20:12:33 -07:00
Cas
de5b20d0bf Update lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php
Allow 'nullable' attribute to be exported for fields, something which already worked in YamlExport. This addition saved me a lot of time during development, not having to manually re-factor after each export.

Don't know why this was missing, maybe it's me who is missing something, so let me know ;)
2012-09-13 03:43:27 +03:00
Fabio B. Silva
8193397d4f remove duplicate code 2012-09-12 21:31:02 -03:00
Guilherme Blanco
dacb51fba2 Merge pull request #437 from sroddy/persistent_collection_matching_fix
Fixes PersistentCollection::matching() when collection is not initialize...
2012-09-07 13:18:45 -07:00
Stefano Rodriguez
9a14b40495 use of assertCount 2012-09-07 14:44:18 +02:00
Stefano Rodriguez
bb8dd6cb11 Fixes PersistentCollection::matching() when collection is not initialized and there are NEW entities in the collection 2012-09-07 10:46:34 +02:00
Stefano Rodriguez
4b3ecfe674 Added a failing test case on PersistentCollection::matching() when collection is not initialized and there are NEW entities in the collection 2012-09-07 10:46:33 +02:00
Thomas Rothe
7acdcd6952 added missing use statement 2012-09-06 21:03:40 +02:00
Thomas Rothe
7beffb5a5f Several fixes for comments
updated @param and @throws annotations
2012-09-06 21:03:26 +02:00
Marco Pivetta
0ff1f418bd Update en/reference/tools.rst
Fixed import as reported by manoelcampos@gmail.com
2012-09-05 22:31:07 +03:00
Benjamin Eberlei
ab990cfba6 Merge branch 'DDC-2003' 2012-09-05 19:28:42 +02:00
Benjamin Eberlei
2c99f97c8b [DDC-2003] Remove unused variable 2012-09-05 19:28:32 +02:00
Bilal Amarni
a9b4debe37 allowed to pass filter objects to the configurator 2012-09-03 10:52:46 +02:00
Josiah Truasheim
959c4f026f Refactored the SqlValueVisitor to move all type processing to the entity persister. 2012-08-31 20:58:16 +07:00
Josiah Truasheim
e0d16331a4 Fixed formatting issues identified by Stof 2012-08-31 19:51:22 +07:00
Josiah Truasheim
a6b6b25267 Removed the closure keyword as it isn't supported in PHP 5.3 2012-08-31 18:16:02 +07:00
Josiah Truasheim
c7f5d9d77d Fixed DDC-2003 using closures to reference the functionality of the calling entity persister from the SQL value visitor. 2012-08-31 18:12:44 +07:00
Josiah Truasheim
783c53d57c Added a failing test for DDC-2003 2012-08-31 17:52:46 +07:00
Bilal Amarni
d47b872292 fixed typo 2012-08-30 16:35:46 +03:00
Bilal Amarni
7c7118d883 simplified interface check 2012-08-30 16:25:50 +03:00
Karsten Dambekalns
4e4cf33342 Update en/reference/working-with-associations.rst
Clarifiy that orphanRemoval works with one-to-one, one-to-many and many-to-many associations.
2012-08-30 11:56:06 +03:00
Benjamin Eberlei
bc2476f342 [DDC-1918] Fix weird results at the end of paginator when using fetch joins 2012-08-29 15:16:07 +02:00
Benjamin Eberlei
9c682efb2f Merge pull request #431 from pitiflautico/patch-1
[ORM] Fix double semicolon
2012-08-29 05:33:12 -07:00
Benjamin Eberlei
267daa5fc1 Merge pull request #428 from chEbba/query-builder-common-criteria
Add QueryBuilder::addCriteria() for Criteria - QueryBuilder bridge
2012-08-29 05:06:50 -07:00
Kirill chEbba Chebunin
ac98f15cfa Fix ORMInvalidArgumentException factory methods with return instead of throw 2012-08-29 14:02:51 +02:00
Kirill chEbba Chebunin
e68807ad4f Change version for QueryExpressionVisitor 2012-08-29 16:00:02 +04:00
Kirill chEbba Chebunin
a162f00ecc Remove builders from QueryExpressionVisitorTest constructor 2012-08-29 15:48:04 +04:00
Daniel Perez Pinazo
5b8ead9db8 [ORM] Fix double semicolon 2012-08-29 13:40:36 +03:00
Francesc Rosàs
e873351624 Remove "before the lists of scheduled changes are cleared"
It is not true.
2012-08-23 21:51:33 +03:00
Benjamin Eberlei
9ae1f804e1 Merge pull request #120 from saltybeagle/master
Minor spelling correction & grammatical formatting
2012-08-23 06:55:26 -07:00
Brett Bieber
46be81115a Minor spelling correction & grammatical formatting 2012-08-23 08:41:19 -05:00
Kirill chEbba Chebunin
2aba7fb374 Add test for QueryBuilderTest::addCriteria with undefined limits 2012-08-20 20:31:57 +04:00
Kirill chEbba Chebunin
1c2f2b5c13 Store QueryExpressionVisitor parameters as array 2012-08-20 20:28:22 +04:00
Kirill chEbba Chebunin
433d208572 Remove extra space from QueryBuilder::addCriteria() 2012-08-20 20:27:55 +04:00
Kirill chEbba Chebunin
d3ab948d88 Overwrite query limits only if set in QueryBuilder::addCriteria() 2012-08-20 20:27:30 +04:00
Kirill chEbba Chebunin
148789600a Remove unnecessary else statements after if with return in QueryExpressionVisitor 2012-08-20 20:27:14 +04:00
Kirill chEbba Chebunin
c6b3899c2d Add QueryBuilder::addCriteria() for Criteria - QueryBuilder bridge 2012-08-20 20:27:03 +04:00
Gary Hockin
b6ddb58634 Update en/tutorials/getting-started.rst
Cleaner to remove the note, and modify the code to put the doctrine_bootstrap.php include first?
2012-08-07 13:26:51 +02:00
Benjamin Eberlei
a9dfe1e10e Merge pull request #118 from Spabby/master
Added a note to explain something that confused me for a while in this tutorial
2012-08-07 04:15:59 -07:00
Gary Hockin
711844296d Update en/tutorials/getting-started.rst
Fixed failed markdown
2012-08-07 13:12:09 +02:00
Gary Hockin
f3dea276e7 Update en/tutorials/getting-started.rst
Added note that tripped me up.
2012-08-07 13:10:25 +02:00
Benjamin Eberlei
2f72219d6e Finish first version of filtering api documentation 2012-08-01 22:15:44 +02:00
Luis Cordova
221ab3b695 [DDC-1872] adjust as per @stof comments on test skipped message 2012-08-01 02:27:23 -05:00
Benjamin Eberlei
eaeda36bdb Add filtering collections 2012-07-31 23:58:37 +02:00
Benjamin Eberlei
83b509e033 Move again into tools 2012-07-31 23:26:40 +02:00
Benjamin Eberlei
71d3f5852d Move Mapping Validation into Configuration section 2012-07-31 23:25:33 +02:00
Benjamin Eberlei
5e9255dda8 Reorganize query builder docs to put the useful stuf fon top. 2012-07-31 23:20:25 +02:00
Luis Cordova
6c932af0a0 [DDC-1872] address comments from beberlei 2012-07-29 23:33:18 -05:00
Luis Cordova
5d0082471f [DDC-1872] skip 5.4 versions for php 5.3 uncompatible tests 2012-07-29 22:55:26 -05:00
Luis Cordova
26608fed5a [DDC-1872] add tutorial for override annotations mappings 2012-07-29 00:39:24 -05:00
Luis Cordova
8742377c3b [DDC-1872] add tests to evaluate annotations overrides with traits 2012-07-29 00:03:25 -05:00
Guilherme Blanco
8a86242a5d Merge pull request #116 from benlumley/patch-2
Fix missing :
2012-07-25 16:34:54 -07:00
Ben Lumley
7c79985460 Fix missing : 2012-07-25 23:31:30 +02:00
Guilherme Blanco
1502d9b552 Merge pull request #111 from Adel-E/patch-1
Fix typo
2012-07-19 08:53:04 -07:00
Adel
f0cc192d7b Fix typo 2012-07-19 16:46:56 +02:00
Guilherme Blanco
a673018508 Merge pull request #109 from michaelperrin/typo_fixes
[Index] Small typo fix
2012-07-11 06:54:24 -07:00
Michaël Perrin
9b2f8dca64 [Index] Small typo fix 2012-07-11 11:51:29 +02:00
Benjamin Eberlei
b62ef939bf Merge pull request #108 from stephpy/fix_typo
fix typo on namespace
2012-07-09 02:12:14 -07:00
Stéphane PY
366bc21ab8 fix typo on namespace 2012-07-09 10:09:28 +02:00
Benjamin Eberlei
b3119c0a5f Merge pull request #107 from frosas/document-pre-and-post-flush-events
Document preFlush and postFlush events
2012-07-08 13:21:54 -07:00
Francesc Rosàs
610295f875 Document preFlush and postFlush events 2012-07-08 21:30:00 +02:00
Benjamin Eberlei
15c9f10bc1 Make composer subtitle to clarify this section applies to Composer only 2012-07-07 18:25:45 +02:00
Benjamin Eberlei
6943244107 Fix docs 2012-07-04 22:15:36 +02:00
Guilherme Blanco
d75569abab Merge pull request #106 from michaelperrin/typo_fixes
Fix typo in the "Getting started database" tutorial
2012-06-28 06:52:04 -07:00
Michaël Perrin
b5e11259e1 Fix typo in the "Getting started database" tutorial 2012-06-28 15:34:58 +02:00
Guilherme Blanco
86884a33f5 Merge pull request #105 from iampersistent/patch-1
Add xml code block for OrderBy
2012-06-20 07:03:41 -07:00
Richard Shank
de9f053cfb Add xml code block for OrderBy 2012-06-20 03:41:20 -07:00
Douglas Greenshields
bc76f33092 added missing JoinColumn node for xml and yaml for many-to-one unidirectional mapping 2012-06-18 19:21:44 +01:00
Guilherme Blanco
b4a9b1550c Merge pull request #103 from Dinduks/add_missing_section_in_configuration
Add a link to the connection configuration
2012-06-18 10:07:47 -07:00
Dinduks
458b0df39e Add a link to the connection configuration 2012-06-18 18:43:10 +02:00
Guilherme Blanco
b292adceb0 Merge pull request #102 from Dinduks/add_missing_section_in_configuration
Add a missing section in the Configuration manual
2012-06-18 09:05:29 -07:00
Dinduks
6e78973eec Add a missing section in the Configuration manual 2012-06-18 17:45:20 +02:00
Benjamin Eberlei
edb7950be8 Getting STarted: Code, Model, Database first 2012-06-16 13:34:23 +02:00
Benjamin Eberlei
1a9443b55a Let docs point to github for XSDs, much better to maintain for us. 2012-06-16 12:44:00 +02:00
Benjamin Eberlei
f854a99e0a Dum 2012-06-16 12:39:58 +02:00
Benjamin Eberlei
50879db001 Fixes 2012-06-16 12:39:01 +02:00
Benjamin Eberlei
63ebaea25a Some more work on index 2012-06-16 12:34:26 +02:00
Benjamin Eberlei
a3883eb306 Reworked docs towards composer, simplified chapters 2012-06-16 12:12:04 +02:00
Benjamin Eberlei
65e2f60b40 Rework configuration and tools section to include Composer 2012-06-16 11:57:56 +02:00
Tim Nagel
e5bac27fcc Initial ResolveTargetEntityListener cookbook entry 2012-06-16 17:36:40 +10:00
Guilherme Blanco
feeef689f3 Merge pull request #100 from adrienbrault/patch-1
Fix typo/Add missing words
2012-06-13 06:07:09 -07:00
Adrien Brault
47febcd7f4 Fix typo/Add missing words 2012-06-13 01:36:40 +03:00
Guilherme Blanco
138ec8411c Merge pull request #99 from ErikDubbelboer/spelling-fixes
Fixed spelling error and missing php open tag
2012-06-07 05:37:01 -07:00
Erik Dubbelboer
24d488b5f1 fixed minor errors 2012-06-07 13:53:58 +02:00
Guilherme Blanco
672b39fb84 Merge pull request #97 from patrick-mcdougle/patch-2
Fixed wording on the Alice and Bob Optimistic locking example.
2012-06-05 14:47:37 -07:00
patrick-mcdougle
9f575aad5b Fixed wording on the Alice and Bob Optimistic locking example. 2012-06-05 16:35:37 -05:00
Guilherme Blanco
988d0001d3 Merge pull request #96 from calumbrodie/patch-1
Fixed inline example of concat method
2012-05-31 06:52:44 -07:00
Guilherme Blanco
2205045ca9 Merge pull request #95 from adanlobato/patch-1
Fixed some typos on Inheritance docs
2012-05-31 06:51:28 -07:00
Calum Brodie
e41704b211 Fixed inline example of concat method 2012-05-31 14:08:56 +02:00
Adán Lobato
b25548414b Fixed some typos on Inheritance docs 2012-05-31 10:51:38 +02:00
Benjamin Eberlei
34f7ccb5fa Merge pull request #94 from shieldo/patch-1
Improved grammar/ punctuation in pagination tutorial, and brought parame...
2012-05-30 07:09:49 -07:00
Douglas Greenshields
671177e162 Improved grammar/ punctuation in pagination tutorial, and brought parameter name in code example into line with actual parameter 2012-05-30 15:59:58 +02:00
Guilherme Blanco
8b4e08d694 Updated docs (trying to fix one-to-many with unidirectional join table example). 2012-05-28 12:39:31 -04:00
Guilherme Blanco
4627c8b3ee Re-synchronized DQL EBNF with current DQL support. 2012-05-21 16:13:15 -04:00
Guilherme Blanco
b5b569afd4 Merge pull request #93 from patrick-mcdougle/patch-1
Updated the decimal type mapping have a string on the php side. (current behavior)
2012-04-26 18:09:22 -07:00
patrick-mcdougle
b184772349 Updated the decimal type mapping have a string on the php side. (current behavior) 2012-04-26 14:21:51 -05:00
Guilherme Blanco
f61bd43621 Merge pull request #86 from FabioBatSilva/patch-5
Docs for NamingStrategy
2012-04-20 21:08:53 -07:00
Guilherme Blanco
da21917dad Merge pull request #85 from Tobion/patch-1
fix confusing typo in ordered collections
2012-04-20 21:07:14 -07:00
Fabio B. Silva
e65dbcf2b5 Fix typo 2012-04-19 20:58:31 -03:00
Fabio B. Silva
a1e7389e71 docs for association/attribute override 2012-04-19 20:56:18 -03:00
Guilherme Blanco
ecd6e1d510 Merge pull request #92 from import/feature/new-cache-driver
New cache driver documentation
2012-04-19 07:02:26 -07:00
Osman Üngür
0222981161 Added notes about configuration 2012-04-19 11:34:12 +03:00
Osman Üngür
488914a4ac Added section about cache driver 2012-04-19 11:33:26 +03:00
Guilherme Blanco
7d9738e8c0 Merge pull request #91 from import/fix/sample-code-error
Fix for collection handling code sample
2012-04-18 08:40:57 -07:00
Osman Üngür
05a188da38 Fix for collection handling code sample 2012-04-18 17:13:14 +03:00
Guilherme Blanco
03a74a250a Merge pull request #89 from krevindiou/fix-1
Fix typo
2012-04-16 08:33:40 -07:00
Arnaud BUCHOUX
6a80ebf985 Fix typo 2012-04-16 17:15:18 +02:00
Guilherme Blanco
4debe46d1f Merge pull request #88 from FabioBatSilva/patch-7
Docs for Named Native Query
2012-04-16 07:57:11 -07:00
Fabio B. Silva
58d4b2a617 docs for Named Native Query 2012-04-15 19:49:42 -03:00
Fabio B. Silva
2007f1ab95 annotations reference for named native query 2012-04-15 16:57:44 -03:00
Fabio B. Silva
987834a2dd wrap lines 2012-04-15 11:34:10 -03:00
Fabio B. Silva
3076e2a1f7 docs for NamingStrategy 2012-04-15 11:34:10 -03:00
Benjamin Eberlei
543a3ddb03 Merge pull request #84 from gedrox/patch-1
[#DWEB-103] Fixed UTC timezone creation
2012-04-13 02:17:43 -07:00
Tobias Schultze
301f4d0346 fix confusing typo in ordered collections 2012-04-12 23:22:06 +03:00
gedrox
429ac54a34 [#DWEB-103] Fixed UTC timezone creation. Constant DateTimeZone::UTC is 1024, string name is required for the DateTimeZone constructor. 2012-04-11 10:02:42 +03:00
Benjamin Eberlei
e168b4e543 More prominent note about ResultSetMappingBuilder in native-sql chapter 2012-04-10 23:16:51 +02:00
Benjamin Eberlei
01381fae1f [DDC-1698] Autoloading proxies 2012-03-14 20:11:17 +01:00
Guilherme Blanco
7782f91131 Merge pull request #82 from augustohp/patch-1
Fixed typo "Assocations" in doc index
2012-02-27 10:00:24 -08:00
Augusto Pascutti
579774f505 Fixed typo "Assocations" 2012-02-27 14:55:52 -03:00
Guilherme Blanco
e8fbafd154 Fixed OneToMany bidirectional association mapping in Annotations and also included the YAML missing one. 2012-02-26 11:40:13 -05:00
Benjamin Eberlei
35ded56fdd Add note about debugging DQL queries. 2012-02-20 10:48:57 +01:00
Benjamin Eberlei
361c88d6ea Merge pull request #55 from michal-pipa/fix-indentation
Fixed indentation.
2012-02-18 15:56:26 -08:00
Benjamin Eberlei
9b484192c4 Merge pull request #81 from meze/patch-1
Fix a typo in a code example (getResults instead of getResult)
2012-02-18 15:55:38 -08:00
meze
86010bdb0d Fix a typo in a code example (getResults instead of getResult) 2012-02-19 03:01:29 +04:00
Benjamin Eberlei
138b67db86 Fix wrong use of private in mapped superclasses, only protected is supported here. 2012-02-13 11:18:35 +01:00
Benjamin Eberlei
ea95bd57ef Rework a bit 2012-02-05 22:54:28 +01:00
Benjamin Eberlei
3def848422 Fix enumeration 2012-02-05 22:51:42 +01:00
Benjamin Eberlei
6fa7580d10 Add tutorial about Pagination 2012-02-05 22:46:43 +01:00
Benjamin Eberlei
984c8f7db1 Fix versions 2012-02-05 22:28:30 +01:00
Benjamin Eberlei
53947bf2e9 Merge pull request #79 from asm89/filters
Fix code blocks
2012-01-31 13:24:41 -08:00
Alexander
bd30a04d0d Fix code blocks 2012-01-31 22:24:36 +01:00
Guilherme Blanco
18bdc53907 Merge pull request #78 from asm89/filters
Documentation for the new filter functionality
2012-01-31 13:20:55 -08:00
Alexander
6f56dbe395 Added filters to the index 2012-01-31 15:58:31 +01:00
Alexander
ff9e7ef64b Processed comments of @Ocramius 2012-01-31 15:56:24 +01:00
Alexander
cfbfac6a51 General information on how the filter work etc + warning on the enabling/disabling part 2012-01-31 15:56:24 +01:00
Alexander
7c8a9c0f9a Initial draft for filter documentation 2012-01-31 15:56:24 +01:00
Benjamin Eberlei
d378b5aec8 Update theme 2012-01-30 00:56:14 +01:00
Benjamin Eberlei
c613351f39 Added 10 quick steps section 2012-01-29 22:40:38 +01:00
Benjamin Eberlei
9bd51f2062 Update theme 2012-01-29 20:56:48 +01:00
Benjamin Eberlei
cfd5eefa7a Update theme 2012-01-29 20:54:51 +01:00
Benjamin Eberlei
781f83f648 Update unitofwork section 2012-01-29 20:45:17 +01:00
Benjamin Eberlei
02e7dcdc87 Fix conf.py for 2.2 release 2012-01-29 20:06:07 +01:00
Benjamin Eberlei
8a39a66057 Some fixes in the docs 2012-01-29 20:03:40 +01:00
Benjamin Eberlei
5e3e48c8dd Rename getting-started tutorial page. 2012-01-29 19:48:09 +01:00
Benjamin Eberlei
0b7d5f2813 Merge pull request #50 from cordoval/patch-3
corrected rst for bash like code blocks
2012-01-29 10:36:41 -08:00
Benjamin Eberlei
ccabbf328f Merge pull request #56 from michal-pipa/fix-underline
Fixed title underline.
2012-01-29 10:36:25 -08:00
Guilherme Blanco
022c08d947 Merge pull request #77 from Ocramius/patch-3
Making the correct usage of LIKE expressions and placeholders explicit
2012-01-29 07:40:52 -08:00
Marco Pivetta
f9523aa419 Making the correct usage of LIKE expressions and placeholders explicit 2012-01-29 15:59:45 +01:00
Benjamin Eberlei
b0ec3dfb47 Started refactoring of the documentation towards smaller chapters, grouped into logical units with better explanations (hopefully). 2012-01-28 00:49:37 +01:00
Benjamin Eberlei
19b7d4d0d4 Simplify section names even more 2012-01-27 21:43:04 +01:00
Benjamin Eberlei
d5a97c0c59 Add fancy new index page, moving toc to toc.rst 2012-01-27 21:36:36 +01:00
Guilherme Blanco
3b9aac21c4 Merge pull request #76 from shiroyuki/patch-1
Changed from "@var string" to "@var int" for the complete version of ent...
2012-01-26 08:57:28 -08:00
Juti Noppornpitak
3880ec6839 Changed from "@var string" to "@var int" for the complete version of entities/User.php. 2012-01-26 11:29:32 -05:00
Benjamin Eberlei
9602e6785e Updated theme 2012-01-23 00:44:19 +01:00
Benjamin Eberlei
7918a42a0e Theme submodule 2012-01-22 23:25:48 +01:00
Benjamin Eberlei
81ba71e8d5 Remove _theme 2012-01-22 23:24:01 +01:00
Benjamin Eberlei
23529e839d Adjust css 2012-01-22 23:15:52 +01:00
Benjamin Eberlei
34a696c3d6 Add all static dependencies instead of loading them from doctrine project site. 2012-01-22 23:09:21 +01:00
Benjamin Eberlei
cad694e469 Explicit Javascripts 2012-01-22 22:49:14 +01:00
Benjamin Eberlei
17d91d173b Change _templates override into a theme "doctrine". 2012-01-22 22:23:19 +01:00
Guilherme Blanco
1b23b4bc47 Merge pull request #75 from hype-/patch-1
Added missing docblock endings.
2012-01-16 13:20:53 -08:00
Mikko Hirvonen
d44d82b694 Added missing docblock endings. 2012-01-16 22:08:49 +02:00
Guilherme Blanco
649d29414f Fixed one-to-many unidirectional with join table chapter. 2012-01-13 01:09:28 -05:00
Benjamin Eberlei
7b4349a9ce Rewrite installation and configuration of Tools chapter. 2012-01-09 10:36:56 +01:00
Benjamin Eberlei
1d5597917b Fix TIP and show new cache instropection API (2.2) 2012-01-09 08:37:12 +01:00
Benjamin Eberlei
ce0873d589 Merge pull request #73 from AmirBehzad/TutorialFixes
Update en/tutorials/getting-started-xml-edition.rst
2012-01-05 13:40:57 -08:00
AmirBehzad Eslami
bc91e5c0fd Separated listings of User.php, Bug.php, and Product.php. Added missed statement to use ArrayCollection in User.php . 2012-01-06 01:03:15 +03:30
Benjamin Eberlei
6d1f716f8b Update en/tutorials/getting-started-xml-edition.rst 2012-01-05 21:40:15 +01:00
Benjamin Eberlei
442227fc89 Update en/reference/architecture.rst 2012-01-05 10:28:46 +01:00
Guilherme Blanco
ae1c171392 Merge pull request #70 from maastermedia/master
Typos fixed
2011-12-30 20:18:50 -08:00
Peter Kokot
65c64f52c8 typo in reference/tools 2011-12-31 04:37:25 +01:00
Peter Kokot
1c31603e17 type varchar replaced with type string 2011-12-31 04:33:46 +01:00
Benjamin Eberlei
894dfd1a6b Update en/reference/yaml-mapping.rst 2011-12-22 23:06:59 +01:00
Guilherme Blanco
d23b3bb056 Merge pull request #69 from keymaster/patch-1
Clarify the performance warning.
2011-12-21 06:56:17 -08:00
keymaster
0b819ca3b0 Clarify the performance warning. 2011-12-21 12:24:37 +02:00
Guilherme Blanco
041a4e0b43 Merge pull request #66 from FabioBatSilva/patch-2
add doc for default repository class
2011-12-20 08:34:27 -08:00
Fabio B. Silva
ca87ddd540 add doc for default repository class 2011-12-20 09:15:37 -02:00
Guilherme Blanco
e29dfabb74 Merge pull request #65 from FabioBatSilva/patch-1
[Annotations] add doc for @MappedSuperclass#repositoryClass
2011-12-19 12:41:15 -08:00
Fabio B. Silva
43e4e1c389 add doc for MappedSuperclass repositoryClass 2011-12-19 18:35:01 -02:00
Benjamin Eberlei
e42d70a2b0 Clarify how the CLI is setup 2011-12-17 23:56:10 +01:00
Benjamin Eberlei
48acbf75cd Reference new cookbook entry 2011-12-17 16:28:46 +01:00
Benjamin Eberlei
22ac3a3099 Add cookbook entry on saving entities in the session. 2011-12-17 16:28:31 +01:00
Guilherme Blanco
373090f223 Merge pull request #64 from ebernhardson/master
Replace non-existant constant T_ABS
2011-12-13 20:04:23 -08:00
ebernhardson
5ea8861bf3 replace non-existant constant Lexer::T_ABS 2011-12-13 10:36:14 -08:00
Guilherme Blanco
e85ce5c02f Merge pull request #63 from Ocramius/patch-2
Fixing minor issues reported by BostjanWrk on IRC about the RSM Builder ...
2011-12-13 06:31:30 -08:00
Marco Pivetta
220c6c4e0e Fixing minor issues reported by BostjanWrk on IRC about the RSM Builder example 2011-12-13 11:30:09 +01:00
Benjamin Eberlei
f057587457 Merge pull request #62 from jsor/cookbook_types_fix
Add missing canRequireSQLConversion() in example type code
2011-12-02 12:58:54 -08:00
Jan Sorgalla
69295ba076 Add missing canRequireSQLConversion() in example type code 2011-12-02 20:59:17 +01:00
Benjamin Eberlei
76254d693d Merge pull request #61 from jsor/cookbook_types
Cookbook article: Advanced field value conversion using custom mapping types
2011-11-29 03:13:26 -08:00
jsor
cbefb7c543 Finalize first version 2011-11-29 10:31:07 +01:00
Jan Sorgalla
228d8517c7 Add type 2011-11-28 21:06:01 +01:00
jsor
bece5f0f91 Change ref name 2011-11-28 15:22:55 +01:00
jsor
1300758499 Rename article and add more text 2011-11-28 15:19:49 +01:00
jsor
129b9d0945 Setup article 2011-11-28 11:52:17 +01:00
Benjamin Eberlei
b9b05fc3eb Fix for DDC-1293 2011-11-18 23:10:40 +01:00
Guilherme Blanco
4c8ab82f9c Merge pull request #60 from Ocramius/patch-1
Fixing invalid XML mapping samples
2011-11-16 11:42:05 -08:00
Marco Pivetta
2200c1f7e1 Fixing invalid XML mapping samples 2011-11-16 20:00:21 +01:00
Guilherme Blanco
59849f73bd Merge pull request #59 from romainneutron/master
Fix some YAML syntax errors
2011-11-15 15:48:30 -08:00
Benjamin Eberlei
51211980a4 Remove ON in docs 2011-11-15 22:21:15 +01:00
Romain Neutron
c53e2772a0 Fix YAML syntax errors in examples 2011-11-15 22:10:56 +01:00
Guilherme Blanco
650a80a61a Merge pull request #57 from volftomas/master
Minor changes
2011-11-08 05:57:38 -08:00
Tomas Paladin Volf
cfe5424cf9 Fixed pear command for install, "boostrap" => "bootstrap" 2011-11-08 14:44:48 +01:00
Guilherme Blanco
59b4641d29 Merge pull request #54 from michal-pipa/fix-link
Changed external link to internal link.
2011-11-07 04:50:09 -08:00
Michał Pipa
64200c405e Fixed title underline. 2011-11-07 08:19:20 +01:00
Michał Pipa
0eed56fae9 Fixed indentation. 2011-11-07 08:18:22 +01:00
Michał Pipa
c0f86e796d Changed external link to internal link. 2011-11-07 07:53:05 +01:00
Guilherme Blanco
0f11b0c61d Merge pull request #53 from michal-pipa/doc
Fixed broken link.
2011-10-31 09:43:13 -07:00
Guilherme Blanco
981a2d4341 Merge pull request #52 from michal-pipa/c21aaebbc4159f27d6db1194d585ba8895057f6a
Changed 'Symfony 2' to 'Symfony2'.
2011-10-31 09:16:11 -07:00
Michał Pipa
299642083b Fixed broken link. 2011-10-31 17:10:30 +01:00
Michał Pipa
c21aaebbc4 Changed 'Symfony 2' to 'Symfony2'.
http://symfony.com/blog/talk-about-symfony2-not-symfony-2
2011-10-31 17:09:15 +01:00
Guilherme Blanco
0317f43987 Merge pull request #51 from asm89/patch-1
Fix typos in 'Working with objects' php examples
2011-10-26 07:48:36 -07:00
Alexander
78ef07f630 Update en/reference/working-with-objects.rst 2011-10-26 14:12:32 +03:00
Benjamin Eberlei
e98cb4f145 Document AbstractQuery#getOneOrNullResult() 2011-10-26 00:54:06 +03:00
Luis Cordova
cf44745d08 corrected rst for bash like code blocks 2011-10-25 15:50:29 -05:00
Benjamin Eberlei
196fd79d5f Merge pull request #27 from chriswoodford/persisting-the-decorator
Persisting the decorator
2011-10-25 13:47:24 -07:00
Benjamin Eberlei
8dc77a7083 Merge pull request #43 from marcw/patch-1
Fixed namespace in code-block
2011-10-25 13:45:58 -07:00
Benjamin Eberlei
a5688f60d3 Merge pull request #44 from mweimerskirch/patch-1
Documented the onClear event
2011-10-25 13:45:41 -07:00
Benjamin Eberlei
288ec8aa5c Merge pull request #45 from oyerli/patch-1
Fixed typo in the orderBy method.
2011-10-25 13:45:24 -07:00
Benjamin Eberlei
c7658d6285 Merge pull request #49 from cordoval/patch-2
typo change from contains to consists of
2011-10-25 13:45:08 -07:00
Luis Cordova
1bc0efba43 typo change from contains to consists of 2011-10-25 15:33:30 -05:00
Guilherme Blanco
de75ea88b6 Merge pull request #48 from cordoval/patch-1
two typos fixed
2011-10-25 13:11:20 -07:00
Luis Cordova
0a989e63d2 two typos fixed 2011-10-25 15:08:52 -05:00
Benjamin Eberlei
237c20c9b6 Enhanced docs on UnitOfWork#computeChangeSets 2011-10-15 18:09:27 +02:00
Benjamin Eberlei
6d4337fc71 More details on partial objects 2011-10-15 16:14:36 +02:00
Benjamin Eberlei
b88ef8b1a5 Add docs on SimplifiedXmlDriver and SimplifiedYamlDriver 2011-10-15 09:47:56 +02:00
Guilherme Blanco
9c389a49c7 Fixes DDC-509 2011-10-11 01:27:30 -03:00
Guilherme Blanco
da4948944d Merge pull request #47 from mdpatrick/interveawed_association_mapping
Corrected a typo (interveawed)
2011-10-04 10:33:48 -07:00
Dan Patrick
1a1d36c73f Corrected a typo (interveawed) 2011-10-04 12:19:10 -05:00
Chris Woodford
d3f6ffb09e Added cookbook/decorator-pattern to index file 2011-09-18 18:39:36 -04:00
Guilherme Blanco
f32a780459 Merge pull request #46 from havvg/master
fix example php code on sequence generator
2011-09-13 20:00:45 -07:00
Toni Uebernickel
3ec55d0cdd fix sequence generator php code on sequenceName 2011-09-13 20:32:04 +02:00
Ozan Yerli
a61c7e59d6 Fixed typo in the orderBy method. 2011-09-04 23:43:43 +03:00
Benjamin Eberlei
8084b6cbf0 Add docs on DDC-659 feature (ClassMetadataBuilder) 2011-09-04 14:25:03 +02:00
Michel Weimerskirch
79c113b532 Documented the onClear event. (using text from the docblock comment) 2011-09-02 22:38:47 +03:00
Benjamin Eberlei
e6e1243852 Merge branch 'master' of github.com:doctrine/orm-documentation 2011-08-27 12:36:56 +02:00
Benjamin Eberlei
5fc0ede5bf Fixed the tutorial, it was a mess! Now its explaining everything step by step and all bugs are removed. Changed introduction and configuration to use the simpler Setup helper class 2011-08-27 12:36:37 +02:00
Marc Weistroff
c90f3cf275 Fixed namespace in code-block 2011-08-26 02:07:56 +03:00
Joseph Rouff
ffa2a545fa Fix typo 2011-08-25 01:36:21 +03:00
Guilherme Blanco
35cb7b97db Merge pull request #42 from rouffj/patch-1
Fix typo
2011-08-24 09:32:11 -07:00
Guilherme Blanco
a5ac76b192 Added new support to DQL: ORDER BY now supports SingleValuedPathExpression and INSTANCE OF now supports multi-value checking. 2011-08-15 01:57:02 -03:00
Benjamin Eberlei
26c56dd0d2 Merge pull request #40 from hhamon/query_builder_fix
[QueryBuilder] added missing $
2011-08-14 09:24:21 -07:00
Hugo Hamon
31f34e95cc [QueryBuilder] added missing $ 2011-08-14 18:21:31 +02:00
Guilherme Blanco
189c729f15 Added support to CaseExpression. 2011-08-08 02:08:19 -03:00
Benjamin Eberlei
60ed7769cd Merge branch 'master' of github.com:doctrine/orm-documentation 2011-08-06 19:13:01 +02:00
Benjamin Eberlei
5ee8861350 Add note about comparing datetime and object by reference 2011-08-06 19:12:42 +02:00
Guilherme Blanco
01caa0d06e Merge pull request #39 from craue/patch-1
completed the sentence in section "postUpdate, postRemove, postPersist"
2011-08-04 16:16:20 -07:00
Christian Raue
1de9b906fd completed the sentence in section "postUpdate, postRemove, postPersist" 2011-08-05 02:10:40 +03:00
Guilherme Blanco
255cf347ec Merge pull request #38 from mdpatrick/grammar_on_getting_started_xml_edition
Small grammar changes in section explaining lazyload/ArrayCollection.
2011-08-01 20:33:08 -07:00
Dan Patrick
6cd7d21db1 Small grammar changes in section explaining lazyload/ArrayCollection. 2011-08-01 18:04:14 -05:00
Guilherme Blanco
67818ea2cc Merge pull request #37 from mdpatrick/associations_grammar_errors
Fixed minor grammatical errors in working-with-associations.rst
2011-07-30 15:40:12 -07:00
Dan Patrick
bfe5bea68d Fixed grammatical errors in working-with-associations.rst 2011-07-30 17:15:44 -05:00
Benjamin Eberlei
3a70ee6662 Merge pull request #25 from wilmoore/master
use 'string' instead of 'varchar'
2011-07-26 14:25:53 -07:00
Benjamin Eberlei
c0860a6018 Merge pull request #35 from mridgway/DDC-1270
[DDC-1270] Fixed invalid expr()->*() function calls; Added isNull and isN
2011-07-26 14:23:38 -07:00
Benjamin Eberlei
da60e86993 Merge pull request #34 from mridgway/DDC-725
[DDC-725] Removed onUpdate property
2011-07-26 14:22:59 -07:00
Benjamin Eberlei
ecb13a87dc Bugfix 2011-07-13 21:31:31 +02:00
Benjamin Eberlei
d8125768a3 Copy new build process for docs into orm docs from DBAL 2011-07-13 21:19:09 +02:00
Benjamin Eberlei
91b2c82c58 Brought most of the documentation up to date on 2.1 2011-07-13 20:31:01 +02:00
Benjamin Eberlei
660ead4b0e FAQ and Composite Key renaming 2011-07-13 19:04:21 +02:00
Benjamin Eberlei
c876f9edb2 Add warning to Association Mapping chapter that referencedColumnName have to primary key columns 2011-07-12 23:01:42 +02:00
Benjamin Eberlei
f4074dbe1d Removed solved limitations and added missing ones 2011-07-12 22:58:24 +02:00
Michael Ridgway
175faeb5f2 [DDC-1270] Fixed invalid expr()->*() function calls; Added isNull and isNotNull functions; Fixed casing on orX and andX 2011-07-11 13:25:15 -04:00
Benjamin Eberlei
82419813df Another inheritance question 2011-07-10 20:34:34 +02:00
Benjamin Eberlei
609e3b8c92 Write something about all FAQ entries that were empty 2011-07-10 20:31:16 +02:00
Benjamin Eberlei
fac1a517df Finish chapter on composite primary keys (still missing XML and YAML example though) 2011-07-10 20:03:59 +02:00
Benjamin Eberlei
653add64f9 Fix bug in mappings of Aggregate Column cookbook entry 2011-07-09 21:26:13 +02:00
Benjamin Eberlei
89b807177e Merge branch 'master' of github.com:doctrine/orm-documentation 2011-06-28 23:29:06 +02:00
Benjamin Eberlei
09d5169a8b Remove methods that not exist anymore from Expr object documentation 2011-06-28 23:28:55 +02:00
Michael Ridgway
8af162711e Removed onUpdate property 2011-06-28 16:34:12 -04:00
Benjamin Eberlei
503b6c1716 Merge pull request #33 from nicodmf/master
Little typo
2011-06-28 08:53:57 -07:00
Nicolas de Marqué Fromentin
ad012a63cb Little typo 2011-06-28 16:22:14 +02:00
Benjamin Eberlei
36e500682a Started FAQ 2011-06-20 21:20:23 +02:00
Benjamin Eberlei
de5952f597 Fix association mappings 2011-06-16 21:52:51 +02:00
Benjamin Eberlei
f6aa387e6f Fix missing chars in xml comment 2011-06-16 21:27:20 +02:00
Benjamin Eberlei
e750b27edb Fix another bug, yet another :) 2011-06-16 21:23:39 +02:00
Benjamin Eberlei
3417f3716e Fix another bug2 2011-06-16 21:20:56 +02:00
Benjamin Eberlei
5c5feab38e Fix another bug 2011-06-16 21:19:25 +02:00
Benjamin Eberlei
97c305452e Fix bug in docs 2011-06-16 21:18:17 +02:00
Benjamin Eberlei
41600667c2 More association mapping yml+xml code blocks 2011-06-12 23:43:29 +02:00
Benjamin Eberlei
c187862e4f Reorder paragraph and fix note. 2011-06-12 23:26:54 +02:00
Benjamin Eberlei
d8f9c5380a More xml+yml configuration blocks 2011-06-12 23:22:28 +02:00
Benjamin Eberlei
b5827ea83f Add tons of xml and yaml configuration blocks to basic- and association-mapping chapters 2011-06-12 23:07:21 +02:00
Benjamin Eberlei
6816816101 Fix bug in previous commit 2011-06-11 10:07:18 +02:00
Benjamin Eberlei
06e45f7587 Fix bug in previous commit 2011-06-11 09:59:17 +02:00
Benjamin Eberlei
71c47f69ec Mention 5.3 dependency in getting started tutorial, and link to entity restrictions aswell. Add the func_get_args() restriction to the list in architecture. 2011-06-11 09:52:17 +02:00
Benjamin Eberlei
6f909b6e69 Clarifications and additions in DQL and Working with objects chapter 2011-06-11 08:59:33 +02:00
Benjamin Eberlei
a5cfd2321f Document Mssql Datetim2 behavior 2011-06-05 17:21:11 +02:00
Guilherme Blanco
26c2690536 Merge pull request #31 from Infranology/typos
fixed constants typos in "Entity State"
2011-06-01 20:19:12 -07:00
Eriksen Costa
729ad9e5c9 fixed constants typos in "Entity State" 2011-06-02 00:14:46 -03:00
Guilherme Blanco
48cf91a3d0 Merge pull request #30 from renansaddam/patch-1
Version 2.0.5 no longer needs $cli call and the namespace for Components
2011-05-19 10:16:40 -07:00
Renan Gonçalves aka renan.saddam
457abbacef Version 2.0.5 no longer needs $cli call and the namespace for Components has changed. 2011-05-19 06:20:50 -07:00
Guilherme Blanco
7adbf5698a Merge pull request #29 from HoffmannP/ORMDOC
Ormdoc
2011-05-18 11:31:42 -07:00
Berengar W. Lehr
f58dcbaa8a 1. Replaced "fi"-ligature to actually "fi"-letters as pdflatex inputenc package does not support that unicode character
2. Replaced external link to internal document with internal link (now actuall link in documentation should work)
2011-05-18 20:08:49 +02:00
Berengar W. Lehr
6d457d9927 1. Changed external links syntax in internal link syntax to repair link (dql-… file)
2. Replaced unicode "fi"-ligatur with "fi" to enable pdflatex-ing b/c utf8-inputenc-package does - for unknown reasons - not support unicode "fi"-ligatur
2011-05-18 19:49:48 +02:00
Guilherme Blanco
e665d9f6df Merge pull request #28 from brikou/patch-1
fixed setQueryHint > setHint
2011-05-18 05:47:33 -07:00
Brikou CARRE
d436ea19df fixed setQueryHint > setHint 2011-05-18 02:51:22 -07:00
Chris Woodford
6cd778f940 wrote a quick introduction 2011-05-05 21:36:18 -04:00
Chris Woodford
e9d096e411 cleaning up some sentences. better flow 2011-05-05 21:23:10 -04:00
Chris Woodford
894b2614ed fixed some typos 2011-05-05 21:16:40 -04:00
Chris Woodford
c242ab4371 some tweaks to class descriptions 2011-05-03 23:04:41 -04:00
Chris Woodford
a51f182cc0 first draft of cookbook article 2011-05-03 22:48:40 -04:00
Guilherme Blanco
302199938f Merged pull request #24 from gimler/master.
tutorial
2011-04-29 20:47:31 -07:00
Guilherme Blanco
8d66809d0b Merged pull request #26 from asartalo/master.
Getting Started tutorial corrections
2011-04-29 20:25:40 -07:00
asartalo
16f132bff0 Corrected annotation for sample code: moved repositoryClass attribute from @Table to @Entity. 2011-04-24 13:24:54 +08:00
asartalo
3cac853dd4 Changed instances of entity manager variable '' to '' for consistency. 2011-04-24 13:21:34 +08:00
asartalo
511268ddd7 Removed another instance of '--force' Option for the orrm:schema-tool:create command as this is not available for this command. 2011-04-24 10:52:18 +08:00
asartalo
01bfc4f2f1 Removed '--force' Option for the orrm:schema-tool:create command as this is not available for this command. 2011-04-24 10:38:35 +08:00
Wil Moore III
3a80896173 Changed registerDoctrineTypeMapping('enum', 'varchar'); to registerDoctrineTypeMapping('enum', 'string'); as 'string' is the correct type to map to whereas varchar is only an alias. 2011-04-08 10:44:55 -07:00
Benjamin Eberlei
f76728818b Add note box on cascade operations being performed in memory and reference to foreign key /database-level onDelete option for deleting associtions 2011-04-06 23:36:50 +02:00
Benjamin Eberlei
a0b41feb72 Update section on mixed query and add notes on multiple entities in FROM clause. (update paragraph) 2011-04-03 20:13:34 +02:00
Benjamin Eberlei
2e777f99b9 Update section on mixed query and add notes on multiple entities in FROM clause. 2011-04-03 20:00:43 +02:00
Benjamin Eberlei
a259ac9bcf Merge branch 'master' of github.com:doctrine/orm-documentation 2011-03-31 21:49:51 +02:00
Benjamin Eberlei
04527a8bcb Add cookbook entry on MySQL Enums. 2011-03-31 21:49:32 +02:00
Gordon Franke
63c431f01d fix entity manager reference in repository code sample 2011-03-31 19:55:57 +08:00
Gordon Franke
7639f1e99b fix entity manager reference in repository code sample 2011-03-31 08:57:27 +02:00
Benjamin Eberlei
abc8f29800 [DDC-692] Add docs on 2.1 read only entities feature 2011-03-29 20:30:10 +02:00
Benjamin Eberlei
22ab3d1256 Fixed several issues and merged pull requests into docs. 2011-03-28 20:40:39 +02:00
Benjamin Eberlei
126e592758 Fix YAML docs on cascade 2011-03-27 22:23:37 +02:00
Benjamin Eberlei
cca642b094 Remove CASE, COALESCSE, NULLIF EBNF rules for the time being, they are not yet supported. 2011-03-20 12:05:28 +01:00
Benjamin Eberlei
c551192b6b DBAL-86 - Fix reference to nonexisting Configuration#setCustomTypes() 2011-02-27 09:03:59 +01:00
Benjamin Eberlei
d0ae95604f Add warnining about date types assuming default timezone. 2011-02-26 16:29:11 +01:00
Benjamin Eberlei
e94d15ec11 Fix DDC-1039 2011-02-26 11:53:43 +01:00
Benjamin Eberlei
0252368af2 Fix some Sphinx/ReST warnings 2011-02-26 11:51:43 +01:00
Benjamin Eberlei
e1c2084eeb Add section on EntityRepository into Getting Started Tutorial. 2011-02-26 11:51:30 +01:00
Benjamin Eberlei
aab2303c37 Fix Getting Started. 2011-02-26 11:12:58 +01:00
Jonathan H. Wage
c8c4c4ed46 Updating css. 2011-02-20 11:12:51 -06:00
Benjamin Eberlei
654dbb1b10 Fix <pre> tag styling having a very small line-height. 2011-02-20 11:34:59 +01:00
Benjamin Eberlei
9a289e2982 Adopt new Sphinx layout 2011-02-20 11:00:39 +01:00
Benjamin Eberlei
51fe83b7be Merge branch 'master' of github.com:doctrine/orm-documentation 2011-02-15 21:33:29 +01:00
Benjamin Eberlei
755f0b3dbb Add section about columnDefinition into basic mapping. 2011-02-15 21:33:24 +01:00
Benjamin Eberlei
35dae4e1f4 Add note about columnDefinition SchemaTool. 2011-02-15 21:31:20 +01:00
Jonathan H. Wage
1d24cb4828 Removing indices and tables as they are just broken links and don't do anything. 2011-02-13 16:24:52 -06:00
Benjamin Eberlei
24defe7100 Add notes to Tools about EntityGenerator, ConvertMapping and Reverse Engineering 2011-02-13 12:57:04 +01:00
Benjamin Eberlei
6309710ccc Fix some glitches in the docs: 2011-02-13 12:47:51 +01:00
Benjamin Eberlei
b794643735 Updated Limitations and Known Issues Chapter 2011-02-05 18:08:39 +01:00
Benjamin Eberlei
a592dc116d First part of a tutorial on composite primary keys and identity through foreign entities. 2011-02-05 18:01:10 +01:00
Benjamin Eberlei
eea5b71da5 Add tutorial on extra lazy associations 2011-02-05 16:14:51 +01:00
Benjamin Eberlei
93207c081e Add another paragraph with functionalities not described yet. 2011-02-05 15:26:58 +01:00
Benjamin Eberlei
94244683b0 Fix typo in new indexed assocations tutorial, thanks @gordonslondon 2011-02-05 15:23:39 +01:00
Benjamin Eberlei
609176a18f Merge branch 'master' of github.com:doctrine/orm-documentation 2011-02-05 13:48:41 +01:00
Benjamin Eberlei
c0cefa8749 Fixed TOCTree for tutorial on indexed associations. 2011-02-05 13:48:12 +01:00
Benjamin Eberlei
0f974c562c Add Working with Indexed Associations Tutorial 2011-02-05 13:43:45 +01:00
Ivar Nesje
af60471b62 fixed indentation on previous commit 2011-02-02 06:37:13 +08:00
Ivar Nesje
8fc7eb295b Fixed lifecycleCallbacks documentation bug when using YAML mapping 2011-02-02 06:37:03 +08:00
Ray Rehbein
2a38e5f408 Syntex and code correction in example 2011-02-02 05:48:49 +08:00
Ray Rehbein
be8d34dc21 Whitespace correction 2011-02-02 05:48:19 +08:00
Ray Rehbein
fc6cec9074 Added setParamater calls to examples
Whitespace changes to break up the long SQL lines into a readable format
2011-02-02 05:48:18 +08:00
Ray Rehbein
0411b1e75d Attempt at correction for a formatting glitch.
It appears Sphinx doesn't use the `` mark in the middle of a word correctly
2011-02-02 05:48:18 +08:00
Ray Rehbein
d81dca3e90 Corrected example calling wrong method name 2011-02-02 05:48:18 +08:00
Ray Rehbein
c89290e507 Corrected mismatch between example and comment entities. 2011-02-02 05:48:17 +08:00
Joel Clermont
754ebc052e DDC-823 - Fix minor grammar and punctuation mistakes 2011-02-02 05:46:25 +08:00
Benjamin Eberlei
7d42497e09 Fix typo with SQLLogger in configuration.rst 2011-01-31 22:56:09 +01:00
Benjamin Eberlei
7093e37b45 Merge branch 'master' of github.com:doctrine/orm-documentation 2011-01-31 22:47:13 +01:00
Benjamin Eberlei
51bdd6499a Fix typo in codeigniter coobkok entry 2011-01-31 22:46:41 +01:00
Benjamin Eberlei
0e5b30902c Enhanced QueryBuilder docs with paragraph on limit and executing queries. 2011-01-23 11:16:25 +01:00
Benjamin Eberlei
721915d61b Fix bug in configurationblock.css destroying simple bullet lists. 2011-01-02 11:53:12 +01:00
Benjamin Eberlei
f4e93e7550 Add section on orphan removal to Working with Associations chapter. 2011-01-02 11:18:22 +01:00
Benjamin Eberlei
4a231a34f2 Clarify MappedSuperclass and Unidirectional associations (with info on one-to-many and many-to-many associations). 2010-12-31 14:44:14 +01:00
Benjamin Eberlei
72081ff7ba Note that Native SQL Query is not to be used for DELETE, UPDATE and INSERT statements. 2010-12-30 22:13:57 +01:00
Benjamin Eberlei
ef8689c400 Incorporate DDC-879 into Cookbook. 2010-12-28 00:23:06 +01:00
Benjamin Eberlei
6bf2f22315 Add another note about how Query#setParameter() accepts named or positional parameters. 2010-12-20 22:19:02 +01:00
Benjamin Eberlei
34057c5e4b Add note about how Query#setParameter() accepts named or positional parameters. 2010-12-20 22:12:24 +01:00
Benjamin Eberlei
53ebc683c9 Update limitations and known issues section in manual. 2010-12-20 21:51:05 +01:00
Benjamin Eberlei
2eab525077 Fix some small problems in the docs. 2010-12-18 17:14:20 +01:00
Benjamin Eberlei
6a0f3f2d7b Add code-configuration directive, thank you Fabien! 2010-12-16 21:59:27 +01:00
Benjamin Eberlei
65fe9b86c5 Fix another code block 2010-12-14 23:35:17 +01:00
Benjamin Eberlei
76743431a4 Fix wrong code in Mini tutorial 2010-12-14 23:34:41 +01:00
Benjamin Eberlei
2e00e9d715 Fix another mess in Mini Tutorial. 2010-12-14 23:33:31 +01:00
Benjamin Eberlei
c8e7ff51c4 Merge branch 'master' of github.com:doctrine/orm-documentation 2010-12-14 23:32:57 +01:00
Benjamin Eberlei
6e7f47bf9e Fix mess in Mini-Tutorial of Introduction chapter. 2010-12-14 23:29:05 +01:00
ajessu
34dc2e6506 Delete duplicate item on menu 2010-12-15 00:41:09 +08:00
Benjamin Eberlei
29b47d3d44 Fix @Table not supporting schema as attribute anymore. 2010-12-12 13:18:18 +01:00
Benjamin Eberlei
0618944f8a DDC-901 - Fix documentation bug in Cookbook SQL Walker entry. 2010-12-12 13:12:44 +01:00
Albert Jessurum
d37120bb15 Fixed links 2010-12-12 18:04:13 +08:00
beberlei
5d58d9171e Merge ReST branch into master, ByeBye Markdown. 2010-12-11 12:31:31 +01:00
beberlei
9c6d3dbecd Add link to Doctrine homepage to docs layout template. 2010-12-11 11:29:37 +01:00
Benjamin Eberlei
eb0fd4d066 Add section about database and unit of work being out of sync and how this effects your code. 2010-12-11 01:01:44 +01:00
Benjamin Eberlei
01c2a09991 Add Working with DateTime chapter. 2010-12-03 22:47:33 +01:00
Benjamin Eberlei
46983465fd Finialized ReST doc changes, merged changes from latest Markdown docs. 2010-12-03 20:13:10 +01:00
Benjamin Eberlei
6789f2c8d1 Several changes and fixes to the docs. 2010-12-03 17:58:01 +01:00
Benjamin Eberlei
1b9e9c019d DDC-862 - Fix Codeigniter Cookbook entry 2010-11-16 22:03:27 +01:00
Benjamin Eberlei
49ecdff016 DDC-763 - Add note about handling merges of multiple entities which share subgraphs of objects 2010-11-10 23:49:07 +01:00
Benjamin Eberlei
f2b20e5949 DDC-736 - Add note about order of the identfication variables during fetch joins requirements. 2010-11-10 23:43:24 +01:00
goran
54a61bbbc2 Changed method name in an example for fetching class metadata 2010-11-11 03:24:14 +08:00
Timo A. Hummel
69d4e185d8 Added documentation notice regarding @version in combination with @id (DDC-873) 2010-11-11 03:23:52 +08:00
Timo A. Hummel
87c1c50bfa Fixed documentation issue for cascade="ALL" (DDC-850) 2010-11-11 03:23:11 +08:00
Benjamin Eberlei
de1d72f348 DDC-866 - Fix EBNF grammer rule 2010-11-09 22:19:07 +01:00
Benjamin Eberlei
ee042bc642 Some changes left 2010-11-09 22:18:25 +01:00
Benjamin Eberlei
c22bddc9a7 DDC-866 Fix deprecated EBNF rule 2010-11-09 22:17:31 +01:00
Benjamin Eberlei
a5a0dfa96e Converted ORM Docs into ReST 2010-11-01 22:03:50 +01:00
Benjamin Eberlei
e7e1f62f72 Add Sphinx Configs 2010-11-01 21:21:07 +01:00
Benjamin Eberlei
aa25b7cc0a Add Sphinx Configs 2010-11-01 21:21:01 +01:00
Benjamin Eberlei
1bfeaf3eaf Initial conversion from Markdown to ReST - Finalized Cookbook 2010-11-01 21:16:12 +01:00
Benjamin Eberlei
6985b671d8 Add note about float type 2010-10-31 15:13:51 +01:00
Benjamin Eberlei
468c878c92 Add hint about multiple directories in Metadata drivers to configuration section 2010-10-31 07:34:43 +01:00
Benjamin Eberlei
05f5ae1519 DDC-732 - Document sql schema requirements for inheritance strategies 2010-10-30 18:16:13 +02:00
Benjamin Eberlei
61640ef7ce Add Note that this is not a holy-grail but may need adjustments for other versions 2010-10-30 12:08:28 +02:00
Benjamin Eberlei
833a4a9319 Clarify not to use prefix blacklash in targetEntity 2010-10-29 14:12:29 +02:00
Benjamin Eberlei
3182f150c1 Fix a little render glitch 2010-10-29 13:30:02 +02:00
Jonathan H. Wage
62aef84205 Merge remote branch 'ralfas/master' 2010-10-18 17:15:38 -05:00
Benjamin Eberlei
9c63363ccc Add details on the negative performance impact of STI and CTI in many-to-one or one-to-one scenarios 2010-10-11 20:36:52 +02:00
Ralfas
e4b1357a22 fixed typos and grammar 2010-10-07 22:39:52 +01:00
Ralfas
f8ba66bb0c fixed typos 2010-10-05 22:43:56 +01:00
Ralfas
61c1f0e1ed fixed typos 2010-10-05 13:11:34 +01:00
Ralfas
8ac6d29c74 fixed typos 2010-10-05 13:09:10 +01:00
Ralfas
6b01986c48 fixed typos 2010-10-05 12:58:43 +01:00
Ralfas
efe2a18189 2010-10-04 11:29:40 -07:00
Benjamin Eberlei
e9617f1e50 DDC-798 - Clarify parameters for MEMBER OF expression 2010-09-30 22:09:49 +02:00
Benjamin Eberlei
4f18aaaf6e DDC-797 - Fix dql example bug 2010-09-30 22:05:39 +02:00
Benjamin Eberlei
d2efb5bbc8 Add NOT INSTANCE OF use-case to DQL manual 2010-09-27 22:59:50 +02:00
Benjamin Eberlei
e8eed2ebae Add legacy identifier quoting to the list of Known Issues 2010-09-23 22:48:39 +02:00
Benjamin Eberlei
7a8b69edbb Extend restriction and caution notes on identifier quoting 2010-09-23 22:40:12 +02:00
Benjamin Eberlei
bebc8cf871 Add section about deferred schema validation into association mapping chapter, add note about private/protected property in entities for lazy-laoding purposes, added subchapters on identity map internals and object graph traversal to the Working with Objects section 2010-09-21 21:55:35 +02:00
Christian Heinrich
453341c754 Updated strategy cookbook introduction
- Removed annoying tabs
- Removed some comments
- Hopefully, it will display now correctly
2010-09-20 10:53:29 -05:00
Michael Ridgway
13a99c30ea Fixed example in Establishing Associations 2010-09-09 12:39:02 -05:00
Christian Heinrich
e342b4536a Added a caution sign for case-sensitivity of mapping types.
This might help to prevent beginners from being confused when an error is raised due to
wrong spelling. (Like DateTime (wrong) <-> datetime (right))
2010-09-06 11:09:54 -05:00
Benjamin Eberlei
3d7eb3bac8 Enhanced Native SQL documentation 2010-09-04 09:19:05 +02:00
Christian Heinrich
d8dd44aa16 Added a naming convention for events 2010-09-03 10:50:56 -05:00
Benjamin Eberlei
d4489d1bdc Added large Note/Warning on SchemaTool usage and assumptions 2010-08-31 23:23:05 +02:00
Benjamin Eberlei
a6cdafe85c Merge branch 'master' of github.com:doctrine/orm-documentation 2010-08-31 23:10:45 +02:00
beberlei
088ccf58fc Updated XML Cookbook Getting Started and Tools Chapter 2010-08-29 10:50:07 +02:00
beberlei
d2d32e5439 Fix Cookbook XML Getting Started still refering to old Doctrine Console 2010-08-29 10:38:57 +02:00
Christian Heinrich
4232509427 Renamed a variable 2010-08-17 14:01:43 -05:00
Benjamin Eberlei
82874181b0 Add section on INSTANCE OF Dql Operator 2010-08-08 21:20:58 +02:00
Benjamin Eberlei
9fa64adbda Added another fact to Limitations section 2010-08-08 21:14:56 +02:00
Jonathan H. Wage
70d12fc57a [DDC-663] Making example for installing via pear more generic. 2010-08-08 12:21:03 -05:00
Your Name
732dee92ad I guess you meant "Now you can test" or "Now test the ..." 2010-08-02 08:41:31 -05:00
Your Name
fa5768b14d Fixed typos 2010-08-02 08:41:31 -05:00
Your Name
1fe35e518c Fixed typo 2010-08-02 08:41:31 -05:00
Benjamin Eberlei
d859a77b52 Fix a typo, reworked main paragraph and moved behavior section to bottom 2010-08-01 17:09:02 +02:00
Benjamin Eberlei
f89646eabe Added note about NestedSet to limitations section 2010-08-01 17:02:27 +02:00
Benjamin Eberlei
ec307a6046 Added small block on not supporting behaviors to limitations section 2010-08-01 16:56:36 +02:00
Benjamin Eberlei
437f14aa4e Fix bug in ticket link 2010-08-01 16:44:01 +02:00
Benjamin Eberlei
c5512b933e Add Limitations and Known Issues Appendix to the manual 2010-08-01 16:41:22 +02:00
Benjamin Eberlei
a6b5ee3cd3 Add Limitations and Known Issues Appendix to the manual 2010-08-01 16:37:53 +02:00
Benjamin Eberlei
93c061b7a5 Fixed some escaping issue with xml reference 2010-08-01 15:47:34 +02:00
Benjamin Eberlei
a34bab7f1e Merge branch 'master' of github.com:doctrine/orm-documentation 2010-08-01 15:44:13 +02:00
Benjamin Eberlei
96538ae2fe Seperated Change Tracking Policies and Partial Objects from Configuration Chapter, Finished XML Mapping Chapter with a reference on all tags and attributes 2010-08-01 15:41:40 +02:00
Roman S. Borschel
fcea78a6cb Updated merging docs. 2010-07-31 12:00:42 +02:00
Your Name
bf23503000 Missing field name? 2010-07-30 01:46:52 +08:00
Benjamin Eberlei
84c1cc8865 Fix bug in the documentation leading to an error 2010-07-24 15:16:13 +02:00
Benjamin Eberlei
c6e423024e Reference Configuration section for bootstrapping after each Install section 2010-07-24 14:23:35 +02:00
Benjamin Eberlei
9c28cc5b19 Rephrased some sentences in Working with Assocations 2010-07-24 10:53:35 +02:00
Benjamin Eberlei
23efc790c1 Split Working with Objects into two chapters, adding Working with Associations 2010-07-24 10:46:54 +02:00
Benjamin Eberlei
99533afd11 Added EntityManager interaction into examples 2010-07-23 23:51:25 +02:00
Benjamin Eberlei
f309190486 Clarified up and downsides of $collection->toArray() 2010-07-23 23:46:47 +02:00
Benjamin Eberlei
3b76ea9ffe Fix code box in Note 2010-07-23 23:43:44 +02:00
Benjamin Eberlei
e0c702d068 Fix little bug in Note box 2010-07-23 23:42:03 +02:00
Benjamin Eberlei
b5e5cb1c65 Removed examples of Nested Path Expressions from DQL docs 2010-07-23 23:39:36 +02:00
Benjamin Eberlei
31b8141fee Reworked Introduction a bit 2010-07-23 23:31:27 +02:00
Benjamin Eberlei
62c4f3e6fb Updated documentation on chosing inverse-owning side for many-to-many associations and came up with a significantly enhanced example for the working with associations chapter 2010-07-23 23:21:17 +02:00
Benjamin Eberlei
298773b2f8 Added section on collection field initialization and added constructors with initialization to all examples of toMany collections 2010-07-23 21:36:25 +02:00
Benjamin Eberlei
0a066533de Rephrased Obtaining Entity Manager and moved some sentences around 2010-07-22 23:38:09 +02:00
Benjamin Eberlei
f8c22abaa6 Use $applicationMode flag in obtaining entity manager example to make sure people understand that different settings for production and development are necessary 2010-07-22 23:30:17 +02:00
Benjamin Eberlei
974e31a307 Use $applicationMode flag in obtaining entity manager example to make sure people understand that different settings for production and development are necessary 2010-07-22 23:29:21 +02:00
Benjamin Eberlei
d5afa35cb8 Added dev vs production configuration section 2010-07-22 23:25:29 +02:00
Benjamin Eberlei
439cccbc0d Enhanced Bootstrap Section for more information on Autoloading in different setups (PEAR vs Git) 2010-07-22 22:55:00 +02:00
Benjamin Eberlei
b1f6fe2528 Updated Introduction to be more consistent with current Github development model 2010-07-18 22:57:20 +02:00
Benjamin Eberlei
a851e08109 Updated Introduction to be more consistent with current Github development model 2010-07-18 22:55:36 +02:00
Benjamin Eberlei
e7066d6f36 Updated Introduction to be more consistent with current Github development model 2010-07-18 22:45:17 +02:00
Benjamin Eberlei
29cb77b964 Merge branch 'master' of github.com:doctrine/orm-documentation 2010-07-18 21:13:07 +02:00
Benjamin Eberlei
4c813f60f9 Updated Aggregate Fields, Added generated SQL for all Association Mappings 2010-07-18 21:12:51 +02:00
beberlei
0c8e0450de Merge branch 'master' of github.com:doctrine/orm-documentation 2010-07-10 08:33:17 +02:00
beberlei
1017e2c6c7 Extended details on how to remove entities and their associations 2010-07-10 08:33:12 +02:00
Roman S. Borschel
0428774ec8 Added more information about flushing semantics. 2010-07-08 00:26:57 +02:00
Benjamin Eberlei
59aafe100b Added Aggregate Fields documentation 2010-07-07 22:34:51 +02:00
eXtreme
400be2a3e6 fixing codeblocks 2010-07-07 22:19:36 +08:00
Benjamin Eberlei
8d2da96480 Fixed error in Sandbox Tutorial 2010-07-04 18:57:51 +02:00
Benjamin Eberlei
80fd691880 Update docs on using own types, aswell as on using Collection::clear() 2010-07-03 16:57:02 +02:00
Lars Olesen
8e7385ccc2 Added the name space in front of Xmldriver, as it is not stated anywhere on this page which namespace is used. 2010-06-21 05:35:07 +08:00
Lars Olesen
16740b9695 Fixes typo. 2010-06-21 05:35:07 +08:00
Jonathan H. Wage
823db6071b Changing case. 2010-06-17 13:32:11 -04:00
Jonathan H. Wage
27233af0d2 Removing reference to Query object before it is introduced 2010-06-17 12:09:11 -04:00
Jonathan H. Wage
e71f00296e Fixing php code 2010-06-17 12:07:43 -04:00
Jonathan H. Wage
c7a5a695d3 [DDC-246] Added section showing DQL examples when dealing with SINGLE_TABLE and JOINED inheritance since it affects the generated SQL of a DQL query. 2010-06-17 12:03:01 -04:00
Jonathan H. Wage
cc2d84c992 Adding php syntax highlighting 2010-06-17 10:07:18 -04:00
Jonathan H. Wage
53ec0700bb Merge branch 'master' of github.com:doctrine/orm-documentation 2010-06-17 10:02:14 -04:00
Jonathan H. Wage
88e5babb56 [DDC-572] Updating documentation for inversedBy and mappedBy 2010-06-17 10:01:49 -04:00
beberlei
50e35e6bdc Merge branch 'master' of github.com:doctrine/orm-documentation 2010-06-10 23:27:28 +02:00
beberlei
d872d7f0b1 Updated Cookbook Getting Started XML Edition Tutorial with Array and Scalar Hydration examples, tweaked several passages a little bit 2010-06-10 21:38:22 +02:00
beberlei
51729cbaa7 Enhanced Description of how the different events work and what restrictions apply to them. 2010-06-10 21:06:23 +02:00
Thibault Duplessis
854a966a22 Fix Query::HYDRATE_SINGLE_SCALAR in single scalar result example 2010-06-07 16:54:22 -07:00
Jonathan H. Wage
9d2f7f0686 Updating documentation for custom hydrators. 2010-06-03 14:13:39 -04:00
Jonathan H. Wage
37124c4e0a Adding section for custom hydrators. 2010-06-02 23:41:41 -04:00
Jonathan H. Wage
30b9cfce3d [DDC-540] Doc improvement 2010-05-25 13:38:16 -04:00
Rich Bowen
e127274aa5 Fixes missing quote. 2010-05-24 15:34:58 -04:00
Roman S. Borschel
c34b5ad1a7 Added strategy cookbook entry to the index. 2010-05-22 13:52:20 +02:00
Jonathan H. Wage
10b676aa2b Merge commit '5c79ca25aabaf574d71d6bc622c13fd6045626d2' 2010-05-20 08:35:02 -04:00
Jonathan H. Wage
0698a8c455 Fixing php code 2010-05-18 11:51:49 -04:00
Jonathan H. Wage
1193c530c9 Merge branch 'master' of github.com:doctrine/orm-documentation 2010-05-18 11:50:18 -04:00
Jonathan H. Wage
18b9c6cd69 Updating documentation for converting mapping information and reverse engineering databases to entities. 2010-05-18 11:49:44 -04:00
Benjamin Eberlei
9231f54313 DDC-153 - Added cookbook entry contributed by merk (thanks!) 2010-05-16 09:50:07 +02:00
Benjamin Eberlei
72c2432586 DDC-590 - Added docs on Locking Support 2010-05-15 11:27:49 +02:00
Christian Heinrich
5c79ca25aa Updated formatting and fixed mistakes 2010-05-15 00:40:18 +02:00
Jonathan H. Wage
a59b62e3ba Merge remote branch 'mridgway/master' 2010-05-14 15:26:28 -04:00
Jonathan H. Wage
f99a096d01 Adding a little more information to the dql chapter. 2010-05-14 15:23:51 -04:00
Michael Ridgway
a3b42392b4 Fixed broken reference to DBAL section 2010-05-14 11:32:12 -07:00
Jonathan H. Wage
5a79963bd2 Removing dbal documentation from orm docs 2010-05-14 11:12:54 -04:00
Roman S. Borschel
184a402f39 Fixed typo 2010-05-13 13:14:37 +02:00
Roman S. Borschel
7d79777bfb Merge commit 'origin/master' 2010-05-13 13:06:20 +02:00
Roman S. Borschel
84c1d34028 Added documentation about control abstractions for transaction handling. 2010-05-13 13:06:16 +02:00
Christian Heinrich
21bbbb1f1f Updated query builder docs 2010-05-13 00:58:17 +02:00
Benjamin Eberlei
177a9ce4d2 Update Getting Started XML Edition to work with inversed-by attributes 2010-05-12 23:53:09 +02:00
Benjamin Eberlei
150bca0498 Merge branch 'master' of github.com:doctrine/orm-documentation 2010-05-12 23:52:38 +02:00
Jonathan H. Wage
0c60efc03b Merge remote branch 'shurakai/master' 2010-05-12 09:29:31 -04:00
Christian Heinrich
b857655992 Improved query builder section
-> Added a new section about binding params to the query
-> Added a short note on how to use $qb->expr()->in() with string values
2010-05-12 14:43:09 +02:00
Jonathan H. Wage
dd6eafbb6c Merge branch 'master' of github.com:doctrine/orm-documentation 2010-05-11 11:45:28 -04:00
Roman S. Borschel
0799a5b5e9 Polished docs on DBAL, Transactions and Concurrency. 2010-05-11 15:26:35 +02:00
Matthieu Bontemps
a1079e692c Fix a few typos in doctrine manual 2010-05-11 11:46:10 +02:00
Christian Heinrich
f9b77f9854 Added strategy cookbook preview 2010-05-10 18:26:32 +02:00
Roman S. Borschel
bc0f853b52 Clarified docs on identifier generation strategies. 2010-05-06 13:08:36 +02:00
Roman S. Borschel
a3feeef88c Clarified docs on identifier generation strategies. 2010-05-06 13:07:54 +02:00
Roman S. Borschel
c18e23b817 Clarified docs on identifier generation strategies. 2010-05-06 13:02:21 +02:00
Roman S. Borschel
4121c1ca72 Clarified docs on identifier generation strategies. 2010-05-06 12:45:46 +02:00
Roman S. Borschel
7ffdd80bb2 Clarified docs on working with objects. 2010-05-05 11:10:00 +02:00
Roman S. Borschel
51f9fb35c6 Merge commit 'origin/master' 2010-05-05 10:58:49 +02:00
Roman S. Borschel
e155e86517 Clarified docs on persisting new objects. 2010-05-05 10:58:11 +02:00
Benjamin Eberlei
d835372e93 Merge branch 'master' of github.com:doctrine/orm-documentation 2010-05-01 03:38:27 +02:00
Benjamin Eberlei
b4bc4b029d Fix DDC-560 2010-05-01 03:38:03 +02:00
Benjamin Eberlei
c0dc0112d3 Fix DDC-557 2010-05-01 03:36:40 +02:00
Jonathan H. Wage
e01510953e [DDC-443] Adding many-to-one unidirectional example 2010-04-30 16:11:35 -04:00
Roman S. Borschel
74ecfca43e Fixed section about partial objects. 2010-04-29 23:44:25 +02:00
Benjamin Eberlei
6d34b91425 DDC-539 - Fix typo 2010-04-26 21:54:10 +02:00
Jonathan H. Wage
605ee881c5 Misc. fixes and initial entry of PHP Mapping and Metadata Drivers chapters. 2010-04-26 11:55:43 -04:00
Jonathan H. Wage
fc290404e1 Changing note to caution 2010-04-23 12:16:31 -04:00
Benjamin Eberlei
d837a2110b Updated XML Getting Started Tutorial to comply with current Console API 2010-04-18 19:31:41 +02:00
Benjamin Eberlei
8426a3da11 DDC-510 - Updated docs to reflect that Annotations Metadata Driver is not a default anymore, but required to specifiy 2010-04-12 21:58:21 +02:00
Benjamin Eberlei
2a1f5e121b DDC-442 - Fix example for timestamp optimistic lock 2010-04-11 14:38:19 +02:00
Benjamin Eberlei
997ef97521 Updated SchemaTool usage docs with mapping paths information example 2010-04-10 12:04:23 +02:00
Benjamin Eberlei
8aed5c72df Updated ORM Tools Documentation 2010-04-10 11:50:40 +02:00
Guilherme Blanco
d1fbaae91f [2.0-DOC] Changed manual with new Console implementation 2010-04-09 01:09:14 -03:00
Jonathan H. Wage
ee34c42697 Adding dbal chapter back 2010-04-06 14:43:34 -04:00
Jonathan H. Wage
2adf9378e4 - 2010-04-06 14:37:15 -04:00
Jonathan H. Wage
1e7193134a - 2010-04-06 14:36:40 -04:00
Jonathan H. Wage
33ff1df4ec Adding cookbook 2010-04-06 14:30:12 -04:00
Jonathan H. Wage
69158cd4f3 first commit 2010-04-06 14:27:51 -04:00
577 changed files with 43834 additions and 7195 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

12
.gitattributes vendored Normal file
View File

@@ -0,0 +1,12 @@
/tests export-ignore
/tools export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.gitmodules export-ignore
.travis.yml export-ignore
build.properties export-ignore
build.properties.dev export-ignore
build.xml export-ignore
CONTRIBUTING.md export-ignore
phpunit.xml.dist export-ignore
run-all.sh export-ignore

5
.gitignore vendored
View File

@@ -8,4 +8,7 @@ lib/Doctrine/Common
lib/Doctrine/DBAL
/.settings/
.buildpath
.project
.project
.idea
vendor/
composer.lock

19
.gitmodules vendored
View File

@@ -1,15 +1,6 @@
[submodule "lib/vendor/doctrine-common"]
path = lib/vendor/doctrine-common
url = git://github.com/doctrine/common.git
[submodule "lib/vendor/doctrine-dbal"]
path = lib/vendor/doctrine-dbal
url = git://github.com/doctrine/dbal.git
[submodule "lib/vendor/Symfony/Component/Console"]
path = lib/vendor/Symfony/Component/Console
url = git://github.com/symfony/Console.git
[submodule "lib/vendor/Symfony/Component/Yaml"]
path = lib/vendor/Symfony/Component/Yaml
url = git://github.com/symfony/Yaml.git
[submodule "docs/en/_theme"]
path = docs/en/_theme
url = git://github.com/doctrine/doctrine-sphinx-theme.git
[submodule "lib/vendor/doctrine-build-common"]
path = lib/vendor/doctrine-build-common
url = git://github.com/doctrine/doctrine-build-common.git
path = lib/vendor/doctrine-build-common
url = git://github.com/doctrine/doctrine-build-common.git

View File

@@ -3,6 +3,8 @@ language: php
php:
- 5.3
- 5.4
- 5.5
env:
- DB=mysql
- DB=pgsql
@@ -14,6 +16,9 @@ before_script:
- 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"
- git submodule update --init
- composer install --prefer-dist --dev
script: phpunit --configuration tests/travis/$DB.travis.xml
script: phpunit --configuration tests/travis/$DB.travis.xml
after_script:
- php vendor/bin/coveralls -v

72
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,72 @@
# Contribute to Doctrine
Thank you for contributing to Doctrine!
Before we can merge your Pull-Request here are some guidelines that you need to follow.
These guidelines exist not to annoy you, but to keep the code base clean,
unified and future proof.
## We only accept PRs to "master"
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.
There is one exception to the rule, when we merged a bug into some stable branches
we do occasionally accept pull requests that merge the same bug fix into earlier
branches.
## Coding Standard
We use PSR-1 and PSR-2:
* https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md
* https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md
with some exceptions/differences:
* Keep the nesting of control structures per method as small as possible
* Align equals (=) signs
* Add spaces between assignment, control and return statements
* Prefer early exit over nesting conditions
* Add spaces around a negation if condition ``if ( ! $cond)``
## Unit-Tests
Always add a test for your pull-request.
* If you want to fix a bug or provide a reproduce case, create a test file in
``tests/Doctrine/Tests/ORM/Functional/Ticket`` with the name of the ticket,
``DDC1234Test.php`` for example.
* If you want to contribute new functionality add unit- or functional tests
depending on the scope of the feature.
You can run the unit-tests by calling ``phpunit`` from the root of the project.
It will run all the tests with an in memory SQLite database.
To run the testsuite against another database, copy the ``phpunit.xml.dist``
to for example ``mysql.phpunit.xml`` and edit the parameters. You can
take a look at the ``tests/travis`` folder for some examples. Then run:
phpunit -c mysql.phpunit.xml
## Travis
We automatically run your pull request through [Travis CI](http://www.travis-ci.org)
against SQLite, MySQL and PostgreSQL. If you break the tests, we cannot merge your code,
so please make sure that your code is working before opening up a Pull-Request.
## 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
everything as fast as possible, but cannot always live up to our own expectations.
Thank you very much again for your contribution!

View File

@@ -1,9 +1,15 @@
# 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),

View File

@@ -1,5 +1,63 @@
# Upgrade to 2.4
## BC BREAK: Compatibility Bugfix in PersistentCollection#matching()
In Doctrine 2.3 it was possible to use the new ``matching($criteria)``
functionality by adding constraints for assocations based on ID:
Criteria::expr()->eq('association', $assocation->getId());
This functionality does not work on InMemory collections however, because
in memory criteria compares object values based on reference.
As of 2.4 the above code will throw an exception. You need to change
offending code to pass the ``$assocation`` reference directly:
Criteria::expr()->eq('association', $assocation);
## Composer is now the default autoloader
The test suite now runs with composer autoloading. Support for PEAR, and tarball autoloading is deprecated.
Support for GIT submodules is removed.
## OnFlush and PostFlush event always called
Before 2.4 the postFlush and onFlush events were only called when there were
actually entities that changed. Now these events are called no matter if there
are entities in the UoW or changes are found.
## Parenthesis are now considered in arithmetic expression
Before 2.4 parenthesis are not considered in arithmetic primary expression.
That's conceptually wrong, since it might result in wrong values. For example:
The DQL:
SELECT 100 / ( 2 * 2 ) FROM MyEntity
Before 2.4 it generates the SQL:
SELECT 100 / 2 * 2 FROM my_entity
Now parenthesis are considered, the previous DQL will generate:
SELECT 100 / (2 * 2) FROM my_entity
# Upgrade to 2.3
## Auto Discriminator Map breaks userland implementations with Listener
The new feature to detect discriminator maps automatically when none
are provided breaks userland implementations doing this with a
listener in ``loadClassMetadata`` event.
## EntityManager#find() not calls EntityRepository#find() anymore
Previous to 2.3, calling ``EntityManager#find()`` would be delegated to
``EntityRepository#find()``. This has lead to some unexpected behavior in the
core of Doctrine when people have overwritten the find method in their
repositories. That is why this behavior has been reversed in 2.3, and
``EntityRepository#find()`` calls ``EntityManager#find()`` instead.
## EntityGenerator add*() method generation
When generating an add*() method for a collection the EntityGenerator will now not
@@ -33,6 +91,12 @@ Also, related functions were affected:
Internal changes were made to DQL and SQL generation. If you have implemented your own TreeWalker,
you probably need to update it. The method walkJoinVariableDeclaration is now named walkJoin.
## New methods in TreeWalker interface *BC break*
Two methods getQueryComponents() and setQueryComponent() were added to the TreeWalker interface and all its implementations
including TreeWalkerAdapter, TreeWalkerChain and SqlWalker. If you have your own implementation not inheriting from one of the
above you must implement these new methods.
## Metadata Drivers
Metadata drivers have been rewritten to reuse code from Doctrine\Common. Anyone who is using the
@@ -99,7 +163,7 @@ from 2.0 have to configure the annotation driver if they don't use `Configuratio
$config->setMetadataDriverImpl($driver);
## Scalar mappings can now be ommitted from DQL result
## Scalar mappings can now be omitted from DQL result
You are now allowed to mark scalar SELECT expressions as HIDDEN an they are not hydrated anymore.
Example:
@@ -280,7 +344,7 @@ them for batch updates like SchemaTool and other commands. However the
annotations driver being a default driver does not really help that much
anyways.
Therefore we decided to break backwards compability in this issue and drop
Therefore we decided to break backwards compatibility in this issue and drop
the support for Annotations as Default Driver and require our users to
specify the driver explicitly (which allows us to ask for the path to all
entities).
@@ -339,7 +403,7 @@ apologize for the inconvenience.
## Default Property for Field Mappings
The "default" option for database column defaults has been removed. If desired, database column defaults can
be implemented by using the columnDefinition attribute of the @Column annotation (or the approriate XML and YAML equivalents).
be implemented by using the columnDefinition attribute of the @Column annotation (or the appropriate XML and YAML equivalents).
Prefer PHP default values, if possible.
## Selecting Partial Objects
@@ -424,7 +488,7 @@ With new required method AbstractTask::buildDocumentation, its implementation de
* "doctrine schema-tool --drop" now always drops the complete database instead of
only those tables defined by the current database model. The previous method had
problems when foreign keys of orphaned tables pointed to tables that were schedulded
problems when foreign keys of orphaned tables pointed to tables that were scheduled
for deletion.
* Use "doctrine schema-tool --update" to get a save incremental update for your
database schema without deleting any unused tables, sequences or foreign keys.

View File

@@ -13,31 +13,47 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
use Symfony\Component\Console\Helper\HelperSet;
use Doctrine\ORM\Tools\Console\ConsoleRunner;
(@include_once __DIR__ . '/../vendor/autoload.php') || @include_once __DIR__ . '/../../../autoload.php';
$configFile = getcwd() . DIRECTORY_SEPARATOR . 'cli-config.php';
$helperSet = null;
if (file_exists($configFile)) {
if ( ! is_readable($configFile)) {
trigger_error(
'Configuration file [' . $configFile . '] does not have read permission.', E_ERROR
);
$directories = array(getcwd(), getcwd() . DIRECTORY_SEPARATOR . 'config');
$configFile = null;
foreach ($directories as $directory) {
$configFile = $directory . DIRECTORY_SEPARATOR . 'cli-config.php';
if (file_exists($configFile)) {
break;
}
}
require $configFile;
if ( ! file_exists($configFile)) {
ConsoleRunner::printCliConfigTemplate();
exit(1);
}
if ( ! is_readable($configFile)) {
echo 'Configuration file [' . $configFile . '] does not have read permission.' . "\n";
exit(1);
}
$commands = array();
$helperSet = require $configFile;
if ( ! ($helperSet instanceof HelperSet)) {
foreach ($GLOBALS as $helperSetCandidate) {
if ($helperSetCandidate instanceof \Symfony\Component\Console\Helper\HelperSet) {
if ($helperSetCandidate instanceof HelperSet) {
$helperSet = $helperSetCandidate;
break;
}
}
}
$helperSet = ($helperSet) ?: new \Symfony\Component\Console\Helper\HelperSet();
\Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet);
\Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet, $commands);

View File

@@ -1,11 +1,3 @@
# Project Name
project.name=DoctrineORM
# Dependency minimum versions
dependencies.common=2.2.0beta1
dependencies.dbal=2.2.0beta1
dependencies.sfconsole=2.0.0
# Version class and file
project.version_class = Doctrine\ORM\Version
project.version_class = Doctrine\\ORM\\Version
project.version_file = lib/Doctrine/ORM/Version.php

199
build.xml
View File

@@ -1,114 +1,101 @@
<?xml version="1.0"?>
<project name="DoctrineORM" default="build" basedir=".">
<taskdef classname="phing.tasks.ext.d51PearPkg2Task" name="d51pearpkg2" />
<import file="${project.basedir}/lib/vendor/doctrine-build-common/packaging.xml" />
<property file="build.properties" />
<!--
Fileset for artifacts shared across all distributed packages.
-->
<fileset id="shared-artifacts" dir=".">
<include name="LICENSE"/>
<include name="UPGRADE*" />
<include name="doctrine-mapping.xsd" />
</fileset>
<!--
Fileset for command line scripts
-->
<fileset id="bin-scripts" dir="./bin">
<include name="doctrine"/>
<include name="doctrine-pear.php"/>
<include name="doctrine.bat"/>
</fileset>
<!--
Fileset for the sources of the Doctrine Common dependency.
-->
<fileset id="common-sources" dir="./lib/vendor/doctrine-common/lib">
<include name="Doctrine/Common/**"/>
</fileset>
<!--
Fileset for the sources of the Doctrine DBAL dependency.
-->
<fileset id="dbal-sources" dir="./lib/vendor/doctrine-dbal/lib">
<include name="Doctrine/DBAL/**"/>
</fileset>
<!--
Fileset for the sources of the Doctrine ORM.
-->
<fileset id="orm-sources" dir="./lib">
<include name="Doctrine/ORM/**"/>
</fileset>
<!--
Fileset for source of the Symfony YAML and Console components.
-->
<fileset id="symfony-sources" dir="./lib/vendor">
<include name="Symfony/Component/**"/>
<exclude name="**/.git/**" />
</fileset>
<!--
Builds ORM package, preparing it for distribution.
-->
<target name="copy-files" depends="prepare">
<copy todir="${build.dir}/${project.name}-${version}">
<fileset refid="shared-artifacts"/>
</copy>
<copy todir="${build.dir}/${project.name}-${version}">
<fileset refid="common-sources"/>
<fileset refid="dbal-sources"/>
<fileset refid="orm-sources"/>
</copy>
<copy todir="${build.dir}/${project.name}-${version}/Doctrine">
<fileset refid="symfony-sources"/>
</copy>
<copy todir="${build.dir}/${project.name}-${version}/bin">
<fileset refid="bin-scripts"/>
</copy>
<target name="php">
<exec executable="which" outputproperty="php_executable">
<arg value="php" />
</exec>
</target>
<!--
Builds distributable PEAR packages.
-->
<target name="define-pear-package" depends="copy-files">
<d51pearpkg2 baseinstalldir="/" dir="${build.dir}/${project.name}-${version}">
<name>DoctrineORM</name>
<summary>Doctrine Object Relational Mapper</summary>
<channel>pear.doctrine-project.org</channel>
<description>The Doctrine ORM package is the primary package containing the object relational mapper.</description>
<lead user="jwage" name="Jonathan H. Wage" email="jonwage@gmail.com" />
<lead user="guilhermeblanco" name="Guilherme Blanco" email="guilhermeblanco@gmail.com" />
<lead user="romanb" name="Roman Borschel" email="roman@code-factory.org" />
<lead user="beberlei" name="Benjamin Eberlei" email="kontakt@beberlei.de" />
<license>LGPL</license>
<version release="${pear.version}" api="${pear.version}" />
<stability release="${pear.stability}" api="${pear.stability}" />
<notes>-</notes>
<dependencies>
<php minimum_version="5.3.0" />
<pear minimum_version="1.6.0" recommended_version="1.6.1" />
<package name="DoctrineCommon" channel="pear.doctrine-project.org" minimum_version="${dependencies.common}" />
<package name="DoctrineDBAL" channel="pear.doctrine-project.org" minimum_version="${dependencies.dbal}" />
<package name="Console" channel="pear.symfony.com" minimum_version="2.0.0" />
<package name="Yaml" channel="pear.symfony.com" minimum_version="2.0.0" />
</dependencies>
<dirroles key="bin">script</dirroles>
<ignore>Doctrine/Common/</ignore>
<ignore>Doctrine/DBAL/</ignore>
<ignore>Symfony/Component/Yaml/</ignore>
<ignore>Symfony/Component/Console/</ignore>
<release>
<install as="doctrine" name="bin/doctrine" />
<install as="doctrine.php" name="bin/doctrine-pear.php" />
<install as="doctrine.bat" name="bin/doctrine.bat" />
</release>
<replacement path="bin/doctrine" type="pear-config" from="@php_bin@" to="php_bin" />
<replacement path="bin/doctrine.bat" type="pear-config" from="@bin_dir@" to="bin_dir" />
</d51pearpkg2>
<target name="prepare">
<mkdir dir="build" />
</target>
<target name="build" depends="check-git-checkout-clean,prepare,php,composer">
<exec executable="${php_executable}">
<arg value="build/composer.phar" />
<arg value="archive" />
<arg value="--dir=build" />
</exec>
</target>
<target name="composer" depends="php,composer-check,composer-download">
<exec executable="${php_executable}">
<arg value="build/composer.phar" />
<arg value="install" />
</exec>
</target>
<target name="composer-check" depends="prepare">
<available file="build/composer.phar" property="composer.present"/>
</target>
<target name="composer-download" unless="composer.present">
<exec executable="wget">
<arg value="-Obuild/composer.phar" />
<arg value="http://getcomposer.org/composer.phar" />
</exec>
</target>
<target name="make-release" depends="check-git-checkout-clean,prepare,php">
<replace file="${project.version_file}" token="-DEV" value="" failOnNoReplacements="true" />
<exec executable="${php_executable}" outputproperty="doctrine.current_version" failonerror="true">
<arg value="-r" />
<arg value="require_once '${project.version_file}';echo ${project.version_class}::VERSION;" />
</exec>
<exec executable="${php_executable}" outputproperty="doctrine.next_version" failonerror="true">
<arg value="-r" />
<arg value="$parts = explode('.', str_ireplace(array('-DEV', '-ALPHA', '-BETA'), '', '${doctrine.current_version}'));
if (count($parts) != 3) {
throw new \InvalidArgumentException('Version is assumed in format x.y.z, ${doctrine.current_version} given');
}
$parts[2]++;
echo implode('.', $parts);
" />
</exec>
<git-commit file="${project.version_file}" message="Release ${doctrine.current_version}" />
<git-tag version="${doctrine.current_version}" />
<replace file="${project.version_file}" token="${doctrine.current_version}" value="${doctrine.next_version}-DEV" />
<git-commit file="${project.version_file}" message="Bump version to ${doctrine.next_version}" />
</target>
<target name="check-git-checkout-clean">
<exec executable="git" failonerror="true">
<arg value="diff-index" />
<arg value="--quiet" />
<arg value="HEAD" />
</exec>
</target>
<macrodef name="git-commit">
<attribute name="file" default="NOT SET"/>
<attribute name="message" default="NOT SET"/>
<sequential>
<exec executable="git">
<arg value="add" />
<arg value="@{file}" />
</exec>
<exec executable="git">
<arg value="commit" />
<arg value="-m" />
<arg value="@{message}" />
</exec>
</sequential>
</macrodef>
<macrodef name="git-tag">
<attribute name="version" default="NOT SET" />
<sequential>
<exec executable="git">
<arg value="tag" />
<arg value="-m" />
<arg value="v@{version}" />
<arg value="v@{version}" />
</exec>
</sequential>
</macrodef>
</project>

View File

@@ -14,19 +14,27 @@
"require": {
"php": ">=5.3.2",
"ext-pdo": "*",
"doctrine/dbal": ">=2.3-dev,<2.5-dev",
"symfony/console": "2.*"
"doctrine/collections": "~1.1",
"doctrine/dbal": "~2.4",
"symfony/console": "~2.0"
},
"require-dev": {
"symfony/yaml": "~2.1",
"satooshi/php-coveralls": "dev-master"
},
"suggest": {
"symfony/yaml": "If you want to use YAML Metadata Mapping Driver"
},
"autoload": {
"psr-0": { "Doctrine\\ORM": "lib/" }
"psr-0": { "Doctrine\\ORM\\": "lib/" }
},
"bin": ["bin/doctrine", "bin/doctrine.php"],
"extra": {
"branch-alias": {
"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", "*coveralls.yml"]
}
}

4
docs/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
en/_exts/configurationblock.pyc
build
en/_build
.idea

3
docs/.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "en/_theme"]
path = en/_theme
url = https://github.com/doctrine/doctrine-sphinx-theme.git

8
docs/README.md Normal file
View File

@@ -0,0 +1,8 @@
# Doctrine ORM Documentation
## How to Generate
1. Run ./bin/install-dependencies.sh
2. Run ./bin/generate-docs.sh
It will generate the documentation into the build directory of the checkout.

10
docs/bin/generate-docs.sh Executable file
View File

@@ -0,0 +1,10 @@
#!/bin/bash
EXECPATH=`dirname $0`
cd $EXECPATH
cd ..
rm build -Rf
sphinx-build en build
sphinx-build -b latex en build/pdf
rubber --into build/pdf --pdf build/pdf/Doctrine2ORM.tex

View File

@@ -0,0 +1,4 @@
#!/bin/bash
sudo apt-get install python25 python25-dev texlive-full rubber
sudo easy_install pygments
sudo easy_install sphinx

89
docs/en/Makefile Normal file
View File

@@ -0,0 +1,89 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Doctrine2ORM.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Doctrine2ORM.qhc"
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
"run these through (pdf)latex."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."

View File

@@ -0,0 +1,93 @@
#Copyright (c) 2010 Fabien Potencier
#
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is furnished
#to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in all
#copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#THE SOFTWARE.
from docutils.parsers.rst import Directive, directives
from docutils import nodes
from string import upper
class configurationblock(nodes.General, nodes.Element):
pass
class ConfigurationBlock(Directive):
has_content = True
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = True
option_spec = {}
formats = {
'html': 'HTML',
'xml': 'XML',
'php': 'PHP',
'yaml': 'YAML',
'jinja': 'Twig',
'html+jinja': 'Twig',
'jinja+html': 'Twig',
'php+html': 'PHP',
'html+php': 'PHP',
'ini': 'INI',
'php-annotations': 'Annotations',
}
def run(self):
env = self.state.document.settings.env
node = nodes.Element()
node.document = self.state.document
self.state.nested_parse(self.content, self.content_offset, node)
entries = []
for i, child in enumerate(node):
if isinstance(child, nodes.literal_block):
# add a title (the language name) before each block
#targetid = "configuration-block-%d" % env.new_serialno('configuration-block')
#targetnode = nodes.target('', '', ids=[targetid])
#targetnode.append(child)
innernode = nodes.emphasis(self.formats[child['language']], self.formats[child['language']])
para = nodes.paragraph()
para += [innernode, child]
entry = nodes.list_item('')
entry.append(para)
entries.append(entry)
resultnode = configurationblock()
resultnode.append(nodes.bullet_list('', *entries))
return [resultnode]
def visit_configurationblock_html(self, node):
self.body.append(self.starttag(node, 'div', CLASS='configuration-block'))
def depart_configurationblock_html(self, node):
self.body.append('</div>\n')
def visit_configurationblock_latex(self, node):
pass
def depart_configurationblock_latex(self, node):
pass
def setup(app):
app.add_node(configurationblock,
html=(visit_configurationblock_html, depart_configurationblock_html),
latex=(visit_configurationblock_latex, depart_configurationblock_latex))
app.add_directive('configuration-block', ConfigurationBlock)

1
docs/en/_theme Submodule

Submodule docs/en/_theme added at 68795c5888

201
docs/en/conf.py Normal file
View File

@@ -0,0 +1,201 @@
# -*- coding: utf-8 -*-
#
# Doctrine 2 ORM documentation build configuration file, created by
# sphinx-quickstart on Fri Dec 3 18:10:24 2010.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys, os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.append(os.path.abspath('_exts'))
# -- General configuration -----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['configurationblock']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Doctrine 2 ORM'
copyright = u'2010-12, Doctrine Project Team'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '2'
# The full version, including alpha/beta/rc tags.
release = '2'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
language = 'en'
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of documents that shouldn't be included in the build.
#unused_docs = []
# List of directories, relative to source directory, that shouldn't be searched
# for source files.
exclude_trees = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
show_authors = True
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
html_theme = 'doctrine'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = ['_theme']
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_use_modindex = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = ''
# Output file base name for HTML help builder.
htmlhelp_basename = 'Doctrine2ORMdoc'
# -- Options for LaTeX output --------------------------------------------------
# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'Doctrine2ORM.tex', u'Doctrine 2 ORM Documentation',
u'Doctrine Project Team', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# Additional stuff for the LaTeX preamble.
#latex_preamble = ''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_use_modindex = True
primary_domain = "dcorm"
def linkcode_resolve(domain, info):
if domain == 'dcorm':
return 'http://'
return None

View File

@@ -0,0 +1,256 @@
Advanced field value conversion using custom mapping types
==========================================================
.. sectionauthor:: Jan Sorgalla <jsorgalla@googlemail.com>
When creating entities, you sometimes have the need to transform field values
before they are saved to the database. In Doctrine you can use Custom Mapping
Types to solve this (see: :ref:`reference-basic-mapping-custom-mapping-types`).
There are several ways to achieve this: converting the value inside the Type
class, converting the value on the database-level or a combination of both.
This article describes the third way by implementing the MySQL specific column
type `Point <http://dev.mysql.com/doc/refman/5.5/en/gis-class-point.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.
The entity
----------
We create a simple entity with a field ``$point`` which holds a value object
``Point`` representing the latitude and longitude of the position.
The entity class:
.. code-block:: php
<?php
namespace Geo\Entity;
/**
* @Entity
*/
class Location
{
/**
* @Column(type="point")
*
* @var \Geo\ValueObject\Point
*/
private $point;
/**
* @Column(type="string")
*
* @var string
*/
private $address;
/**
* @param \Geo\ValueObject\Point $point
*/
public function setPoint(\Geo\ValueObject\Point $point)
{
$this->point = $point;
}
/**
* @return \Geo\ValueObject\Point
*/
public function getPoint()
{
return $this->point;
}
/**
* @param string $address
*/
public function setAddress($address)
{
$this->address = $address;
}
/**
* @return string
*/
public function getAddress()
{
return $this->address;
}
}
We use the custom type ``point`` in the ``@Column`` docblock annotation of the
``$point`` field. We will create this custom mapping type in the next chapter.
The point class:
.. code-block:: php
<?php
namespace Geo\ValueObject;
class Point
{
/**
* @param float $latitude
* @param float $longitude
*/
public function __construct($latitude, $longitude)
{
$this->latitude = $latitude;
$this->longitude = $longitude;
}
/**
* @return float
*/
public function getLatitude()
{
return $this->latitude;
}
/**
* @return float
*/
public function getLongitude()
{
return $this->longitude;
}
}
The mapping type
----------------
Now we're going to create the ``point`` type and implement all required methods.
.. code-block:: php
<?php
namespace Geo\Types;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Geo\ValueObject\Point;
class PointType extends Type
{
const POINT = 'point';
public function getName()
{
return self::POINT;
}
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
return 'POINT';
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
list($longitude, $latitude) = sscanf($value, 'POINT(%f %f)');
return new Point($latitude, $longitude);
}
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
if ($value instanceof Point) {
$value = sprintf('POINT(%F %F)', $value->getLongitude(), $value->getLatitude());
}
return $value;
}
public function canRequireSQLConversion()
{
return true;
}
public function convertToPHPValueSQL($sqlExpr, AbstractPlatform $platform)
{
return sprintf('AsText(%s)', $sqlExpr);
}
public function convertToDatabaseValueSQL($sqlExpr, AbstractPlatform $platform)
{
return sprintf('PointFromText(%s)', $sqlExpr);
}
}
We do a 2-step conversion here. In the first step, we convert the ``Point``
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.
Internally, MySQL stores geometry values in a binary format that is not
identical to the WKT format. So, we need to let MySQL transform the WKT
representation into its internal format.
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 `PointFromText <http://dev.mysql.com/doc/refman/5.5/en/creating-spatial-values.html#function_pointfromtext>`_
and `AsText <http://dev.mysql.com/doc/refman/5.5/en/functions-to-convert-geometries-between-formats.html#function_astext>`_
which convert WKT strings to and from the internal format of MySQL.
.. note::
When using DQL queries, the ``convertToPHPValueSQL`` and
``convertToDatabaseValueSQL`` methods only apply to identification variables
and path expressions in SELECT clauses. Expressions in WHERE clauses are
**not** wrapped!
If you want to use Point values in WHERE clauses, you have to implement a
:doc:`user defined function <dql-user-defined-functions>` for
``PointFromText``.
Example usage
-------------
.. code-block:: php
<?php
// Bootstrapping stuff...
// $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config);
// Setup custom mapping type
use Doctrine\DBAL\Types\Type;
Type::addType('point', 'Geo\Types\Point');
$em->getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping('point', 'point');
// Store a Location object
use Geo\Entity\Location;
use Geo\ValueObject\Point;
$location = new Location();
$location->setAddress('1600 Amphitheatre Parkway, Mountain View, CA');
$location->setPoint(new Point(37.4220761, -122.0845187));
$em->persist($location);
$em->flush();
$em->clear();
// Fetch the Location object
$query = $em->createQuery("SELECT l FROM Geo\Entity\Location WHERE l.address = '1600 Amphitheatre Parkway, Mountain View, CA'");
$location = $query->getSingleResult();
/* @var Geo\ValueObject\Point */
$point = $location->getPoint();

View File

@@ -0,0 +1,376 @@
Aggregate Fields
================
.. sectionauthor:: Benjamin Eberlei <kontakt@beberlei.de>
You will often come across the requirement to display aggregate
values of data that can be computed by using the MIN, MAX, COUNT or
SUM SQL functions. For any ORM this is a tricky issue
traditionally. Doctrine 2 offers several ways to get access to
these values and this article will describe all of them from
different perspectives.
You will see that aggregate fields can become very explicit
features in your domain model and how this potentially complex
business rules can be easily tested.
An example model
----------------
Say you want to model a bank account and all their entries. Entries
into the account can either be of positive or negative money
values. Each account has a credit limit and the account is never
allowed to have a balance below that value.
For simplicity we live in a world were money is composed of
integers only. Also we omit the receiver/sender name, stated reason
for transfer and the execution date. These all would have to be
added on the ``Entry`` object.
Our entities look like:
.. code-block:: php
<?php
namespace Bank\Entities;
/**
* @Entity
*/
class Account
{
/** @Id @GeneratedValue @Column(type="integer") */
private $id;
/** @Column(type="string", unique=true) */
private $no;
/**
* @OneToMany(targetEntity="Entry", mappedBy="account", cascade={"persist"})
*/
private $entries;
/**
* @Column(type="integer")
*/
private $maxCredit = 0;
public function __construct($no, $maxCredit = 0)
{
$this->no = $no;
$this->maxCredit = $maxCredit;
$this->entries = new \Doctrine\Common\Collections\ArrayCollection();
}
}
/**
* @Entity
*/
class Entry
{
/** @Id @GeneratedValue @Column(type="integer") */
private $id;
/**
* @ManyToOne(targetEntity="Account", inversedBy="entries")
*/
private $account;
/**
* @Column(type="integer")
*/
private $amount;
public function __construct($account, $amount)
{
$this->account = $account;
$this->amount = $amount;
// more stuff here, from/to whom, stated reason, execution date and such
}
public function getAmount()
{
return $this->amount;
}
}
Using DQL
---------
The Doctrine Query Language allows you to select for aggregate
values computed from fields of your Domain Model. You can select
the current balance of your account by calling:
.. code-block:: php
<?php
$dql = "SELECT SUM(e.amount) AS balance FROM Bank\Entities\Entry e " .
"WHERE e.account = ?1";
$balance = $em->createQuery($dql)
->setParameter(1, $myAccountId)
->getSingleScalarResult();
The ``$em`` variable in this (and forthcoming) example holds the
Doctrine ``EntityManager``. We create a query for the SUM of all
amounts (negative amounts are withdraws) and retrieve them as a
single scalar result, essentially return only the first column of
the first row.
This approach is simple and powerful, however it has a serious
drawback. We have to execute a specific query for the balance
whenever we need it.
To implement a powerful domain model we would rather have access to
the balance from our ``Account`` entity during all times (even if
the Account was not persisted in the database before!).
Also an additional requirement is the max credit per ``Account``
rule.
We cannot reliably enforce this rule in our ``Account`` entity with
the DQL retrieval of the balance. There are many different ways to
retrieve accounts. We cannot guarantee that we can execute the
aggregation query for all these use-cases, let alone that a
userland programmer checks this balance against newly added
entries.
Using your Domain Model
-----------------------
``Account`` and all the ``Entry`` instances are connected through a
collection, which means we can compute this value at runtime:
.. code-block:: php
<?php
class Account
{
// .. previous code
public function getBalance()
{
$balance = 0;
foreach ($this->entries AS $entry) {
$balance += $entry->getAmount();
}
return $balance;
}
}
Now we can always call ``Account::getBalance()`` to access the
current account balance.
To enforce the max credit rule we have to implement the "Aggregate
Root" pattern as described in Eric Evans book on Domain Driven
Design. Described with one sentence, an aggregate root controls the
instance creation, access and manipulation of its children.
In our case we want to enforce that new entries can only added to
the ``Account`` by using a designated method. The ``Account`` is
the aggregate root of this relation. We can also enforce the
correctness of the bi-directional ``Account`` <-> ``Entry``
relation with this method:
.. code-block:: php
<?php
class Account
{
public function addEntry($amount)
{
$this->assertAcceptEntryAllowed($amount);
$e = new Entry($this, $amount);
$this->entries[] = $e;
return $e;
}
}
Now look at the following test-code for our entities:
.. code-block:: php
<?php
class AccountTest extends \PHPUnit_Framework_TestCase
{
public function testAddEntry()
{
$account = new Account("123456", $maxCredit = 200);
$this->assertEquals(0, $account->getBalance());
$account->addEntry(500);
$this->assertEquals(500, $account->getBalance());
$account->addEntry(-700);
$this->assertEquals(-200, $account->getBalance());
}
public function testExceedMaxLimit()
{
$account = new Account("123456", $maxCredit = 200);
$this->setExpectedException("Exception");
$account->addEntry(-1000);
}
}
To enforce our rule we can now implement the assertion in
``Account::addEntry``:
.. code-block:: php
<?php
class Account
{
private function assertAcceptEntryAllowed($amount)
{
$futureBalance = $this->getBalance() + $amount;
$allowedMinimalBalance = ($this->maxCredit * -1);
if ($futureBalance < $allowedMinimalBalance) {
throw new Exception("Credit Limit exceeded, entry is not allowed!");
}
}
}
We haven't talked to the entity manager for persistence of our
account example before. You can call
``EntityManager::persist($account)`` and then
``EntityManager::flush()`` at any point to save the account to the
database. All the nested ``Entry`` objects are automatically
flushed to the database also.
.. code-block:: php
<?php
$account = new Account("123456", 200);
$account->addEntry(500);
$account->addEntry(-200);
$em->persist($account);
$em->flush();
The current implementation has a considerable drawback. To get the
balance, we have to initialize the complete ``Account::$entries``
collection, possibly a very large one. This can considerably hurt
the performance of your application.
Using an Aggregate Field
------------------------
To overcome the previously mentioned issue (initializing the whole
entries collection) we want to add an aggregate field called
"balance" on the Account and adjust the code in
``Account::getBalance()`` and ``Account:addEntry()``:
.. code-block:: php
<?php
class Account
{
/**
* @Column(type="integer")
*/
private $balance = 0;
public function getBalance()
{
return $this->balance;
}
public function addEntry($amount)
{
$this->assertAcceptEntryAllowed($amount);
$e = new Entry($this, $amount);
$this->entries[] = $e;
$this->balance += $amount;
return $e;
}
}
This is a very simple change, but all the tests still pass. Our
account entities return the correct balance. Now calling the
``Account::getBalance()`` method will not occur the overhead of
loading all entries anymore. Adding a new Entry to the
``Account::$entities`` will also not initialize the collection
internally.
Adding a new entry is therefore very performant and explicitly
hooked into the domain model. It will only update the account with
the current balance and insert the new entry into the database.
Tackling Race Conditions with Aggregate Fields
----------------------------------------------
Whenever you denormalize your database schema race-conditions can
potentially lead to inconsistent state. See this example:
.. code-block:: php
<?php
// The Account $accId has a balance of 0 and a max credit limit of 200:
// request 1 account
$account1 = $em->find('Bank\Entities\Account', $accId);
// request 2 account
$account2 = $em->find('Bank\Entities\Account', $accId);
$account1->addEntry(-200);
$account2->addEntry(-200);
// now request 1 and 2 both flush the changes.
The aggregate field ``Account::$balance`` is now -200, however the
SUM over all entries amounts yields -400. A violation of our max
credit rule.
You can use both optimistic or pessimistic locking to 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.
Optimistic locking is as easy as adding a version column:
.. code-block:: php
<?php
class Amount
{
/** @Column(type="integer") @Version */
private $version;
}
The previous example would then throw an exception in the face of
whatever request saves the entity last (and would create the
inconsistent state).
Pessimistic locking requires an additional flag set on the
``EntityManager::find()`` call, enabling write locking directly in
the database using a FOR UPDATE.
.. code-block:: php
<?php
use Doctrine\DBAL\LockMode;
$account = $em->find('Bank\Entities\Account', $accId, LockMode::PESSIMISTIC_READ);
Keeping Updates and Deletes in Sync
-----------------------------------
The example shown in this article does not allow changes to the
value in ``Entry``, which considerably simplifies the effort to
keep ``Account::$balance`` in sync. If your use-case allows fields
to be updated or related entities to be removed you have to
encapsulate this logic in your "Aggregate Root" entity and adjust
the aggregate field accordingly.
Conclusion
----------
This article described how to obtain aggregate values using DQL or
your domain model. It showed how you can easily add an aggregate
field that offers serious performance benefits over iterating all
the related objects that make up an aggregate value. Finally I
showed how you can ensure that your aggregate fields do not get out
of sync due to race-conditions and concurrent access.

View File

@@ -0,0 +1,273 @@
Persisting the Decorator Pattern
================================
.. sectionauthor:: Chris Woodford <chris.woodford@gmail.com>
This recipe will show you a simple example of how you can use
Doctrine 2 to persist an implementation of the
`Decorator Pattern <http://en.wikipedia.org/wiki/Decorator_pattern>`_
Component
---------
The ``Component`` class needs to be persisted, so it's going to
be an ``Entity``. As the top of the inheritance hierarchy, it's going
to have to define the persistent inheritance. For this example, we
will use Single Table Inheritance, but Class Table Inheritance
would work as well. In the discriminator map, we will define two
concrete subclasses, ``ConcreteComponent`` and ``ConcreteDecorator``.
.. code-block:: php
<?php
namespace Test;
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"cc" = "Test\Component\ConcreteComponent",
"cd" = "Test\Decorator\ConcreteDecorator"})
*/
abstract class Component
{
/**
* @Id @Column(type="integer")
* @GeneratedValue(strategy="AUTO")
*/
protected $id;
/** @Column(type="string", nullable=true) */
protected $name;
/**
* Get id
* @return integer $id
*/
public function getId()
{
return $this->id;
}
/**
* Set name
* @param string $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* Get name
* @return string $name
*/
public function getName()
{
return $this->name;
}
}
ConcreteComponent
-----------------
The ``ConcreteComponent`` class is pretty simple and doesn't do much
more than extend the abstract ``Component`` class (only for the
purpose of keeping this example simple).
.. code-block:: php
<?php
namespace Test\Component;
use Test\Component;
/** @Entity */
class ConcreteComponent extends Component
{}
Decorator
---------
The ``Decorator`` class doesn't need to be persisted, but it does
need to define an association with a persisted ``Entity``. We can
use a ``MappedSuperclass`` for this.
.. code-block:: php
<?php
namespace Test;
/** @MappedSuperclass */
abstract class Decorator extends Component
{
/**
* @OneToOne(targetEntity="Test\Component", cascade={"all"})
* @JoinColumn(name="decorates", referencedColumnName="id")
*/
protected $decorates;
/**
* initialize the decorator
* @param Component $c
*/
public function __construct(Component $c)
{
$this->setDecorates($c);
}
/**
* (non-PHPdoc)
* @see Test.Component::getName()
*/
public function getName()
{
return 'Decorated ' . $this->getDecorates()->getName();
}
/**
* the component being decorated
* @return Component
*/
protected function getDecorates()
{
return $this->decorates;
}
/**
* sets the component being decorated
* @param Component $c
*/
protected function setDecorates(Component $c)
{
$this->decorates = $c;
}
}
All operations on the ``Decorator`` (i.e. persist, remove, etc) will
cascade from the ``Decorator`` to the ``Component``. This means that
when we persist a ``Decorator``, Doctrine will take care of
persisting the chain of decorated objects for us. A ``Decorator`` can
be treated exactly as a ``Component`` when it comes time to
persisting it.
The ``Decorator's`` constructor accepts an instance of a
``Component``, as defined by the ``Decorator`` pattern. The
setDecorates/getDecorates methods have been defined as protected to
hide the fact that a ``Decorator`` is decorating a ``Component`` and
keeps the ``Component`` interface and the ``Decorator`` interface
identical.
To illustrate the intended result of the ``Decorator`` pattern, the
getName() method has been overridden to append a string to the
``Component's`` getName() method.
ConcreteDecorator
-----------------
The final class required to complete a simple implementation of the
Decorator pattern is the ``ConcreteDecorator``. In order to further
illustrate how the ``Decorator`` can alter data as it moves through
the chain of decoration, a new field, "special", has been added to
this class. The getName() has been overridden and appends the value
of the getSpecial() method to its return value.
.. code-block:: php
<?php
namespace Test\Decorator;
use Test\Decorator;
/** @Entity */
class ConcreteDecorator extends Decorator
{
/** @Column(type="string", nullable=true) */
protected $special;
/**
* Set special
* @param string $special
*/
public function setSpecial($special)
{
$this->special = $special;
}
/**
* Get special
* @return string $special
*/
public function getSpecial()
{
return $this->special;
}
/**
* (non-PHPdoc)
* @see Test.Component::getName()
*/
public function getName()
{
return '[' . $this->getSpecial()
. '] ' . parent::getName();
}
}
Examples
--------
Here is an example of how to persist and retrieve your decorated
objects
.. code-block:: php
<?php
use Test\Component\ConcreteComponent,
Test\Decorator\ConcreteDecorator;
// assumes Doctrine 2 is configured and an instance of
// an EntityManager is available as $em
// create a new concrete component
$c = new ConcreteComponent();
$c->setName('Test Component 1');
$em->persist($c); // assigned unique ID = 1
// create a new concrete decorator
$c = new ConcreteComponent();
$c->setName('Test Component 2');
$d = new ConcreteDecorator($c);
$d->setSpecial('Really');
$em->persist($d);
// assigns c as unique ID = 2, and d as unique ID = 3
$em->flush();
$c = $em->find('Test\Component', 1);
$d = $em->find('Test\Component', 3);
echo get_class($c);
// prints: Test\Component\ConcreteComponent
echo $c->getName();
// prints: Test Component 1
echo get_class($d)
// prints: Test\Component\ConcreteDecorator
echo $d->getName();
// prints: [Really] Decorated Test Component 2

View File

@@ -0,0 +1,217 @@
Extending DQL in Doctrine 2: Custom AST Walkers
===============================================
.. sectionauthor:: Benjamin Eberlei <kontakt@beberlei.de>
The Doctrine Query Language (DQL) is a proprietary sql-dialect that
substitutes tables and columns for Entity names and their fields.
Using DQL you write a query against the database using your
entities. With the help of the metadata you can write very concise,
compact and powerful queries that are then translated into SQL by
the Doctrine ORM.
In Doctrine 1 the DQL language was not implemented using a real
parser. This made modifications of the DQL by the user impossible.
Doctrine 2 in contrast has a real parser for the DQL language,
which transforms the DQL statement into an
`Abstract Syntax Tree <http://en.wikipedia.org/wiki/Abstract_syntax_tree>`_
and generates the appropriate SQL statement for it. Since this
process is deterministic Doctrine heavily caches the SQL that is
generated from any given DQL query, which reduces the performance
overhead of the parsing process to zero.
You can modify the Abstract syntax tree by hooking into DQL parsing
process by adding a Custom Tree Walker. A walker is an interface
that walks each node of the Abstract syntax tree, thereby
generating the SQL statement.
There are two types of custom tree walkers that you can hook into
the DQL parser:
- An output walker. This one actually generates the SQL, and there
is only ever one of them. We implemented the default SqlWalker
implementation for it.
- A tree walker. There can be many tree walkers, they cannot
generate the sql, however they can modify the AST before its
rendered to sql.
Now this is all awfully technical, so let me come to some use-cases
fast to keep you motivated. Using walker implementation you can for
example:
- Modify the AST to generate a Count Query to be used with a
paginator for any given DQL query.
- Modify the Output Walker to generate vendor-specific SQL
(instead of ANSI).
- Modify the AST to add additional where clauses for specific
entities (example ACL, country-specific content...)
- Modify the Output walker to pretty print the SQL for debugging
purposes.
In this cookbook-entry I will show examples on the first two
points. There are probably much more use-cases.
Generic count query for pagination
----------------------------------
Say you have a blog and posts all with one category and one author.
A query for the front-page or any archive page might look something
like:
.. code-block:: sql
SELECT p, c, a FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ...
Now in this query the blog post is the root entity, meaning its the
one that is hydrated directly from the query and returned as an
array of blog posts. In contrast the comment and author are loaded
for deeper use in the object tree.
A pagination for this query would want to approximate the number of
posts that match the WHERE clause of this query to be able to
predict the number of pages to show to the user. A draft of the DQL
query for pagination would look like:
.. code-block:: sql
SELECT count(DISTINCT p.id) FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ...
Now you could go and write each of these queries by hand, or you
can use a tree walker to modify the AST for you. Lets see how the
API would look for this use-case:
.. code-block:: php
<?php
$pageNum = 1;
$query = $em->createQuery($dql);
$query->setFirstResult( ($pageNum-1) * 20)->setMaxResults(20);
$totalResults = Paginate::count($query);
$results = $query->getResult();
The ``Paginate::count(Query $query)`` looks like:
.. code-block:: php
<?php
class Paginate
{
static public function count(Query $query)
{
/* @var $countQuery Query */
$countQuery = clone $query;
$countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('DoctrineExtensions\Paginate\CountSqlWalker'));
$countQuery->setFirstResult(null)->setMaxResults(null);
return $countQuery->getSingleScalarResult();
}
}
It clones the query, resets the limit clause first and max results
and registers the ``CountSqlWalker`` customer tree walker which
will modify the AST to execute a count query. The walkers
implementation is:
.. code-block:: php
<?php
class CountSqlWalker extends TreeWalkerAdapter
{
/**
* Walks down a SelectStatement AST node, thereby generating the appropriate SQL.
*
* @return string The SQL.
*/
public function walkSelectStatement(SelectStatement $AST)
{
$parent = null;
$parentName = null;
foreach ($this->_getQueryComponents() AS $dqlAlias => $qComp) {
if ($qComp['parent'] === null && $qComp['nestingLevel'] == 0) {
$parent = $qComp;
$parentName = $dqlAlias;
break;
}
}
$pathExpression = new PathExpression(
PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName,
$parent['metadata']->getSingleIdentifierFieldName()
);
$pathExpression->type = PathExpression::TYPE_STATE_FIELD;
$AST->selectClause->selectExpressions = array(
new SelectExpression(
new AggregateExpression('count', $pathExpression, true), null
)
);
}
}
This will delete any given select expressions and replace them with
a distinct count query for the root entities primary key. This will
only work if your entity has only one identifier field (composite
keys won't work).
Modify the Output Walker to generate Vendor specific SQL
--------------------------------------------------------
Most RMDBS have vendor-specific features for optimizing select
query execution plans. You can write your own output walker to
introduce certain keywords using the Query Hint API. A query hint
can be set via ``Query::setHint($name, $value)`` as shown in the
previous example with the ``HINT_CUSTOM_TREE_WALKERS`` query hint.
We will implement a custom Output Walker that allows to specify the
SQL\_NO\_CACHE query hint.
.. code-block:: php
<?php
$dql = "SELECT p, c, a FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ...";
$query = $m->createQuery($dql);
$query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'DoctrineExtensions\Query\MysqlWalker');
$query->setHint("mysqlWalker.sqlNoCache", true);
$results = $query->getResult();
Our ``MysqlWalker`` will extend the default ``SqlWalker``. We will
modify the generation of the SELECT clause, adding the
SQL\_NO\_CACHE on those queries that need it:
.. code-block:: php
<?php
class MysqlWalker extends SqlWalker
{
/**
* Walks down a SelectClause AST node, thereby generating the appropriate SQL.
*
* @param $selectClause
* @return string The SQL.
*/
public function walkSelectClause($selectClause)
{
$sql = parent::walkSelectClause($selectClause);
if ($this->getQuery()->getHint('mysqlWalker.sqlNoCache') === true) {
if ($selectClause->isDistinct) {
$sql = str_replace('SELECT DISTINCT', 'SELECT DISTINCT SQL_NO_CACHE', $sql);
} else {
$sql = str_replace('SELECT', 'SELECT SQL_NO_CACHE', $sql);
}
}
return $sql;
}
}
Writing extensions to the Output Walker requires a very deep
understanding of the DQL Parser and Walkers, but may offer your
huge benefits with using vendor specific features. This would still
allow you write DQL queries instead of NativeQueries to make use of
vendor specific features.

View File

@@ -0,0 +1,240 @@
DQL User Defined Functions
==========================
.. sectionauthor:: Benjamin Eberlei <kontakt@beberlei.de>
By default DQL supports a limited subset of all the vendor-specific
SQL functions common between all the vendors. However in many cases
once you have decided on a specific database vendor, you will never
change it during the life of your project. This decision for a
specific vendor potentially allows you to make use of powerful SQL
features that are unique to the vendor.
It is worth to mention that Doctrine 2 also allows you to handwrite
your SQL instead of extending the DQL parser. Extending DQL is sort of an
advanced extension point. You can map arbitrary SQL to your objects
and gain access to vendor specific functionalities using the
``EntityManager#createNativeQuery()`` API as described in
the :doc:`Native Query <../reference/native-sql>` chapter.
The DQL Parser has hooks to register functions that can then be
used in your DQL queries and transformed into SQL, allowing to
extend Doctrines Query capabilities to the vendors strength. This
post explains the Used-Defined Functions API (UDF) of the Dql
Parser and shows some examples to give you some hints how you would
extend DQL.
There are three types of functions in DQL, those that return a
numerical value, those that return a string and those that return a
Date. Your custom method has to be registered as either one of
those. The return type information is used by the DQL parser to
check possible syntax errors during the parsing process, for
example using a string function return value in a math expression.
Registering your own DQL functions
----------------------------------
You can register your functions adding them to the ORM
configuration:
.. code-block:: php
<?php
$config = new \Doctrine\ORM\Configuration();
$config->addCustomStringFunction($name, $class);
$config->addCustomNumericFunction($name, $class);
$config->addCustomDatetimeFunction($name, $class);
$em = EntityManager::create($dbParams, $config);
The ``$name`` is the name the function will be referred to in the
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.
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 <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``.
The DQL parser is a top-down recursive descent parser to generate
the Abstract-Syntax Tree (AST) and uses a TreeWalker approach to
generate the appropriate SQL from the AST. This makes reading the
Parser/TreeWalker code manageable in a finite amount of time.
The ``FunctionNode`` class I referred to earlier requires you to
implement two methods, one for the parsing process (obviously)
called ``parse`` and one for the TreeWalker process called
``getSql()``. I show you the code for the DateDiff method and
discuss it step by step:
.. code-block:: php
<?php
/**
* DateDiffFunction ::= "DATEDIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
*/
class DateDiff extends FunctionNode
{
// (1)
public $firstDateExpression = null;
public $secondDateExpression = null;
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER); // (2)
$parser->match(Lexer::T_OPEN_PARENTHESIS); // (3)
$this->firstDateExpression = $parser->ArithmeticPrimary(); // (4)
$parser->match(Lexer::T_COMMA); // (5)
$this->secondDateExpression = $parser->ArithmeticPrimary(); // (6)
$parser->match(Lexer::T_CLOSE_PARENTHESIS); // (3)
}
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
return 'DATEDIFF(' .
$this->firstDateExpression->dispatch($sqlWalker) . ', ' .
$this->secondDateExpression->dispatch($sqlWalker) .
')'; // (7)
}
}
The Parsing process of the DATEDIFF function is going to find two
expressions the date1 and the date2 values, whose AST Node
representations will be saved in the variables of the DateDiff
FunctionNode instance at (1).
The parse() method has to cut the function call "DATEDIFF" and its
argument into pieces. Since the parser detects the function using a
lookahead the T\_IDENTIFIER of the function name has to be taken
from the stack (2), followed by a detection of the arguments in
(4)-(6). The opening and closing parenthesis have to be detected
also. This happens during the Parsing process and leads to the
generation of a DateDiff FunctionNode somewhere in the AST of the
dql statement.
The ``ArithmeticPrimary`` method call is the most common
denominator of valid EBNF tokens taken from the
`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
looking at the Parser source code.
Now in the TreeWalker process we have to pick up this node and
generate SQL from it, which apparently is quite easy looking at the
code in (7). Since we don't know which type of AST Node the first
and second Date expression are we are just dispatching them back to
the SQL Walker to generate SQL from and then wrap our DATEDIFF
function call around this output.
Now registering this DateDiff FunctionNode with the ORM using:
.. code-block:: php
<?php
$config = new \Doctrine\ORM\Configuration();
$config->addCustomStringFunction('DATEDIFF', 'DoctrineExtensions\Query\MySql\DateDiff');
We can do fancy stuff like:
.. code-block:: sql
SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATEDIFF(CURRENT_TIME(), p.created) < 7
Date Add
--------
Often useful it the ability to do some simple date calculations in
your DQL query using
`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:
.. code-block:: php
<?php
/**
* DateAddFunction ::=
* "DATE_ADD" "(" ArithmeticPrimary ", INTERVAL" ArithmeticPrimary Identifier ")"
*/
class DateAdd extends FunctionNode
{
public $firstDateExpression = null;
public $intervalExpression = null;
public $unit = null;
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->firstDateExpression = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_COMMA);
$parser->match(Lexer::T_IDENTIFIER);
$this->intervalExpression = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_IDENTIFIER);
/* @var $lexer Lexer */
$lexer = $parser->getLexer();
$this->unit = $lexer->token['value'];
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
return 'DATE_ADD(' .
$this->firstDateExpression->dispatch($sqlWalker) . ', INTERVAL ' .
$this->intervalExpression->dispatch($sqlWalker) . ' ' . $this->unit .
')';
}
}
The only difference compared to the DATEDIFF here is, we
additionally need the ``Lexer`` to access the value of the
``T_IDENTIFIER`` token for the Date Interval unit, for example the
MONTH in:
.. code-block:: sql
SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATE_ADD(CURRENT_TIME(), INTERVAL 4 MONTH) > p.created
The above method now only supports the specification using
``INTERVAL``, to also allow a real date in DATE\_ADD we need to add
some decision logic to the parsing process (makes up for a nice
exercise).
Now as you see, the Parsing process doesn't catch all the possible
SQL errors, here we don't match for all the valid inputs for the
interval unit. However where necessary we rely on the database
vendors SQL parser to show us further errors in the parsing
process, for example if the Unit would not be one of the supported
values by MySql.
Conclusion
----------
Now that you all know how you can implement vendor specific SQL
functionalities in DQL, we would be excited to see user extensions
that add vendor specific function packages, for example more math
functions, XML + GIS Support, Hashing functions and so on.
For 2.0 we will come with the current set of functions, however for
a future version we will re-evaluate if we can abstract even more
vendor sql functions and extend the DQL languages scope.
Code for this Extension to DQL and other Doctrine Extensions can be
found
`in my Github DoctrineExtensions repository <http://github.com/beberlei/DoctrineExtensions>`_.

View File

@@ -0,0 +1,68 @@
Entities in the Session
=======================
There are several use-cases to save entities in the session, for example:
1. User object
2. Multi-step forms
To achieve this with Doctrine you have to pay attention to some details to get
this working.
Merging entity into an EntityManager
------------------------------------
In Doctrine an entity objects has to be "managed" by an EntityManager to be
updateable. Entities saved into the session are not managed in the next request
anymore. This means that you have to register these entities with an
EntityManager again if you want to change them or use them as part of
references between other entities. You can achieve this by calling
``EntityManager#merge()``.
For a representative User object the code to get turn an instance from
the session into a managed Doctrine object looks like this:
.. code-block:: php
<?php
require_once 'bootstrap.php';
$em = GetEntityManager(); // creates an EntityManager
session_start();
if (isset($_SESSION['user']) && $_SESSION['user'] instanceof User) {
$user = $_SESSION['user'];
$user = $em->merge($user);
}
.. note::
A frequent mistake is not to get the merged user object from the return
value of ``EntityManager#merge()``. The entity object passed to merge is
not necessarily the same object that is returned from the method.
Serializing entity into the session
-----------------------------------
Entities that are serialized into the session normally contain references to
other entities as well. Think of the user entity has a reference to his
articles, groups, photos or many other different entities. If you serialize
this object into the session then you don't want to serialize the related
entities as well. This is why you should call ``EntityManager#detach()`` on this
object or implement the __sleep() magic method on your entity.
.. code-block:: php
<?php
require_once 'bootstrap.php';
$em = GetEntityManager(); // creates an EntityManager
$user = $em->find("User", 1);
$em->detach($user);
$_SESSION['user'] = $user;
.. note::
When you called detach on your objects they get "unmanaged" with that
entity manager. This means you cannot use them as part of write operations
during ``EntityManager#flush()`` anymore in this request.

View File

@@ -0,0 +1,112 @@
Implementing ArrayAccess for Domain Objects
===========================================
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
This recipe will show you how to implement ArrayAccess for your
domain objects in order to allow more uniform access, for example
in templates. In these examples we will implement ArrayAccess on a
`Layer Supertype <http://martinfowler.com/eaaCatalog/layerSupertype.html>`_
for all our domain objects.
Option 1
--------
In this implementation we will make use of PHPs highly dynamic
nature to dynamically access properties of a subtype in a supertype
at runtime. Note that this implementation has 2 main caveats:
- It will not work with private fields
- It will not go through any getters/setters
.. code-block:: php
<?php
abstract class DomainObject implements ArrayAccess
{
public function offsetExists($offset) {
return isset($this->$offset);
}
public function offsetSet($offset, $value) {
$this->$offset = $value;
}
public function offsetGet($offset) {
return $this->$offset;
}
public function offsetUnset($offset) {
$this->$offset = null;
}
}
Option 2
--------
In this implementation we will dynamically invoke getters/setters.
Again we use PHPs dynamic nature to invoke methods on a subtype
from a supertype at runtime. This implementation has the following
caveats:
- It relies on a naming convention
- The semantics of offsetExists can differ
- offsetUnset will not work with typehinted setters
.. code-block:: php
<?php
abstract class DomainObject implements ArrayAccess
{
public function offsetExists($offset) {
// In this example we say that exists means it is not null
$value = $this->{"get$offset"}();
return $value !== null;
}
public function offsetSet($offset, $value) {
$this->{"set$offset"}($value);
}
public function offsetGet($offset) {
return $this->{"get$offset"}();
}
public function offsetUnset($offset) {
$this->{"set$offset"}(null);
}
}
Read-only
---------
You can slightly tweak option 1 or option 2 in order to make array
access read-only. This will also circumvent some of the caveats of
each option. Simply make offsetSet and offsetUnset throw an
exception (i.e. BadMethodCallException).
.. code-block:: php
<?php
abstract class DomainObject implements ArrayAccess
{
public function offsetExists($offset) {
// option 1 or option 2
}
public function offsetSet($offset, $value) {
throw new BadMethodCallException("Array access of class " . get_class($this) . " is read-only!");
}
public function offsetGet($offset) {
// option 1 or option 2
}
public function offsetUnset($offset) {
throw new BadMethodCallException("Array access of class " . get_class($this) . " is read-only!");
}
}

View File

@@ -0,0 +1,72 @@
Implementing the Notify ChangeTracking Policy
=============================================
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
The NOTIFY change-tracking policy is the most effective
change-tracking policy provided by Doctrine but it requires some
boilerplate code. This recipe will show you how this boilerplate
code should look like. We will implement it on a
`Layer Supertype <http://martinfowler.com/eaaCatalog/layerSupertype.html>`_
for all our domain objects.
Implementing NotifyPropertyChanged
----------------------------------
The NOTIFY policy is based on the assumption that the entities
notify interested listeners of changes to their properties. For
that purpose, a class that wants to use this policy needs to
implement the ``NotifyPropertyChanged`` interface from the
``Doctrine\Common`` namespace.
.. code-block:: php
<?php
use Doctrine\Common\NotifyPropertyChanged;
use Doctrine\Common\PropertyChangedListener;
abstract class DomainObject implements NotifyPropertyChanged
{
private $listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener) {
$this->listeners[] = $listener;
}
/** Notifies listeners of a change. */
protected function onPropertyChanged($propName, $oldValue, $newValue) {
if ($this->listeners) {
foreach ($this->listeners as $listener) {
$listener->propertyChanged($this, $propName, $oldValue, $newValue);
}
}
}
}
Then, in each property setter of concrete, derived domain classes,
you need to invoke onPropertyChanged as follows to notify
listeners:
.. code-block:: php
<?php
// Mapping not shown, either in annotations, xml or yaml as usual
class MyEntity extends DomainObject
{
private $data;
// ... other fields as usual
public function setData($data) {
if ($data != $this->data) { // check: is it actually modified?
$this->onPropertyChanged('data', $this->data, $data);
$this->data = $data;
}
}
}
The check whether the new value is different from the old one is
not mandatory but recommended. That way you can avoid unnecessary
updates and also have full control over when you consider a
property changed.

View File

@@ -0,0 +1,78 @@
Implementing Wakeup or Clone
============================
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
As explained in the
`restrictions for entity classes in the manual <http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities>`_,
it is usually not allowed for an entity to implement ``__wakeup``
or ``__clone``, because Doctrine makes special use of them.
However, it is quite easy to make use of these methods in a safe
way by guarding the custom wakeup or clone code with an entity
identity check, as demonstrated in the following sections.
Safely implementing \_\_wakeup
------------------------------
To safely implement ``__wakeup``, simply enclose your
implementation code in an identity check as follows:
.. code-block:: php
<?php
class MyEntity
{
private $id; // This is the identifier of the entity.
//...
public function __wakeup()
{
// If the entity has an identity, proceed as normal.
if ($this->id) {
// ... Your code here as normal ...
}
// otherwise do nothing, do NOT throw an exception!
}
//...
}
Safely implementing \_\_clone
-----------------------------
Safely implementing ``__clone`` is pretty much the same:
.. code-block:: php
<?php
class MyEntity
{
private $id; // This is the identifier of the entity.
//...
public function __clone()
{
// If the entity has an identity, proceed as normal.
if ($this->id) {
// ... Your code here as normal ...
}
// otherwise do nothing, do NOT throw an exception!
}
//...
}
Summary
-------
As you have seen, it is quite easy to safely make use of
``__wakeup`` and ``__clone`` in your entities without adding any
really Doctrine-specific or Doctrine-dependant code.
These implementations are possible and safe because when Doctrine
invokes these methods, the entities never have an identity (yet).
Furthermore, it is possibly a good idea to check for the identity
in your code anyway, since it's rarely the case that you want to
unserialize or clone an entity with no identity.

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

@@ -0,0 +1,189 @@
Mysql Enums
===========
The type system of Doctrine 2 consists of flyweights, which means there is only
one instance of any given type. Additionally types do not contain state. Both
assumptions make it rather complicated to work with the Enum Type of MySQL that
is used quite a lot by developers.
When using Enums with a non-tweaked Doctrine 2 application you will get
errors from the Schema-Tool commands due to the unknown database type "enum".
By default Doctrine does not map the MySQL enum type to a Doctrine type.
This is because Enums contain state (their allowed values) and Doctrine
types don't.
This cookbook entry shows two possible solutions to work with MySQL enums.
But first a word of warning. The MySQL Enum type has considerable downsides:
- Adding new values requires to rebuild the whole table, which can take hours
depending on the size.
- Enums are ordered in the way the values are specified, not in their "natural" order.
- Enums validation mechanism for allowed values is not necessarily good,
specifying invalid values leads to an empty enum for the default MySQL error
settings. You can easily replicate the "allow only some values" requirement
in your Doctrine entities.
Solution 1: Mapping to Varchars
-------------------------------
You can map ENUMs to varchars. You can register MySQL ENUMs to map to Doctrine
varchars. This way Doctrine always resolves ENUMs to Doctrine varchars. It
will even detect this match correctly when using SchemaTool update commands.
.. code-block:: php
<?php
$conn = $em->getConnection();
$conn->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string');
In this case you have to ensure that each varchar field that is an enum in the
database only gets passed the allowed values. You can easily enforce this in your
entities:
.. code-block:: php
<?php
/** @Entity */
class Article
{
const STATUS_VISIBLE = 'visible';
const STATUS_INVISIBLE = 'invisible';
/** @Column(type="string") */
private $status;
public function setStatus($status)
{
if (!in_array($status, array(self::STATUS_VISIBLE, self::STATUS_INVISIBLE))) {
throw new \InvalidArgumentException("Invalid status");
}
$this->status = $status;
}
}
If you want to actively create enums through the Doctrine Schema-Tool by using
the **columnDefinition** attribute.
.. code-block:: php
<?php
/** @Entity */
class Article
{
/** @Column(type="string", columnDefinition="ENUM('visible', 'invisible')") */
private $status;
}
In this case however Schema-Tool update will have a hard time not to request changes for this column on each call.
Solution 2: Defining a Type
---------------------------
You can make a stateless ENUM type by creating a type class for each unique set of ENUM values.
For example for the previous enum type:
.. code-block:: php
<?php
namespace MyProject\DBAL;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;
class EnumVisibilityType extends Type
{
const ENUM_VISIBILITY = 'enumvisibility';
const STATUS_VISIBLE = 'visible';
const STATUS_INVISIBLE = 'invisible';
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
return "ENUM('visible', 'invisible') COMMENT '(DC2Type:enumvisibility)'";
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
return $value;
}
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
if (!in_array($value, array(self::STATUS_VISIBLE, self::STATUS_INVISIBLE))) {
throw new \InvalidArgumentException("Invalid status");
}
return $value;
}
public function getName()
{
return self::ENUM_VISIBILITY;
}
}
You can register this type with ``Type::addType('enumvisibility', 'MyProject\DBAL\EnumVisibilityType');``.
Then in your entity you can just use this type:
.. code-block:: php
<?php
/** @Entity */
class Article
{
/** @Column(type="enumvisibility") */
private $status;
}
You can generalize this approach easily to create a base class for enums:
.. code-block:: php
<?php
namespace MyProject\DBAL;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;
abstract class EnumType extends Type
{
protected $name;
protected $values = array();
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
$values = array_map(function($val) { return "'".$val."'"; }, $this->values);
return "ENUM(".implode(", ", $values).") COMMENT '(DC2Type:".$this->name.")'";
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
return $value;
}
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
if (!in_array($value, $this->values)) {
throw new \InvalidArgumentException("Invalid '".$this->name."' value.");
}
return $value;
}
public function getName()
{
return $this->name;
}
}
With this base class you can define an enum as easily as:
.. code-block:: php
<?php
namespace MyProject\DBAL;
class EnumVisibilityType extends EnumType
{
protected $name = 'enumvisibility';
protected $values = array('visible', 'invisible');
}

View File

@@ -0,0 +1,137 @@
Keeping your Modules independent
=================================
.. versionadded:: 2.2
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.
Doctrine 2.2 includes a new utility called the ``ResolveTargetEntityListener``,
that functions by intercepting certain calls inside Doctrine and rewrite
targetEntity parameters in your metadata mapping at runtime. It means that
in your bundle you are able to use an interface or abstract class in your
mappings and expect correct mapping to a concrete entity at runtime.
This functionality allows you to define relationships between different entities
but not making them hard dependencies.
Background
----------
In the following example, the situation is we have an `InvoiceModule`
which provides invoicing functionality, and a `CustomerModule` that
contains customer management tools. We want to keep these separated,
because they can be used in other systems without each other, but for
our application we want to use them together.
In this case, we have an ``Invoice`` entity with a relationship to a
non-existent object, an ``InvoiceSubjectInterface``. The goal is to get
the ``ResolveTargetEntityListener`` to replace any mention of the interface
with a real object that implements that interface.
Set up
------
We're going to use the following basic entities (which are incomplete
for brevity) to explain how to set up and use the RTEL.
A Customer entity
.. code-block:: php
// src/Acme/AppModule/Entity/Customer.php
namespace Acme\AppModule\Entity;
use Doctrine\ORM\Mapping as ORM;
use Acme\CustomerModule\Entity\Customer as BaseCustomer;
use Acme\InvoiceModule\Model\InvoiceSubjectInterface;
/**
* @ORM\Entity
* @ORM\Table(name="customer")
*/
class Customer extends BaseCustomer implements InvoiceSubjectInterface
{
// In our example, any methods defined in the InvoiceSubjectInterface
// are already implemented in the BaseCustomer
}
An Invoice entity
.. code-block:: php
// src/Acme/InvoiceModule/Entity/Invoice.php
namespace Acme\InvoiceModule\Entity;
use Doctrine\ORM\Mapping AS ORM;
use Acme\InvoiceModule\Model\InvoiceSubjectInterface;
/**
* Represents an Invoice.
*
* @ORM\Entity
* @ORM\Table(name="invoice")
*/
class Invoice
{
/**
* @ORM\ManyToOne(targetEntity="Acme\InvoiceModule\Model\InvoiceSubjectInterface")
* @var InvoiceSubjectInterface
*/
protected $subject;
}
An InvoiceSubjectInterface
.. code-block:: php
// src/Acme/InvoiceModule/Model/InvoiceSubjectInterface.php
namespace Acme\InvoiceModule\Model;
/**
* An interface that the invoice Subject object should implement.
* In most circumstances, only a single object should implement
* this interface as the ResolveTargetEntityListener can only
* change the target to a single object.
*/
interface InvoiceSubjectInterface
{
// List any additional methods that your InvoiceModule
// will need to access on the subject so that you can
// be sure that you have access to those methods.
/**
* @return string
*/
public function getName();
}
Next, we need to configure the listener. Add this to the area you set up Doctrine. You
must set this up in the way outlined below, otherwise you can not be guaranteed that
the targetEntity resolution will occur reliably:
.. code-block:: php
$evm = new \Doctrine\Common\EventManager;
$rtel = new \Doctrine\ORM\Tools\ResolveTargetEntityListener;
$rtel->addResolveTargetEntity('Acme\\InvoiceModule\\Model\\InvoiceSubjectInterface', 'Acme\\CustomerModule\\Entity\\Customer', array());
// Add the ResolveTargetEntityListener
$evm->addEventSubscriber($rtel);
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);
Final Thoughts
--------------
With the ``ResolveTargetEntityListener``, we are able to decouple our
bundles, keeping them usable by themselves, but still being able to
define relationships between different objects. By using this method,
I've found my bundles end up being easier to maintain independently.

View File

@@ -0,0 +1,80 @@
SQL-Table Prefixes
==================
This recipe is intended as an example of implementing a
loadClassMetadata listener to provide a Table Prefix option for
your application. The method used below is not a hack, but fully
integrates into the Doctrine system, all SQL generated will include
the appropriate table prefix.
In most circumstances it is desirable to separate different
applications into individual databases, but in certain cases, it
may be beneficial to have a table prefix for your Entities to
separate them from other vendor products in the same database.
Implementing the listener
-------------------------
The listener in this example has been set up with the
DoctrineExtensions namespace. You create this file in your
library/DoctrineExtensions directory, but will need to set up
appropriate autoloaders.
.. code-block:: php
<?php
namespace DoctrineExtensions;
use \Doctrine\ORM\Event\LoadClassMetadataEventArgs;
class TablePrefix
{
protected $prefix = '';
public function __construct($prefix)
{
$this->prefix = (string) $prefix;
}
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
{
$classMetadata = $eventArgs->getClassMetadata();
$classMetadata->setTableName($this->prefix . $classMetadata->getTableName());
foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) {
if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY) {
$mappedTableName = $classMetadata->associationMappings[$fieldName]['joinTable']['name'];
$classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix . $mappedTableName;
}
}
}
}
Telling the EntityManager about our listener
--------------------------------------------
A listener of this type must be set up before the EntityManager has
been initialised, otherwise an Entity might be created or cached
before the prefix has been set.
.. note::
If you set this listener up, be aware that you will need
to clear your caches and drop then recreate your database schema.
.. code-block:: php
<?php
// $connectionOptions and $config set earlier
$evm = new \Doctrine\Common\EventManager;
// Table Prefix
$tablePrefix = new \DoctrineExtensions\TablePrefix('prefix_');
$evm->addEventListener(\Doctrine\ORM\Events::loadClassMetadata, $tablePrefix);
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);

View File

@@ -0,0 +1,254 @@
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
listeners
Scenario / Problem
------------------
Given a Content-Management-System, we probably want to add / edit
some so-called "blocks" and "panels". What are they for?
- A block might be a registration form, some text content, a table
with information. A good example might also be a small calendar.
- A panel is by definition a block that can itself contain blocks.
A good example for a panel might be a sidebar box: You could easily
add a small calendar into it.
So, in this scenario, when building your CMS, you will surely add
lots of blocks and panels to your pages and you will find yourself
highly uncomfortable because of the following:
- Every existing page needs to know about the panels it contains -
therefore, you'll have an association to your panels. But if you've
got several types of panels - what do you do? Add an association to
every panel-type? This wouldn't be flexible. You might be tempted
to add an AbstractPanelEntity and an AbstractBlockEntity that use
class inheritance. Your page could then only confer to the
AbstractPanelType and Doctrine 2 would do the rest for you, i.e.
load the right entities. But - you'll for sure have lots of panels
and blocks, and even worse, you'd have to edit the discriminator
map *manually* every time you or another developer implements a new
block / entity. This would tear down any effort of modular
programming.
Therefore, we need something that's far more flexible.
Solution
--------
The solution itself is pretty easy. We will have one base class
that will be loaded via the page and that has specific behaviour -
a Block class might render the front-end and even the backend, for
example. Now, every block that you'll write might look different or
need different data - therefore, we'll offer an API to these
methods but internally, we use a strategy that exactly knows what
to do.
First of all, we need to make sure that we have an interface that
contains every needed action. Such actions would be rendering the
front-end or the backend, solving dependencies (blocks that are
supposed to be placed in the sidebar could refuse to be placed in
the middle of your page, for example).
Such an interface could look like this:
.. code-block:: php
<?php
/**
* This interface defines the basic actions that a block / panel needs to support.
*
* Every blockstrategy is *only* responsible for rendering a block and declaring some basic
* support, but *not* for updating its configuration etc. For this purpose, use controllers
* and models.
*/
interface BlockStrategyInterface {
/**
* This could configure your entity
*/
public function setConfig(Config\EntityConfig $config);
/**
* Returns the config this strategy is configured with.
* @return Core\Model\Config\EntityConfig
*/
public function getConfig();
/**
* Set the view object.
* @param \Zend_View_Interface $view
* @return \Zend_View_Helper_Interface
*/
public function setView(\Zend_View_Interface $view);
/**
* @return \Zend_View_Interface
*/
public function getView();
/**
* Renders this strategy. This method will be called when the user
* displays the site.
*
* @return string
*/
public function renderFrontend();
/**
* Renders the backend of this block. This method will be called when
* a user tries to reconfigure this block instance.
*
* Most of the time, this method will return / output a simple form which in turn
* calls some controllers.
*
* @return string
*/
public function renderBackend();
/**
* Returns all possible types of panels this block can be stacked onto
*
* @return array
*/
public function getRequiredPanelTypes();
/**
* Determines whether a Block is able to use a given type or not
* @param string $typeName The typename
* @return boolean
*/
public function canUsePanelType($typeName);
public function setBlockEntity(AbstractBlock $block);
public function getBlockEntity();
}
As you can see, we have a method "setBlockEntity" which ties a potential strategy to an object of type AbstractBlock. This type will simply define the basic behaviour of our blocks and could potentially look something like this:
.. code-block:: php
<?php
/**
* This is the base class for both Panels and Blocks.
* It shouldn't be extended by your own blocks - simply write a strategy!
*/
abstract class AbstractBlock {
/**
* The id of the block item instance
* This is a doctrine field, so you need to setup generation for it
* @var integer
*/
private $id;
// Add code for relation to the parent panel, configuration objects, ....
/**
* This var contains the classname of the strategy
* that is used for this blockitem. (This string (!) value will be persisted by Doctrine 2)
*
* This is a doctrine field, so make sure that you use an @column annotation or setup your
* yaml or xml files correctly
* @var string
*/
protected $strategyClassName;
/**
* This var contains an instance of $this->blockStrategy. Will not be persisted by Doctrine 2.
*
* @var BlockStrategyInterface
*/
protected $strategyInstance;
/**
* Returns the strategy that is used for this blockitem.
*
* The strategy itself defines how this block can be rendered etc.
*
* @return string
*/
public function getStrategyClassName() {
return $this->strategyClassName;
}
/**
* Returns the instantiated strategy
*
* @return BlockStrategyInterface
*/
public function getStrategyInstance() {
return $this->strategyInstance;
}
/**
* Sets the strategy this block / panel should work as. Make sure that you've used
* this method before persisting the block!
*
* @param BlockStrategyInterface $strategy
*/
public function setStrategy(BlockStrategyInterface $strategy) {
$this->strategyInstance = $strategy;
$this->strategyClassName = get_class($strategy);
$strategy->setBlockEntity($this);
}
Now, the important point is that $strategyClassName is a Doctrine 2
field, i.e. Doctrine will persist this value. This is only the
class name of your strategy and not an instance!
Finishing your strategy pattern, we hook into the Doctrine postLoad
event and check whether a block has been loaded. If so, you will
initialize it - i.e. get the strategies classname, create an
instance of it and set it via setStrategyBlock().
This might look like this:
.. code-block:: php
<?php
use \Doctrine\ORM,
\Doctrine\Common;
/**
* The BlockStrategyEventListener will initialize a strategy after the
* block itself was loaded.
*/
class BlockStrategyEventListener implements Common\EventSubscriber {
protected $view;
public function __construct(\Zend_View_Interface $view) {
$this->view = $view;
}
public function getSubscribedEvents() {
return array(ORM\Events::postLoad);
}
public function postLoad(ORM\Event\LifecycleEventArgs $args) {
$blockItem = $args->getEntity();
// Both blocks and panels are instances of Block\AbstractBlock
if ($blockItem instanceof Block\AbstractBlock) {
$strategy = $blockItem->getStrategyClassName();
$strategyInstance = new $strategy();
if (null !== $blockItem->getConfig()) {
$strategyInstance->setConfig($blockItem->getConfig());
}
$strategyInstance->setView($this->view);
$blockItem->setStrategy($strategyInstance);
}
}
}
In this example, even some variables are set - like a view object
or a specific configuration object.

View File

@@ -0,0 +1,137 @@
Validation of Entities
======================
.. sectionauthor:: Benjamin Eberlei <kontakt@beberlei.de>
Doctrine 2 does not ship with any internal validators, the reason
being that we think all the frameworks out there already ship with
quite decent ones that can be integrated into your Domain easily.
What we offer are hooks to execute any kind of validation.
.. note::
You don't need to validate your entities in the lifecycle
events. Its only one of many options. Of course you can also
perform validations in value setters or any other method of your
entities that are used in your code.
Entities can register lifecycle event methods with Doctrine that
are called on different occasions. For validation we would need to
hook into the events called before persisting and updating. Even
though we don't support validation out of the box, the
implementation is even simpler than in Doctrine 1 and you will get
the additional benefit of being able to re-use your validation in
any other part of your domain.
Say we have an ``Order`` with several ``OrderLine`` instances. We
never want to allow any customer to order for a larger sum than he
is allowed to:
.. code-block:: php
<?php
class Order
{
public function assertCustomerAllowedBuying()
{
$orderLimit = $this->customer->getOrderLimit();
$amount = 0;
foreach ($this->orderLines AS $line) {
$amount += $line->getAmount();
}
if ($amount > $orderLimit) {
throw new CustomerOrderLimitExceededException();
}
}
}
Now this is some pretty important piece of business logic in your
code, enforcing it at any time is important so that customers with
a unknown reputation don't owe your business too much money.
We can enforce this constraint in any of the metadata drivers.
First Annotations:
.. code-block:: php
<?php
/**
* @Entity
* @HasLifecycleCallbacks
*/
class Order
{
/**
* @PrePersist @PreUpdate
*/
public function assertCustomerAllowedBuying() {}
}
In XML Mappings:
.. code-block:: xml
<doctrine-mapping>
<entity name="Order">
<lifecycle-callbacks>
<lifecycle-callback type="prePersist" method="assertCustomerallowedBuying" />
<lifecycle-callback type="preUpdate" method="assertCustomerallowedBuying" />
</lifecycle-callbacks>
</entity>
</doctrine-mapping>
YAML needs some little change yet, to allow multiple lifecycle
events for one method, this will happen before Beta 1 though.
Now validation is performed whenever you call
``EntityManager#persist($order)`` or when you call
``EntityManager#flush()`` and an order is about to be updated. Any
Exception that happens in the lifecycle callbacks will be cached by
the EntityManager and the current transaction is rolled back.
Of course you can do any type of primitive checks, not null,
email-validation, string size, integer and date ranges in your
validation callbacks.
.. code-block:: php
<?php
class Order
{
/**
* @PrePersist @PreUpdate
*/
public function validate()
{
if (!($this->plannedShipDate instanceof DateTime)) {
throw new ValidateException();
}
if ($this->plannedShipDate->format('U') < time()) {
throw new ValidateException();
}
if ($this->customer == null) {
throw new OrderRequiresCustomerException();
}
}
}
What is nice about lifecycle events is, you can also re-use the
methods at other places in your domain, for example in combination
with your form library. Additionally there is no limitation in the
number of methods you register on one particular event, i.e. you
can register multiple methods for validation in "PrePersist" or
"PreUpdate" or mix and share them in any combinations between those
two events.
There is no limit to what you can and can't validate in
"PrePersist" and "PreUpdate" as long as you don't create new entity
instances. This was already discussed in the previous blog post on
the Versionable extension, which requires another type of event
called "onFlush".
Further readings: :doc:`Lifecycle Events <../reference/events>`

View File

@@ -0,0 +1,168 @@
Working with DateTime Instances
===============================
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.
DateTime changes are detected by Reference
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When calling ``EntityManager#flush()`` Doctrine computes the changesets of all the currently managed entities
and saves the differences to the database. In case of object properties (@Column(type="datetime") or @Column(type="object"))
these comparisons are always made **BY REFERENCE**. That means the following change will **NOT** be saved into the database:
.. code-block:: php
<?php
/** @Entity */
class Article
{
/** @Column(type="datetime") */
private $updated;
public function setUpdated()
{
// will NOT be saved in the database
$this->updated->modify("now");
}
}
The way to go would be:
.. code-block:: php
<?php
class Article
{
public function setUpdated()
{
// WILL be saved in the database
$this->updated = new \DateTime("now");
}
}
Default Timezone Gotcha
~~~~~~~~~~~~~~~~~~~~~~~
By default Doctrine assumes that you are working with a default timezone. Each DateTime instance that
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 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 you are still optimistic to manage this mess,
however let me crush your expectations fast. There is not a single database out there (supported by Doctrine 2)
that supports timezones correctly. Correctly here means that you can cover all the use-cases that
can come up with timezones. If you don't believe me you should read up on `Storing DateTime
in Databases <http://derickrethans.nl/storing-date-time-in-database.html>`_.
The problem is simple. Not a single database vendor saves the timezone, only the differences to UTC.
However with frequent daylight saving and political timezone changes you can have a UTC offset that moves
in different offset directions depending on the real location.
The solution for this dilemma is simple. Don't use timezones with DateTime and Doctrine 2. However there is a workaround
that even allows correct date-time handling with timezones:
1. Always convert any DateTime instance to UTC.
2. Only set Timezones for displaying purposes
3. Save the Timezone in the Entity for persistence.
Say we have an application for an international postal company and employees insert events regarding postal-package
around the world, in their current timezones. To determine the exact time an event occurred means to save both
the UTC time at the time of the booking and the timezone the event happened in.
.. code-block:: php
<?php
namespace DoctrineExtensions\DBAL\Types;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\ConversionException;
class UTCDateTimeType extends DateTimeType
{
static private $utc = null;
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
if ($value === null) {
return null;
}
return $value->format($platform->getDateTimeFormatString(),
(self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone('UTC'))
);
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
if ($value === null) {
return null;
}
$val = \DateTime::createFromFormat(
$platform->getDateTimeFormatString(),
$value,
(self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone('UTC'))
);
if (!$val) {
throw ConversionException::conversionFailed($value, $this->getName());
}
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 be able to transform these values
back into their real timezone you have to save the timezone in a separate field of the entity
requiring timezoned datetimes:
.. code-block:: php
<?php
namespace Shipping;
/**
* @Entity
*/
class Event
{
/** @Column(type="datetime") */
private $created;
/** @Column(type="string") */
private $timezone;
/**
* @var bool
*/
private $localized = false;
public function __construct(\DateTime $createDate)
{
$this->localized = true;
$this->created = $createDate;
$this->timezone = $createDate->getTimeZone()->getName();
}
public function getCreated()
{
if (!$this->localized) {
$this->created->setTimeZone(new \DateTimeZone($this->timezone));
}
return $this->created;
}
}
This snippet makes use of the previously discussed "changeset by reference only" property of
objects. That means a new DateTime will only be used during updating if the reference
changes between retrieval and flush operation. This means we can easily go and modify
the instance by setting the previous local timezone.

122
docs/en/index.rst Normal file
View File

@@ -0,0 +1,122 @@
Welcome to Doctrine 2 ORM's documentation!
==========================================
The Doctrine documentation is comprised of tutorials, a reference section and
cookbook articles that explain different parts of the Object Relational mapper.
Doctrine DBAL and Doctrine Common both have their own documentation.
Getting Help
------------
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 <http://groups.google.com/group/doctrine-user>`_
- Internet Relay Chat (IRC) in `#doctrine on Freenode <irc://irc.freenode.net/doctrine>`_
- Report a bug on `JIRA <http://www.doctrine-project.org/jira>`_.
- On `Twitter <https://twitter.com/search/%23doctrine2>`_ with ``#doctrine2``
- On `StackOverflow <http://stackoverflow.com/questions/tagged/doctrine2>`_
If you need more structure over the different topics you can browse the :doc:`table
of contents <toc>`.
Getting Started
---------------
* **Tutorial**:
:doc:`Getting Started with Doctrine <tutorials/getting-started>`
* **Setup**:
:doc:`Installation & Configuration <reference/configuration>`
Mapping Objects onto a Database
-------------------------------
* **Mapping**:
:doc:`Objects <reference/basic-mapping>` |
:doc:`Associations <reference/association-mapping>` |
:doc:`Inheritance <reference/inheritance-mapping>`
* **Drivers**:
:doc:`Docblock Annotations <reference/annotations-reference>` |
:doc:`XML <reference/xml-mapping>` |
:doc:`YAML <reference/yaml-mapping>` |
:doc:`PHP <reference/php-mapping>`
Working with Objects
--------------------
* **Basic Reference**:
:doc:`Entities <reference/working-with-objects>` |
:doc:`Associations <reference/working-with-associations>` |
:doc:`Events <reference/events>`
* **Query Reference**:
:doc:`DQL <reference/dql-doctrine-query-language>` |
:doc:`QueryBuilder <reference/query-builder>` |
:doc:`Native SQL <reference/native-sql>`
* **Internals**:
:doc:`Internals explained <reference/unitofwork>` |
:doc:`Associations <reference/unitofwork-associations>`
Advanced Topics
---------------
* :doc:`Architecture <reference/architecture>`
* :doc:`Advanced Configuration <reference/advanced-configuration>`
* :doc:`Limitations and knowns issues <reference/limitations-and-known-issues>`
* :doc:`Commandline Tools <reference/tools>`
* :doc:`Transactions and Concurrency <reference/transactions-and-concurrency>`
* :doc:`Filters <reference/filters>`
* :doc:`NamingStrategy <reference/namingstrategy>`
* :doc:`Improving Performance <reference/improving-performance>`
* :doc:`Caching <reference/caching>`
* :doc:`Partial Objects <reference/partial-objects>`
* :doc:`Change Tracking Policies <reference/change-tracking-policies>`
* :doc:`Best Practices <reference/best-practices>`
* :doc:`Metadata Drivers <reference/metadata-drivers>`
Tutorials
---------
* :doc:`Indexed associations <tutorials/working-with-indexed-associations>`
* :doc:`Extra Lazy Associations <tutorials/extra-lazy-associations>`
* :doc:`Composite Primary Keys <tutorials/composite-primary-keys>`
* :doc:`Ordered associations <tutorials/ordered-associations>`
* :doc:`Pagination <tutorials/pagination>`
* :doc:`Override Field/Association Mappings In Subclasses <tutorials/override-field-association-mappings-in-subclasses>`
Cookbook
--------
* **Patterns**:
:doc:`Aggregate Fields <cookbook/aggregate-fields>` |
:doc:`Decorator Pattern <cookbook/decorator-pattern>` |
:doc:`Strategy Pattern <cookbook/strategy-cookbook-introduction>`
* **DQL Extension Points**:
:doc:`DQL Custom Walkers <cookbook/dql-custom-walkers>` |
:doc:`DQL User-Defined-Functions <cookbook/dql-user-defined-functions>`
* **Implementation**:
:doc:`Array Access <cookbook/implementing-arrayaccess-for-domain-objects>` |
:doc:`Notify ChangeTracking Example <cookbook/implementing-the-notify-changetracking-policy>` |
:doc:`Using Wakeup Or Clone <cookbook/implementing-wakeup-or-clone>` |
:doc:`Working with DateTime <cookbook/working-with-datetime>` |
:doc:`Validation <cookbook/validation-of-entities>` |
:doc:`Entities in the Session <cookbook/entities-in-session>` |
:doc:`Keeping your Modules independent <cookbook/resolve-target-entity-listener>`
* **Integration into Frameworks/Libraries**
:doc:`CodeIgniter <cookbook/integrating-with-codeigniter>`
* **Hidden Gems**
:doc:`Prefixing Table Name <cookbook/sql-table-prefixes>`
* **Custom Datatypes**
:doc:`MySQL Enums <cookbook/mysql-enums>`
:doc:`Advanced Field Value Conversion <cookbook/advanced-field-value-conversion-using-custom-mapping-types>`

113
docs/en/make.bat Normal file
View File

@@ -0,0 +1,113 @@
@ECHO OFF
REM Command file for Sphinx documentation
set SPHINXBUILD=sphinx-build
set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^<target^>` where ^<target^> is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. changes to make an overview over all changed/added/deprecated items
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Doctrine2ORM.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Doctrine2ORM.ghc
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
:end

View File

@@ -0,0 +1,432 @@
Advanced Configuration
======================
The configuration of the EntityManager requires a
``Doctrine\ORM\Configuration`` instance as well as some database
connection parameters. This example shows all the potential
steps of configuration.
.. code-block:: php
<?php
use Doctrine\ORM\EntityManager,
Doctrine\ORM\Configuration;
// ...
if ($applicationMode == "development") {
$cache = new \Doctrine\Common\Cache\ArrayCache;
} else {
$cache = new \Doctrine\Common\Cache\ApcCache;
}
$config = new Configuration;
$config->setMetadataCacheImpl($cache);
$driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
$config->setMetadataDriverImpl($driverImpl);
$config->setQueryCacheImpl($cache);
$config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies');
$config->setProxyNamespace('MyProject\Proxies');
if ($applicationMode == "development") {
$config->setAutoGenerateProxyClasses(true);
} else {
$config->setAutoGenerateProxyClasses(false);
}
$connectionOptions = array(
'driver' => 'pdo_sqlite',
'path' => 'database.sqlite'
);
$em = EntityManager::create($connectionOptions, $config);
.. note::
Do not use Doctrine without a metadata and query cache!
Doctrine is optimized for working with caches. The main
parts in Doctrine that are optimized for caching are the metadata
mapping information with the metadata cache and the DQL to SQL
conversions with the query cache. These 2 caches require only an
absolute minimum of memory yet they heavily improve the runtime
performance of Doctrine. The recommended cache driver to use with
Doctrine is `APC <http://www.php.net/apc>`_. APC provides you with
an opcode-cache (which is highly recommended anyway) and a very
fast in-memory cache storage that you can use for the metadata and
query caches as seen in the previous code snippet.
Configuration Options
---------------------
The following sections describe all the configuration options
available on a ``Doctrine\ORM\Configuration`` instance.
Proxy Directory (***REQUIRED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?php
$config->setProxyDir($dir);
$config->getProxyDir();
Gets or sets the directory where Doctrine generates any proxy
classes. For a detailed explanation on proxy classes and how they
are used in Doctrine, refer to the "Proxy Objects" section further
down.
Proxy Namespace (***REQUIRED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?php
$config->setProxyNamespace($namespace);
$config->getProxyNamespace();
Gets or sets the namespace to use for generated proxy classes. For
a detailed explanation on proxy classes and how they are used in
Doctrine, refer to the "Proxy Objects" section further down.
Metadata Driver (***REQUIRED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?php
$config->setMetadataDriverImpl($driver);
$config->getMetadataDriverImpl();
Gets or sets the metadata driver implementation that is used by
Doctrine to acquire the object-relational metadata for your
classes.
There are currently 4 available implementations:
- ``Doctrine\ORM\Mapping\Driver\AnnotationDriver``
- ``Doctrine\ORM\Mapping\Driver\XmlDriver``
- ``Doctrine\ORM\Mapping\Driver\YamlDriver``
- ``Doctrine\ORM\Mapping\Driver\DriverChain``
Throughout the most part of this manual the AnnotationDriver is
used in the examples. For information on the usage of the XmlDriver
or YamlDriver please refer to the dedicated chapters
``XML Mapping`` and ``YAML Mapping``.
The annotation driver can be configured with a factory method on
the ``Doctrine\ORM\Configuration``:
.. code-block:: php
<?php
$driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
$config->setMetadataDriverImpl($driverImpl);
The path information to the entities is required for the annotation
driver, because otherwise mass-operations on all entities through
the console could not work correctly. All of metadata drivers
accept either a single directory as a string or an array of
directories. With this feature a single driver can support multiple
directories of Entities.
Metadata Cache (***RECOMMENDED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?php
$config->setMetadataCacheImpl($cache);
$config->getMetadataCacheImpl();
Gets or sets the cache implementation to use for caching metadata
information, that is, all the information you supply via
annotations, xml or yaml, so that they do not need to be parsed and
loaded from scratch on every single request which is a waste of
resources. The cache implementation must implement the
``Doctrine\Common\Cache\Cache`` interface.
Usage of a metadata cache is highly recommended.
The recommended implementations for production are:
- ``Doctrine\Common\Cache\ApcCache``
- ``Doctrine\Common\Cache\MemcacheCache``
- ``Doctrine\Common\Cache\XcacheCache``
- ``Doctrine\Common\Cache\RedisCache``
For development you should use the
``Doctrine\Common\Cache\ArrayCache`` which only caches data on a
per-request basis.
Query Cache (***RECOMMENDED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?php
$config->setQueryCacheImpl($cache);
$config->getQueryCacheImpl();
Gets or sets the cache implementation to use for caching DQL
queries, that is, the result of a DQL parsing process that includes
the final SQL as well as meta information about how to process the
SQL result set of a query. Note that the query cache does not
affect query results. You do not get stale data. This is a pure
optimization cache without any negative side-effects (except some
minimal memory usage in your cache).
Usage of a query cache is highly recommended.
The recommended implementations for production are:
- ``Doctrine\Common\Cache\ApcCache``
- ``Doctrine\Common\Cache\MemcacheCache``
- ``Doctrine\Common\Cache\XcacheCache``
- ``Doctrine\Common\Cache\RedisCache``
For development you should use the
``Doctrine\Common\Cache\ArrayCache`` which only caches data on a
per-request basis.
SQL Logger (***Optional***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?php
$config->setSQLLogger($logger);
$config->getSQLLogger();
Gets or sets the logger to use for logging all SQL statements
executed by Doctrine. The logger class must implement the
``Doctrine\DBAL\Logging\SQLLogger`` interface. A simple default
implementation that logs to the standard output using ``echo`` and
``var_dump`` can be found at
``Doctrine\DBAL\Logging\EchoSQLLogger``.
Auto-generating Proxy Classes (***OPTIONAL***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
<?php
$config->setAutoGenerateProxyClasses($bool);
$config->getAutoGenerateProxyClasses();
Gets or sets whether proxy classes should be generated
automatically at runtime by Doctrine. If set to ``FALSE``, proxy
classes must be generated manually through the doctrine command
line task ``generate-proxies``. The strongly recommended value for
a production environment is ``FALSE``.
Development vs Production Configuration
---------------------------------------
You should code your Doctrine2 bootstrapping with two different
runtime models in mind. There are some serious benefits of using
APC or Memcache in production. In development however this will
frequently give you fatal errors, when you change your entities and
the cache still keeps the outdated metadata. That is why we
recommend the ``ArrayCache`` for development.
Furthermore you should have the Auto-generating Proxy Classes
option to true in development and to false in production. If this
option is set to ``TRUE`` it can seriously hurt your script
performance if several proxy classes are re-generated during script
execution. Filesystem calls of that magnitude can even slower than
all the database queries Doctrine issues. Additionally writing a
proxy sets an exclusive file lock which can cause serious
performance bottlenecks in systems with regular concurrent
requests.
Connection Options
------------------
The ``$connectionOptions`` passed as the first argument to
``EntityManager::create()`` has to be either an array or an
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 <./../../../../../dbal/2.0/docs/reference/configuration/en>`_.
Proxy Objects
-------------
A proxy object is an object that is put in place or used instead of
the "real" object. A proxy object can add behavior to the object
being proxied without that object being aware of it. In Doctrine 2,
proxy objects are used to realize several features but mainly for
transparent lazy-loading.
Proxy objects with their lazy-loading facilities help to keep the
subset of objects that are already in memory connected to the rest
of the objects. This is an essential property as without it there
would always be fragile partial objects at the outer edges of your
object graph.
Doctrine 2 implements a variant of the proxy pattern where it
generates classes that extend your entity classes and adds
lazy-loading capabilities to them. Doctrine can then give you an
instance of such a proxy class whenever you request an object of
the class being proxied. This happens in two situations:
Reference Proxies
~~~~~~~~~~~~~~~~~
The method ``EntityManager#getReference($entityName, $identifier)``
lets you obtain a reference to an entity for which the identifier
is known, without loading that entity from the database. This is
useful, for example, as a performance enhancement, when you want to
establish an association to an entity for which you have the
identifier. You could simply do this:
.. code-block:: php
<?php
// $em instanceof EntityManager, $cart instanceof MyProject\Model\Cart
// $itemId comes from somewhere, probably a request parameter
$item = $em->getReference('MyProject\Model\Item', $itemId);
$cart->addItem($item);
Here, we added an Item to a Cart without loading the Item from the
database. If you invoke any method on the Item instance, it would
fully initialize its state transparently from the database. Here
$item is actually an instance of the proxy class that was generated
for the Item class but your code does not need to care. In fact it
**should not care**. Proxy objects should be transparent to your
code.
Association proxies
~~~~~~~~~~~~~~~~~~~
The second most important situation where Doctrine uses proxy
objects is when querying for objects. Whenever you query for an
object that has a single-valued association to another object that
is configured LAZY, without joining that association in the same
query, Doctrine puts proxy objects in place where normally the
associated object would be. Just like other proxies it will
transparently initialize itself on first access.
.. note::
Joining an association in a DQL or native query
essentially means eager loading of that association in that query.
This will override the 'fetch' option specified in the mapping for
that association, but only for that query.
Generating Proxy classes
~~~~~~~~~~~~~~~~~~~~~~~~
Proxy classes can either be generated manually through the Doctrine
Console or automatically by Doctrine. The configuration option that
controls this behavior is:
.. code-block:: php
<?php
$config->setAutoGenerateProxyClasses($bool);
$config->getAutoGenerateProxyClasses();
The default value is ``TRUE`` for convenient development. However,
this setting is not optimal for performance and therefore not
recommended for a production environment. To eliminate the overhead
of proxy class generation during runtime, set this configuration
option to ``FALSE``. When you do this in a development environment,
note that you may get class/file not found errors if certain proxy
classes are not available or failing lazy-loads if new methods were
added to the entity class that are not yet in the proxy class. In
such a case, simply use the Doctrine Console to (re)generate the
proxy classes like so:
.. code-block:: php
$ ./doctrine orm:generate-proxies
Autoloading Proxies
-------------------
When you deserialize proxy objects from the session or any other storage
it is necessary to have an autoloading mechanism in place for these classes.
For implementation reasons Proxy class names are not PSR-0 compliant. This
means that you have to register a special autoloader for these classes:
.. code-block:: php
<?php
use Doctrine\ORM\Proxy\Autoloader;
$proxyDir = "/path/to/proxies";
$proxyNamespace = "MyProxies";
Autoloader::register($proxyDir, $proxyNamespace);
If you want to execute additional logic to intercept the proxy file not found
state you can pass a closure as the third argument. It will be called with
the arguments proxydir, namespace and className when the proxy file could not
be found.
Multiple Metadata Sources
-------------------------
When using different components using Doctrine 2 you may end up
with them using two different metadata drivers, for example XML and
YAML. You can use the DriverChain Metadata implementations to
aggregate these drivers based on namespaces:
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\Driver\DriverChain;
$chain = new DriverChain();
$chain->addDriver($xmlDriver, 'Doctrine\Tests\Models\Company');
$chain->addDriver($yamlDriver, 'Doctrine\Tests\ORM\Mapping');
Based on the namespace of the entity the loading of entities is
delegated to the appropriate driver. The chain semantics come from
the fact that the driver loops through all namespaces and matches
the entity class name against the namespace using a
``strpos() === 0`` call. This means you need to order the drivers
correctly if sub-namespaces use different metadata driver
implementations.
Default Repository (***OPTIONAL***)
-----------------------------------
Specifies the FQCN of a subclass of the EntityRepository.
That will be available for all entities without a custom repository class.
.. code-block:: php
<?php
$config->setDefaultRepositoryClassName($fqcn);
$config->getDefaultRepositoryClassName();
The default value is ``Doctrine\ORM\EntityRepository``.
Any repository class must be a subclass of EntityRepository otherwise you got an ORMException
Setting up the Console
----------------------
Doctrine uses the Symfony Console component for generating the command
line interface. You can take a look at the ``vendor/bin/doctrine.php``
script and the ``Doctrine\ORM\Tools\Console\ConsoleRunner`` command
for inspiration how to setup the cli.
In general the required code looks like this:
.. code-block:: php
<?php
$cli = new Application('Doctrine Command Line Interface', \Doctrine\ORM\Version::VERSION);
$cli->setCatchExceptions(true);
$cli->setHelperSet($helperSet);
Doctrine\ORM\Tools\Console\ConsoleRunner::addCommands($cli);
$cli->run();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,197 @@
Architecture
============
This chapter gives an overview of the overall architecture,
terminology and constraints of Doctrine 2. It is recommended to
read this chapter carefully.
Using an Object-Relational Mapper
---------------------------------
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 not primarily work with objects Doctrine 2 is not suited very
well.
Requirements
------------
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
-------------------
Doctrine 2 is divided into three main packages.
- Common
- DBAL (includes Common)
- ORM (includes DBAL+Common)
This manual mainly covers the ORM package, sometimes touching parts
of the underlying DBAL and Common packages. The Doctrine code base
is split in to these packages for a few reasons and they are to...
- ...make things more maintainable and decoupled
- ...allow you to use the code in Doctrine Common without the ORM
or DBAL
- ...allow you to use the DBAL without the ORM
The Common Package
~~~~~~~~~~~~~~~~~~
The Common package contains highly reusable components that have no
dependencies beyond the package itself (and PHP, of course). The
root namespace of the Common package is ``Doctrine\Common``.
The DBAL Package
~~~~~~~~~~~~~~~~
The DBAL package contains an enhanced database abstraction layer on
top of PDO but is not strongly bound to PDO. The purpose of this
layer is to provide a single API that bridges most of the
differences between the different RDBMS vendors. The root namespace
of the DBAL package is ``Doctrine\DBAL``.
The ORM Package
~~~~~~~~~~~~~~~
The ORM package contains the object-relational mapping toolkit that
provides transparent relational persistence for plain PHP objects.
The root namespace of the ORM package is ``Doctrine\ORM``.
Terminology
-----------
Entities
~~~~~~~~
An entity is a lightweight, persistent domain object. An entity can
be any regular PHP class observing the following restrictions:
- An entity class must not be final or contain final methods.
- All persistent properties/field of any entity class should
always be private or protected, otherwise lazy-loading might not
work as expected. In case you serialize entities (for example Session)
properties should be protected (See Serialize section below).
- An entity class must not implement ``__clone`` or
:doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
- An entity class must not implement ``__wakeup`` or
:doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
Also consider implementing
`Serializable <http://de3.php.net/manual/en/class.serializable.php>`_
instead.
- Any two entity classes in a class hierarchy that inherit
directly or indirectly from one another must not have a mapped
property with the same name. That is, if B inherits from A then B
must not have a mapped field with the same name as an already
mapped field that is inherited from A.
- An entity cannot make use of func_get_args() to implement variable parameters.
Generated proxies do not support this for performance reasons and your code might
actually fail to work when violating this restriction.
Entities support inheritance, polymorphic associations, and
polymorphic queries. Both abstract and concrete classes can be
entities. Entities may extend non-entity classes as well as entity
classes, and non-entity classes may extend entity classes.
.. note::
The constructor of an entity is only ever invoked when
*you* construct a new instance with the *new* keyword. Doctrine
never calls entity constructors, thus you are free to use them as
you wish and even have it require arguments of any type.
Entity states
~~~~~~~~~~~~~
An entity instance can be characterized as being NEW, MANAGED,
DETACHED or REMOVED.
- A NEW entity instance has no persistent identity, and is not yet
associated with an EntityManager and a UnitOfWork (i.e. those just
created with the "new" operator).
- A MANAGED entity instance is an instance with a persistent
identity that is associated with an EntityManager and whose
persistence is thus managed.
- A DETACHED entity instance is an instance with a persistent
identity that is not (or no longer) associated with an
EntityManager and a UnitOfWork.
- A REMOVED entity instance is an instance with a persistent
identity, associated with an EntityManager, that will be removed
from the database upon transaction commit.
.. _architecture_persistent_fields:
Persistent fields
~~~~~~~~~~~~~~~~~
The persistent state of an entity is represented by instance
variables. An instance variable must be directly accessed only from
within the methods of the entity by the entity instance itself.
Instance variables must not be accessed by clients of the entity.
The state of the entity is available to clients only through the
entitys methods, i.e. accessor methods (getter/setter methods) or
other business methods.
Collection-valued persistent fields and properties must be defined
in terms of the ``Doctrine\Common\Collections\Collection``
interface. The collection implementation type may be used by the
application to initialize fields or properties before the entity is
made persistent. Once the entity becomes managed (or detached),
subsequent access must be through the interface type.
Serializing entities
~~~~~~~~~~~~~~~~~~~~
Serializing entities can be problematic and is not really
recommended, at least not as long as an entity instance still holds
references to proxy objects or is still managed by an
EntityManager. If you intend to serialize (and unserialize) entity
instances that still hold references to proxy objects you may run
into problems with private properties because of technical
limitations. Proxy objects implement ``__sleep`` and it is not
possible for ``__sleep`` to return names of private properties in
parent classes. On the other hand it is not a solution for proxy
objects to implement ``Serializable`` because Serializable does not
work well with any potential cyclic object references (at least we
did not find a way yet, if you did, please contact us).
The EntityManager
~~~~~~~~~~~~~~~~~
The ``EntityManager`` class is a central access point to the ORM
functionality provided by Doctrine 2. The ``EntityManager`` API is
used to manage the persistence of your objects and to query for
persistent objects.
Transactional write-behind
~~~~~~~~~~~~~~~~~~~~~~~~~~
An ``EntityManager`` and the underlying ``UnitOfWork`` employ a
strategy called "transactional write-behind" that delays the
execution of SQL statements in order to execute them in the most
efficient way and to execute them at the end of a transaction so
that all write locks are quickly released. You should see Doctrine
as a tool to synchronize your in-memory objects with the database
in well defined units of work. Work with your objects and modify
them as usual and when you're done call ``EntityManager#flush()``
to make your changes persistent.
The Unit of Work
~~~~~~~~~~~~~~~~
Internally an ``EntityManager`` uses a ``UnitOfWork``, which is a
typical implementation of the
`Unit of Work pattern <http://martinfowler.com/eaaCatalog/unitOfWork.html>`_,
to keep track of all the things that need to be done the next time
``flush`` is invoked. You usually do not directly interact with a
``UnitOfWork`` but with the ``EntityManager`` instead.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,683 @@
Basic Mapping
=============
This chapter explains the basic mapping of objects and properties.
Mapping of associations will be covered in the next chapter
"Association Mapping".
Mapping Drivers
---------------
Doctrine provides several different ways for specifying
object-relational mapping metadata:
- Docblock Annotations
- XML
- YAML
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::
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.
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::
.. code-block:: php
<?php
/** @Entity */
class MyPersistentClass
{
//...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyPersistentClass">
<!-- ... -->
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyPersistentClass:
type: entity
# ...
By default, the entity will be persisted to a table with the same
name as the class name. In order to change that, you can use the
``@Table`` annotation as follows:
.. configuration-block::
.. code-block:: php
<?php
/**
* @Entity
* @Table(name="my_persistent_class")
*/
class MyPersistentClass
{
//...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyPersistentClass" table="my_persistent_class">
<!-- ... -->
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyPersistentClass:
type: entity
table: my_persistent_class
# ...
Now instances of MyPersistentClass will be persisted into a table
named ``my_persistent_class``.
Doctrine Mapping Types
----------------------
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.
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 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.
- ``time``: Type that maps a SQL TIME to a PHP DateTime object.
- ``datetime``: Type that maps a SQL DATETIME/TIMESTAMP to a PHP
DateTime object.
- ``datetimetz``: Type that maps a SQL DATETIME/TIMESTAMP to a PHP
DateTime object with timezone.
- ``text``: Type that maps a SQL CLOB to a PHP string.
- ``object``: Type that maps a SQL CLOB to a PHP object using
``serialize()`` and ``unserialize()``
- ``array``: Type that maps a SQL CLOB to a PHP array using
``serialize()`` and ``unserialize()``
- ``simple_array``: Type that maps a SQL CLOB to a PHP array using
``implode()`` and ``explode()``, with a comma as delimiter. *IMPORTANT*
Only use this type if you are sure that your values cannot contain a ",".
- ``json_array``: Type that maps a SQL CLOB to a PHP array using
``json_encode()`` and ``json_decode()``
- ``float``: Type that maps a SQL Float (Double Precision) to a
PHP double. *IMPORTANT*: Works only with locale settings that use
decimal points as separator.
- ``guid``: Type that maps a database GUID/UUID to a PHP string. Defaults to
varchar but uses a specific type if the platform supports it.
- ``blob``: Type that maps a SQL BLOB to a PHP resource stream
.. 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.
.. warning::
All Date types assume that you are exclusively using the default timezone
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.
If you need specific timezone handling you have to handle this
in your domain, converting all the values back and forth from UTC.
There is also a :doc:`cookbook entry <../cookbook/working-with-datetime>`
on working with datetimes that gives hints for implementing
multi timezone applications.
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
/** @Entity */
class MyPersistentClass
{
/** @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;
//...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyPersistentClass">
<id name="id" type="integer" />
<field name="name" length="50" />
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyPersistentClass:
type: entity
id:
id:
type: integer
fields:
name:
length: 50
Without doing anything else, the identifier is assumed to be
manually assigned. That means your code would need to properly set
the identifier property before passing a new entity to
``EntityManager#persist($entity)``.
A common alternative strategy is to use a generated value as the
identifier. To do this, you use the ``@GeneratedValue`` annotation
like this:
.. configuration-block::
.. code-block:: php
<?php
class MyPersistentClass
{
/**
* @Id @Column(type="integer")
* @GeneratedValue
*/
private $id;
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyPersistentClass">
<id name="id" type="integer">
<generator strategy="AUTO" />
</id>
<field name="name" length="50" />
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyPersistentClass:
type: entity
id:
id:
type: integer
generator:
strategy: AUTO
fields:
name:
length: 50
This tells Doctrine to automatically generate a value for the
identifier. How this value is generated is specified by the
``strategy`` attribute, which is optional and defaults to 'AUTO'. A
value of ``AUTO`` tells Doctrine to use the generation strategy
that is preferred by the currently used database platform. See
below for details.
Identifier Generation Strategies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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 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 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 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
(AUTO\_INCREMENT), MSSQL (IDENTITY) and PostgreSQL (SERIAL).
- ``TABLE``: Tells Doctrine to use a separate table for ID
generation. This strategy provides full portability.
***This strategy is not yet implemented!***
- ``NONE``: Tells Doctrine that the identifiers are assigned (and
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.
Sequence Generator
^^^^^^^^^^^^^^^^^^
The Sequence Generator can currently be used in conjunction with
Oracle or Postgres and allows some additional configuration options
besides specifying the sequence's name:
.. configuration-block::
.. code-block:: php
<?php
class User
{
/**
* @Id
* @GeneratedValue(strategy="SEQUENCE")
* @SequenceGenerator(sequenceName="tablename_seq", initialValue=1, allocationSize=100)
*/
protected $id = null;
}
.. code-block:: xml
<doctrine-mapping>
<entity name="User">
<id name="id" type="integer">
<generator strategy="SEQUENCE" />
<sequence-generator sequence-name="tablename_seq" allocation-size="100" initial-value="1" />
</id>
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyPersistentClass:
type: entity
id:
id:
type: integer
generator:
strategy: SEQUENCE
sequenceGenerator:
sequenceName: tablename_seq
allocationSize: 100
initialValue: 1
The initial value specifies at which value the sequence should
start.
The allocationSize is a powerful feature to optimize INSERT
performance of Doctrine. The allocationSize specifies by how much
values the sequence is incremented whenever the next value is
retrieved. If this is larger than 1 (one) Doctrine can generate
identifier values for the allocationSizes amount of entities. In
the above example with ``allocationSize=100`` Doctrine 2 would only
need to access the sequence once to generate the identifiers for
100 new entities.
*The default allocationSize for a @SequenceGenerator is currently 10.*
.. caution::
The allocationSize is detected by SchemaTool and
transformed into an "INCREMENT BY " clause in the CREATE SEQUENCE
statement. For a database schema created manually (and not
SchemaTool) you have to make sure that the allocationSize
configuration option is never larger than the actual sequences
INCREMENT BY value, otherwise you may get duplicate keys.
.. note::
It is possible to use strategy="AUTO" and at the same time
specifying a @SequenceGenerator. In such a case, your custom
sequence settings are used in the case where the preferred strategy
of the underlying platform is SEQUENCE, such as for Oracle and
PostgreSQL.
Composite Keys
~~~~~~~~~~~~~~
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.
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
----------------------
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
<?php
/** @Column(name="`number`", type="integer") */
private $number;
Doctrine will then quote this column name in all SQL statements
according to the used database platform.
.. warning::
Identifier Quoting is not supported for join column
names or discriminator column names.
.. warning::
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).

View File

@@ -0,0 +1,179 @@
Batch Processing
================
This chapter shows you how to accomplish bulk inserts, updates and
deletes with Doctrine in an efficient way. The main problem with
bulk operations is usually not to run out of memory and this is
especially what the strategies presented here provide help with.
.. warning::
An ORM tool is not primarily well-suited for mass
inserts, updates or deletions. Every RDBMS has its own, most
effective way of dealing with such operations and if the options
outlined below are not sufficient for your purposes we recommend
you use the tools for your particular RDBMS for these bulk
operations.
Bulk Inserts
------------
Bulk inserts in Doctrine are best performed in batches, taking
advantage of the transactional write-behind behavior of an
``EntityManager``. The following code shows an example for
inserting 10000 objects with a batch size of 20. You may need to
experiment with the batch size to find the size that works best for
you. Larger batch sizes mean more prepared statement reuse
internally but also mean more work during ``flush``.
.. code-block:: php
<?php
$batchSize = 20;
for ($i = 1; $i <= 10000; ++$i) {
$user = new CmsUser;
$user->setStatus('user');
$user->setUsername('user' . $i);
$user->setName('Mr.Smith-' . $i);
$em->persist($user);
if (($i % $batchSize) === 0) {
$em->flush();
$em->clear(); // Detaches all objects from Doctrine!
}
}
Bulk Updates
------------
There are 2 possibilities for bulk updates with Doctrine.
DQL UPDATE
~~~~~~~~~~
The by far most efficient way for bulk updates is to use a DQL
UPDATE query. Example:
.. code-block:: php
<?php
$q = $em->createQuery('update MyProject\Model\Manager m set m.salary = m.salary * 0.9');
$numUpdated = $q->execute();
Iterating results
~~~~~~~~~~~~~~~~~
An alternative solution for bulk updates is to use the
``Query#iterate()`` facility to iterate over the query results step
by step instead of loading the whole result into memory at once.
The following example shows how to do this, combining the iteration
with the batching strategy that was already used for bulk inserts:
.. code-block:: php
<?php
$batchSize = 20;
$i = 0;
$q = $em->createQuery('select u from MyProject\Model\User u');
$iterableResult = $q->iterate();
foreach($iterableResult AS $row) {
$user = $row[0];
$user->increaseCredit();
$user->calculateNewBonuses();
if (($i % $batchSize) === 0) {
$em->flush(); // Executes all updates.
$em->clear(); // Detaches all objects from Doctrine!
}
++$i;
}
$em->flush();
.. note::
Iterating results is not possible with queries that
fetch-join a collection-valued association. The nature of such SQL
result sets is not suitable for incremental hydration.
Bulk Deletes
------------
There are two possibilities for bulk deletes with Doctrine. You can
either issue a single DQL DELETE query or you can iterate over
results removing them one at a time.
DQL DELETE
~~~~~~~~~~
The by far most efficient way for bulk deletes is to use a DQL
DELETE query.
Example:
.. code-block:: php
<?php
$q = $em->createQuery('delete from MyProject\Model\Manager m where m.salary > 100000');
$numDeleted = $q->execute();
Iterating results
~~~~~~~~~~~~~~~~~
An alternative solution for bulk deletes is to use the
``Query#iterate()`` facility to iterate over the query results step
by step instead of loading the whole result into memory at once.
The following example shows how to do this:
.. code-block:: php
<?php
$batchSize = 20;
$i = 0;
$q = $em->createQuery('select u from MyProject\Model\User u');
$iterableResult = $q->iterate();
while (($row = $iterableResult->next()) !== false) {
$em->remove($row[0]);
if (($i % $batchSize) === 0) {
$em->flush(); // Executes all deletions.
$em->clear(); // Detaches all objects from Doctrine!
}
++$i;
}
$em->flush();
.. note::
Iterating results is not possible with queries that
fetch-join a collection-valued association. The nature of such SQL
result sets is not suitable for incremental hydration.
Iterating Large Results for Data-Processing
-------------------------------------------
You can use the ``iterate()`` method just to iterate over a large
result and no UPDATE or DELETE intention. The ``IterableResult``
instance returned from ``$query->iterate()`` implements the
Iterator interface so you can process a large result without memory
problems using the following approach:
.. code-block:: php
<?php
$q = $this->_em->createQuery('select u from MyProject\Model\User u');
$iterableResult = $q->iterate();
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
$this->_em->detach($row[0]);
}
.. note::
Iterating results is not possible with queries that
fetch-join a collection-valued association. The nature of such SQL
result sets is not suitable for incremental hydration.

View File

@@ -0,0 +1,127 @@
Best Practices
==============
The best practices mentioned here that affect database
design generally refer to best practices when working with Doctrine
and do not necessarily reflect best practices for database design
in general.
Don't use public properties on entities
---------------------------------------
It is very important that you don't map public properties on
entities, but only protected or private ones. The reason for this
is simple, whenever you access a public property of a proxy object
that hasn't been initialized yet the return value will be null.
Doctrine cannot hook into this process and magically make the
entity lazy load.
This can create situations where it is very hard to debug the
current failure. We therefore urge you to map only private and
protected properties on entities and use getter methods or magic
\_\_get() to access them.
Constrain relationships as much as possible
-------------------------------------------
It is important to constrain relationships as much as possible.
This means:
- Impose a traversal direction (avoid bidirectional associations
if possible)
- Eliminate nonessential associations
This has several benefits:
- Reduced coupling in your domain model
- Simpler code in your domain model (no need to maintain
bidirectionality properly)
- Less work for Doctrine
Avoid composite keys
--------------------
Even though Doctrine fully supports composite keys it is best not
to use them if possible. Composite keys require additional work by
Doctrine and thus have a higher probability of errors.
Use events judiciously
----------------------
The event system of Doctrine is great and fast. Even though making
heavy use of events, especially lifecycle events, can have a
negative impact on the performance of your application. Thus you
should use events judiciously.
Use cascades judiciously
------------------------
Automatic cascades of the persist/remove/merge/etc. operations are
very handy but should be used wisely. Do NOT simply add all
cascades to all associations. Think about which cascades actually
do make sense for you for a particular association, given the
scenarios it is most likely used in.
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 (PHP6).
Don't use identifier quoting
----------------------------
Identifier quoting is a workaround for using reserved words that
often causes problems in edge cases. Do not use identifier quoting
and avoid using reserved words as table or column names.
Initialize collections in the constructor
-----------------------------------------
It is recommended best practice to initialize any business
collections in entities in the constructor. Example:
.. code-block:: php
<?php
namespace MyProject\Model;
use Doctrine\Common\Collections\ArrayCollection;
class User {
private $addresses;
private $articles;
public function __construct() {
$this->addresses = new ArrayCollection;
$this->articles = new ArrayCollection;
}
}
Don't map foreign keys to fields in an entity
---------------------------------------------
Foreign keys have no meaning whatsoever in an object model. Foreign
keys are how a relational database establishes relationships. Your
object model establishes relationships through object references.
Thus mapping foreign keys to object fields heavily leaks details of
the relational model into the object model, something you really
should not do.
Use explicit transaction demarcation
------------------------------------
While Doctrine will automatically wrap all DML operations in a
transaction on flush(), it is considered best practice to
explicitly set the transaction boundaries yourself. Otherwise every
single query is wrapped in a small transaction (Yes, SELECT
queries, too) since you can not talk to your database outside of a
transaction. While such short transactions for read-only (SELECT)
queries generally don't have any noticeable performance impact, it
is still preferable to use fewer, well-defined transactions that
are established through explicit transaction boundaries.

View File

@@ -0,0 +1,439 @@
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, 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\AbstractCache`` which implements
the before mentioned interface.
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.
- delete($id) - Deletes a cache entry.
Each driver extends the ``AbstractCache`` class which defines a few
abstract protected methods that each of the drivers must
implement.
- \_doFetch($id)
- \_doContains($id)
- \_doSave($id, $data, $lifeTime = false)
- \_doDelete($id)
The public methods ``fetch()``, ``contains()``, etc. utilize the
above protected methods that are implemented by the drivers. The
code is organized this way so that the protected methods in the
drivers do the raw interaction with the cache implementation and
the ``AbstractCache`` can build custom functionality on top of
these methods.
APC
~~~
In order to use the APC cache driver you must have it compiled and
enabled in your php.ini. You can read about APC
`in the PHP Documentation <http://us2.php.net/apc>`_. It will give
you a little background information about what it is and how you
can use it as well as how to install it.
Below is a simple example of how you could use the APC cache driver
by itself.
.. code-block:: php
<?php
$cacheDriver = new \Doctrine\Common\Cache\ApcCache();
$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://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.
Below is a simple example of how you could use the Memcache cache
driver by itself.
.. code-block:: php
<?php
$memcache = new Memcache();
$memcache->connect('memcache_host', 11211);
$cacheDriver = new \Doctrine\Common\Cache\MemcacheCache();
$cacheDriver->setMemcache($memcache);
$cacheDriver->save('cache_id', 'my_data');
Xcache
~~~~~~
In order to use the Xcache cache driver you must have it compiled
and enabled in your php.ini. You can read about Xcache
`here <http://xcache.lighttpd.net/>`_. It will give you a little
background information about what it is and how you can use it as
well as how to install it.
Below is a simple example of how you could use the Xcache cache
driver by itself.
.. code-block:: php
<?php
$cacheDriver = new \Doctrine\Common\Cache\XcacheCache();
$cacheDriver->save('cache_id', 'my_data');
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 is Redis
`from here <http://redis.io/>`_. Also check
`here <https://github.com/nicolasff/phpredis/>`_ for how you can use
and install Redis PHP extension.
Below is a simple example of how you could use the Redis cache
driver by itself.
.. code-block:: php
<?php
$redis = new Redis();
$redis->connect('redis_host', 6379);
$cacheDriver = new \Doctrine\Common\Cache\RedisCache();
$cacheDriver->setRedis($redis);
$cacheDriver->save('cache_id', 'my_data');
Using Cache Drivers
-------------------
In this section we'll describe how you can fully utilize the API of
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
<?php
$cacheDriver = new \Doctrine\Common\Cache\ArrayCache();
Saving
~~~~~~
To save some data to the cache driver it is as simple as using the
``save()`` method.
.. code-block:: php
<?php
$cacheDriver->save('cache_id', 'my_data');
The ``save()`` method accepts three arguments which are described
below.
- ``$id`` - The cache id
- ``$data`` - The cache entry/data.
- ``$lifeTime`` - The lifetime. If != false, sets a specific
lifetime for this cache entry (null => infinite lifeTime).
You can save any type of data whether it be a string, array,
object, etc.
.. code-block:: php
<?php
$array = array(
'key1' => 'value1',
'key2' => 'value2'
);
$cacheDriver->save('my_array', $array);
Checking
~~~~~~~~
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.
.. code-block:: php
<?php
if ($cacheDriver->contains('cache_id')) {
echo 'cache exists';
} else {
echo 'cache does not exist';
}
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 the ID of the cache entry.
.. code-block:: php
<?php
$array = $cacheDriver->fetch('my_array');
Deleting
~~~~~~~~
As you might guess, deleting is just as easy as saving, checking
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
^^^^^^^^^^^
.. code-block:: php
<?php
$cacheDriver->delete('my_array');
All
^^^
If you simply want to delete all cache entries you can do so with
the ``deleteAll()`` method.
.. code-block:: php
<?php
$deleted = $cacheDriver->deleteAll();
Namespaces
~~~~~~~~~~
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.
You can set the namespace a cache driver should use by using the
``setNamespace()`` method.
.. code-block:: php
<?php
$cacheDriver->setNamespace('my_namespace_');
Integrating with the ORM
------------------------
The Doctrine ORM package is tightly integrated with the cache
drivers to allow you to improve performance of various aspects of
Doctrine by just simply making some additional configurations and
method calls.
Query Cache
~~~~~~~~~~~
It is highly recommended that in a production environment you cache
the transformation of a DQL query to its SQL counterpart. It
doesn't make sense to do this parsing multiple times as it doesn't
change unless you alter the DQL query.
This can be done by configuring the query cache implementation to
use on your ORM configuration.
.. code-block:: php
<?php
$config = new \Doctrine\ORM\Configuration();
$config->setQueryCacheImpl(new \Doctrine\Common\Cache\ApcCache());
Result Cache
~~~~~~~~~~~~
The result cache can be used to cache the results of your queries
so that we don't have to query the database or hydrate the data
again after the first time. You just need to configure the result
cache implementation.
.. code-block:: php
<?php
$config->setResultCacheImpl(new \Doctrine\Common\Cache\ApcCache());
Now when you're executing DQL queries you can configure them to use
the result cache.
.. code-block:: php
<?php
$query = $em->createQuery('select u from \Entities\User u');
$query->useResultCache(true);
You can also configure an individual query to use a different
result cache driver.
.. code-block:: php
<?php
$query->setResultCacheDriver(new \Doctrine\Common\Cache\ApcCache());
.. note::
Setting the result cache driver on the query will
automatically enable the result cache for the query. If you want to
disable it pass false to ``useResultCache()``.
::
<?php
$query->useResultCache(false);
If you want to set the time the cache has to live you can use the
``setResultCacheLifetime()`` method.
.. code-block:: php
<?php
$query->setResultCacheLifetime(3600);
The ID used to store the result set cache is a hash which is
automatically generated for you if you don't set a custom ID
yourself with the ``setResultCacheId()`` method.
.. code-block:: php
<?php
$query->setResultCacheId('my_custom_id');
You can also set the lifetime and cache ID by passing the values as
the second and third argument to ``useResultCache()``.
.. code-block:: php
<?php
$query->useResultCache(true, 3600, 'my_custom_id');
Metadata Cache
~~~~~~~~~~~~~~
Your class metadata can be parsed from a few different sources like
YAML, XML, Annotations, etc. Instead of parsing this information on
each request we should cache it using one of the cache drivers.
Just like the query and result cache we need to configure it
first.
.. code-block:: php
<?php
$config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcCache());
Now the metadata information will only be parsed once and stored in
the cache driver.
Clearing the Cache
------------------
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 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 command.
.. code-block:: php
$ ./doctrine clear-cache
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 clear-cache --query
To clear the metadata cache use the ``--metadata`` option.
.. code-block:: php
$ ./doctrine clear-cache --metadata
To clear the result cache use the ``--result`` option.
.. code-block:: php
$ ./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.
Cache Slams
-----------
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 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.
You can read more about cache slams
`in this blog post <http://notmysock.org/blog/php/user-cache-timebomb.html>`_.

View File

@@ -0,0 +1,151 @@
Change Tracking Policies
========================
Change tracking is the process of determining what has changed in
managed entities since the last time they were synchronized with
the database.
Doctrine provides 3 different change tracking policies, each having
its particular advantages and disadvantages. The change tracking
policy can be defined on a per-class basis (or more precisely,
per-hierarchy).
Deferred Implicit
~~~~~~~~~~~~~~~~~
The deferred implicit policy is the default change tracking policy
and the most convenient one. With this policy, Doctrine detects the
changes by a property-by-property comparison at commit time and
also detects changes to entities or new entities that are
referenced by other managed entities ("persistence by
reachability"). Although the most convenient policy, it can have
negative effects on performance if you are dealing with large units
of work (see "Understanding the Unit of Work"). Since Doctrine
can't know what has changed, it needs to check all managed entities
for changes every time you invoke EntityManager#flush(), making
this operation rather costly.
Deferred Explicit
~~~~~~~~~~~~~~~~~
The deferred explicit policy is similar to the deferred implicit
policy in that it detects changes through a property-by-property
comparison at commit time. The difference is that Doctrine 2 only
considers entities that have been explicitly marked for change detection
through a call to EntityManager#persist(entity) or through a save
cascade. All other entities are skipped. This policy therefore
gives improved performance for larger units of work while
sacrificing the behavior of "automatic dirty checking".
Therefore, flush() operations are potentially cheaper with this
policy. The negative aspect this has is that if you have a rather
large application and you pass your objects through several layers
for processing purposes and business tasks you may need to track
yourself which entities have changed on the way so you can pass
them to EntityManager#persist().
This policy can be configured as follows:
.. code-block:: php
<?php
/**
* @Entity
* @ChangeTrackingPolicy("DEFERRED_EXPLICIT")
*/
class User
{
// ...
}
Notify
~~~~~~
This policy is based on the assumption that the entities notify
interested listeners of changes to their properties. For that
purpose, a class that wants to use this policy needs to implement
the ``NotifyPropertyChanged`` interface from the Doctrine
namespace. As a guideline, such an implementation can look as
follows:
.. code-block:: php
<?php
use Doctrine\Common\NotifyPropertyChanged,
Doctrine\Common\PropertyChangedListener;
/**
* @Entity
* @ChangeTrackingPolicy("NOTIFY")
*/
class MyEntity implements NotifyPropertyChanged
{
// ...
private $_listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener)
{
$this->_listeners[] = $listener;
}
}
Then, in each property setter of this class or derived classes, you
need to notify all the ``PropertyChangedListener`` instances. As an
example we add a convenience method on ``MyEntity`` that shows this
behaviour:
.. code-block:: php
<?php
// ...
class MyEntity implements NotifyPropertyChanged
{
// ...
protected function _onPropertyChanged($propName, $oldValue, $newValue)
{
if ($this->_listeners) {
foreach ($this->_listeners as $listener) {
$listener->propertyChanged($this, $propName, $oldValue, $newValue);
}
}
}
public function setData($data)
{
if ($data != $this->data) {
$this->_onPropertyChanged('data', $this->data, $data);
$this->data = $data;
}
}
}
You have to invoke ``_onPropertyChanged`` inside every method that
changes the persistent state of ``MyEntity``.
The check whether the new value is different from the old one is
not mandatory but recommended. That way you also have full control
over when you consider a property changed.
The negative point of this policy is obvious: You need implement an
interface and write some plumbing code. But also note that we tried
hard to keep this notification functionality abstract. Strictly
speaking, it has nothing to do with the persistence layer and the
Doctrine ORM or DBAL. You may find that property notification
events come in handy in many other scenarios as well. As mentioned
earlier, the ``Doctrine\Common`` namespace is not that evil and
consists solely of very small classes and interfaces that have
almost no external dependencies (none to the DBAL and none to the
ORM) and that you can easily take with you should you want to swap
out the persistence layer. This change tracking policy does not
introduce a dependency on the Doctrine DBAL/ORM or the persistence
layer.
The positive point and main advantage of this policy is its
effectiveness. It has the best performance characteristics of the 3
policies with larger units of work and a flush() operation is very
cheap when nothing has changed.

View File

@@ -0,0 +1,140 @@
Installation and Configuration
==============================
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:
::
{
"require": {
"doctrine/orm": "*"
}
}
Then call ``composer install`` from your command line. If you don't know
how Composer works, check out their `Getting Started
<http://getcomposer.org/doc/00-intro.md>`_ to set up.
Class loading
-------------
Autoloading is taken care of by Composer. You just have to include the composer autoload file in your project:
.. code-block:: php
<?php
// bootstrap.php
// Include Composer Autoload (relative to project root).
require_once "vendor/autoload.php";
Obtaining an EntityManager
--------------------------
Once you have prepared the class loading, you acquire an
*EntityManager* instance. The EntityManager class is the primary
access point to ORM functionality provided by Doctrine.
.. code-block:: php
<?php
// bootstrap.php
require_once "vendor/autoload.php";
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;
$paths = array("/path/to/entities-or-mapping-files");
$isDevMode = false;
// the connection configuration
$dbParams = array(
'driver' => 'pdo_mysql',
'user' => 'root',
'password' => '',
'dbname' => 'foo',
);
$config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);
Or if you prefer XML:
.. code-block:: php
<?php
$config = Setup::createXMLMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);
Or if you prefer YAML:
.. code-block:: php
<?php
$config = Setup::createYAMLMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);
Inside the ``Setup`` methods several assumptions are made:
- If `$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.
.. note::
You can learn more about the database connection configuration in the
`Doctrine DBAL connection configuration reference <http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html>`_.
Setting up the Commandline Tool
-------------------------------
Doctrine ships with a number of command line tools that are very helpful
during development. You can call this command from the Composer binary
directory:
.. code-block::
$ php vendor/bin/doctrine
You need to register your applications EntityManager to the console tool
to make use of the tasks by creating a ``cli-config.php`` file with the
following content:
On Doctrine 2.4 and above:
.. code-block:: php
<?php
use Doctrine\ORM\Tools\Console\ConsoleRunner;
// replace with file to your own project bootstrap
require_once 'bootstrap.php';
// replace with mechanism to retrieve EntityManager in your app
$entityManager = GetEntityManager();
return ConsoleRunner::createHelperSet($entityManager);
On Doctrine 2.3 and below:
.. code-block:: php
<?php
// cli-config.php
require_once 'my_bootstrap.php';
// Any way to access the EntityManager from your application
$em = GetMyEntityManager();
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()),
'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em)
));

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,955 @@
Events
======
Doctrine 2 features a lightweight event system that is part of the
Common package. Doctrine uses it to dispatch system events, mainly
:ref:`lifecycle events <reference-events-lifecycle-events>`.
You can also use it for your own custom events.
The Event System
----------------
The event system is controlled by the ``EventManager``. It is the
central point of Doctrine's event listener system. Listeners are
registered on the manager and events are dispatched through the
manager.
.. code-block:: php
<?php
$evm = new EventManager();
Now we can add some event listeners to the ``$evm``. Let's create a
``EventTest`` class to play around with.
.. code-block:: php
<?php
class EventTest
{
const preFoo = 'preFoo';
const postFoo = 'postFoo';
private $_evm;
public $preFooInvoked = false;
public $postFooInvoked = false;
public function __construct($evm)
{
$evm->addEventListener(array(self::preFoo, self::postFoo), $this);
}
public function preFoo(EventArgs $e)
{
$this->preFooInvoked = true;
}
public function postFoo(EventArgs $e)
{
$this->postFooInvoked = true;
}
}
// Create a new instance
$test = new EventTest($evm);
Events can be dispatched by using the ``dispatchEvent()`` method.
.. code-block:: php
<?php
$evm->dispatchEvent(EventTest::preFoo);
$evm->dispatchEvent(EventTest::postFoo);
You can easily remove a listener with the ``removeEventListener()``
method.
.. code-block:: php
<?php
$evm->removeEventListener(array(self::preFoo, self::postFoo), $this);
The Doctrine 2 event system also has a simple concept of event
subscribers. We can define a simple ``TestEventSubscriber`` class
which implements the ``\Doctrine\Common\EventSubscriber`` interface
and implements a ``getSubscribedEvents()`` method which returns an
array of events it should be subscribed to.
.. code-block:: php
<?php
class TestEventSubscriber implements \Doctrine\Common\EventSubscriber
{
public $preFooInvoked = false;
public function preFoo()
{
$this->preFooInvoked = true;
}
public function getSubscribedEvents()
{
return array(TestEvent::preFoo);
}
}
$eventSubscriber = new TestEventSubscriber();
$evm->addEventSubscriber($eventSubscriber);
.. note::
The array to return in the ``getSubscribedEvents`` method is a simple array
with the values being the event names. The subscriber must have a method
that is named exactly like the event.
Now when you dispatch an event, any event subscribers will be
notified for that event.
.. code-block:: php
<?php
$evm->dispatchEvent(TestEvent::preFoo);
Now you can test the ``$eventSubscriber`` instance to see if the
``preFoo()`` method was invoked.
.. code-block:: php
<?php
if ($eventSubscriber->preFooInvoked) {
echo 'pre foo invoked!';
}
Naming convention
~~~~~~~~~~~~~~~~~
Events being used with the Doctrine 2 EventManager are best named
with camelcase and the value of the corresponding constant should
be the name of the constant itself, even with spelling. This has
several reasons:
- It is easy to read.
- Simplicity.
- Each method within an EventSubscriber is named after the
corresponding constant. 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
``EventTest`` above.
.. _reference-events-lifecycle-events:
Lifecycle Events
----------------
The EntityManager and UnitOfWork trigger a bunch of events during
the life-time of their registered entities.
- preRemove - The preRemove event occurs for a given entity before
the respective EntityManager remove operation for that entity is
executed. It is not called for a DQL DELETE statement.
- postRemove - The postRemove event occurs for an entity after the
entity has been deleted. It will be invoked after the database
delete operations. It is not called for a DQL DELETE statement.
- 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
- 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.
- 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
entity has been loaded into the current EntityManager from the
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).
- preFlush - The preFlush event occurs at the very beginning of a flush
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.
- postFlush - The postFlush event occurs at the end of a flush operation. This
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.
.. warning::
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.
You can access the Event constants from the ``Events`` class in the
ORM package.
.. code-block:: php
<?php
use Doctrine\ORM\Events;
echo Events::preUpdate;
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. They receives some kind of ``EventArgs``.
- Lifecycle Event Listeners and Subscribers are classes with specific callback
methods that receives some kind of ``EventArgs`` instance which
give access to the entity, EntityManager or other relevant data.
.. note::
All Lifecycle events that happen during the ``flush()`` of
an EntityManager have very specific constraints on the allowed
operations that can be executed. Please read the
:ref:`reference-events-implementing-listeners` section very carefully
to understand which operations are allowed in which lifecycle event.
Lifecycle Callbacks
-------------------
A lifecycle event is a regular event with the additional feature of
providing a mechanism to register direct callbacks inside the
corresponding entity classes that are executed when the lifecycle
event occurs.
.. code-block:: php
<?php
/** @Entity @HasLifecycleCallbacks */
class User
{
// ...
/**
* @Column(type="string", length=255)
*/
public $value;
/** @Column(name="created_at", type="string", length=255) */
private $createdAt;
/** @PrePersist */
public function doStuffOnPrePersist()
{
$this->createdAt = date('Y-m-d H:i:s');
}
/** @PrePersist */
public function doOtherStuffOnPrePersist()
{
$this->value = 'changed from prePersist callback!';
}
/** @PostPersist */
public function doStuffOnPostPersist()
{
$this->value = 'changed from postPersist callback!';
}
/** @PostLoad */
public function doStuffOnPostLoad()
{
$this->value = 'changed from postLoad callback!';
}
/** @PreUpdate */
public function doStuffOnPreUpdate()
{
$this->value = 'changed from preUpdate callback!';
}
}
Note that when using annotations you have to apply the
@HasLifecycleCallbacks marker annotation on the entity class.
If you want to register lifecycle callbacks from YAML or XML you
can do it with the following.
.. code-block:: yaml
User:
type: entity
fields:
# ...
name:
type: string(50)
lifecycleCallbacks:
prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ]
postPersist: [ doStuffOnPostPersist ]
XML would look something like this:
.. code-block:: xml
<?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
/Users/robo/dev/php/Doctrine/doctrine-mapping.xsd">
<entity name="User">
<lifecycle-callbacks>
<lifecycle-callback type="prePersist" method="doStuffOnPrePersist"/>
<lifecycle-callback type="postPersist" method="doStuffOnPostPersist"/>
</lifecycle-callbacks>
</entity>
</doctrine-mapping>
You just need to make sure a public ``doStuffOnPrePersist()`` and
``doStuffOnPostPersist()`` method is defined on your ``User``
model.
.. code-block:: php
<?php
// ...
class User
{
// ...
public function doStuffOnPrePersist()
{
// ...
}
public function doStuffOnPostPersist()
{
// ...
}
}
The ``key`` of the lifecycleCallbacks is the name of the method and
the value is the event type. The allowed event types are the ones
listed in the previous Lifecycle Events section.
Lifecycle Callbacks Event Argument
-----------------------------------
.. versionadded:: 2.4
Since 2.4 the triggered event is given to the lifecycle-callback.
With the additional argument you have access to the
``EntityManager`` and ``UnitOfWork`` APIs inside these callback methods.
.. code-block:: php
<?php
// ...
class User
{
public function preUpdate(PreUpdateEventArgs $event)
{
if ($event->hasChangedField('username')) {
// Do something when the username is changed.
}
}
}
Listening and subscribing to Lifecycle Events
---------------------------------------------
Lifecycle event listeners are much more powerful than the simple
lifecycle callbacks that are defined on the entity classes. They
allow to implement re-usable behaviors between different entity
classes, yet require much more detailed knowledge about the inner
workings of the EntityManager and UnitOfWork. Please read the
*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
public methods that expect the relevant arguments.
A lifecycle event listener looks like the following:
.. code-block:: php
<?php
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
class MyEventListener
{
public function preUpdate(LifecycleEventArgs $args)
{
$entity = $args->getObject();
$entityManager = $args->getObjectManager();
// perhaps you only want to act on some "Product" entity
if ($entity instanceof Product) {
// do something with the Product
}
}
}
A lifecycle event subscriber may looks like this:
.. code-block:: php
<?php
use Doctrine\ORM\Events;
use Doctrine\Common\EventSubscriber;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
class MyEventSubscriber implements EventSubscriber
{
public function getSubscribedEvents()
{
return array(
Events::postUpdate,
);
}
public function postUpdate(LifecycleEventArgs $args)
{
$entity = $args->getObject();
$entityManager = $args->getObjectManager();
// perhaps you only want to act on some "Product" entity
if ($entity instanceof Product) {
// do something with the Product
}
}
.. note::
Lifecycle events are triggered for all entities. It is the responsibility
of the listeners and subscribers to check if the entity is of a type
it wants to handle.
To register an event listener or subscriber, you have to hook it into the
EventManager that is passed to the EntityManager factory:
.. code-block:: php
<?php
$eventManager = new EventManager();
$eventManager->addEventListener(array(Events::preUpdate), new MyEventListener());
$eventManager->addEventSubscriber(new MyEventSubscriber());
$entityManager = EntityManager::create($dbOpts, $config, $eventManager);
You can also retrieve the event manager instance after the
EntityManager was created:
.. code-block:: php
<?php
$entityManager->getEventManager()->addEventListener(array(Events::preUpdate), new MyEventListener());
$entityManager->getEventManager()->addEventSubscriber(new MyEventSubscriber());
.. _reference-events-implementing-listeners:
Implementing Event Listeners
----------------------------
This section explains what is and what is not allowed during
specific lifecycle events of the UnitOfWork. Although you get
passed the EntityManager in all of these events, you have to follow
these restrictions very carefully since operations in the wrong
event may produce lots of different errors, such as inconsistent
data and lost updates/persists/removes.
For the described events that are also lifecycle callback events
the restrictions apply as well, with the additional restriction
that you do not have access to the EntityManager or UnitOfWork APIs
inside these events.
prePersist
~~~~~~~~~~
There are two ways for the ``prePersist`` event to be triggered.
One is obviously when you call ``EntityManager#persist()``. The
event is also called for all cascaded associations.
There is another way for ``prePersist`` to be called, inside the
``flush()`` method when changes to associations are computed and
this association is marked as cascade persist. Any new entity found
during this operation is also persisted and ``prePersist`` called
on it. This is called "persistence by reachability".
In both cases you get passed a ``LifecycleEventArgs`` instance
which has access to the entity and the entity manager.
The following restrictions apply to ``prePersist``:
- If you are using a PrePersist Identity Generator such as
sequences the ID value will *NOT* be available within any
PrePersist events.
- Doctrine will not recognize changes made to relations in a pre
persist event called by "reachability" through a cascade persist
unless you use the internal ``UnitOfWork`` API. We do not recommend
such operations in the persistence by reachability context, so do
this at your own risk and possibly supported by unit-tests.
preRemove
~~~~~~~~~
The ``preRemove`` event is called on every entity when its passed
to the ``EntityManager#remove()`` method. It is cascaded for all
associations that are marked as cascade delete.
There are no restrictions to what methods can be called inside the
``preRemove`` event, except when the remove method itself was
called during a flush operation.
preFlush
~~~~~~~~
``preFlush`` is called at ``EntityManager#flush()`` before
anything else. ``EntityManager#flush()`` can be called safely
inside its listeners.
.. code-block:: php
<?php
use Doctrine\ORM\Event\PreFlushEventArgs;
class PreFlushExampleListener
{
public function preFlush(PreFlushEventArgs $args)
{
// ...
}
}
onFlush
~~~~~~~
OnFlush is a very powerful event. It is called inside
``EntityManager#flush()`` after the changes to all the managed
entities and their associations have been computed. This means, the
``onFlush`` event has access to the sets of:
- Entities scheduled for insert
- Entities scheduled for update
- Entities scheduled for removal
- Collections scheduled for update
- Collections scheduled for removal
To make use of the onFlush event you have to be familiar with the
internal UnitOfWork API, which grants you access to the previously
mentioned sets. See this example:
.. code-block:: php
<?php
class FlushExampleListener
{
public function onFlush(OnFlushEventArgs $eventArgs)
{
$em = $eventArgs->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityInsertions() AS $entity) {
}
foreach ($uow->getScheduledEntityUpdates() AS $entity) {
}
foreach ($uow->getScheduledEntityDeletions() AS $entity) {
}
foreach ($uow->getScheduledCollectionDeletions() AS $col) {
}
foreach ($uow->getScheduledCollectionUpdates() AS $col) {
}
}
}
The following restrictions apply to the onFlush event:
- 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 either calling
``$unitOfWork->recomputeSingleEntityChangeSet($classMetadata, $entity)``.
postFlush
~~~~~~~~~
``postFlush`` is called at the end of ``EntityManager#flush()``.
``EntityManager#flush()`` can **NOT** be called safely inside its listeners.
.. code-block:: php
<?php
use Doctrine\ORM\Event\PostFlushEventArgs;
class PostFlushExampleListener
{
public function postFlush(PostFlushEventArgs $args)
{
// ...
}
}
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.
Changes to associations of the updated entity are never allowed in
this event, since Doctrine cannot guarantee to correctly handle
referential integrity at this point of the flush operation. This
event has a powerful feature however, it is executed with a
``PreUpdateEventArgs`` instance, which contains a reference to the
computed change-set of this entity.
This means you have access to all the fields that have changed for
this entity with their old and new value. The following methods are
available on the ``PreUpdateEventArgs``:
- ``getEntity()`` to get access to the actual entity.
- ``getEntityChangeSet()`` to get a copy of the changeset array.
Changes to this returned array do not affect updating.
- ``hasChangedField($fieldName)`` to check if the given field name
of the current entity changed.
- ``getOldValue($fieldName)`` and ``getNewValue($fieldName)`` to
access the values of a field.
- ``setNewValue($fieldName, $value)`` to change the value of a
field to be updated.
A simple example for this event looks like:
.. code-block:: php
<?php
class NeverAliceOnlyBobListener
{
public function preUpdate(PreUpdateEventArgs $eventArgs)
{
if ($eventArgs->getEntity() instanceof User) {
if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue('name') == 'Alice') {
$eventArgs->setNewValue('name', 'Bob');
}
}
}
}
You could also use this listener to implement validation of all the
fields that have changed. This is more efficient than using a
lifecycle callback when there are expensive validations to call:
.. code-block:: php
<?php
class ValidCreditCardListener
{
public function preUpdate(PreUpdateEventArgs $eventArgs)
{
if ($eventArgs->getEntity() instanceof Account) {
if ($eventArgs->hasChangedField('creditCard')) {
$this->validateCreditCard($eventArgs->getNewValue('creditCard'));
}
}
}
private function validateCreditCard($no)
{
// throw an exception to interrupt flush event. Transaction will be rolled back.
}
}
Restrictions for this event:
- Changes to associations of the passed entities are not
recognized by the flush operation anymore.
- Changes to fields of the passed entities are not recognized by
the flush operation anymore, use the computed change-set passed to
the event to modify primitive field values.
- Any calls to ``EntityManager#persist()`` or
``EntityManager#remove()``, even in combination with the UnitOfWork
API are strongly discouraged and don't work as expected outside the
flush operation.
postUpdate, postRemove, postPersist
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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
directly mapped by Doctrine.
postLoad
~~~~~~~~
This event is called after an entity is constructed by the
EntityManager.
Entity listeners
----------------
.. versionadded:: 2.4
An entity listeners is a lifecycle listener classes used for an entity.
- 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::
.. code-block:: php
<?php
namespace MyProject\Entity;
/** @Entity @EntityListeners({"UserListener"}) */
class User
{
// ....
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyProject\Entity\User">
<entity-listeners>
<entity-listener class="UserListener"/>
</entity-listeners>
<!-- .... -->
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyProject\Entity\User:
type: entity
entityListeners:
UserListener:
# ....
.. _reference-entity-listeners:
Entity listeners class
~~~~~~~~~~~~~~~~~~~~~~
An ``Entity Listener`` could be any class, by default it should be a class with a no-arg constructor.
- Different from :ref:`reference-events-implementing-listeners` an ``Entity Listener`` is invoked just to the specified entity
- An entity listener method receives two arguments, the entity instance and the lifecycle event.
- A callback method could be defined by naming convention or specifying a method mapping.
- When the listener mapping is not given the parser will lookup for methods that match with the naming convention.
- When the listener mapping is given the parser won't lookup for any naming convention.
.. code-block:: php
<?php
class UserListener
{
public function preUpdate(User $user, PreUpdateEventArgs $event)
{
// Do something on pre update.
}
}
To define a specific event listener method
you should map the listener method using the event type mapping.
.. configuration-block::
.. code-block:: php
<?php
class UserListener
{
/** @PrePersist */
public function prePersistHandler(User $user, LifecycleEventArgs $event) { // ... }
/** @PostPersist */
public function postPersistHandler(User $user, LifecycleEventArgs $event) { // ... }
/** @PreUpdate */
public function preUpdateHandler(User $user, PreUpdateEventArgs $event) { // ... }
/** @PostUpdate */
public function postUpdateHandler(User $user, LifecycleEventArgs $event) { // ... }
/** @PostRemove */
public function postRemoveHandler(User $user, LifecycleEventArgs $event) { // ... }
/** @PreRemove */
public function preRemoveHandler(User $user, LifecycleEventArgs $event) { // ... }
/** @PreFlush */
public function preFlushHandler(User $user, PreFlushEventArgs $event) { // ... }
/** @PostLoad */
public function postLoadHandler(User $user, LifecycleEventArgs $event) { // ... }
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyProject\Entity\User">
<entity-listeners>
<entity-listener class="UserListener">
<lifecycle-callback type="preFlush" method="preFlushHandler"/>
<lifecycle-callback type="postLoad" method="postLoadHandler"/>
<lifecycle-callback type="postPersist" method="postPersistHandler"/>
<lifecycle-callback type="prePersist" method="prePersistHandler"/>
<lifecycle-callback type="postUpdate" method="postUpdateHandler"/>
<lifecycle-callback type="preUpdate" method="preUpdateHandler"/>
<lifecycle-callback type="postRemove" method="postRemoveHandler"/>
<lifecycle-callback type="preRemove" method="preRemoveHandler"/>
</entity-listener>
</entity-listeners>
<!-- .... -->
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyProject\Entity\User:
type: entity
entityListeners:
UserListener:
preFlush: [preFlushHandler]
postLoad: [postLoadHandler]
postPersist: [postPersistHandler]
prePersist: [prePersistHandler]
postUpdate: [postUpdateHandler]
preUpdate: [preUpdateHandler]
postRemove: [postRemoveHandler]
preRemove: [preRemoveHandler]
# ....
Entity listeners resolver
~~~~~~~~~~~~~~~~~~~~~~~~~~
Doctrine invoke the listener resolver to get the 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 :
.. code-block:: php
<?php
// User.php
/** @Entity @EntityListeners({"UserListener"}) */
class User
{
// ....
}
// UserListener.php
class UserListener
{
public function __construct(MyService $service)
{
$this->service = $service;
}
public function preUpdate(User $user, PreUpdateEventArgs $event)
{
$this->service->doSomething($user);
}
}
// register a entity listener.
$listener = $container->get('user_listener');
$em->getConfiguration()->getEntityListenerResolver()->register($listener);
Implementing your own resolver :
.. code-block:: php
<?php
class MyEntityListenerResolver extends \Doctrine\ORM\Mapping\DefaultEntityListenerResolver
{
public function __construct($container)
{
$this->container = $container;
}
public function resolve($className)
{
// resolve the service id by the given class name;
$id = 'user_listener';
return $this->container->get($id);
}
}
// configure the listener resolver.
$em->getConfiguration()->setEntityListenerResolver($container->get('my_resolver'));
Load ClassMetadata Event
------------------------
When the mapping information for an entity is read, it is populated
in to a ``ClassMetadataInfo`` instance. You can hook in to this
process and manipulate the instance.
.. code-block:: php
<?php
$test = new EventTest();
$metadataFactory = $em->getMetadataFactory();
$evm = $em->getEventManager();
$evm->addEventListener(Events::loadClassMetadata, $test);
class EventTest
{
public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs)
{
$classMetadata = $eventArgs->getClassMetadata();
$fieldMapping = array(
'fieldName' => 'about',
'type' => 'string',
'length' => 255
);
$classMetadata->mapField($fieldMapping);
}
}

224
docs/en/reference/faq.rst Normal file
View File

@@ -0,0 +1,224 @@
Frequently Asked Questions
==========================
.. note::
This FAQ is a work in progress. We will add lots of questions and not answer them right away just to remember
what is often asked. If you stumble across an unanswered question please write a mail to the mailing-list or
join the #doctrine channel on Freenode IRC.
Database Schema
---------------
How do I set the charset and collation for MySQL tables?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can't set these values inside the annotations, yml or xml mapping files. To make a database
work with the default charset and collation you should configure MySQL to use it as default charset,
or create the database with charset and collation details. This way they get inherited to all newly
created database tables and columns.
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?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Doctrine does not support to set the default values in columns through the "DEFAULT" keyword in SQL.
This is not necessary however, you can just use your class properties as default values. These are then used
upon insert:
.. code-block:: php
class User
{
const STATUS_DISABLED = 0;
const STATUS_ENABLED = 1;
private $algorithm = "sha1";
private $status = self:STATUS_DISABLED;
}
.
Mapping
-------
Why do I get exceptions about unique constraint failures during ``$em->flush()``?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Doctrine does not check if you are re-adding entities with a primary key that already exists
or adding entities to a collection twice. You have to check for both conditions yourself
in the code before calling ``$em->flush()`` if you know that unique constraint failures
can occur.
In `Symfony2 <http://www.symfony.com>`_ for example there is a Unique Entity Validator
to achieve this task.
For collections you can check with ``$collection->contains($entity)`` if an entity is already
part of this collection. For a FETCH=LAZY collection this will initialize the collection,
however for FETCH=EXTRA_LAZY this method will use SQL to determine if this entity is already
part of the collection.
Associations
------------
What is wrong when I get an InvalidArgumentException "A new entity was found through the relationship.."?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This exception is thrown during ``EntityManager#flush()`` when there exists an object in the identity map
that contains a reference to an object that Doctrine does not know about. Say for example you grab
a "User"-entity from the database with a specific id and set a completely new object into one of the associations
of the User object. If you then call ``EntityManager#flush()`` without letting Doctrine know about
this new object using ``EntityManager#persist($newObject)`` you will see this exception.
You can solve this exception by:
* Calling ``EntityManager#persist($newObject)`` on the new object
* Using cascade=persist on the association that contains the new object
How can I filter an association?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Natively you can't filter associations in 2.0 and 2.1. You should use DQL queries to query for the filtered set of entities.
I call clear() on a One-To-Many collection but the entities are not deleted
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This is an expected behavior that has to do with the inverse/owning side handling of Doctrine.
By definition a One-To-Many association is on the inverse side, that means changes to it
will not be recognized by Doctrine.
If you want to perform the equivalent of the clear operation you have to iterate the
collection and set the owning side many-to-one reference to NULL as well to detach all entities
from the collection. This will trigger the appropriate UPDATE statements on the database.
How can I add columns to a many-to-many table?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The many-to-many association is only supporting foreign keys in the table definition
To work with many-to-many tables containing extra columns you have to use the
foreign keys as primary keys feature of Doctrine introduced in version 2.1.
See :doc:`the tutorial on composite primary keys for more information<../tutorials/composite-primary-keys>`.
How can i paginate fetch-joined collections?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you are issuing a DQL statement that fetches a collection as well you cannot easily iterate
over this collection using a LIMIT statement (or vendor equivalent).
Doctrine does not offer a solution for this out of the box but there are several extensions
that do:
* `DoctrineExtensions <http://github.com/beberlei/DoctrineExtensions>`_
* `Pagerfanta <http://github.com/whiteoctober/pagerfanta>`_
Why does pagination not work correctly with fetch joins?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Pagination in Doctrine uses a LIMIT clause (or vendor equivalent) to restrict the results.
However when fetch-joining this is not returning the correct number of results since joining
with a one-to-many or many-to-many association multiplies the number of rows by the number
of associated entities.
See the previous question for a solution to this task.
Inheritance
-----------
Can I use Inheritance with Doctrine 2?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Yes, you can use Single- or Joined-Table Inheritance in Doctrine 2.
See the documentation chapter on :doc:`inheritance mapping <inheritance-mapping>` for
the details.
Why does Doctrine not create proxy objects for my inheritance hierarchy?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you set a many-to-one or one-to-one association target-entity to any parent class of
an inheritance hierarchy Doctrine does not know what PHP class the foreign is actually of.
To find this out it has to execute a SQL query to look this information up in the database.
EntityGenerator
---------------
Why does the EntityGenerator not do X?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The EntityGenerator is not a full fledged code-generator that solves all tasks. Code-Generation
is not a first-class priority in Doctrine 2 anymore (compared to Doctrine 1). The EntityGenerator
is supposed to kick-start you, but not towards 100%.
Why does the EntityGenerator not generate inheritance correctly?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Just from the details of the discriminator map the EntityGenerator cannot guess the inheritance hierarchy.
This is why the generation of inherited entities does not fully work. You have to adjust some additional
code to get this one working correctly.
Performance
-----------
Why is an extra SQL query executed every time I fetch an entity with a one-to-one relation?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If Doctrine detects that you are fetching an inverse side one-to-one association
it has to execute an additional query to load this object, because it cannot know
if there is no such object (setting null) or if it should set a proxy and which id this proxy has.
To solve this problem currently a query has to be executed to find out this information.
Doctrine Query Language
-----------------------
What is DQL?
~~~~~~~~~~~~
DQL stands for Doctrine Query Language, a query language that very much looks like SQL
but has some important benefits when using Doctrine:
- It uses class names and fields instead of tables and columns, separating concerns between backend and your object model.
- It utilizes the metadata defined to offer a range of shortcuts when writing. For example you do not have to specify the ON clause of joins, since Doctrine already knows about them.
- It adds some functionality that is related to object management and transforms them into SQL.
It also has some drawbacks of course:
- The syntax is slightly different to SQL so you have to learn and remember the differences.
- To be vendor independent it can only implement a subset of all the existing SQL dialects. Vendor specific functionality and optimizations cannot be used through DQL unless implemented by you explicitly.
- For some DQL constructs subselects are used which are known to be slow in MySQL.
Can I sort by a function (for example ORDER BY RAND()) in DQL?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
No, it is not supported to sort by function in DQL. If you need this functionality you should either
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.
A Query fails, how can I debug it?
----------------------------------
First, if you are using the QueryBuilder you can use
``$queryBuilder->getDQL()`` to get the DQL string of this query. The
corresponding SQL you can get from the Query instance by calling
``$query->getSQL()``.
.. code-block:: php
<?php
$dql = "SELECT u FROM User u";
$query = $entityManager->createQuery($dql);
var_dump($query->getSQL());
$qb = $entityManager->createQueryBuilder();
$qb->select('u')->from('User', 'u');
var_dump($qb->getDQL());

View File

@@ -0,0 +1,93 @@
Filters
=======
.. versionadded:: 2.2
Doctrine 2.2 features a filter system that allows the developer to add SQL to
the conditional clauses of queries, regardless the place where the SQL is
generated (e.g. from a DQL query, or by loading associated entities).
The filter functionality works on SQL level. Whether a SQL query is generated
in a Persister, during lazy loading, in extra lazy collections or from DQL.
Each time the system iterates over all the enabled filters, adding a new SQL
part as a filter returns.
By adding SQL to the conditional clauses of queries, the filter system filters
out rows belonging to the entities at the level of the SQL result set. This
means that the filtered entities are never hydrated (which can be expensive).
Example filter class
--------------------
Throughout this document the example ``MyLocaleFilter`` class will be used to
illustrate how the filter feature works. A filter class must extend the base
``Doctrine\ORM\Query\Filter\SQLFilter`` class and implement the ``addFilterConstraint``
method. The method receives the ``ClassMetadata`` of the filtered entity and the
table alias of the SQL table of the entity.
.. note::
In the case of joined or single table inheritance, you always get passed the ClassMetadata of the
inheritance root. This is necessary to avoid edge cases that would break the SQL when applying the filters.
Parameters for the query should be set on the filter object by
``SQLFilter#setParameter()``. Only parameters set via this function can be used
in filters. The ``SQLFilter#getParameter()`` function takes care of the
proper quoting of parameters.
.. code-block:: php
<?php
namespace Example;
use Doctrine\ORM\Mapping\ClassMetaData,
Doctrine\ORM\Query\Filter\SQLFilter;
class MyLocaleFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
{
// Check if the entity implements the LocalAware interface
if (!$targetEntity->reflClass->implementsInterface('LocaleAware')) {
return "";
}
return $targetTableAlias.'.locale = ' . $this->getParameter('locale'); // getParameter applies quoting automatically
}
}
Configuration
-------------
Filter classes are added to the configuration as following:
.. code-block:: php
<?php
$config->addFilter("locale", "\Doctrine\Tests\ORM\Functional\MyLocaleFilter");
The ``Configuration#addFilter()`` method takes a name for the filter and the name of the
class responsible for the actual filtering.
Disabling/Enabling Filters and Setting Parameters
---------------------------------------------------
Filters can be disabled and enabled via the ``FilterCollection`` which is
stored in the ``EntityManager``. The ``FilterCollection#enable($name)`` method
will retrieve the filter object. You can set the filter parameters on that
object.
.. code-block:: php
<?php
$filter = $em->getFilters()->enable("locale");
$filter->setParameter('locale', 'en');
// Disable it
$filter = $em->getFilters()->disable("locale");
.. warning::
Disabling and enabling filters has no effect on managed entities. If you
want to refresh or reload an object after having modified a filter or the
FilterCollection, then you should clear the EntityManager and re-fetch your
entities, having the new rules for filtering applied.

View File

@@ -0,0 +1,64 @@
Improving Performance
=====================
Bytecode Cache
--------------
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.
"If you care about performance and don't use a bytecode
cache then you don't really care about performance. Please get one
and start using it."
*Stas Malyshev, Core Contributor to PHP and Zend Employee*
Metadata and Query caches
-------------------------
As already mentioned earlier in the chapter about configuring
Doctrine, it is strongly discouraged to use Doctrine without a
Metadata and Query cache (preferably with APC or Memcache as the
cache driver). Operating Doctrine without these caches means
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.
Alternative Query Result Formats
--------------------------------
Make effective use of the available alternative query result
formats like nested array graphs or pure scalar results, especially
in scenarios where data is loaded for read-only purposes.
Read-Only Entities
------------------
Starting with Doctrine 2.1 you can mark entities as read only (See metadata mapping
references for details). This means that the entity marked as read only is never considered
for updates, which means when you call flush on the EntityManager these entities are skipped
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.
Extra-Lazy Collections
----------------------
If entities hold references to large collections you will get performance and memory problems initializing them.
To solve this issue you can use the EXTRA_LAZY fetch-mode feature for collections. See the :doc:`tutorial <../tutorials/extra-lazy-associations>`
for more information on how this fetch mode works.
Temporarily change fetch mode in DQL
------------------------------------
See :ref:`Doctrine Query Language chapter <dql-temporarily-change-fetch-mode>`
Apply Best Practices
--------------------
A lot of the points mentioned in the Best Practices chapter will
also positively affect the performance of Doctrine.

View File

@@ -0,0 +1,559 @@
Inheritance Mapping
===================
Mapped Superclasses
-------------------
A 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. Typically, the purpose of such a
mapped superclass is to define state and mapping information that
is common to multiple entity classes.
Mapped superclasses, just as regular, non-mapped classes, can
appear in the middle of an otherwise mapped inheritance hierarchy
(through Single Table Inheritance or Class Table Inheritance).
.. note::
A mapped superclass cannot be an entity, it is not query-able and
persistent relationships defined by a mapped superclass must be
unidirectional (with an owning side only). This means that One-To-Many
associations are not possible on a mapped superclass at all.
Furthermore Many-To-Many associations are only possible if the
mapped superclass is only used in exactly one entity at the moment.
For further support of inheritance, the single or
joined table inheritance features have to be used.
Example:
.. code-block:: php
<?php
/** @MappedSuperclass */
class MappedSuperclassBase
{
/** @Column(type="integer") */
protected $mapped1;
/** @Column(type="string") */
protected $mapped2;
/**
* @OneToOne(targetEntity="MappedSuperclassRelated1")
* @JoinColumn(name="related1_id", referencedColumnName="id")
*/
protected $mappedRelated1;
// ... more fields and methods
}
/** @Entity */
class EntitySubClass extends MappedSuperclassBase
{
/** @Id @Column(type="integer") */
private $id;
/** @Column(type="string") */
private $name;
// ... more fields and methods
}
The DDL for the corresponding database schema would look something
like this (this is for SQLite):
.. code-block:: sql
CREATE TABLE EntitySubClass (mapped1 INTEGER NOT NULL, mapped2 TEXT NOT NULL, id INTEGER NOT NULL, name TEXT NOT NULL, related1_id INTEGER DEFAULT NULL, PRIMARY KEY(id))
As you can see from this DDL snippet, there is only a single table
for the entity subclass. All the mappings from the mapped
superclass were inherited to the subclass as if they had been
defined on that class directly.
Single Table Inheritance
------------------------
`Single Table Inheritance <http://martinfowler.com/eaaCatalog/singleTableInheritance.html>`_
is an inheritance mapping strategy where all classes of a hierarchy
are mapped to a single database table. In order to distinguish
which row represents which type in the hierarchy a so-called
discriminator column is used.
Example:
.. code-block:: php
<?php
namespace MyProject\Model;
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
class Person
{
// ...
}
/**
* @Entity
*/
class Employee extends Person
{
// ...
}
Things to note:
- 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``.
- 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.
Design-time considerations
~~~~~~~~~~~~~~~~~~~~~~~~~~
This mapping approach works well when the type hierarchy is fairly
simple and stable. Adding a new type to the hierarchy and adding
fields to existing supertypes simply involves adding new columns to
the table, though in large deployments this may have an adverse
impact on the index and column layout inside the database.
Performance impact
~~~~~~~~~~~~~~~~~~
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 performant.
There is a general performance consideration with Single Table
Inheritance: If the target-entity of a many-to-one or one-to-one
association is an STI entity, it is preferable for performance reasons that it
be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
Otherwise Doctrine *CANNOT* create proxy instances
of this entity and will *ALWAYS* load the entity eagerly.
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 allows
null values. Columns that have NOT NULL constraints have to be on
the root entity of the single-table inheritance hierarchy.
Class Table Inheritance
-----------------------
`Class Table Inheritance <http://martinfowler.com/eaaCatalog/classTableInheritance.html>`_
is an inheritance mapping strategy where each class in a hierarchy
is mapped to several tables: its own table and the tables of all
parent classes. The table of a child class is linked to the table
of a parent class through a foreign key constraint. Doctrine 2
implements this strategy through the use of a discriminator column
in the topmost table of the hierarchy because this is the easiest
way to achieve polymorphic queries with Class Table Inheritance.
Example:
.. code-block:: php
<?php
namespace MyProject\Model;
/**
* @Entity
* @InheritanceType("JOINED")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
class Person
{
// ...
}
/** @Entity */
class Employee extends Person
{
// ...
}
Things to note:
- 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 which 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``.
- 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.
.. note::
When you do not use the SchemaTool to generate the
required SQL you should know that deleting a class table
inheritance makes use of the foreign key property
``ON DELETE CASCADE`` in all database implementations. A failure to
implement this yourself will lead to dead rows in the database.
Design-time considerations
~~~~~~~~~~~~~~~~~~~~~~~~~~
Introducing a new type to the hierarchy, at any level, simply
involves interjecting a new table into the schema. Subtypes of that
type will automatically join with that new type at runtime.
Similarly, modifying any entity type in the hierarchy by adding,
modifying or removing fields affects only the immediate table
mapped to that type. This mapping strategy provides the greatest
flexibility at design time, since changes to any type are always
limited to that type's dedicated table.
Performance impact
~~~~~~~~~~~~~~~~~~
This strategy inherently requires multiple JOIN operations to
perform just about any query which can have a negative impact on
performance, especially with large tables and/or large hierarchies.
When partial objects are allowed, either globally or on the
specific query, then querying for any type will not cause the
tables of subtypes to be OUTER JOINed which can increase
performance but the resulting partial objects will not fully load
themselves on access of any subtype fields, so accessing fields of
subtypes after such a query is not safe.
There is a general performance consideration with Class Table
Inheritance: If the target-entity of a many-to-one or one-to-one
association is a CTI entity, it is preferable for performance reasons that it
be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
Otherwise Doctrine *CANNOT* create proxy instances
of this entity and will *ALWAYS* load the entity eagerly.
SQL Schema considerations
~~~~~~~~~~~~~~~~~~~~~~~~~
For each entity in the Class-Table Inheritance hierarchy all the
mapped fields have to be columns on the table of this entity.
Additionally each child table has to have an id column that matches
the id column definition on the root table (except for any sequence
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.
Overrides
---------
Used to override a mapping for an entity field or relationship.
May be applied to an entity that extends a mapped superclass
to override a relationship or field mapping defined by the mapped superclass.
Association Override
~~~~~~~~~~~~~~~~~~~~
Override a mapping for an entity relationship.
Could be used by an entity that extends a mapped superclass
to override a relationship mapping defined by the mapped superclass.
Example:
.. configuration-block::
.. code-block:: php
<?php
// user mapping
namespace MyProject\Model;
/**
* @MappedSuperclass
*/
class User
{
//other fields mapping
/**
* @ManyToMany(targetEntity="Group", inversedBy="users")
* @JoinTable(name="users_groups",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
* )
*/
protected $groups;
/**
* @ManyToOne(targetEntity="Address")
* @JoinColumn(name="address_id", referencedColumnName="id")
*/
protected $address;
}
// admin mapping
namespace MyProject\Model;
/**
* @Entity
* @AssociationOverrides({
* @AssociationOverride(name="groups",
* joinTable=@JoinTable(
* name="users_admingroups",
* joinColumns=@JoinColumn(name="adminuser_id"),
* inverseJoinColumns=@JoinColumn(name="admingroup_id")
* )
* ),
* @AssociationOverride(name="address",
* joinColumns=@JoinColumn(
* name="adminaddress_id", referencedColumnName="id"
* )
* )
* })
*/
class Admin extends User
{
}
.. code-block:: xml
<!-- user mapping -->
<doctrine-mapping>
<mapped-superclass name="MyProject\Model\User">
<!-- other fields mapping -->
<many-to-many field="groups" target-entity="Group" inversed-by="users">
<cascade>
<cascade-persist/>
<cascade-merge/>
<cascade-detach/>
</cascade>
<join-table name="users_groups">
<join-columns>
<join-column name="user_id" referenced-column-name="id" />
</join-columns>
<inverse-join-columns>
<join-column name="group_id" referenced-column-name="id" />
</inverse-join-columns>
</join-table>
</many-to-many>
</mapped-superclass>
</doctrine-mapping>
<!-- admin mapping -->
<doctrine-mapping>
<entity name="MyProject\Model\Admin">
<association-overrides>
<association-override name="groups">
<join-table name="users_admingroups">
<join-columns>
<join-column name="adminuser_id"/>
</join-columns>
<inverse-join-columns>
<join-column name="admingroup_id"/>
</inverse-join-columns>
</join-table>
</association-override>
<association-override name="address">
<join-columns>
<join-column name="adminaddress_id" referenced-column-name="id"/>
</join-columns>
</association-override>
</association-overrides>
</entity>
</doctrine-mapping>
.. code-block:: yaml
# user mapping
MyProject\Model\User:
type: mappedSuperclass
# other fields mapping
manyToOne:
address:
targetEntity: Address
joinColumn:
name: address_id
referencedColumnName: id
cascade: [ persist, merge ]
manyToMany:
groups:
targetEntity: Group
joinTable:
name: users_groups
joinColumns:
user_id:
referencedColumnName: id
inverseJoinColumns:
group_id:
referencedColumnName: id
cascade: [ persist, merge, detach ]
# admin mapping
MyProject\Model\Admin:
type: entity
associationOverride:
address:
joinColumn:
adminaddress_id:
name: adminaddress_id
referencedColumnName: id
groups:
joinTable:
name: users_admingroups
joinColumns:
adminuser_id:
referencedColumnName: id
inverseJoinColumns:
admingroup_id:
referencedColumnName: id
Things to note:
- The "association override" specifies the overrides base on the property name.
- 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.
Attribute Override
~~~~~~~~~~~~~~~~~~~~
Override the mapping of a field.
Could be used by an entity that extends a mapped superclass to override a field mapping defined by the mapped superclass.
.. configuration-block::
.. code-block:: php
<?php
// user mapping
namespace MyProject\Model;
/**
* @MappedSuperclass
*/
class User
{
/** @Id @GeneratedValue @Column(type="integer", name="user_id", length=150) */
protected $id;
/** @Column(name="user_name", nullable=true, unique=false, length=250) */
protected $name;
// other fields mapping
}
// guest mapping
namespace MyProject\Model;
/**
* @Entity
* @AttributeOverrides({
* @AttributeOverride(name="id",
* column=@Column(
* name = "guest_id",
* type = "integer",
length = 140
* )
* ),
* @AttributeOverride(name="name",
* column=@Column(
* name = "guest_name",
* nullable = false,
* unique = true,
length = 240
* )
* )
* })
*/
class Guest extends User
{
}
.. code-block:: xml
<!-- user mapping -->
<doctrine-mapping>
<mapped-superclass name="MyProject\Model\User">
<id name="id" type="integer" column="user_id" length="150">
<generator strategy="AUTO"/>
</id>
<field name="name" column="user_name" type="string" length="250" nullable="true" unique="false" />
<many-to-one field="address" target-entity="Address">
<cascade>
<cascade-persist/>
<cascade-merge/>
</cascade>
<join-column name="address_id" referenced-column-name="id"/>
</many-to-one>
<!-- other fields mapping -->
</mapped-superclass>
</doctrine-mapping>
<!-- admin mapping -->
<doctrine-mapping>
<entity name="MyProject\Model\Guest">
<attribute-overrides>
<attribute-override name="id">
<field column="guest_id" length="140"/>
</attribute-override>
<attribute-override name="name">
<field column="guest_name" type="string" length="240" nullable="false" unique="true" />
</attribute-override>
</attribute-overrides>
</entity>
</doctrine-mapping>
.. code-block:: yaml
# user mapping
MyProject\Model\User:
type: mappedSuperclass
id:
id:
type: integer
column: user_id
length: 150
generator:
strategy: AUTO
fields:
name:
type: string
column: user_name
length: 250
nullable: true
unique: false
#other fields mapping
# guest mapping
MyProject\Model\Guest:
type: entity
attributeOverride:
id:
column: guest_id
type: integer
length: 140
name:
column: guest_name
type: string
length: 240
nullable: false
unique: true
Things to note:
- The "attribute override" specifies the overrides base on the property name.
- The column type *CANNOT* be changed. if the column type is not equals you got a ``MappingException``
- The override can redefine all the column except the type.

View File

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

View File

@@ -0,0 +1,189 @@
Limitations and Known Issues
============================
We try to make using Doctrine2 a very pleasant experience.
Therefore we think it is very important to be honest about the
current limitations to our users. Much like every other piece of
software Doctrine2 is not perfect and far from feature complete.
This section should give you an overview of current limitations of
Doctrine 2 as well as critical known issues that you should know
about.
Current Limitations
-------------------
There is a set of limitations that exist currently which might be
solved in the future. Any of this limitations now stated has at
least one ticket in the Tracker and is discussed for future
releases.
Join-Columns with non-primary keys
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is not possible to use join columns pointing to non-primary keys. Doctrine will think these are the primary
keys and create lazy-loading proxies with the data, which can lead to unexpected results. Doctrine can for performance
reasons not validate the correctness of this settings at runtime but only through the Validate Schema command.
Mapping Arrays to a Join Table
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Related to the previous limitation with "Foreign Keys as
Identifier" you might be interested in mapping the same table
structure as given above to an array. However this is not yet
possible either. See the following example:
.. code-block:: sql
CREATE TABLE product (
id INTEGER,
name VARCHAR,
PRIMARY KEY(id)
);
CREATE TABLE product_attributes (
product_id INTEGER,
attribute_name VARCHAR,
attribute_value VARCHAR,
PRIMARY KEY (product_id, attribute_name)
);
This schema should be mapped to a Product Entity as follows:
.. code-block:: php
class Product
{
private $id;
private $name;
private $attributes = array();
}
Where the ``attribute_name`` column contains the key and
``attribute_value`` contains the value of each array element in
``$attributes``.
The feature request for persistence of primitive value arrays
`is described in the DDC-298 ticket <http://www.doctrine-project.org/jira/browse/DDC-298>`_.
Value Objects
~~~~~~~~~~~~~
There is currently no native support value objects in Doctrine
other than for ``DateTime`` instances or if you serialize the
objects using ``serialize()/deserialize()`` which the DBAL Type
"object" supports.
The feature request for full value-object support
`is described in the DDC-93 ticket <http://www.doctrine-project.org/jira/browse/DDC-93>`_.
Cascade Merge with Bi-directional Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are two bugs now that concern the use of cascade merge in combination with bi-directional associations.
Make sure to study the behavior of cascade merge if you are using it:
- `DDC-875 <http://www.doctrine-project.org/jira/browse/DDC-875>`_ Merge can sometimes add the same entity twice into a collection
- `DDC-763 <http://www.doctrine-project.org/jira/browse/DDC-763>`_ Cascade merge on associated entities can insert too many rows through "Persistence by Reachability"
Custom Persisters
~~~~~~~~~~~~~~~~~
A Persister in Doctrine is an object that is responsible for the
hydration and write operations of an entity against the database.
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 <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
~~~~~~~~~~~~~~~~~~~~~~~~~~~
PHP Arrays are ordered hash-maps and so should be the
``Doctrine\Common\Collections\Collection`` interface. We plan to
evaluate a feature that optionally persists and hydrates the keys
of a Collection instance.
`Ticket DDC-213 <http://www.doctrine-project.org/jira/browse/DDC-213>`_
Mapping many tables to one entity
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is not possible to map several equally looking tables onto one
entity. For example if you have a production and an archive table
of a certain business concept then you cannot have both tables map
to the same entity.
Behaviors
~~~~~~~~~
Doctrine 2 will **never** include a behavior system like Doctrine 1
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/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
core functionality of Doctrine2 however, you will have to rely on
third party extensions for magical behaviors.
Nested Set
~~~~~~~~~~
NestedSet was offered as a behavior in Doctrine 1 and will not be
included in the core of Doctrine 2. However there are already two
extensions out there that offer support for Nested Set with
Doctrine 2:
- `Doctrine2 Hierarchical-Structural Behavior <http://github.com/guilhermeblanco/Doctrine2-Hierarchical-Structural-Behavior>`_
- `Doctrine2 NestedSet <http://github.com/blt04/doctrine2-nestedset>`_
Known Issues
------------
The Known Issues section describes critical/blocker bugs and other
issues that are either complicated to fix, not fixable due to
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 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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For compatibility reasons between all the supported vendors and
edge case problems Doctrine 2 does **NOT** do automatic identifier
quoting. This can lead to problems when trying to get
legacy-databases to work with Doctrine 2.
- You can quote column-names as described in the
:doc:`Basic-Mapping <basic-mapping>` section.
- You cannot quote join column names.
- You cannot use non [a-zA-Z0-9\_]+ characters, they will break
several SQL statements.
Having problems with these kind of column names? Many databases
support all CRUD operations on views that semantically map to
certain tables. You can create views for all your problematic
tables and column names to avoid the legacy quoting nightmare.
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).

View File

@@ -0,0 +1,194 @@
Metadata Drivers
================
The heart of an object relational mapper is the mapping information
that glues everything together. It instructs the EntityManager how
it should behave when dealing with the different entities.
Core Metadata Drivers
---------------------
Doctrine provides a few different ways for you to specify your
metadata:
- **XML files** (XmlDriver)
- **Class DocBlock Annotations** (AnnotationDriver)
- **YAML files** (YamlDriver)
- **PHP Code in files or static functions** (PhpDriver)
Something important to note about the above drivers is they are all
an intermediate step to the same end result. The mapping
information is populated to ``Doctrine\ORM\Mapping\ClassMetadata``
instances. So in the end, Doctrine only ever has to work with the
API of the ``ClassMetadata`` class to get mapping information for
an entity.
.. note::
The populated ``ClassMetadata`` instances are also cached
so in a production environment the parsing and populating only ever
happens once. You can configure the metadata cache implementation
using the ``setMetadataCacheImpl()`` method on the
``Doctrine\ORM\Configuration`` class:
.. code-block:: php
<?php
$em->getConfiguration()->setMetadataCacheImpl(new ApcCache());
If you want to use one of the included core metadata drivers you
just need to configure it. All the drivers are in the
``Doctrine\ORM\Mapping\Driver`` namespace:
.. code-block:: php
<?php
$driver = new \Doctrine\ORM\Mapping\Driver\XmlDriver('/path/to/mapping/files');
$em->getConfiguration()->setMetadataDriverImpl($driver);
Implementing Metadata Drivers
-----------------------------
In addition to the included metadata drivers you can very easily
implement your own. All you need to do is define a class which
implements the ``Driver`` interface:
.. code-block:: php
<?php
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
interface Driver
{
/**
* Loads the metadata for the specified class into the provided container.
*
* @param string $className
* @param ClassMetadataInfo $metadata
*/
function loadMetadataForClass($className, ClassMetadataInfo $metadata);
/**
* Gets the names of all mapped classes known to this driver.
*
* @return array The names of all mapped classes known to this driver.
*/
function getAllClassNames();
/**
* Whether the class with the specified name should have its metadata loaded.
* This is only the case if it is either mapped as an Entity or a
* MappedSuperclass.
*
* @param string $className
* @return boolean
*/
function isTransient($className);
}
If you want to write a metadata driver to parse information from
some file format we've made your life a little easier by providing
the ``AbstractFileDriver`` implementation for you to extend from:
.. code-block:: php
<?php
class MyMetadataDriver extends AbstractFileDriver
{
/**
* {@inheritdoc}
*/
protected $_fileExtension = '.dcm.ext';
/**
* {@inheritdoc}
*/
public function loadMetadataForClass($className, ClassMetadataInfo $metadata)
{
$data = $this->_loadMappingFile($file);
// populate ClassMetadataInfo instance from $data
}
/**
* {@inheritdoc}
*/
protected function _loadMappingFile($file)
{
// parse contents of $file and return php data structure
}
}
.. note::
When using the ``AbstractFileDriver`` it requires that you
only have one entity defined per file and the file named after the
class described inside where namespace separators are replaced by
periods. So if you have an entity named ``Entities\User`` and you
wanted to write a mapping file for your driver above you would need
to name the file ``Entities.User.dcm.ext`` for it to be
recognized.
Now you can use your ``MyMetadataDriver`` implementation by setting
it with the ``setMetadataDriverImpl()`` method:
.. code-block:: php
<?php
$driver = new MyMetadataDriver('/path/to/mapping/files');
$em->getConfiguration()->setMetadataDriverImpl($driver);
ClassMetadata
-------------
The last piece you need to know and understand about metadata in
Doctrine 2 is the API of the ``ClassMetadata`` classes. You need to
be familiar with them in order to implement your own drivers but
more importantly to retrieve mapping information for a certain
entity when needed.
You have all the methods you need to manually specify the mapping
information instead of using some mapping file to populate it from.
The base ``ClassMetadataInfo`` class is responsible for only data
storage and is not meant for runtime use. It does not require that
the class actually exists yet so it is useful for describing some
entity before it exists and using that information to generate for
example the entities themselves. The class ``ClassMetadata``
extends ``ClassMetadataInfo`` and adds some functionality required
for runtime usage and requires that the PHP class is present and
can be autoloaded.
You can read more about the API of the ``ClassMetadata`` classes in
the PHP Mapping chapter.
Getting ClassMetadata Instances
-------------------------------
If you want to get the ``ClassMetadata`` instance for an entity in
your project to programmatically use some mapping information to
generate some HTML or something similar you can retrieve it through
the ``ClassMetadataFactory``:
.. code-block:: php
<?php
$cmf = $em->getMetadataFactory();
$class = $cmf->getMetadataFor('MyEntityName');
Now you can learn about the entity and use the data stored in the
``ClassMetadata`` instance to get all mapped fields for example and
iterate over them:
.. code-block:: php
<?php
foreach ($class->fieldMappings as $fieldMapping) {
echo $fieldMapping['fieldName'] . "\n";
}

View File

@@ -0,0 +1,150 @@
Implementing a NamingStrategy
==============================
.. versionadded:: 2.3
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 attributes names to generate tables and columns
You can specify a different strategy by calling ``Doctrine\ORM\Configuration#setNamingStrategy()`` :
.. code-block:: php
<?php
$namingStrategy = new MyNamingStrategy();
$configuration()->setNamingStrategy($namingStrategy);
Underscore naming 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);
Then SomeEntityName will generate the table SOME_ENTITY_NAME when CASE_UPPER
or some_entity_name using CASE_LOWER is given.
Naming strategy interface
-------------------------
The interface ``Doctrine\ORM\Mapping\NamingStrategy`` allows you to specify
a "naming standard" for database tables and columns.
.. code-block:: php
<?php
/**
* Return a table name for an entity class
*
* @param string $className The fully-qualified class name
* @return string A table name
*/
function classToTableName($className);
/**
* Return a column name for a property
*
* @param string $propertyName A property
* @return string A column name
*/
function propertyToColumnName($propertyName);
/**
* Return the default reference column name
*
* @return string A column name
*/
function referenceColumnName();
/**
* Return a join column name for a property
*
* @param string $propertyName A property
* @return string A join column name
*/
function joinColumnName($propertyName);
/**
* Return a join table name
*
* @param string $sourceEntity The source entity
* @param string $targetEntity The target entity
* @param string $propertyName A property
* @return string A join table name
*/
function joinTableName($sourceEntity, $targetEntity, $propertyName = null);
/**
* Return the foreign key column name for the given parameters
*
* @param string $entityName A entity
* @param string $referencedColumnName A property
* @return string A join column name
*/
function joinKeyColumnName($entityName, $referencedColumnName = null);
Implementing a naming strategy
-------------------------------
If you have database naming standards like all tables names should be prefixed
by the application prefix, all column names should be upper case,
you can easily achieve such standards by implementing a naming strategy.
You need to implements NamingStrategy first. Following is an example
.. code-block:: php
<?php
class MyAppNamingStrategy implements NamingStrategy
{
public function classToTableName($className)
{
return 'MyApp_' . substr($className, strrpos($className, '\\') + 1);
}
public function propertyToColumnName($propertyName)
{
return $propertyName;
}
public function referenceColumnName()
{
return 'id';
}
public function joinColumnName($propertyName)
{
return $propertyName . '_' . $this->referenceColumnName();
}
public function joinTableName($sourceEntity, $targetEntity, $propertyName = null)
{
return strtolower($this->classToTableName($sourceEntity) . '_' .
$this->classToTableName($targetEntity));
}
public function joinKeyColumnName($entityName, $referencedColumnName = null)
{
return strtolower($this->classToTableName($entityName) . '_' .
($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

@@ -0,0 +1,905 @@
Native SQL
==========
With ``NativeQuery`` you can execute native SELECT SQL statements
and map the results to Doctrine entities or any other result format
supported by Doctrine.
In order to make this mapping possible, you need to describe
to Doctrine what columns in the result map to which entity property.
This description is represented by a ``ResultSetMapping`` object.
With this feature you can map arbitrary SQL code to objects, such as highly
vendor-optimized SQL or stored-procedures.
Writing ``ResultSetMapping`` from scratch is complex, but there is a convenience
wrapper around it called a ``ResultSetMappingBuilder``. It can generate
the mappings for you based on Entities and even generates the ``SELECT``
clause based on this information for you.
.. note::
If you want to execute DELETE, UPDATE or INSERT statements
the Native SQL API cannot be used and will probably throw errors.
Use ``EntityManager#getConnection()`` to access the native database
connection and call the ``executeUpdate()`` method for these
queries.
The NativeQuery class
---------------------
To create a ``NativeQuery`` you use the method
``EntityManager#createNativeQuery($sql, $resultSetMapping)``. As you can see in
the signature of this method, it expects 2 ingredients: The SQL you want to
execute and the ``ResultSetMapping`` that describes how the results will be
mapped.
Once you obtained an instance of a ``NativeQuery``, you can bind parameters to
it with the same API that ``Query`` has and execute it.
.. code-block:: php
<?php
use Doctrine\ORM\Query\ResultSetMapping;
$rsm = new ResultSetMapping();
// build rsm here
$query = $entityManager->createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
ResultSetMappingBuilder
-----------------------
An easy start into ResultSet mapping is the ``ResultSetMappingBuilder`` object.
This has several benefits:
- The builder takes care of automatically updating your ``ResultSetMapping``
when the fields or associations change on the metadata of an entity.
- You can generate the required ``SELECT`` expression for a builder
by converting it to a string.
- The API is much simpler than the usual ``ResultSetMapping`` API.
One downside is that the builder API does not yet support entities
with inheritance hierachies.
.. code-block:: php
<?php
use Doctrine\ORM\Query\ResultSetMappingBuilder;
$sql = "SELECT u.id, u.name, a.id AS address_id, a.street, a.city " .
"FROM users u INNER JOIN address a ON u.address_id = a.id";
$rsm = new ResultSetMappingBuilder($entityManager);
$rsm->addRootEntityFromClassMetadata('MyProject\User', 'u');
$rsm->addJoinedEntityFromClassMetadata('MyProject\Address', 'a', 'u', 'address', array('id' => 'address_id'));
The builder extends the ``ResultSetMapping`` class and as such has all the functionality of it as well.
..versionadded:: 2.4
Starting with Doctrine ORM 2.4 you can generate the ``SELECT`` clause
from a ``ResultSetMappingBuilder``. You can either cast the builder
object to ``(string)`` and the DQL aliases are used as SQL table aliases
or use the ``generateSelectClause($tableAliases)`` method and pass
a mapping from DQL alias (key) to SQL alias (value)
.. code-block:: php
<?php
$selectClause = $builder->generateSelectClause(array(
'u' => 't1',
'g' => 't2'
));
$sql = "SELECT " . $selectClause . " FROM users t1 JOIN groups t2 ON t1.group_id = t2.id";
The ResultSetMapping
--------------------
Understanding the ``ResultSetMapping`` is the key to using a
``NativeQuery``. A Doctrine result can contain the following
components:
- Entity results. These represent root result elements.
- Joined entity results. These represent joined entities in
associations of root entity results.
- Field results. These represent a column in the result set that
maps to a field of an entity. A field result always belongs to an
entity result or joined entity result.
- Scalar results. These represent scalar values in the result set
that will appear in each result row. Adding scalar results to a
ResultSetMapping can also cause the overall result to become
**mixed** (see DQL - Doctrine Query Language) if the same
ResultSetMapping also contains entity results.
- Meta results. These represent columns that contain
meta-information, such as foreign keys and discriminator columns.
When querying for objects (``getResult()``), all meta columns of
root entities or joined entities must be present in the SQL query
and mapped accordingly using ``ResultSetMapping#addMetaResult``.
.. note::
It might not surprise you that Doctrine uses
``ResultSetMapping`` internally when you create DQL queries. As
the query gets parsed and transformed to SQL, Doctrine fills a
``ResultSetMapping`` that describes how the results should be
processed by the hydration routines.
We will now look at each of the result types that can appear in a
ResultSetMapping in detail.
Entity results
~~~~~~~~~~~~~~
An entity result describes an entity type that appears as a root
element in the transformed result. You add an entity result through
``ResultSetMapping#addEntityResult()``. Let's take a look at the
method signature in detail:
.. code-block:: php
<?php
/**
* Adds an entity result to this ResultSetMapping.
*
* @param string $class The class name of the entity.
* @param string $alias The alias for the class. The alias must be unique among all entity
* results or joined entity results within this ResultSetMapping.
*/
public function addEntityResult($class, $alias)
The first parameter is the fully qualified name of the entity
class. The second parameter is some arbitrary alias for this entity
result that must be unique within a ``ResultSetMapping``. You use
this alias to attach field results to the entity result. It is very
similar to an identification variable that you use in DQL to alias
classes or relationships.
An entity result alone is not enough to form a valid
``ResultSetMapping``. An entity result or joined entity result
always needs a set of field results, which we will look at soon.
Joined entity results
~~~~~~~~~~~~~~~~~~~~~
A joined entity result describes an entity type that appears as a
joined relationship element in the transformed result, attached to
a (root) entity result. You add a joined entity result through
``ResultSetMapping#addJoinedEntityResult()``. Let's take a look at
the method signature in detail:
.. code-block:: php
<?php
/**
* Adds a joined entity result.
*
* @param string $class The class name of the joined entity.
* @param string $alias The unique alias to use for the joined entity.
* @param string $parentAlias The alias of the entity result that is the parent of this joined result.
* @param object $relation The association field that connects the parent entity result with the joined entity result.
*/
public function addJoinedEntityResult($class, $alias, $parentAlias, $relation)
The first parameter is the class name of the joined entity. The
second parameter is an arbitrary alias for the joined entity that
must be unique within the ``ResultSetMapping``. You use this alias
to attach field results to the entity result. The third parameter
is the alias of the entity result that is the parent type of the
joined relationship. The fourth and last parameter is the name of
the field on the parent entity result that should contain the
joined entity result.
Field results
~~~~~~~~~~~~~
A field result describes the mapping of a single column in a SQL
result set to a field in an entity. As such, field results are
inherently bound to entity results. You add a field result through
``ResultSetMapping#addFieldResult()``. Again, let's examine the
method signature in detail:
.. code-block:: php
<?php
/**
* Adds a field result that is part of an entity result or joined entity result.
*
* @param string $alias The alias of the entity result or joined entity result.
* @param string $columnName The name of the column in the SQL result set.
* @param string $fieldName The name of the field on the (joined) entity.
*/
public function addFieldResult($alias, $columnName, $fieldName)
The first parameter is the alias of the entity result to which the
field result will belong. The second parameter is the name of the
column in the SQL result set. Note that this name is case
sensitive, i.e. if you use a native query against Oracle it must be
all uppercase. The third parameter is the name of the field on the
entity result identified by ``$alias`` into which the value of the
column should be set.
Scalar results
~~~~~~~~~~~~~~
A scalar result describes the mapping of a single column in a SQL
result set to a scalar value in the Doctrine result. Scalar results
are typically used for aggregate values but any column in the SQL
result set can be mapped as a scalar value. To add a scalar result
use ``ResultSetMapping#addScalarResult()``. The method signature in
detail:
.. code-block:: php
<?php
/**
* Adds a scalar result mapping.
*
* @param string $columnName The name of the column in the SQL result set.
* @param string $alias The result alias with which the scalar result should be placed in the result structure.
*/
public function addScalarResult($columnName, $alias)
The first parameter is the name of the column in the SQL result set
and the second parameter is the result alias under which the value
of the column will be placed in the transformed Doctrine result.
Meta results
~~~~~~~~~~~~
A meta result describes a single column in a SQL result set that
is either a foreign key or a discriminator column. These columns
are essential for Doctrine to properly construct objects out of SQL
result sets. To add a column as a meta result use
``ResultSetMapping#addMetaResult()``. The method signature in
detail:
.. code-block:: php
<?php
/**
* Adds a meta column (foreign key or discriminator column) to the result set.
*
* @param string $alias
* @param string $columnAlias
* @param string $columnName
* @param boolean $isIdentifierColumn
*/
public function addMetaResult($alias, $columnAlias, $columnName, $isIdentifierColumn = false)
The first parameter is the alias of the entity result to which the
meta column belongs. A meta result column (foreign key or
discriminator column) always belongs to an entity result. The
second parameter is the column alias/name of the column in the SQL
result set and the third parameter is the column name used in the
mapping.
The fourth parameter should be set to true in case the primary key
of the entity is the foreign key you're adding.
Discriminator Column
~~~~~~~~~~~~~~~~~~~~
When joining an inheritance tree you have to give Doctrine a hint
which meta-column is the discriminator column of this tree.
.. code-block:: php
<?php
/**
* Sets a discriminator column for an entity result or joined entity result.
* The discriminator column will be used to determine the concrete class name to
* instantiate.
*
* @param string $alias The alias of the entity result or joined entity result the discriminator
* column should be used for.
* @param string $discrColumn The name of the discriminator column in the SQL result set.
*/
public function setDiscriminatorColumn($alias, $discrColumn)
Examples
~~~~~~~~
Understanding a ResultSetMapping is probably easiest through
looking at some examples.
First a basic example that describes the mapping of a single
entity.
.. code-block:: php
<?php
// Equivalent DQL query: "select u from User u where u.name=?1"
// User owns no associations.
$rsm = new ResultSetMapping;
$rsm->addEntityResult('User', 'u');
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$query = $this->_em->createNativeQuery('SELECT id, name FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
The result would look like this:
.. code-block:: php
array(
[0] => User (Object)
)
Note that this would be a partial object if the entity has more
fields than just id and name. In the example above the column and
field names are identical but that is not necessary, of course.
Also note that the query string passed to createNativeQuery is
**real native SQL**. Doctrine does not touch this SQL in any way.
In the previous basic example, a User had no relations and the
table the class is mapped to owns no foreign keys. The next example
assumes User has a unidirectional or bidirectional one-to-one
association to a CmsAddress, where the User is the owning side and
thus owns the foreign key.
.. code-block:: php
<?php
// Equivalent DQL query: "select u from User u where u.name=?1"
// User owns an association to an Address but the Address is not loaded in the query.
$rsm = new ResultSetMapping;
$rsm->addEntityResult('User', 'u');
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$rsm->addMetaResult('u', 'address_id', 'address_id');
$query = $this->_em->createNativeQuery('SELECT id, name, address_id FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
Foreign keys are used by Doctrine for lazy-loading purposes when
querying for objects. In the previous example, each user object in
the result will have a proxy (a "ghost") in place of the address
that contains the address\_id. When the ghost proxy is accessed, it
loads itself based on this key.
Consequently, associations that are *fetch-joined* do not require
the foreign keys to be present in the SQL result set, only
associations that are lazy.
.. code-block:: php
<?php
// Equivalent DQL query: "select u from User u join u.address a WHERE u.name = ?1"
// User owns association to an Address and the Address is loaded in the query.
$rsm = new ResultSetMapping;
$rsm->addEntityResult('User', 'u');
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$rsm->addJoinedEntityResult('Address' , 'a', 'u', 'address');
$rsm->addFieldResult('a', 'address_id', 'id');
$rsm->addFieldResult('a', 'street', 'street');
$rsm->addFieldResult('a', 'city', 'city');
$sql = 'SELECT u.id, u.name, a.id AS address_id, a.street, a.city FROM users u ' .
'INNER JOIN address a ON u.address_id = a.id WHERE u.name = ?';
$query = $this->_em->createNativeQuery($sql, $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
In this case the nested entity ``Address`` is registered with the
``ResultSetMapping#addJoinedEntityResult`` method, which notifies
Doctrine that this entity is not hydrated at the root level, but as
a joined entity somewhere inside the object graph. In this case we
specify the alias 'u' as third parameter and ``address`` as fourth
parameter, which means the ``Address`` is hydrated into the
``User::$address`` property.
If a fetched entity is part of a mapped hierarchy that requires a
discriminator column, this column must be present in the result set
as a meta column so that Doctrine can create the appropriate
concrete type. This is shown in the following example where we
assume that there are one or more subclasses that extend User and
either Class Table Inheritance or Single Table Inheritance is used
to map the hierarchy (both use a discriminator column).
.. code-block:: php
<?php
// Equivalent DQL query: "select u from User u where u.name=?1"
// User is a mapped base class for other classes. User owns no associations.
$rsm = new ResultSetMapping;
$rsm->addEntityResult('User', 'u');
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$rsm->addMetaResult('u', 'discr', 'discr'); // discriminator column
$rsm->setDiscriminatorColumn('u', 'discr');
$query = $this->_em->createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
Note that in the case of Class Table Inheritance, an example as
above would result in partial objects if any objects in the result
are actually a subtype of User. When using DQL, Doctrine
automatically includes the necessary joins for this mapping
strategy but with native SQL it is your responsibility.
Named Native Query
------------------
You can also map a native query using a named native query mapping.
To achieve that, you must describe the SQL resultset structure
using named native query (and sql resultset mappings if is a several resultset mappings).
Like named query, a named native query can be defined at class level or in a XML or YAML file.
A resultSetMapping parameter is defined in @NamedNativeQuery,
it represents the name of a defined @SqlResultSetMapping.
.. configuration-block::
.. code-block:: php
<?php
namespace MyProject\Model;
/**
* @NamedNativeQueries({
* @NamedNativeQuery(
* name = "fetchMultipleJoinsEntityResults",
* resultSetMapping= "mappingMultipleJoinsEntityResults",
* query = "SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM users u INNER JOIN addresses a ON u.id = a.user_id INNER JOIN phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username"
* ),
* })
* @SqlResultSetMappings({
* @SqlResultSetMapping(
* name = "mappingMultipleJoinsEntityResults",
* entities= {
* @EntityResult(
* entityClass = "__CLASS__",
* fields = {
* @FieldResult(name = "id", column="u_id"),
* @FieldResult(name = "name", column="u_name"),
* @FieldResult(name = "status", column="u_status"),
* }
* ),
* @EntityResult(
* entityClass = "Address",
* fields = {
* @FieldResult(name = "id", column="a_id"),
* @FieldResult(name = "zip", column="a_zip"),
* @FieldResult(name = "country", column="a_country"),
* }
* )
* },
* columns = {
* @ColumnResult("numphones")
* }
* )
*})
*/
class User
{
/** @Id @Column(type="integer") @GeneratedValue */
public $id;
/** @Column(type="string", length=50, nullable=true) */
public $status;
/** @Column(type="string", length=255, unique=true) */
public $username;
/** @Column(type="string", length=255) */
public $name;
/** @OneToMany(targetEntity="Phonenumber") */
public $phonenumbers;
/** @OneToOne(targetEntity="Address") */
public $address;
// ....
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyProject\Model\User">
<named-native-queries>
<named-native-query name="fetchMultipleJoinsEntityResults" result-set-mapping="mappingMultipleJoinsEntityResults">
<query>SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM users u INNER JOIN addresses a ON u.id = a.user_id INNER JOIN phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username</query>
</named-native-query>
</named-native-queries>
<sql-result-set-mappings>
<sql-result-set-mapping name="mappingMultipleJoinsEntityResults">
<entity-result entity-class="__CLASS__">
<field-result name="id" column="u_id"/>
<field-result name="name" column="u_name"/>
<field-result name="status" column="u_status"/>
</entity-result>
<entity-result entity-class="Address">
<field-result name="id" column="a_id"/>
<field-result name="zip" column="a_zip"/>
<field-result name="country" column="a_country"/>
</entity-result>
<column-result name="numphones"/>
</sql-result-set-mapping>
</sql-result-set-mappings>
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyProject\Model\User:
type: entity
namedNativeQueries:
fetchMultipleJoinsEntityResults:
name: fetchMultipleJoinsEntityResults
resultSetMapping: mappingMultipleJoinsEntityResults
query: SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM users u INNER JOIN addresses a ON u.id = a.user_id INNER JOIN phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username
sqlResultSetMappings:
mappingMultipleJoinsEntityResults:
name: mappingMultipleJoinsEntityResults
columnResult:
0:
name: numphones
entityResult:
0:
entityClass: __CLASS__
fieldResult:
0:
name: id
column: u_id
1:
name: name
column: u_name
2:
name: status
column: u_status
1:
entityClass: Address
fieldResult:
0:
name: id
column: a_id
1:
name: zip
column: a_zip
2:
name: country
column: a_country
Things to note:
- The resultset mapping declares the entities retrieved by this native query.
- Each field of the entity is bound to a SQL alias (or column name).
- All fields of the entity including the ones of subclasses
and the foreign key columns of related entities have to be present in the SQL query.
- Field definitions are optional provided that they map to the same
column name as the one declared on the class property.
- ``__CLASS__`` is an alias for the mapped class
In the above example,
the ``fetchJoinedAddress`` named query use the joinMapping result set mapping.
This mapping returns 2 entities, User and Address, each property is declared and associated to a column name,
actually the column name retrieved by the query.
Let's now see an implicit declaration of the property / column.
.. configuration-block::
.. code-block:: php
<?php
namespace MyProject\Model;
/**
* @NamedNativeQueries({
* @NamedNativeQuery(
* name = "findAll",
* resultSetMapping = "mappingFindAll",
* query = "SELECT * FROM addresses"
* ),
* })
* @SqlResultSetMappings({
* @SqlResultSetMapping(
* name = "mappingFindAll",
* entities= {
* @EntityResult(
* entityClass = "Address"
* )
* }
* )
* })
*/
class Address
{
/** @Id @Column(type="integer") @GeneratedValue */
public $id;
/** @Column() */
public $country;
/** @Column() */
public $zip;
/** @Column()*/
public $city;
// ....
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyProject\Model\Address">
<named-native-queries>
<named-native-query name="findAll" result-set-mapping="mappingFindAll">
<query>SELECT * FROM addresses</query>
</named-native-query>
</named-native-queries>
<sql-result-set-mappings>
<sql-result-set-mapping name="mappingFindAll">
<entity-result entity-class="Address"/>
</sql-result-set-mapping>
</sql-result-set-mappings>
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyProject\Model\Address:
type: entity
namedNativeQueries:
findAll:
resultSetMapping: mappingFindAll
query: SELECT * FROM addresses
sqlResultSetMappings:
mappingFindAll:
name: mappingFindAll
entityResult:
address:
entityClass: Address
In this example, we only describe the entity member of the result set mapping.
The property / column mappings is done using the entity mapping values.
In this case the model property is bound to the model_txt column.
If the association to a related entity involve a composite primary key,
a @FieldResult element should be used for each foreign key column.
The @FieldResult name is composed of the property name for the relationship,
followed by a dot ("."), followed by the name or the field or property of the primary key.
.. configuration-block::
.. code-block:: php
<?php
namespace MyProject\Model;
/**
* @NamedNativeQueries({
* @NamedNativeQuery(
* name = "fetchJoinedAddress",
* resultSetMapping= "mappingJoinedAddress",
* query = "SELECT u.id, u.name, u.status, a.id AS a_id, a.country AS a_country, a.zip AS a_zip, a.city AS a_city FROM users u INNER JOIN addresses a ON u.id = a.user_id WHERE u.username = ?"
* ),
* })
* @SqlResultSetMappings({
* @SqlResultSetMapping(
* name = "mappingJoinedAddress",
* entities= {
* @EntityResult(
* entityClass = "__CLASS__",
* fields = {
* @FieldResult(name = "id"),
* @FieldResult(name = "name"),
* @FieldResult(name = "status"),
* @FieldResult(name = "address.id", column = "a_id"),
* @FieldResult(name = "address.zip", column = "a_zip"),
* @FieldResult(name = "address.city", column = "a_city"),
* @FieldResult(name = "address.country", column = "a_country"),
* }
* )
* }
* )
* })
*/
class User
{
/** @Id @Column(type="integer") @GeneratedValue */
public $id;
/** @Column(type="string", length=50, nullable=true) */
public $status;
/** @Column(type="string", length=255, unique=true) */
public $username;
/** @Column(type="string", length=255) */
public $name;
/** @OneToOne(targetEntity="Address") */
public $address;
// ....
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyProject\Model\User">
<named-native-queries>
<named-native-query name="fetchJoinedAddress" result-set-mapping="mappingJoinedAddress">
<query>SELECT u.id, u.name, u.status, a.id AS a_id, a.country AS a_country, a.zip AS a_zip, a.city AS a_city FROM users u INNER JOIN addresses a ON u.id = a.user_id WHERE u.username = ?</query>
</named-native-query>
</named-native-queries>
<sql-result-set-mappings>
<sql-result-set-mapping name="mappingJoinedAddress">
<entity-result entity-class="__CLASS__">
<field-result name="id"/>
<field-result name="name"/>
<field-result name="status"/>
<field-result name="address.id" column="a_id"/>
<field-result name="address.zip" column="a_zip"/>
<field-result name="address.city" column="a_city"/>
<field-result name="address.country" column="a_country"/>
</entity-result>
</sql-result-set-mapping>
</sql-result-set-mappings>
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyProject\Model\User:
type: entity
namedNativeQueries:
fetchJoinedAddress:
name: fetchJoinedAddress
resultSetMapping: mappingJoinedAddress
query: SELECT u.id, u.name, u.status, a.id AS a_id, a.country AS a_country, a.zip AS a_zip, a.city AS a_city FROM users u INNER JOIN addresses a ON u.id = a.user_id WHERE u.username = ?
sqlResultSetMappings:
mappingJoinedAddress:
entityResult:
0:
entityClass: __CLASS__
fieldResult:
0:
name: id
1:
name: name
2:
name: status
3:
name: address.id
column: a_id
4:
name: address.zip
column: a_zip
5:
name: address.city
column: a_city
6:
name: address.country
column: a_country
If you retrieve a single entity and if you use the default mapping,
you can use the resultClass attribute instead of resultSetMapping:
.. configuration-block::
.. code-block:: php
<?php
namespace MyProject\Model;
/**
* @NamedNativeQueries({
* @NamedNativeQuery(
* name = "find-by-id",
* resultClass = "Address",
* query = "SELECT * FROM addresses"
* ),
* })
*/
class Address
{
// ....
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyProject\Model\Address">
<named-native-queries>
<named-native-query name="find-by-id" result-class="Address">
<query>SELECT * FROM addresses WHERE id = ?</query>
</named-native-query>
</named-native-queries>
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyProject\Model\Address:
type: entity
namedNativeQueries:
findAll:
name: findAll
resultClass: Address
query: SELECT * FROM addresses
In some of your native queries, you'll have to return scalar values,
for example when building report queries.
You can map them in the @SqlResultsetMapping through @ColumnResult.
You actually can even mix, entities and scalar returns in the same native query (this is probably not that common though).
.. configuration-block::
.. code-block:: php
<?php
namespace MyProject\Model;
/**
* @NamedNativeQueries({
* @NamedNativeQuery(
* name = "count",
* resultSetMapping= "mappingCount",
* query = "SELECT COUNT(*) AS count FROM addresses"
* )
* })
* @SqlResultSetMappings({
* @SqlResultSetMapping(
* name = "mappingCount",
* columns = {
* @ColumnResult(
* name = "count"
* )
* }
* )
* })
*/
class Address
{
// ....
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyProject\Model\Address">
<named-native-query name="count" result-set-mapping="mappingCount">
<query>SELECT COUNT(*) AS count FROM addresses</query>
</named-native-query>
<sql-result-set-mappings>
<sql-result-set-mapping name="mappingCount">
<column-result name="count"/>
</sql-result-set-mapping>
</sql-result-set-mappings>
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyProject\Model\Address:
type: entity
namedNativeQueries:
count:
name: count
resultSetMapping: mappingCount
query: SELECT COUNT(*) AS count FROM addresses
sqlResultSetMappings:
mappingCount:
name: mappingCount
columnResult:
count:
name: count

View File

@@ -0,0 +1,90 @@
Partial Objects
===============
A partial object is an object whose state is not fully initialized
after being reconstituted from the database and that is
disconnected from the rest of its data. The following section will
describe why partial objects are problematic and what the approach
of Doctrine2 to this problem is.
.. note::
The partial object problem in general does not apply to
methods or queries where you do not retrieve the query result as
objects. Examples are: ``Query#getArrayResult()``,
``Query#getScalarResult()``, ``Query#getSingleScalarResult()``,
etc.
.. warning::
Use of partial objects is tricky. Fields that are not retrieved
from the database will not be updated by the UnitOfWork even if they
get changed in your objects. You can only promote a partial object
to a fully-loaded object by calling ``EntityManager#refresh()``
or a DQL query with the refresh flag.
What is the problem?
--------------------
In short, partial objects are problematic because they are usually
objects with broken invariants. As such, code that uses these
partial objects tends to be very fragile and either needs to "know"
which fields or methods can be safely accessed or add checks around
every field access or method invocation. The same holds true for
the internals, i.e. the method implementations, of such objects.
You usually simply assume the state you need in the method is
available, after all you properly constructed this object before
you pushed it into the database, right? These blind assumptions can
quickly lead to null reference errors when working with such
partial objects.
It gets worse with the scenario of an optional association (0..1 to
1). When the associated field is NULL, you don't know whether this
object does not have an associated object or whether it was simply
not loaded when the owning object was loaded from the database.
These are reasons why many ORMs do not allow partial objects at all
and instead you always have to load an object with all its fields
(associations being proxied). One secure way to allow partial
objects is if the programming language/platform allows the ORM tool
to hook deeply into the object and instrument it in such a way that
individual fields (not only associations) can be loaded lazily on
first access. This is possible in Java, for example, through
bytecode instrumentation. In PHP though this is not possible, so
there is no way to have "secure" partial objects in an ORM with
transparent persistence.
Doctrine, by default, does not allow partial objects. That means,
any query that only selects partial object data and wants to
retrieve the result as objects (i.e. ``Query#getResult()``) will
raise an exception telling you that partial objects are dangerous.
If you want to force a query to return you partial objects,
possibly as a performance tweak, you can use the ``partial``
keyword as follows:
.. code-block:: php
<?php
$q = $em->createQuery("select partial u.{id,name} from MyApp\Domain\User u");
You can also get a partial reference instead of a proxy reference by
calling:
.. code-block:: php
<?php
$reference = $em->getPartialReference('MyApp\Domain\User', 1);
Partial references are objects with only the identifiers set as they
are passed to the second argument of the ``getPartialReference()`` method.
All other fields are null.
When should I force partial objects?
------------------------------------
Mainly for optimization purposes, but be careful of premature
optimization as partial objects lead to potentially more fragile
code.

View File

@@ -0,0 +1,310 @@
PHP Mapping
===========
Doctrine 2 also allows you to provide the ORM metadata in the form
of plain PHP code using the ``ClassMetadata`` API. You can write
the code in PHP files or inside of a static function named
``loadMetadata($class)`` on the entity class itself.
PHP Files
---------
If you wish to write your mapping information inside PHP files that
are named after the entity and included to populate the metadata
for an entity you can do so by using the ``PHPDriver``:
.. code-block:: php
<?php
$driver = new PHPDriver('/path/to/php/mapping/files');
$em->getConfiguration()->setMetadataDriverImpl($driver);
Now imagine we had an entity named ``Entities\User`` and we wanted
to write a mapping file for it using the above configured
``PHPDriver`` instance:
.. code-block:: php
<?php
namespace Entities;
class User
{
private $id;
private $username;
}
To write the mapping information you just need to create a file
named ``Entities.User.php`` inside of the
``/path/to/php/mapping/files`` folder:
.. code-block:: php
<?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'
));
Now we can easily retrieve the populated ``ClassMetadata`` instance
where the ``PHPDriver`` includes the file and the
``ClassMetadataFactory`` caches it for later retrieval:
.. code-block:: php
<?php
$class = $em->getClassMetadata('Entities\User');
// or
$class = $em->getMetadataFactory()->getMetadataFor('Entities\User');
Static Function
---------------
In addition to the PHP files you can also specify your mapping
information inside of a static function defined on the entity class
itself. This is useful for cases where you want to keep your entity
and mapping information together but don't want to use annotations.
For this you just need to use the ``StaticPHPDriver``:
.. code-block:: php
<?php
$driver = new StaticPHPDriver('/path/to/entities');
$em->getConfiguration()->setMetadataDriverImpl($driver);
Now you just need to define a static function named
``loadMetadata($metadata)`` on your entity:
.. code-block:: php
<?php
namespace Entities;
use Doctrine\ORM\Mapping\ClassMetadata;
class User
{
// ...
public static function loadMetadata(ClassMetadata $metadata)
{
$metadata->mapField(array(
'id' => true,
'fieldName' => 'id',
'type' => 'integer'
));
$metadata->mapField(array(
'fieldName' => 'username',
'type' => 'string'
));
}
}
ClassMetadataBuilder
--------------------
To ease the use of the ClassMetadata API (which is very raw) there is a ``ClassMetadataBuilder`` that you can use.
.. code-block:: php
<?php
namespace Entities;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder;
class User
{
// ...
public static function loadMetadata(ClassMetadata $metadata)
{
$builder = new ClassMetadataBuilder($metadata);
$builder->createField('id', 'integer')->isPrimaryKey()->generatedValue()->build();
$builder->addField('username', 'string');
}
}
The API of the ClassMetadataBuilder has the following methods with a fluent interface:
- ``addField($name, $type, array $mapping)``
- ``setMappedSuperclass()``
- ``setReadOnly()``
- ``setCustomRepositoryClass($className)``
- ``setTable($name)``
- ``addIndex(array $columns, $indexName)``
- ``addUniqueConstraint(array $columns, $constraintName)``
- ``addNamedQuery($name, $dqlQuery)``
- ``setJoinedTableInheritance()``
- ``setSingleTableInheritance()``
- ``setDiscriminatorColumn($name, $type = 'string', $length = 255)``
- ``addDiscriminatorMapClass($name, $class)``
- ``setChangeTrackingPolicyDeferredExplicit()``
- ``setChangeTrackingPolicyNotify()``
- ``addLifecycleEvent($methodName, $event)``
- ``addManyToOne($name, $targetEntity, $inversedBy = null)``
- ``addInverseOneToOne($name, $targetEntity, $mappedBy)``
- ``addOwningOneToOne($name, $targetEntity, $inversedBy = null)``
- ``addOwningManyToMany($name, $targetEntity, $inversedBy = null)``
- ``addInverseManyToMany($name, $targetEntity, $mappedBy)``
- ``addOneToMany($name, $targetEntity, $mappedBy)``
It also has several methods that create builders (which are necessary for advanced mappings):
- ``createField($name, $type)`` returns a ``FieldBuilder`` instance
- ``createManyToOne($name, $targetEntity)`` returns an ``AssociationBuilder`` instance
- ``createOneToOne($name, $targetEntity)`` returns an ``AssociationBuilder`` instance
- ``createManyToMany($name, $targetEntity)`` returns an ``ManyToManyAssociationBuilder`` instance
- ``createOneToMany($name, $targetEntity)`` returns an ``OneToManyAssociationBuilder`` instance
ClassMetadataInfo API
---------------------
The ``ClassMetadataInfo`` class is the base data object for storing
the mapping metadata for a single entity. It contains all the
getters and setters you need populate and retrieve information for
an entity.
General Setters
~~~~~~~~~~~~~~~
- ``setTableName($tableName)``
- ``setPrimaryTable(array $primaryTableDefinition)``
- ``setCustomRepositoryClass($repositoryClassName)``
- ``setIdGeneratorType($generatorType)``
- ``setIdGenerator($generator)``
- ``setSequenceGeneratorDefinition(array $definition)``
- ``setChangeTrackingPolicy($policy)``
- ``setIdentifier(array $identifier)``
Inheritance Setters
~~~~~~~~~~~~~~~~~~~
- ``setInheritanceType($type)``
- ``setSubclasses(array $subclasses)``
- ``setParentClasses(array $classNames)``
- ``setDiscriminatorColumn($columnDef)``
- ``setDiscriminatorMap(array $map)``
Field Mapping Setters
~~~~~~~~~~~~~~~~~~~~~
- ``mapField(array $mapping)``
- ``mapOneToOne(array $mapping)``
- ``mapOneToMany(array $mapping)``
- ``mapManyToOne(array $mapping)``
- ``mapManyToMany(array $mapping)``
Lifecycle Callback Setters
~~~~~~~~~~~~~~~~~~~~~~~~~~
- ``addLifecycleCallback($callback, $event)``
- ``setLifecycleCallbacks(array $callbacks)``
Versioning Setters
~~~~~~~~~~~~~~~~~~
- ``setVersionMapping(array &$mapping)``
- ``setVersioned($bool)``
- ``setVersionField()``
General Getters
~~~~~~~~~~~~~~~
- ``getTableName()``
- ``getTemporaryIdTableName()``
Identifier Getters
~~~~~~~~~~~~~~~~~~
- ``getIdentifierColumnNames()``
- ``usesIdGenerator()``
- ``isIdentifier($fieldName)``
- ``isIdGeneratorIdentity()``
- ``isIdGeneratorSequence()``
- ``isIdGeneratorTable()``
- ``isIdentifierNatural()``
- ``getIdentifierFieldNames()``
- ``getSingleIdentifierFieldName()``
- ``getSingleIdentifierColumnName()``
Inheritance Getters
~~~~~~~~~~~~~~~~~~~
- ``isInheritanceTypeNone()``
- ``isInheritanceTypeJoined()``
- ``isInheritanceTypeSingleTable()``
- ``isInheritanceTypeTablePerClass()``
- ``isInheritedField($fieldName)``
- ``isInheritedAssociation($fieldName)``
Change Tracking Getters
~~~~~~~~~~~~~~~~~~~~~~~
- ``isChangeTrackingDeferredExplicit()``
- ``isChangeTrackingDeferredImplicit()``
- ``isChangeTrackingNotify()``
Field & Association Getters
~~~~~~~~~~~~~~~~~~~~~~~~~~~
- ``isUniqueField($fieldName)``
- ``isNullable($fieldName)``
- ``getColumnName($fieldName)``
- ``getFieldMapping($fieldName)``
- ``getAssociationMapping($fieldName)``
- ``getAssociationMappings()``
- ``getFieldName($columnName)``
- ``hasField($fieldName)``
- ``getColumnNames(array $fieldNames = null)``
- ``getTypeOfField($fieldName)``
- ``getTypeOfColumn($columnName)``
- ``hasAssociation($fieldName)``
- ``isSingleValuedAssociation($fieldName)``
- ``isCollectionValuedAssociation($fieldName)``
Lifecycle Callback Getters
~~~~~~~~~~~~~~~~~~~~~~~~~~
- ``hasLifecycleCallbacks($lifecycleEvent)``
- ``getLifecycleCallbacks($event)``
ClassMetadata API
-----------------
The ``ClassMetadata`` class extends ``ClassMetadataInfo`` and adds
the runtime functionality required by Doctrine. It adds a few extra
methods related to runtime reflection for working with the entities
themselves.
- ``getReflectionClass()``
- ``getReflectionProperties()``
- ``getReflectionProperty($name)``
- ``getSingleIdReflectionProperty()``
- ``getIdentifierValues($entity)``
- ``setIdentifierValues($entity, $id)``
- ``setFieldValue($entity, $field, $value)``
- ``getFieldValue($entity, $field)``

View File

@@ -0,0 +1,541 @@
The QueryBuilder
================
A ``QueryBuilder`` provides an API that is designed for
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, 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, just providing the correct method name. Here is an example
how to build a ``QueryBuilder`` object:
.. code-block:: php
<?php
// $em instanceof EntityManager
// example1: creating a QueryBuilder instance
$qb = $em->createQueryBuilder();
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
<?php
// $qb instanceof QueryBuilder
// example2: retrieving type of QueryBuilder
echo $qb->getType(); // Prints: 0
There're currently 3 possible return values for ``getType()``:
- ``QueryBuilder::SELECT``, which returns value 0
- ``QueryBuilder::DELETE``, returning value 1
- ``QueryBuilder::UPDATE``, which returns value 2
It is possible to retrieve the associated ``EntityManager`` of the
current ``QueryBuilder``, its DQL and also a ``Query`` object when
you finish building your DQL.
.. code-block:: php
<?php
// $qb instanceof QueryBuilder
// example3: retrieve the associated EntityManager
$em = $qb->getEntityManager();
// example4: retrieve the DQL string of what was defined in QueryBuilder
$dql = $qb->getDql();
// example5: retrieve the associated Query object with the processed DQL
$q = $qb->getQuery();
Internally, ``QueryBuilder`` works with a DQL cache to increase
performance. Any changes that may affect the generated DQL actually
modifies the state of ``QueryBuilder`` to a stage we call
STATE\_DIRTY. One ``QueryBuilder`` can be in two different states:
- ``QueryBuilder::STATE_CLEAN``, which means DQL haven't been
altered since last retrieval or nothing were added since its
instantiation
- ``QueryBuilder::STATE_DIRTY``, means DQL query must (and will)
be processed on next retrieval
Working with QueryBuilder
~~~~~~~~~~~~~~~~~~~~~~~~~
High level API 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
<?php
// $qb instanceof QueryBuilder
$qb->select('u')
->from('User', 'u')
->where('u.id = ?1')
->orderBy('u.name', 'ASC');
``QueryBuilder`` helper methods are considered the standard way to
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
<?php
// $qb instanceof QueryBuilder
$qb->select(array('u')) // string 'u' is converted to array internally
->from('User', 'u')
->where($qb->expr()->orX(
$qb->expr()->eq('u.id', '?1'),
$qb->expr()->like('u.nickname', '?2')
))
->orderBy('u.surname', 'ASC'));
Here is a complete list of helper methods available in ``QueryBuilder``:
.. code-block:: php
<?php
class QueryBuilder
{
// Example - $qb->select('u')
// Example - $qb->select(array('u', 'p'))
// Example - $qb->select($qb->expr()->select('u', 'p'))
public function select($select = null);
// Example - $qb->delete('User', 'u')
public function delete($delete = null, $alias = null);
// Example - $qb->update('Group', 'g')
public function update($update = null, $alias = null);
// Example - $qb->set('u.firstName', $qb->expr()->literal('Arnold'))
// Example - $qb->set('u.numChilds', 'u.numChilds + ?1')
// Example - $qb->set('u.numChilds', $qb->expr()->sum('u.numChilds', '?1'))
public function set($key, $value);
// Example - $qb->from('Phonenumber', 'p')
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')
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')
public function leftJoin($join, $alias = null, $conditionType = null, $condition = null);
// NOTE: ->where() overrides all previously set conditions
//
// Example - $qb->where('u.firstName = ?1', $qb->expr()->eq('u.surname', '?2'))
// Example - $qb->where($qb->expr()->andX($qb->expr()->eq('u.firstName', '?1'), $qb->expr()->eq('u.surname', '?2')))
// Example - $qb->where('u.firstName = ?1 AND u.surname = ?2')
public function where($where);
// Example - $qb->andWhere($qb->expr()->orX($qb->expr()->lte('u.age', 40), 'u.numChild = 0'))
public function andWhere($where);
// Example - $qb->orWhere($qb->expr()->between('u.id', 1, 10));
public function orWhere($where);
// NOTE: -> groupBy() overrides all previously set grouping conditions
//
// Example - $qb->groupBy('u.id')
public function groupBy($groupBy);
// Example - $qb->addGroupBy('g.name')
public function addGroupBy($groupBy);
// NOTE: -> having() overrides all previously set having conditions
//
// Example - $qb->having('u.salary >= ?1')
// Example - $qb->having($qb->expr()->gte('u.salary', '?1'))
public function having($having);
// Example - $qb->andHaving($qb->expr()->gt($qb->expr()->count('u.numChild'), 0))
public function andHaving($having);
// Example - $qb->orHaving($qb->expr()->lte('g.managerLevel', '100'))
public function orHaving($having);
// NOTE: -> orderBy() overrides all previously set ordering conditions
//
// Example - $qb->orderBy('u.surname', 'DESC')
public function orderBy($sort, $order = null);
// Example - $qb->addOrderBy('u.firstName')
public function addOrderBy($sort, $order = null); // Default $order = 'ASC'
}
Binding parameters to your query
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Doctrine supports dynamic binding of parameters to your query,
similar to preparing queries. You can use both strings and numbers
as placeholders, although both have a slightly different syntax.
Additionally, you must make your choice: Mixing both styles is not
allowed. Binding parameters can simply be achieved as follows:
.. code-block:: php
<?php
// $qb instanceof QueryBuilder
$qb->select('u')
->from('User u')
->where('u.id = ?1')
->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
alternative syntax is available:
.. code-block:: php
<?php
// $qb instanceof QueryBuilder
$qb->select('u')
->from('User u')
->where('u.id = :identifier')
->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
while the named placeholders start with a : followed by a string.
Calling ``setParameter()`` automatically infers which type you are setting as
value. This works for integers, arrays of strings/integers, DateTime instances
and for managed entities. If you want to set a type explicitly you can call
the third argument to ``setParameter()`` explicitly. It accepts either a PDO
type or a DBAL Type name for conversion.
If you've got several parameters to bind to your query, you can
also use setParameters() instead of setParameter() with the
following syntax:
.. code-block:: php
<?php
// $qb instanceof QueryBuilder
// Query here...
$qb->setParameters(array(1 => 'value for ?1', 2 => 'value for ?2'));
Getting already bound parameters is easy - simply use the above
mentioned syntax with "getParameter()" or "getParameters()":
.. code-block:: php
<?php
// $qb instanceof QueryBuilder
// See example above
$params = $qb->getParameters();
// $params instanceof \Doctrine\Common\Collections\ArrayCollection
// Equivalent to
$param = $qb->getParameter(1);
// $param instanceof \Doctrine\ORM\Query\Parameter
Note: If you try to get a parameter that was not bound yet,
getParameter() simply returns NULL.
The API of a Query Parameter is:
.. code-block:: php
namespace Doctrine\ORM\Query;
class Parameter
{
public function getName();
public function getValue();
public function getType();
public function setValue($value, $type = null);
}
Limiting the Result
^^^^^^^^^^^^^^^^^^^
To limit a result the query builder has some methods in common with
the Query object which can be retrieved from ``EntityManager#createQuery()``.
.. code-block:: php
<?php
// $qb instanceof QueryBuilder
$offset = (int)$_GET['offset'];
$limit = (int)$_GET['limit'];
$qb->add('select', 'u')
->add('from', 'User u')
->add('orderBy', 'u.name ASC')
->setFirstResult( $offset )
->setMaxResults( $limit );
Executing a Query
^^^^^^^^^^^^^^^^^
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:
.. code-block:: php
<?php
// $qb instanceof QueryBuilder
$query = $qb->getQuery();
// Set additional Query options
$query->setQueryHint('foo', 'bar');
$query->useResultCache('my_cache_id');
// Execute Query
$result = $query->getResult();
$single = $query->getSingleResult();
$array = $query->getArrayResult();
$scalar = $query->getScalarResult();
$singleScalar = $query->getSingleScalarResult();
The Expr class
^^^^^^^^^^^^^^
To workaround some of the issues that ``add()`` method may cause,
Doctrine created a class that can be considered as a helper for
building expressions. This class is called ``Expr``, which provides a
set of useful methods to help build expressions:
.. code-block:: php
<?php
// $qb instanceof QueryBuilder
// 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(
$qb->expr()->eq('u.id', '?1'),
$qb->expr()->like('u.nickname', '?2')
))
->add('orderBy', new Expr\OrderBy('u.name', 'ASC'));
Although it still sounds complex, the ability to programmatically
create conditions are the main feature of ``Expr``. Here it is a
complete list of supported helper methods available:
.. code-block:: php
<?php
class Expr
{
/** Conditional objects **/
// Example - $qb->expr()->andX($cond1 [, $condN])->add(...)->...
public function andX($x = null); // Returns Expr\AndX instance
// Example - $qb->expr()->orX($cond1 [, $condN])->add(...)->...
public function orX($x = null); // Returns Expr\OrX instance
/** Comparison objects **/
// Example - $qb->expr()->eq('u.id', '?1') => u.id = ?1
public function eq($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->neq('u.id', '?1') => u.id <> ?1
public function neq($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->lt('u.id', '?1') => u.id < ?1
public function lt($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->lte('u.id', '?1') => u.id <= ?1
public function lte($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->gt('u.id', '?1') => u.id > ?1
public function gt($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->gte('u.id', '?1') => u.id >= ?1
public function gte($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->isNull('u.id') => u.id IS NULL
public function isNull($x); // Returns string
// Example - $qb->expr()->isNotNull('u.id') => u.id IS NOT NULL
public function isNotNull($x); // Returns string
/** Arithmetic objects **/
// Example - $qb->expr()->prod('u.id', '2') => u.id * 2
public function prod($x, $y); // Returns Expr\Math instance
// Example - $qb->expr()->diff('u.id', '2') => u.id - 2
public function diff($x, $y); // Returns Expr\Math instance
// Example - $qb->expr()->sum('u.id', '2') => u.id + 2
public function sum($x, $y); // Returns Expr\Math instance
// Example - $qb->expr()->quot('u.id', '2') => u.id / 2
public function quot($x, $y); // Returns Expr\Math instance
/** Pseudo-function objects **/
// Example - $qb->expr()->exists($qb2->getDql())
public function exists($subquery); // Returns Expr\Func instance
// Example - $qb->expr()->all($qb2->getDql())
public function all($subquery); // Returns Expr\Func instance
// Example - $qb->expr()->some($qb2->getDql())
public function some($subquery); // Returns Expr\Func instance
// Example - $qb->expr()->any($qb2->getDql())
public function any($subquery); // Returns Expr\Func instance
// Example - $qb->expr()->not($qb->expr()->eq('u.id', '?1'))
public function not($restriction); // Returns Expr\Func instance
// Example - $qb->expr()->in('u.id', array(1, 2, 3))
// Make sure that you do NOT use something similar to $qb->expr()->in('value', array('stringvalue')) as this will cause Doctrine to throw an Exception.
// Instead, use $qb->expr()->in('value', array('?1')) and bind your parameter to ?1 (see section above)
public function in($x, $y); // Returns Expr\Func instance
// Example - $qb->expr()->notIn('u.id', '2')
public function notIn($x, $y); // Returns Expr\Func instance
// Example - $qb->expr()->like('u.firstname', $qb->expr()->literal('Gui%'))
public function like($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->between('u.id', '1', '10')
public function between($val, $x, $y); // Returns Expr\Func
/** Function objects **/
// Example - $qb->expr()->trim('u.firstname')
public function trim($x); // Returns Expr\Func
// 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()->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
// Example - $qb->expr()->upper('u.firstname')
public function upper($x); // Returns Expr\Func
// Example - $qb->expr()->length('u.firstname')
public function length($x); // Returns Expr\Func
// Example - $qb->expr()->avg('u.age')
public function avg($x); // Returns Expr\Func
// Example - $qb->expr()->max('u.age')
public function max($x); // Returns Expr\Func
// Example - $qb->expr()->min('u.age')
public function min($x); // Returns Expr\Func
// Example - $qb->expr()->abs('u.currentBalance')
public function abs($x); // Returns Expr\Func
// Example - $qb->expr()->sqrt('u.currentBalance')
public function sqrt($x); // Returns Expr\Func
// Example - $qb->expr()->count('u.firstname')
public function count($x); // Returns Expr\Func
// Example - $qb->expr()->countDistinct('u.surname')
public function countDistinct($x); // Returns Expr\Func
}
Low Level API
^^^^^^^^^^^^^
Now we have describe the low level (thought of as the
hardcore method) of creating queries. It may be useful to work at
this level for optimization purposes, but most of the time it is
preferred to work at a higher level of abstraction.
All helper methods in ``QueryBuilder`` actually rely on a single
one: ``add()``. This method is responsible of building every piece
of DQL. It takes 3 parameters: ``$dqlPartName``, ``$dqlPart`` and
``$append`` (default=false)
- ``$dqlPartName``: Where the ``$dqlPart`` should be placed.
Possible values: select, from, where, groupBy, having, orderBy
- ``$dqlPart``: What should be placed in ``$dqlPartName``. Accepts
a string or any instance of ``Doctrine\ORM\Query\Expr\*``
- ``$append``: Optional flag (default=false) if the ``$dqlPart``
should override all previously defined items in ``$dqlPartName`` or
not (no effect on the ``where`` and ``having`` DQL query parts,
which always override all previously defined items)
-
.. code-block:: php
<?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
$qb->add('select', 'u')
->add('from', 'User u')
->add('where', 'u.id = ?1')
->add('orderBy', 'u.name ASC');
Expr\* classes
^^^^^^^^^^^^^^
When you call ``add()`` with string, it internally evaluates to an
instance of ``Doctrine\ORM\Query\Expr\Expr\*`` class. Here is the
same query of example 6 written using
``Doctrine\ORM\Query\Expr\Expr\*`` classes:
.. code-block:: php
<?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
$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.

509
docs/en/reference/tools.rst Normal file
View File

@@ -0,0 +1,509 @@
Tools
=====
Doctrine Console
----------------
The Doctrine Console is a Command Line Interface tool for simplifying common
administration tasks during the development of a project that uses Doctrine 2.
Take a look at the :doc:`Installation and Configuration <configuration>`
chapter for more information how to setup the console command.
Display Help Information
~~~~~~~~~~~~~~~~~~~~~~~~
Type ``php vendor/bin/doctrine`` on the command line and you should see an
overview of the available commands or use the --help flag to get
information on the available commands. If you want to know more
about the use of generate entities for example, you can call:
.. code-block:: php
$> php vendor/bin/doctrine orm:generate-entities --help
Configuration
~~~~~~~~~~~~~
Whenever the ``doctrine`` command line tool is invoked, it can
access all Commands that were registered by developer. There is no
auto-detection mechanism at work. The Doctrine binary
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
using so called Helper-Sets. This requires either the ``db``
or the ``em`` helpers to be defined in order to work correctly.
Whenever you invoke the Doctrine binary the current folder is searched for a
``cli-config.php`` file. This file contains the project specific configuration:
.. code-block:: php
<?php
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($conn)
));
$cli->setHelperSet($helperSet);
When dealing with the ORM package, the EntityManagerHelper is
required:
.. code-block:: php
<?php
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em)
));
$cli->setHelperSet($helperSet);
The HelperSet instance has to be generated in a separate file (i.e.
``cli-config.php``) that contains typical Doctrine bootstrap code
and predefines the needed HelperSet attributes mentioned above. A
sample ``cli-config.php`` file looks as follows:
.. code-block:: php
<?php
// cli-config.php
require_once 'my_bootstrap.php';
// Any way to access the EntityManager from your application
$em = GetMyEntityManager();
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()),
'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em)
));
It is important to define a correct HelperSet that Doctrine binary
script will ultimately use. The Doctrine Binary will automatically
find the first instance of HelperSet in the global variable
namespace and use this.
.. note::
You have to adjust this snippet for your specific application or framework
and use their facilities to access the Doctrine EntityManager and
Connection Resources.
Command Overview
~~~~~~~~~~~~~~~~
The following Commands are currently available:
- ``help`` Displays help for a command (?)
- ``list`` Lists commands
- ``dbal:import`` Import SQL file(s) directly to Database.
- ``dbal:run-sql`` Executes arbitrary SQL directly from the
command line.
- ``orm:clear-cache:metadata`` Clear all metadata cache of the
various cache drivers.
- ``orm:clear-cache:query`` Clear all query cache of the various
cache drivers.
- ``orm:clear-cache:result`` Clear result cache of the various
cache drivers.
- ``orm:convert-d1-schema`` Converts Doctrine 1.X schema into a
Doctrine 2.X schema.
- ``orm:convert-mapping`` Convert mapping information between
supported formats.
- ``orm:ensure-production-settings`` Verify that Doctrine is
properly configured for a production environment.
- ``orm:generate-entities`` Generate entity classes and method
stubs from your mapping information.
- ``orm:generate-proxies`` Generates proxy classes for entity
classes.
- ``orm:generate-repositories`` Generate repository classes from
your mapping information.
- ``orm:run-dql`` Executes arbitrary DQL directly from the command
line.
- ``orm:schema-tool:create`` Processes the schema and either
create it directly on EntityManager Storage Connection or generate
the SQL output.
- ``orm:schema-tool:drop`` Processes the schema and either drop
the database schema of EntityManager Storage Connection or generate
the SQL output.
- ``orm:schema-tool:update`` Processes the schema and either
update the database schema of EntityManager Storage Connection or
generate the SQL output.
For these commands are also available aliases:
- ``orm:convert:d1-schema`` is alias for ``orm:convert-d1-schema``.
- ``orm:convert:mapping`` is alias for ``orm:convert-mapping``.
- ``orm:generate:entities`` is alias for ``orm:generate-entities``.
- ``orm:generate:proxies`` is alias for ``orm:generate-proxies``.
- ``orm:generate:repositories`` is alias for ``orm:generate-repositories``.
.. note::
Console also supports auto completion, for example, instead of
``orm:clear-cache:query`` you can use just ``o:c:q``.
Database Schema Generation
--------------------------
.. note::
SchemaTool can do harm to your database. It will drop or alter
tables, indexes, sequences and such. Please use this tool with
caution in development and not on a production server. It is meant
for helping you develop your Database Schema, but NOT with
migrating schema from A to B in production. A safe approach would
be generating the SQL on development server and saving it into SQL
Migration files that are executed manually on the production
server.
SchemaTool assumes your Doctrine Project uses the given database on
its own. Update and Drop commands will mess with other tables if
they are not related to the current project that is using Doctrine.
Please be careful!
To generate your database schema from your Doctrine mapping files
you can use the ``SchemaTool`` class or the ``schema-tool`` Console
Command.
When using the SchemaTool class directly, create your schema using
the ``createSchema()`` method. First create an instance of the
``SchemaTool`` and pass it an instance of the ``EntityManager``
that you want to use to create the schema. This method receives an
array of ``ClassMetadataInfo`` instances.
.. code-block:: php
<?php
$tool = new \Doctrine\ORM\Tools\SchemaTool($em);
$classes = array(
$em->getClassMetadata('Entities\User'),
$em->getClassMetadata('Entities\Profile')
);
$tool->createSchema($classes);
To drop the schema you can use the ``dropSchema()`` method.
.. code-block:: php
<?php
$tool->dropSchema($classes);
This drops all the tables that are currently used by your metadata
model. When you are changing your metadata a lot during development
you might want to drop the complete database instead of only the
tables of the current model to clean up with orphaned tables.
.. code-block:: php
<?php
$tool->dropSchema($classes, \Doctrine\ORM\Tools\SchemaTool::DROP_DATABASE);
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
``ClassMetdataInfo`` instances.
.. code-block:: php
<?php
$tool->updateSchema($classes);
If you want to use this functionality from the command line you can
use the ``schema-tool`` command.
To create the schema use the ``create`` command:
.. code-block:: php
$ php doctrine orm:schema-tool:create
To drop the schema use the ``drop`` command:
.. code-block:: php
$ php doctrine orm:schema-tool:drop
If you want to drop and then recreate the schema then use both
options:
.. code-block:: php
$ php doctrine orm:schema-tool:drop
$ php doctrine orm:schema-tool:create
As you would think, if you want to update your schema use the
``update`` command:
.. code-block:: php
$ php doctrine orm:schema-tool:update
All of the above commands also accept a ``--dump-sql`` option that
will output the SQL for the ran operation.
.. code-block:: php
$ php doctrine orm:schema-tool:create --dump-sql
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
-----------------
Generate entity classes and method stubs from your mapping information.
.. code-block:: php
$ php doctrine orm:generate-entities
$ php doctrine orm:generate-entities --update-entities
$ php doctrine orm:generate-entities --regenerate-entities
This command is not suited for constant usage. It is a little helper and does
not support all the mapping edge cases very well. You still have to put work
in your entities after using this command.
It is possible to use the EntityGenerator on code that you have already written. It will
not be lost. The EntityGenerator will only append new code to your
file and will not delete the old code. However this approach may still be prone
to error and we suggest you use code repositories such as GIT or SVN to make
backups of your code.
It makes sense to generate the entity code if you are using entities as Data
Access Objects only and don't put much additional logic on them. If you are
however putting much more logic on the entities you should refrain from using
the entity-generator and code your entities manually.
.. note::
Even if you specified Inheritance options in your
XML or YAML Mapping files the generator cannot generate the base and
child classes for you correctly, because it doesn't know which
class is supposed to extend which. You have to adjust the entity
code manually for inheritance to work!
Convert Mapping Information
---------------------------
Convert mapping information between supported formats.
This is an **execute one-time** command. It should not be necessary for
you to call this method multiple times, especially when using the ``--from-database``
flag.
Converting an existing database schema into mapping files only solves about 70-80%
of the necessary mapping information. Additionally the detection from an existing
database cannot detect inverse associations, inheritance types,
entities with foreign keys as primary keys and many of the
semantical operations on associations such as cascade.
.. note::
There is no need to convert YAML or XML mapping files to annotations
every time you make changes. All mapping drivers are first class citizens
in Doctrine 2 and can be used as runtime mapping for the ORM. See the
docs on XML and YAML Mapping for an example how to register this metadata
drivers as primary mapping source.
To convert some mapping information between the various supported
formats you can use the ``ClassMetadataExporter`` to get exporter
instances for the different formats:
.. code-block:: php
<?php
$cme = new \Doctrine\ORM\Tools\Export\ClassMetadataExporter();
Once you have a instance you can use it to get an exporter. For
example, the yml exporter:
.. code-block:: php
<?php
$exporter = $cme->getExporter('yml', '/path/to/export/yml');
Now you can export some ``ClassMetadata`` instances:
.. code-block:: php
<?php
$classes = array(
$em->getClassMetadata('Entities\User'),
$em->getClassMetadata('Entities\Profile')
);
$exporter->setMetadata($classes);
$exporter->export();
This functionality is also available from the command line to
convert your loaded mapping information to another format. The
``orm:convert-mapping`` command accepts two arguments, the type to
convert to and the path to generate it:
.. code-block:: php
$ php doctrine orm:convert-mapping xml /path/to/mapping-path-converted-to-xml
Reverse Engineering
-------------------
You can use the ``DatabaseDriver`` to reverse engineer a database
to an array of ``ClassMetadataInfo`` instances and generate YAML,
XML, etc. from them.
.. note::
Reverse Engineering is a **one-time** process that can get you started with a project.
Converting an existing database schema into mapping files only detects about 70-80%
of the necessary mapping information. Additionally the detection from an existing
database cannot detect inverse associations, inheritance types,
entities with foreign keys as primary keys and many of the
semantical operations on associations such as cascade.
First you need to retrieve the metadata instances with the
``DatabaseDriver``:
.. code-block:: php
<?php
$em->getConfiguration()->setMetadataDriverImpl(
new \Doctrine\ORM\Mapping\Driver\DatabaseDriver(
$em->getConnection()->getSchemaManager()
)
);
$cmf = new DisconnectedClassMetadataFactory();
$cmf->setEntityManager($em);
$metadata = $cmf->getAllMetadata();
Now you can get an exporter instance and export the loaded metadata
to yml:
.. code-block:: php
<?php
$exporter = $cme->getExporter('yml', '/path/to/export/yml');
$exporter->setMetadata($metadata);
$exporter->export();
You can also reverse engineer a database using the
``orm:convert-mapping`` command:
.. code-block:: php
$ php doctrine orm:convert-mapping --from-database yml /path/to/mapping-path-converted-to-yml
.. note::
Reverse Engineering is not always working perfectly
depending on special cases. It will only detect Many-To-One
relations (even if they are One-To-One) and will try to create
entities from Many-To-Many tables. It also has problems with naming
of foreign keys that have multiple column names. Any Reverse
Engineered Database-Schema needs considerable manual work to become
a useful domain model.
Runtime vs Development Mapping Validation
-----------------------------------------
For performance reasons Doctrine 2 has to skip some of the
necessary validation of metadata mappings. You have to execute
this validation in your development workflow to verify the
associations are correctly defined.
You can either use the Doctrine Command Line Tool:
.. code-block:: php
doctrine orm:validate-schema
Or you can trigger the validation manually:
.. code-block:: php
<?php
use Doctrine\ORM\Tools\SchemaValidator;
$validator = new SchemaValidator($entityManager);
$errors = $validator->validateMapping();
if (count($errors) > 0) {
// Lots of errors!
echo implode("\n\n", $errors);
}
If the mapping is invalid the errors array contains a positive
number of elements with error messages.
.. warning::
One mapping option that is not validated is the use of the referenced column name.
It has to point to the equivalent primary key otherwise Doctrine will not work.
.. note::
One common error is to use a backlash in front of the
fully-qualified class-name. Whenever a FQCN is represented inside a
string (such as in your mapping definitions) you have to drop the
prefix backslash. PHP does this with ``get_class()`` or Reflection
methods for backwards compatibility reasons.
Adding own commands
-------------------
You can also add your own commands on-top of the Doctrine supported
tools if you are using a manually built console script.
To include a new command on Doctrine Console, you need to do modify the
``doctrine.php`` file a little:
.. code-block:: php
<?php
// doctrine.php
use Symfony\Component\Console\Helper\Application;
// as before ...
// replace the ConsoleRunner::run() statement with:
$cli = new Application('Doctrine Command Line Interface', \Doctrine\ORM\Version::VERSION);
$cli->setCatchExceptions(true);
$cli->setHelperSet($helperSet);
// Register All Doctrine Commands
ConsoleRunner::addCommands($cli);
// Register your own command
$cli->addCommand(new \MyProject\Tools\Console\Commands\MyCustomCommand);
// Runs console application
$cli->run();
Additionally, include multiple commands (and overriding previously
defined ones) is possible through the command:
.. code-block:: php
<?php
$cli->addCommands(array(
new \MyProject\Tools\Console\Commands\MyCustomCommand(),
new \MyProject\Tools\Console\Commands\SomethingCommand(),
new \MyProject\Tools\Console\Commands\AnotherCommand(),
new \MyProject\Tools\Console\Commands\OneMoreCommand(),
));

View File

@@ -0,0 +1,353 @@
Transactions and Concurrency
============================
Transaction Demarcation
-----------------------
Transaction demarcation is the task of defining your transaction
boundaries. Proper transaction demarcation is very important
because if not done properly it can negatively affect the
performance of your application. Many databases and database
abstraction layers like PDO by default operate in auto-commit mode,
which means that every single SQL statement is wrapped in a small
transaction. Without any explicit transaction demarcation from your
side, this quickly results in poor performance because transactions
are not cheap.
For the most part, Doctrine 2 already takes care of proper
transaction demarcation for you: All the write operations
(INSERT/UPDATE/DELETE) are queued until ``EntityManager#flush()``
is invoked which wraps all of these changes in a single
transaction.
However, Doctrine 2 also allows (and encourages) you to take over
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.
Approach 1: Implicitly
~~~~~~~~~~~~~~~~~~~~~~
The first approach is to use the implicit transaction handling
provided by the Doctrine ORM EntityManager. Given the following
code snippet, without any explicit transaction demarcation:
.. code-block:: php
<?php
// $em instanceof EntityManager
$user = new User;
$user->setName('George');
$em->persist($user);
$em->flush();
Since we do not do any custom transaction demarcation in the above
code, ``EntityManager#flush()`` will begin and commit/rollback a
transaction. This behavior is made possible by the aggregation of
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.
Approach 2: Explicitly
~~~~~~~~~~~~~~~~~~~~~~
The explicit alternative is to use the ``Doctrine\DBAL\Connection``
API directly to control the transaction boundaries. The code then
looks like this:
.. code-block:: php
<?php
// $em instanceof EntityManager
$em->getConnection()->beginTransaction(); // suspend auto-commit
try {
//... do some work
$user = new User;
$user->setName('George');
$em->persist($user);
$em->flush();
$em->getConnection()->commit();
} catch (Exception $e) {
$em->getConnection()->rollback();
$em->close();
throw $e;
}
Explicit transaction demarcation is required when you want to
include custom DBAL operations in a unit of work or when you want
to make use of some methods of the ``EntityManager`` API that
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 or close the ``EntityManager``, apart from the obvious
code reduction. An example that is functionally equivalent to the
previously shown code looks as follows:
.. code-block:: php
<?php
// $em instanceof EntityManager
$em->transactional(function($em) {
//... do some work
$user = new User;
$user->setName('George');
$em->persist($user);
});
The difference between ``Connection#transactional($func)`` and
``EntityManager#transactional($func)`` is that the latter
abstraction flushes the ``EntityManager`` prior to transaction
commit and also closes the ``EntityManager`` properly when an
exception occurs (in addition to rolling back the transaction).
Exception Handling
~~~~~~~~~~~~~~~~~~
When using implicit transaction demarcation and an exception occurs
during ``EntityManager#flush()``, the transaction is automatically
rolled back and the ``EntityManager`` closed.
When using explicit transaction demarcation and an exception
occurs, the transaction should be rolled back immediately and the
``EntityManager`` closed by invoking ``EntityManager#close()`` and
subsequently discarded, as demonstrated in the example above. This
can be handled elegantly by the control abstractions shown earlier.
Note that when catching ``Exception`` you should generally re-throw
the exception. If you intend to recover from some exceptions, catch
them explicitly in earlier catch blocks (but do not forget to
rollback the transaction and close the ``EntityManager`` there as
well). All other best practices of exception handling apply
similarly (i.e. either log or re-throw, not both, etc.).
As a result of this procedure, all previously managed or removed
instances of the ``EntityManager`` become detached. The state of
the detached objects will be the state at the point at which the
transaction was rolled back. The state of the objects is in no way
rolled back and thus the objects are now out of synch with the
database. The application can continue to use the detached objects,
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``.
Locking Support
---------------
Doctrine 2 offers support for Pessimistic- and Optimistic-locking
strategies natively. This allows to take very fine-grained control
over what kind of locking is required for your Entities in your
application.
Optimistic Locking
~~~~~~~~~~~~~~~~~~
Database transactions are fine for concurrency control during a
single request. However, a database transaction should not span
across requests, the so-called "user think time". Therefore a
long-running "business transaction" that spans multiple requests
needs to involve several database transactions. Thus, database
transactions alone can no longer control concurrency during such a
long-running business transaction. Concurrency control becomes the
partial responsibility of the application itself.
Doctrine has integrated support for automatic optimistic locking
via a version field. In this approach any entity that should be
protected against concurrent modifications during long-running
business transactions gets a version field that is either a simple
number (mapping type: integer) or a timestamp (mapping type:
datetime). When changes to such an entity are persisted at the end
of a long-running conversation the version of the entity is
compared to the version in the database and if they don't match, an
``OptimisticLockException`` is thrown, indicating that the entity
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.
.. code-block:: php
<?php
class User
{
// ...
/** @Version @Column(type="integer") */
private $version;
// ...
}
Alternatively a datetime type can be used (which maps to a SQL
timestamp or datetime):
.. code-block:: php
<?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
environment, unlike timestamps where this is a possibility,
depending on the resolution of the timestamp on the particular
database platform.
When a version conflict is encountered during
``EntityManager#flush()``, an ``OptimisticLockException`` is thrown
and the active transaction rolled back (or marked for rollback).
This exception can be caught and handled. Potential responses to an
OptimisticLockException are to present the conflict to the user or
to refresh or reload objects in a new transaction and then retrying
the transaction.
With PHP promoting a share-nothing architecture, the time between
showing an update form and actually modifying the entity can in the
worst scenario be as long as your applications session timeout. If
changes happen to the entity in that time frame you want to know
directly when retrieving the entity that you will hit an optimistic
locking exception:
You can always verify the version of an entity during a request
either when calling ``EntityManager#find()``:
.. code-block:: php
<?php
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\OptimisticLockException;
$theEntityId = 1;
$expectedVersion = 184;
try {
$entity = $em->find('User', $theEntityId, LockMode::OPTIMISTIC, $expectedVersion);
// do the work
$em->flush();
} catch(OptimisticLockException $e) {
echo "Sorry, but someone else has already changed this entity. Please apply the changes again!";
}
Or you can use ``EntityManager#lock()`` to find out:
.. code-block:: php
<?php
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\OptimisticLockException;
$theEntityId = 1;
$expectedVersion = 184;
$entity = $em->find('User', $theEntityId);
try {
// assert version
$em->lock($entity, LockMode::OPTIMISTIC, $expectedVersion);
} catch(OptimisticLockException $e) {
echo "Sorry, but someone else has already changed this entity. Please apply the changes again!";
}
Important Implementation Notes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can easily get the optimistic locking workflow wrong if you
compare the wrong versions. Say you have Alice and Bob editing a
hypothetical blog post:
- Alice reads the headline of the blog post being "Foo", at
optimistic lock version 1 (GET Request)
- Bob reads the headline of the blog post being "Foo", at
optimistic lock version 1 (GET Request)
- Bob updates the headline to "Bar", upgrading the optimistic lock
version to 2 (POST Request of a Form)
- Alice updates the headline to "Baz", ... (POST Request of a
Form)
Now at the last stage of this scenario the blog post has to be read
again from the database before Alice's headline can be applied. At
this point you will want to check if the blog post is still at
version 1 (which it is not in this scenario).
Using optimistic locking correctly, you *have* to add the version
as an additional hidden field (or into the SESSION for more
safety). Otherwise you cannot verify the version is still the one
being originally read from the database when Alice performed her
GET request for the blog post. If this happens you might see lost
updates you wanted to prevent with Optimistic Locking.
See the example code, The form (GET Request):
.. code-block:: php
<?php
$post = $em->find('BlogPost', 123456);
echo '<input type="hidden" name="id" value="' . $post->getId() . '" />';
echo '<input type="hidden" name="version" value="' . $post->getCurrentVersion() . '" />';
And the change headline action (POST Request):
.. code-block:: php
<?php
$postId = (int)$_GET['id'];
$postVersion = (int)$_GET['version'];
$post = $em->find('BlogPost', $postId, \Doctrine\DBAL\LockMode::OPTIMISTIC, $postVersion);
Pessimistic Locking
~~~~~~~~~~~~~~~~~~~
Doctrine 2 supports Pessimistic Locking at the database level. No
attempt is being made to implement pessimistic locking inside
Doctrine, rather vendor-specific and ANSI-SQL commands are used to
acquire row-level locks. Every Entity can be part of a pessimistic
lock, there is no special metadata required to use this feature.
However for Pessimistic Locking to work you have to disable the
Auto-Commit Mode of your Database and start a transaction around
your pessimistic lock use-case using the "Approach 2: Explicit
Transaction Demarcation" described above. Doctrine 2 will throw an
Exception if you attempt to acquire an pessimistic lock and no
transaction is running.
Doctrine 2 currently supports two pessimistic lock modes:
- Pessimistic Write
(``Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE``), locks the
underlying database rows for concurrent Read and Write Operations.
- Pessimistic Read (``Doctrine\DBAL\LockMode::PESSIMISTIC_READ``),
locks other concurrent requests that attempt to update or lock rows
in write mode.
You can use pessimistic locks in three different scenarios:
1. Using
``EntityManager#find($className, $id, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
or
``EntityManager#find($className, $id, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``
2. Using
``EntityManager#lock($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
or
``EntityManager#lock($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``
3. Using
``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
or
``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``

View File

@@ -0,0 +1,60 @@
Association Updates: Owning Side and Inverse Side
=================================================
When mapping bidirectional associations it is important to
understand the concept of the owning and inverse sides. The
following general rules apply:
- Relationships may be bidirectional or unidirectional.
- A bidirectional relationship has both an owning side and an inverse side
- A unidirectional relationship only has an owning side.
- Doctrine will **only** check the owning side of an association for changes.
Bidirectional Associations
--------------------------
The following rules apply to **bidirectional** associations:
- 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 use the ``inversedBy`` attribute of the
OneToOne, ManyToOne, or ManyToMany mapping declaration.
The inversedBy attribute contains the name of the association-field
on the inverse-side.
- ManyToOne is always the owning side of a bidirectional association.
- OneToMany is always the inverse side of a bidirectional association.
- The owning side of a OneToOne association is the entity with the table
containing the foreign key.
- You can pick the owning side of a many-to-many association yourself.
Important concepts
------------------
**Doctrine will only check the owning side of an association for changes.**
To fully understand this, remember how bidirectional associations
are maintained in the object world. There are 2 references on each
side of the association and these 2 references both represent the
same association but can change independently of one another. Of
course, in a correct application the semantics of the bidirectional
association are properly maintained by the application developer
(that's his responsibility). Doctrine needs to know which of these
two in-memory references is the one that should be persisted and
which not. This is what the owning/inverse concept is mainly used
for.
**Changes made only to the inverse side of an association are ignored. Make sure to update both sides of a bidirectional association (or at least the owning side, from Doctrine's point of view)**
The owning side of a bidirectional association is the side Doctrine
"looks at" when determining the state of the association, and
consequently whether there is anything to do to update the
association in the database.
.. note::
"Owning side" and "inverse side" are technical concepts of
the ORM technology, not concepts of your domain model. What you
consider as the owning side in your domain model can be different
from what the owning side is for Doctrine. These are unrelated.

View File

@@ -0,0 +1,201 @@
Doctrine Internals explained
============================
Object relational mapping is a complex topic and sufficiently understanding how Doctrine works internally helps you use its full power.
How Doctrine keeps track of Objects
-----------------------------------
Doctrine uses the Identity Map pattern to track objects. Whenever you fetch an
object from the database, Doctrine will keep a reference to this object inside
its UnitOfWork. The array holding all the entity references is two-levels deep
and has the keys "root entity name" and "id". Since Doctrine allows composite
keys the id is a sorted, serialized version of all the key columns.
This allows Doctrine room for optimizations. If you call the EntityManager and
ask for an entity with a specific ID twice, it will return the same instance:
.. code-block:: php
public function testIdentityMap()
{
$objectA = $this->entityManager->find('EntityName', 1);
$objectB = $this->entityManager->find('EntityName', 1);
$this->assertSame($objectA, $objectB)
}
Only one SELECT query will be fired against the database here. In the second
``EntityManager#find()`` call Doctrine will check the identity map first and
doesn't need to make that database roundtrip.
Even if you get a proxy object first then fetch the object by the same id you
will still end up with the same reference:
.. code-block:: php
public function testIdentityMapReference()
{
$objectA = $this->entityManager->getReference('EntityName', 1);
// check for proxyinterface
$this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $objectA);
$objectB = $this->entityManager->find('EntityName', 1);
$this->assertSame($objectA, $objectB)
}
The identity map being indexed by primary keys only allows shortcuts when you
ask for objects by primary key. Assume you have the following ``persons``
table:
::
id | name
-------------
1 | Benjamin
2 | Bud
Take the following example where two
consecutive calls are made against a repository to fetch an entity by a set of
criteria:
.. code-block:: php
public function testIdentityMapRepositoryFindBy()
{
$repository = $this->entityManager->getRepository('Person');
$objectA = $repository->findOneBy(array('name' => 'Benjamin'));
$objectB = $repository->findOneBy(array('name' => 'Benjamin'));
$this->assertSame($objectA, $objectB);
}
This query will still return the same references and `$objectA` and `$objectB`
are indeed referencing the same object. However when checking your SQL logs you
will realize that two queries have been executed against the database. Doctrine
only knows objects by id, so a query for different criteria has to go to the
database, even if it was executed just before.
But instead of creating a second Person object Doctrine first gets the primary
key from the row and check if it already has an object inside the UnitOfWork
with that primary key. In our example it finds an object and decides to return
this instead of creating a new one.
The identity map has a second use-case. When you call ``EntityManager#flush``
Doctrine will ask the identity map for all objects that are currently managed.
This means you don't have to call ``EntityManager#persist`` over and over again
to pass known objects to the EntityManager. This is a NO-OP for known entities,
but leads to much code written that is confusing to other developers.
The following code WILL update your database with the changes made to the
``Person`` object, even if you did not call ``EntityManager#persist``:
.. code-block:: php
<?php
$user = $entityManager->find("Person", 1);
$user->setName("Guilherme");
$entityManager->flush();
How Doctrine Detects Changes
----------------------------
Doctrine is a data-mapper that tries to achieve persistence-ignorance (PI).
This means you map php objects into a relational database that don't
necessarily know about the database at all. A natural question would now be,
"how does Doctrine even detect objects have changed?".
For this Doctrine keeps a second map inside the UnitOfWork. Whenever you fetch
an object from the database Doctrine will keep a copy of all the properties and
associations inside the UnitOfWork. Because variables in the PHP language are
subject to "copy-on-write" the memory usage of a PHP request that only reads
objects from the database is the same as if Doctrine did not keep this variable
copy. Only if you start changing variables PHP will create new variables internally
that consume new memory.
Now whenever you call ``EntityManager#flush`` Doctrine will iterate over the
Identity Map and for each object compares the original property and association
values with the values that are currently set on the object. If changes are
detected then the object is queued for a SQL UPDATE operation. Only the fields
that actually changed are updated.
This process has an obvious performance impact. The larger the size of the
UnitOfWork is, the longer this computation takes. There are several ways to
optimize the performance of the Flush Operation:
- Mark entities as read only. These entities can only be inserted or removed,
but are never updated. They are omitted in the changeset calculation.
- Temporarily mark entities as read only. If you have a very large UnitOfWork
but know that a large set of entities has not changed, just mark them as read
only with ``$entityManager->getUnitOfWork()->markReadOnly($entity)``.
- Flush only a single entity with ``$entityManager->flush($entity)``.
- Use :doc:`Change Tracking Policies <change-tracking-policies>` to use more
explicit strategies of notifying the UnitOfWork what objects/properties
changed.
Query Internals
---------------
The different ORM Layers
------------------------
Doctrine ships with a set of layers with different responsibilities. This
section gives a short explanation of each layer.
Hydration
~~~~~~~~~
Responsible for creating a final result from a raw database statement and a
result-set mapping object. The developer can choose which kind of result he
wishes to be hydrated. Default result-types include:
- SQL to Entities
- SQL to structured Arrays
- SQL to simple scalar result arrays
- SQL to a single result variable
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.
- Mixed results of objects and scalar values
- Hydration of results by a given scalar value as key.
Persisters
~~~~~~~~~~
tbr
UnitOfWork
~~~~~~~~~~
tbr
ResultSetMapping
~~~~~~~~~~~~~~~~
tbr
DQL Parser
~~~~~~~~~~
tbr
SQLWalker
~~~~~~~~~
tbr
EntityManager
~~~~~~~~~~~~~
tbr
ClassMetadataFactory
~~~~~~~~~~~~~~~~~~~~
tbr

View File

@@ -0,0 +1,712 @@
Working with Associations
=========================
Associations between entities are represented just like in regular
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:
- 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.
- 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
----------------------------
We will use a simple comment system with Users and Comments as
entities to show examples of association management. See the PHP
docblocks of each association in the following example for
information about its type and if it's the owning or inverse side.
.. code-block:: php
<?php
/** @Entity */
class User
{
/** @Id @GeneratedValue @Column(type="string") */
private $id;
/**
* Bidirectional - Many users have Many favorite comments (OWNING SIDE)
*
* @ManyToMany(targetEntity="Comment", inversedBy="userFavorites")
* @JoinTable(name="user_favorite_comments",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="favorite_comment_id", referencedColumnName="id")}
* )
*/
private $favorites;
/**
* Unidirectional - Many users have marked many comments as read
*
* @ManyToMany(targetEntity="Comment")
* @JoinTable(name="user_read_comments",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="comment_id", referencedColumnName="id")}
* )
*/
private $commentsRead;
/**
* Bidirectional - One-To-Many (INVERSE SIDE)
*
* @OneToMany(targetEntity="Comment", mappedBy="author")
*/
private $commentsAuthored;
/**
* Unidirectional - Many-To-One
*
* @ManyToOne(targetEntity="Comment")
*/
private $firstComment;
}
/** @Entity */
class Comment
{
/** @Id @GeneratedValue @Column(type="string") */
private $id;
/**
* Bidirectional - Many comments are favorited by many users (INVERSE SIDE)
*
* @ManyToMany(targetEntity="User", mappedBy="favorites")
*/
private $userFavorites;
/**
* Bidirectional - Many Comments are authored by one user (OWNING SIDE)
*
* @ManyToOne(targetEntity="User", inversedBy="commentsAuthored")
*/
private $author;
}
This two entities generate the following MySQL Schema (Foreign Key
definitions omitted):
.. code-block:: sql
CREATE TABLE User (
id VARCHAR(255) NOT NULL,
firstComment_id VARCHAR(255) DEFAULT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE Comment (
id VARCHAR(255) NOT NULL,
author_id VARCHAR(255) DEFAULT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE user_favorite_comments (
user_id VARCHAR(255) NOT NULL,
favorite_comment_id VARCHAR(255) NOT NULL,
PRIMARY KEY(user_id, favorite_comment_id)
) ENGINE = InnoDB;
CREATE TABLE user_read_comments (
user_id VARCHAR(255) NOT NULL,
comment_id VARCHAR(255) NOT NULL,
PRIMARY KEY(user_id, comment_id)
) ENGINE = InnoDB;
Establishing Associations
-------------------------
Establishing an association between two entities is
straight-forward. Here are some examples for the unidirectional
relations of the ``User``:
.. code-block:: php
<?php
class User
{
// ...
public function getReadComments() {
return $this->commentsRead;
}
public function setFirstComment(Comment $c) {
$this->firstComment = $c;
}
}
The interaction code would then look like in the following snippet
(``$em`` here is an instance of the EntityManager):
.. code-block:: php
<?php
$user = $em->find('User', $userId);
// unidirectional many to many
$comment = $em->find('Comment', $readCommentId);
$user->getReadComments()->add($comment);
$em->flush();
// unidirectional many to one
$myFirstComment = new Comment();
$user->setFirstComment($myFirstComment);
$em->persist($myFirstComment);
$em->flush();
In the case of bi-directional associations you have to update the
fields on both sides:
.. code-block:: php
<?php
class User
{
// ..
public function getAuthoredComments() {
return $this->commentsAuthored;
}
public function getFavoriteComments() {
return $this->favorites;
}
}
class Comment
{
// ...
public function getUserFavorites() {
return $this->userFavorites;
}
public function setAuthor(User $author = null) {
$this->author = $author;
}
}
// Many-to-Many
$user->getFavorites()->add($favoriteComment);
$favoriteComment->getUserFavorites()->add($user);
$em->flush();
// Many-To-One / One-To-Many Bidirectional
$newComment = new Comment();
$user->getAuthoredComments()->add($newComment);
$newComment->setAuthor($user);
$em->persist($newComment);
$em->flush();
Notice how always both sides of the bidirectional association are
updated. The previous unidirectional associations were simpler to
handle.
Removing Associations
---------------------
Removing an association between two entities is similarly
straight-forward. There are two strategies to do so, by key and by
element. Here are some examples:
.. code-block:: php
<?php
// Remove by Elements
$user->getComments()->removeElement($comment);
$comment->setAuthor(null);
$user->getFavorites()->removeElement($comment);
$comment->getUserFavorites()->removeElement($user);
// Remove by Key
$user->getComments()->remove($ithComment);
$comment->setAuthor(null);
You need to call ``$em->flush()`` to make persist these changes in
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, 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
``remove`` operation accepts an index/key. ``removeElement`` is a
separate method that has O(n) complexity using ``array_search``,
where n is the size of the map.
.. note::
Since Doctrine always only looks at the owning side of a
bidirectional association for updates, it is not necessary for
write operations that an inverse collection of a bidirectional
one-to-many or many-to-many association is updated. This knowledge
can often be used to improve performance by avoiding the loading of
the inverse collection.
You can also clear the contents of a whole collection using the
``Collections::clear()`` method. You should be aware that using
this method can lead to a straight and optimized database delete or
update call during the flush operation that is not aware of
entities that have been re-added to the collection.
Say you clear a collection of tags by calling
``$post->getTags()->clear();`` and then call
``$post->getTags()->add($tag)``. This will not recognize the tag having
already been added previously and will consequently issue two separate database
calls.
Association Management Methods
------------------------------
It is generally a good idea to encapsulate proper association
management inside the entity classes. This makes it easier to use
the class correctly and can encapsulate details about how the
association is maintained.
The following code shows updates to the previous User and Comment
example that encapsulate much of the association management code:
.. code-block:: php
<?php
class User
{
//...
public function markCommentRead(Comment $comment) {
// Collections implement ArrayAccess
$this->commentsRead[] = $comment;
}
public function addComment(Comment $comment) {
if (count($this->commentsAuthored) == 0) {
$this->setFirstComment($comment);
}
$this->comments[] = $comment;
$comment->setAuthor($this);
}
private function setFirstComment(Comment $c) {
$this->firstComment = $c;
}
public function addFavorite(Comment $comment) {
$this->favorites->add($comment);
$comment->addUserFavorite($this);
}
public function removeFavorite(Comment $comment) {
$this->favorites->removeElement($comment);
$comment->removeUserFavorite($this);
}
}
class Comment
{
// ..
public function addUserFavorite(User $user) {
$this->userFavorites[] = $user;
}
public function removeUserFavorite(User $user) {
$this->userFavorites->removeElement($user);
}
}
You will notice that ``addUserFavorite`` and ``removeUserFavorite``
do not call ``addFavorite`` and ``removeFavorite``, thus the
bidirectional association is strictly-speaking still incomplete.
However if you would naively add the ``addFavorite`` in
``addUserFavorite``, you end up with an infinite loop, so more work
is needed. As you can see, proper bidirectional association
management in plain OOP is a non-trivial task and encapsulating all
the details inside the classes can be challenging.
.. note::
If you want to make sure that your collections are perfectly
encapsulated you should not return them from a
``getCollectionName()`` method directly, but call
``$collection->toArray()``. This way a client programmer for the
entity cannot circumvent the logic you implement on your entity for
association management. For example:
.. code-block:: php
<?php
class User {
public function getReadComments() {
return $this->commentsRead->toArray();
}
}
This will however always initialize the collection, with all the
performance penalties given the size. In some scenarios of large
collections it might even be a good idea to completely hide the
read access behind methods on the EntityRepository.
There is no single, best way for association management. It greatly
depends on the requirements of your concrete domain model as well
as your preferences.
Synchronizing Bidirectional Collections
---------------------------------------
In the case of Many-To-Many associations you as the developer have the
responsibility of keeping the collections on the owning and inverse side
in sync when you apply changes to them. Doctrine can only
guarantee a consistent state for the hydration, not for your client
code.
Using the User-Comment entities from above, a very simple example
can show the possible caveats you can encounter:
.. code-block:: php
<?php
$user->getFavorites()->add($favoriteComment);
// not calling $favoriteComment->getUserFavorites()->add($user);
$user->getFavorites()->contains($favoriteComment); // TRUE
$favoriteComment->getUserFavorites()->contains($user); // FALSE
There are two approaches to handle this problem in your code:
1. Ignore updating the inverse side of bidirectional collections,
BUT never read from them in requests that changed their state. In
the next Request Doctrine hydrates the consistent collection state
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 / Cascade Operations
-------------------------------------------
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 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
<?php
$user = new User();
$myFirstComment = new Comment();
$user->addComment($myFirstComment);
$em->persist($user);
$em->persist($myFirstComment);
$em->flush();
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
$user = $em->find('User', $deleteUserId);
foreach ($user->getAuthoredComments() AS $comment) {
$em->remove($comment);
}
$em->remove($user);
$em->flush();
Without the loop over all the authored comments Doctrine would use
an UPDATE statement only to set the foreign key to NULL and only
the User would be deleted from the database during the
flush()-Operation.
To have Doctrine handle both cases automatically we can change the
``User#commentsAuthored`` property to cascade both the "persist"
and the "remove" operation.
.. code-block:: php
<?php
class User
{
//...
/**
* Bidirectional - One-To-Many (INVERSE SIDE)
*
* @OneToMany(targetEntity="Comment", mappedBy="author", cascade={"persist", "remove"})
*/
private $commentsAuthored;
//...
}
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
applies that operation to the association, be it single or
collection valued.
Persistence by Reachability: Cascade Persist
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are additional semantics that apply to the Cascade Persist
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
directly persisted by Doctrine.
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.
Orphan Removal
--------------
There is another concept of cascading that is relevant only when removing entities
from collections. If an Entity of type ``A`` contains references to privately
owned Entities ``B`` then if the reference from ``A`` to ``B`` is removed the
entity ``B`` should also be removed, because it is not used anymore.
OrphanRemoval works with one-to-one, one-to-many and many-to-many associations.
.. note::
When using the ``orphanRemoval=true`` option Doctrine makes the assumption
that the entities are privately owned and will **NOT** be reused by other entities.
If you neglect this assumption your entities will get deleted by Doctrine even if
you assigned the orphaned entity to another one.
As a better example consider an Addressbook application where you have Contacts, Addresses
and StandingData:
.. code-block:: php
<?php
namespace Addressbook;
use Doctrine\Common\Collections\ArrayCollection;
/**
* @Entity
*/
class Contact
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @OneToOne(targetEntity="StandingData", orphanRemoval=true) */
private $standingData;
/** @OneToMany(targetEntity="Address", mappedBy="contact", orphanRemoval=true) */
private $addresses;
public function __construct()
{
$this->addresses = new ArrayCollection();
}
public function newStandingData(StandingData $sd)
{
$this->standingData = $sd;
}
public function removeAddress($pos)
{
unset($this->addresses[$pos]);
}
}
Now two examples of what happens when you remove the references:
.. code-block:: php
<?php
$contact = $em->find("Addressbook\Contact", $contactId);
$contact->newStandingData(new StandingData("Firstname", "Lastname", "Street"));
$contact->removeAddress(1);
$em->flush();
In this case you have not only changed the ``Contact`` entity itself but
you have also removed the references for standing data and as well as one
address reference. When flush is called not only are the references removed
but both the old standing data and the one address entity are also deleted
from the database.
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
large collections.
.. code-block:: php
<?php
use Doctrine\Common\Collections\Criteria;
$group = $entityManager->find('Group', $groupId);
$userCollection = $group->getUsers();
$criteria = Criteria::create()
->where(Criteria::expr()->eq("birthday", "1982-02-17"))
->orderBy(array("username" => "ASC"))
->setFirstResult(0)
->setMaxResults(20)
;
$birthdayUsers = $userCollection->matching($criteria);
.. tip::
You can move the access of slices of collections into dedicated methods of
an entity. For example ``Group#getTodaysBirthdayUsers()``.
The Criteria has a limited matching language that works both on the
SQL and on the PHP collection level. This means you can use collection matching
interchangeably, independent of in-memory or sql-backed collections.
.. code-block:: php
<?php
use Doctrine\Common\Collections;
class Criteria
{
/**
* @return Criteria
*/
static public function create();
/**
* @param Expression $where
* @return Criteria
*/
public function where(Expression $where);
/**
* @param Expression $where
* @return Criteria
*/
public function andWhere(Expression $where);
/**
* @param Expression $where
* @return Criteria
*/
public function orWhere(Expression $where);
/**
* @param array $orderings
* @return Criteria
*/
public function orderBy(array $orderings);
/**
* @param int $firstResult
* @return Criteria
*/
public function setFirstResult($firstResult);
/**
* @param int $maxResults
* @return Criteria
*/
public function setMaxResults($maxResults);
public function getOrderings();
public function getWhereExpression();
public function getFirstResult();
public function getMaxResults();
}
You can build expressions through the ExpressionBuilder. It has the following
methods:
* ``andX($arg1, $arg2, ...)``
* ``orX($arg1, $arg2, ...)``
* ``eq($field, $value)``
* ``gt($field, $value)``
* ``lt($field, $value)``
* ``lte($field, $value)``
* ``gte($field, $value)``
* ``neq($field, $value)``
* ``isNull($field)``
* ``in($field, array $values)``
* ``notIn($field, array $values)``

View File

@@ -0,0 +1,850 @@
Working with Objects
====================
In this chapter we will help you understand the ``EntityManager``
and the ``UnitOfWork``. A Unit of Work is similar to an
object-level transaction. A new Unit of Work is implicitly started
when an EntityManager is initially created or after
``EntityManager#flush()`` has been invoked. A Unit of Work is
committed (and a new one started) by invoking
``EntityManager#flush()``.
A Unit of Work can be manually closed by calling
EntityManager#close(). Any changes to objects within this Unit of
Work that have not yet been persisted are lost.
.. note::
It is very important to understand that only
``EntityManager#flush()`` ever causes write operations against the
database to be executed. Any other methods such as
``EntityManager#persist($entity)`` or
``EntityManager#remove($entity)`` only notify the UnitOfWork to
perform these operations during flush.
Not calling ``EntityManager#flush()`` will lead to all changes
during that request being lost.
Entities and the Identity Map
-----------------------------
Entities are objects with identity. Their identity has a conceptual
meaning inside your domain. In a CMS application each article has a
unique id. You can uniquely identify each article by that id.
Take the following example, where you find an article with the
headline "Hello World" with the ID 1234:
.. code-block:: php
<?php
$article = $entityManager->find('CMS\Article', 1234);
$article->setHeadline('Hello World dude!');
$article2 = $entityManager->find('CMS\Article', 1234);
echo $article2->getHeadline();
In this case the Article is accessed from the entity manager twice,
but modified in between. Doctrine 2 realizes this and will only
ever give you access to one instance of the Article with ID 1234,
no matter how often do you retrieve it from the EntityManager and
even no matter what kind of Query method you are using (find,
Repository Finder or DQL). This is called "Identity Map" pattern,
which means Doctrine keeps a map of each entity and ids that have
been retrieved per PHP request and keeps returning you the same
instances.
In the previous example the echo prints "Hello World dude!" to the
screen. You can even verify that ``$article`` and ``$article2`` are
indeed pointing to the same instance by running the following
code:
.. code-block:: php
<?php
if ($article === $article2) {
echo "Yes we are the same!";
}
Sometimes you want to clear the identity map of an EntityManager to
start over. We use this regularly in our unit-tests to enforce
loading objects from the database again instead of serving them
from the identity map. You can call ``EntityManager#clear()`` to
achieve this result.
Entity Object Graph Traversal
-----------------------------
Although Doctrine allows for a complete separation of your domain
model (Entity classes) there will never be a situation where
objects are "missing" when traversing associations. You can walk
all the associations inside your entity models as deep as you
want.
Take the following example of a single ``Article`` entity fetched
from newly opened EntityManager.
.. code-block:: php
<?php
/** @Entity */
class Article
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @Column(type="string") */
private $headline;
/** @ManyToOne(targetEntity="User") */
private $author;
/** @OneToMany(targetEntity="Comment", mappedBy="article") */
private $comments;
public function __construct {
$this->comments = new ArrayCollection();
}
public function getAuthor() { return $this->author; }
public function getComments() { return $this->comments; }
}
$article = $em->find('Article', 1);
This code only retrieves the ``Article`` instance with id 1 executing
a single SELECT statement against the user table in the database.
You can still access the associated properties author and comments
and the associated objects they contain.
This works by utilizing the lazy loading pattern. Instead of
passing you back a real Author instance and a collection of
comments Doctrine will create proxy instances for you. Only if you
access these proxies for the first time they will go through the
EntityManager and load their state from the database.
This lazy-loading process happens behind the scenes, hidden from
your code. See the following code:
.. code-block:: php
<?php
$article = $em->find('Article', 1);
// accessing a method of the user instance triggers the lazy-load
echo "Author: " . $article->getAuthor()->getName() . "\n";
// Lazy Loading Proxies pass instanceof tests:
if ($article->getAuthor() instanceof User) {
// a User Proxy is a generated "UserProxy" class
}
// accessing the comments as an iterator triggers the lazy-load
// retrieving ALL the comments of this article from the database
// using a single SELECT statement
foreach ($article->getComments() AS $comment) {
echo $comment->getText() . "\n\n";
}
// Article::$comments passes instanceof tests for the Collection interface
// But it will NOT pass for the ArrayCollection interface
if ($article->getComments() instanceof \Doctrine\Common\Collections\Collection) {
echo "This will always be true!";
}
A slice of the generated proxy classes code looks like the
following piece of code. A real proxy class override ALL public
methods along the lines of the ``getName()`` method shown below:
.. code-block:: php
<?php
class UserProxy extends User implements Proxy
{
private function _load()
{
// lazy loading code
}
public function getName()
{
$this->_load();
return parent::getName();
}
// .. other public methods of User
}
.. warning::
Traversing the object graph for parts that are lazy-loaded will
easily trigger lots of SQL queries and will perform badly if used
to heavily. Make sure to use DQL to fetch-join all the parts of the
object-graph that you need as efficiently as possible.
Persisting entities
-------------------
An entity can be made persistent by passing it to the
``EntityManager#persist($entity)`` method. By applying the persist
operation on some entity, that entity becomes MANAGED, which means
that its persistence is from now on managed by an EntityManager. As
a result the persistent state of such an entity will subsequently
be properly synchronized with the database when
``EntityManager#flush()`` is invoked.
.. note::
Invoking the ``persist`` method on an entity does NOT
cause an immediate SQL INSERT to be issued on the database.
Doctrine applies a strategy called "transactional write-behind",
which means that it will delay most SQL commands until
``EntityManager#flush()`` is invoked which will then issue all
necessary SQL statements to synchronize your objects with the
database in the most efficient way and a single, short transaction,
taking care of maintaining referential integrity.
Example:
.. code-block:: php
<?php
$user = new User;
$user->setName('Mr.Right');
$em->persist($user);
$em->flush();
.. note::
Generated entity identifiers / primary keys are
guaranteed to be available after the next successful flush
operation that involves the entity in question. You can not rely on
a generated identifier to be available directly after invoking
``persist``. The inverse is also true. You can not rely on a
generated identifier being not available after a failed flush
operation.
The semantics of the persist operation, applied on an entity X, are
as follows:
- If X is a new entity, it becomes managed. The entity X will be
entered into the database as a result of the flush operation.
- If X is a preexisting managed entity, it is ignored by the
persist operation. However, the persist operation is cascaded to
entities referenced by X, if the relationships from X to these
other entities are mapped with cascade=PERSIST or cascade=ALL (see
"Transitive Persistence").
- If X is a removed entity, it becomes managed.
- If X is a detached entity, an exception will be thrown on
flush.
Removing entities
-----------------
An entity can be removed from persistent storage by passing it to
the ``EntityManager#remove($entity)`` method. By applying the
``remove`` operation on some entity, that entity becomes REMOVED,
which means that its persistent state will be deleted once
``EntityManager#flush()`` is invoked.
.. note::
Just like ``persist``, invoking ``remove`` on an entity
does NOT cause an immediate SQL DELETE to be issued on the
database. The entity will be deleted on the next invocation of
``EntityManager#flush()`` that involves that entity. This
means that entities scheduled for removal can still be queried
for and appear in query and collection results. See
the section on :ref:`Database and UnitOfWork Out-Of-Sync <workingobjects_database_uow_outofsync>`
for more information.
Example:
.. code-block:: php
<?php
$em->remove($user);
$em->flush();
The semantics of the remove operation, applied to an entity X are
as follows:
- If X is a new entity, it is ignored by the remove operation.
However, the remove operation is cascaded to entities referenced by
X, if the relationship from X to these other entities is mapped
with cascade=REMOVE or cascade=ALL (see "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
"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.
- A removed entity X will be removed from the database as a result
of the flush operation.
After an entity has been removed its in-memory state is the same as
before the removal, except for generated identifiers.
Removing an entity will also automatically delete any existing
records in many-to-many join tables that link this entity. The
action taken depends on the value of the ``@joinColumn`` mapping
attribute "onDelete". Either Doctrine issues a dedicated ``DELETE``
statement for records of each join table or it depends on the
foreign key semantics of onDelete="CASCADE".
Deleting an object with all its associated objects can be achieved
in multiple ways with very different performance impacts.
1. If an association is marked as ``CASCADE=REMOVE`` Doctrine 2
will fetch this association. If its a Single association it will
pass this entity to
´EntityManager#remove()``. If the association is a collection, Doctrine will loop over all its elements and pass them to``EntityManager#remove()\`.
In both cases the cascade remove semantics are applied recursively.
For large object graphs this removal strategy can be very costly.
2. Using a DQL ``DELETE`` statement allows you to delete multiple
entities of a type with a single command and without hydrating
these entities. This can be very efficient to delete large object
graphs from the database.
3. Using foreign key semantics ``onDelete="CASCADE"`` can force the
database to remove all associated objects internally. This strategy
is a bit tricky to get right but can be very powerful and fast. You
should be aware however that using strategy 1 (``CASCADE=REMOVE``)
completely by-passes any foreign key ``onDelete=CASCADE`` option,
because Doctrine will fetch and remove all associated entities
explicitly nevertheless.
Detaching entities
------------------
An entity is detached from an EntityManager and thus no longer
managed by invoking the ``EntityManager#detach($entity)`` method on
it or by cascading the detach operation to it. Changes made to the
detached entity, if any (including removal of the entity), will not
be synchronized to the database after the entity has been
detached.
Doctrine will not hold on to any references to a detached entity.
Example:
.. code-block:: php
<?php
$em->detach($entity);
The semantics of the detach operation, applied to an entity X are
as follows:
- If X is a managed entity, the detach operation causes it to
become detached. The detach operation is cascaded to entities
referenced by X, if the relationships from X to these other
entities is mapped with cascade=DETACH or cascade=ALL (see
"Transitive Persistence"). Entities which previously referenced X
will continue to reference X.
- If X is a new or detached entity, it is ignored by the detach
operation.
- If X is a removed entity, the detach operation is cascaded to
entities referenced by X, if the relationships from X to these
other entities is mapped with cascade=DETACH or cascade=ALL (see
"Transitive Persistence"). Entities which previously referenced X
will continue to reference X.
There are several situations in which an entity is detached
automatically without invoking the ``detach`` method:
- When ``EntityManager#clear()`` is invoked, all entities that are
currently managed by the EntityManager instance become detached.
- When serializing an entity. The entity retrieved upon subsequent
unserialization will be detached (This is the case for all entities
that are serialized and stored in some cache, i.e. when using the
Query Result Cache).
The ``detach`` operation is usually not as frequently needed and
used as ``persist`` and ``remove``.
Merging entities
----------------
Merging entities refers to the merging of (usually detached)
entities into the context of an EntityManager so that they become
managed again. To merge the state of an entity into an
EntityManager use the ``EntityManager#merge($entity)`` method. The
state of the passed entity will be merged into a managed copy of
this entity and this copy will subsequently be returned.
Example:
.. code-block:: php
<?php
$detachedEntity = unserialize($serializedEntity); // some detached entity
$entity = $em->merge($detachedEntity);
// $entity now refers to the fully managed copy returned by the merge operation.
// The EntityManager $em now manages the persistence of $entity as usual.
.. note::
When you want to serialize/unserialize entities you
have to make all entity properties protected, never private. The
reason for this is, if you serialize a class that was a proxy
instance before, the private variables won't be serialized and a
PHP Notice is thrown.
The semantics of the merge operation, applied to an entity X, are
as follows:
- If X is a detached entity, the state of X is copied onto a
pre-existing managed entity instance X' of the same identity.
- If X is a new entity instance, a new managed copy X' will be
created and the state of X is copied onto this managed instance.
- If X is a removed entity instance, an InvalidArgumentException
will be thrown.
- If X is a managed entity, it is ignored by the merge operation,
however, the merge operation is cascaded to entities referenced by
relationships from X if these relationships have been mapped with
the cascade element value MERGE or ALL (see "Transitive
Persistence").
- For all entities Y referenced by relationships from X having the
cascade element value MERGE or ALL, Y is merged recursively as Y'.
For all such Y referenced by X, X' is set to reference Y'. (Note
that if X is managed then X is the same object as X'.)
- If X is an entity merged to X', with a reference to another
entity Y, where cascade=MERGE or cascade=ALL is not specified, then
navigation of the same association from X' yields a reference to a
managed object Y' with the same persistent identity as Y.
The ``merge`` operation will throw an ``OptimisticLockException``
if the entity being merged uses optimistic locking through a
version field and the versions of the entity being merged and the
managed copy don't match. This usually means that the entity has
been modified while being detached.
The ``merge`` operation is usually not as frequently needed and
used as ``persist`` and ``remove``. The most common scenario for
the ``merge`` operation is to reattach entities to an EntityManager
that come from some cache (and are therefore detached) and you want
to modify and persist such an entity.
.. warning::
If you need to perform multiple merges of entities that share certain subparts
of their object-graphs and cascade merge, then you have to call ``EntityManager#clear()`` between the
successive calls to ``EntityManager#merge()``. Otherwise you might end up with
multiple copies of the "same" object in the database, however with different ids.
.. note::
If you load some detached entities from a cache and you do
not need to persist or delete them or otherwise make use of them
without the need for persistence services there is no need to use
``merge``. I.e. you can simply pass detached objects from a cache
directly to the view.
Synchronization with the Database
---------------------------------
The state of persistent entities is synchronized with the database
on flush of an ``EntityManager`` which commits the underlying
``UnitOfWork``. The synchronization involves writing any updates to
persistent entities and their relationships to the database.
Thereby bidirectional relationships are persisted based on the
references held by the owning side of the relationship as explained
in the Association Mapping chapter.
When ``EntityManager#flush()`` is called, Doctrine inspects all
managed, new and removed entities and will perform the following
operations.
.. _workingobjects_database_uow_outofsync:
Effects of Database and UnitOfWork being Out-Of-Sync
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As soon as you begin to change the state of entities, call persist or remove the
contents of the UnitOfWork and the database will drive out of sync. They can
only be synchronized by calling ``EntityManager#flush()``. This section
describes the effects of database and UnitOfWork being out of sync.
- Entities that are scheduled for removal can still be queried from the database.
They are returned from DQL and Repository queries and are visible in collections.
- Entities that are passed to ``EntityManager#persist`` do not turn up in query
results.
- Entities that have changed will not be overwritten with the state from the database.
This is because the identity map will detect the construction of an already existing
entity and assumes its the most up to date version.
``EntityManager#flush()`` is never called implicitly by Doctrine. You always have to trigger it manually.
Synchronizing New and Managed Entities
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The flush operation applies to a managed entity with the following
semantics:
- The entity itself is synchronized to the database using a SQL
UPDATE statement, only if at least one persistent field has
changed.
- No SQL updates are executed if the entity did not change.
The flush operation applies to a new entity with the following
semantics:
- The entity itself is synchronized to the database using a SQL
INSERT statement.
For all (initialized) relationships of the new or managed entity
the following semantics apply to each associated entity X:
- If X is new and persist operations are configured to cascade on
the relationship, X will be persisted.
- If X is new and no persist operations are configured to cascade
on the relationship, an exception will be thrown as this indicates
a programming error.
- If X is removed and persist operations are configured to cascade
on the relationship, an exception will be thrown as this indicates
a programming error (X would be re-persisted by the cascade).
- If X is detached and persist operations are configured to
cascade on the relationship, an exception will be thrown (This is
semantically the same as passing X to persist()).
Synchronizing Removed Entities
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The flush operation applies to a removed entity by deleting its
persistent state from the database. No cascade options are relevant
for removed entities on flush, the cascade remove option is already
executed during ``EntityManager#remove($entity)``.
The size of a Unit of Work
~~~~~~~~~~~~~~~~~~~~~~~~~~
The size of a Unit of Work mainly refers to the number of managed
entities at a particular point in time.
The cost of flushing
~~~~~~~~~~~~~~~~~~~~
How costly a flush operation is, mainly depends on two factors:
- The size of the EntityManager's current UnitOfWork.
- The configured change tracking policies
You can get the size of a UnitOfWork as follows:
.. code-block:: php
<?php
$uowSize = $em->getUnitOfWork()->size();
The size represents the number of managed entities in the Unit of
Work. This size affects the performance of flush() operations due
to change tracking (see "Change Tracking Policies") and, of course,
memory consumption, so you may want to check it from time to time
during development.
.. note::
Do not invoke ``flush`` after every change to an entity
or every single invocation of persist/remove/merge/... This is an
anti-pattern and unnecessarily reduces the performance of your
application. Instead, form units of work that operate on your
objects and call ``flush`` when you are done. While serving a
single HTTP request there should be usually no need for invoking
``flush`` more than 0-2 times.
Direct access to a Unit of Work
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can get direct access to the Unit of Work by calling
``EntityManager#getUnitOfWork()``. This will return the UnitOfWork
instance the EntityManager is currently using.
.. code-block:: php
<?php
$uow = $em->getUnitOfWork();
.. note::
Directly manipulating a UnitOfWork is not recommended.
When working directly with the UnitOfWork API, respect methods
marked as INTERNAL by not using them and carefully read the API
documentation.
Entity State
~~~~~~~~~~~~
As outlined in the architecture overview an entity can be in one of
four possible states: NEW, MANAGED, REMOVED, DETACHED. If you
explicitly need to find out what the current state of an entity is
in the context of a certain ``EntityManager`` you can ask the
underlying ``UnitOfWork``:
.. code-block:: php
<?php
switch ($em->getUnitOfWork()->getEntityState($entity)) {
case UnitOfWork::STATE_MANAGED:
...
case UnitOfWork::STATE_REMOVED:
...
case UnitOfWork::STATE_DETACHED:
...
case UnitOfWork::STATE_NEW:
...
}
An entity is in MANAGED state if it is associated with an
``EntityManager`` and it is not REMOVED.
An entity is in REMOVED state after it has been passed to
``EntityManager#remove()`` until the next flush operation of the
same EntityManager. A REMOVED entity is still associated with an
``EntityManager`` until the next flush operation.
An entity is in DETACHED state if it has persistent state and
identity but is currently not associated with an
``EntityManager``.
An entity is in NEW state if has no persistent state and identity
and is not associated with an ``EntityManager`` (for example those
just created via the "new" operator).
Querying
--------
Doctrine 2 provides the following ways, in increasing level of
power and flexibility, to query for persistent objects. You should
always start with the simplest one that suits your needs.
By Primary Key
~~~~~~~~~~~~~~
The most basic way to query for a persistent object is by its
identifier / primary key using the
``EntityManager#find($entityName, $id)`` method. Here is an
example:
.. code-block:: php
<?php
// $em instanceof EntityManager
$user = $em->find('MyProject\Domain\User', $id);
The return value is either the found entity instance or null if no
instance could be found with the given identifier.
Essentially, ``EntityManager#find()`` is just a shortcut for the
following:
.. code-block:: php
<?php
// $em instanceof EntityManager
$user = $em->getRepository('MyProject\Domain\User')->find($id);
``EntityManager#getRepository($entityName)`` returns a repository
object which provides many ways to retrieve entities of the
specified type. By default, the repository instance is of type
``Doctrine\ORM\EntityRepository``. You can also use custom
repository classes as shown later.
By Simple Conditions
~~~~~~~~~~~~~~~~~~~~
To query for one or more entities based on several conditions that
form a logical conjunction, use the ``findBy`` and ``findOneBy``
methods on a repository as follows:
.. code-block:: php
<?php
// $em instanceof EntityManager
// All users that are 20 years old
$users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20));
// All users that are 20 years old and have a surname of 'Miller'
$users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20, 'surname' => 'Miller'));
// A single user by its nickname
$user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb'));
You can also load by owning side associations through the repository:
.. code-block:: php
<?php
$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
<?php
$tenUsers = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20), array('name' => 'ASC'), 10, 0);
If you pass an array of values Doctrine will convert the query into a WHERE field IN (..) query automatically:
.. code-block:: php
<?php
$users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => array(20, 30, 40)));
// translates roughly to: SELECT * FROM users WHERE age IN (20, 30, 40)
An EntityRepository also provides a mechanism for more concise
calls through its use of ``__call``. Thus, the following two
examples are equivalent:
.. code-block:: php
<?php
// A single user by its nickname
$user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb'));
// A single user by its nickname (__call magic)
$user = $em->getRepository('MyProject\Domain\User')->findOneByNickname('romanb');
By Criteria
~~~~~~~~~~~
.. versionadded:: 2.3
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 the :ref:`Working with Associations: Filtering collections
<filtering-collections>`.
By Eager Loading
~~~~~~~~~~~~~~~~
Whenever you query for an entity that has persistent associations
and these associations are mapped as EAGER, they will automatically
be loaded together with the entity being queried and is thus
immediately available to your application.
By Lazy Loading
~~~~~~~~~~~~~~~
Whenever you have a managed entity instance at hand, you can
traverse and use any associations of that entity that are
configured LAZY as if they were in-memory already. Doctrine will
automatically load the associated objects on demand through the
concept of lazy-loading.
By DQL
~~~~~~
The most powerful and flexible method to query for persistent
objects is the Doctrine Query Language, an object query language.
DQL enables you to query for persistent objects in the language of
objects. DQL understands classes, fields, inheritance and
associations. DQL is syntactically very similar to the familiar SQL
but *it is not SQL*.
A DQL query is represented by an instance of the
``Doctrine\ORM\Query`` class. You create a query using
``EntityManager#createQuery($dql)``. Here is a simple example:
.. code-block:: php
<?php
// $em instanceof EntityManager
// All users with an age between 20 and 30 (inclusive).
$q = $em->createQuery("select u from MyDomain\Model\User u where u.age >= 20 and u.age <= 30");
$users = $q->getResult();
Note that this query contains no knowledge about the relational
schema, only about the object model. DQL supports positional as
well as named parameters, many functions, (fetch) joins,
aggregates, subqueries and much more. Detailed information about
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. More information on
constructing queries with a QueryBuilder can be found
:doc:`in Query Builder chapter <query-builder>`.
By Native Queries
~~~~~~~~~~~~~~~~~
As an alternative to DQL or as a fallback for special SQL
statements native queries can be used. Native queries are built by
using a hand-crafted SQL query and a ResultSetMapping that
describes how the SQL result set should be transformed by Doctrine.
More information about native queries can be found in
:doc:`the dedicated chapter <native-sql>`.
Custom Repositories
~~~~~~~~~~~~~~~~~~~
By default the EntityManager returns a default implementation of
``Doctrine\ORM\EntityRepository`` when you call
``EntityManager#getRepository($entityClass)``. You can overwrite
this behaviour by specifying the class name of your own Entity
Repository in the Annotation, XML or YAML metadata. In large
applications that require lots of specialized DQL queries using a
custom repository is one recommended way of grouping these queries
in a central location.
.. code-block:: php
<?php
namespace MyDomain\Model;
use Doctrine\ORM\EntityRepository;
/**
* @entity(repositoryClass="MyDomain\Model\UserRepository")
*/
class User
{
}
class UserRepository extends EntityRepository
{
public function getAllAdminUsers()
{
return $this->_em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"')
->getResult();
}
}
You can access your repository now by calling:
.. code-block:: php
<?php
// $em instanceof EntityManager
$admins = $em->getRepository('MyDomain\Model\User')->getAllAdminUsers();

View File

@@ -0,0 +1,747 @@
XML Mapping
===========
The XML mapping driver enables you to provide the ORM metadata in
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
`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
XML mapping files is to use an IDE/editor that can provide
code-completion based on such an XML Schema document. The following
is an outline of a XML mapping document with the proper xmlns/xsi
setup for the latest code in trunk.
.. code-block:: xml
<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://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
...
</doctrine-mapping>
The XML mapping document of a class is loaded on-demand the first
time it is requested and subsequently stored in the metadata cache.
In order to work, this requires certain conventions:
- Each entity/mapped superclass must get its own dedicated XML
mapping document.
- The name of the mapping document must consist of the fully
qualified name of the class, where namespace separators are
replaced by dots (.). For example an Entity with the fully
qualified class-name "MyProject" would require a mapping file
"MyProject.Entities.User.dcm.xml" unless the extension is changed.
- All mapping documents should get the extension ".dcm.xml" to
identify it as a Doctrine mapping file. This is more of a
convention and you are not forced to do this. You can change the
file extension easily enough.
-
.. code-block:: php
<?php
$driver->setFileExtension('.xml');
It is recommended to put all XML mapping documents in a single
folder but you can spread the documents over several folders if you
want to. In order to tell the XmlDriver where to look for your
mapping documents, supply an array of paths as the first argument
of the constructor, like this:
.. code-block:: php
<?php
$config = new \Doctrine\ORM\Configuration();
$driver = new \Doctrine\ORM\Mapping\Driver\XmlDriver(array('/path/to/files1', '/path/to/files2'));
$config->setMetadataDriverImpl($driver);
Simplified XML Driver
~~~~~~~~~~~~~~~~~~~~~
The Symfony project sponsored a driver that simplifies usage of the XML Driver.
The changes between the original driver are:
1. File Extension is .orm.xml
2. Filenames are shortened, "MyProject\Entities\User" will become User.orm.xml
3. You can add a global file and add multiple entities in this file.
Configuration of this client works a little bit different:
.. code-block:: php
<?php
$namespaces = array(
'MyProject\Entities' => '/path/to/files1',
'OtherProject\Entities' => '/path/to/files2'
);
$driver = new \Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver($namespaces);
$driver->setGlobalBasename('global'); // global.orm.xml
Example
-------
As a quick start, here is a small example document that makes use
of several common elements:
.. code-block:: xml
// Doctrine.Tests.ORM.Mapping.User.dcm.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
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/>
</cascade>
<order-by>
<order-by-field name="number" direction="ASC" />
</order-by>
</one-to-many>
<many-to-many field="groups" target-entity="Group">
<cascade>
<cascade-all/>
</cascade>
<join-table name="cms_users_groups">
<join-columns>
<join-column name="user_id" referenced-column-name="id" nullable="false" unique="false" />
</join-columns>
<inverse-join-columns>
<join-column name="group_id" referenced-column-name="id" column-definition="INT NULL" />
</inverse-join-columns>
</join-table>
</many-to-many>
</entity>
</doctrine-mapping>
Be aware that class-names specified in the XML files should be
fully qualified.
XML-Element Reference
---------------------
The XML-Element reference explains all the tags and attributes that
the Doctrine Mapping XSD Schema defines. You should read the
Basic-, Association- and Inheritance Mapping chapters to understand
what each of this definitions means in detail.
Defining an Entity
~~~~~~~~~~~~~~~~~~
Each XML Mapping File contains the definition of one entity,
specified as the ``<entity />`` element as a direct child of the
``<doctrine-mapping />`` element:
.. code-block:: xml
<doctrine-mapping>
<entity name="MyProject\User" table="cms_users" repository-class="MyProject\UserRepository">
<!-- definition here -->
</entity>
</doctrine-mapping>
Required attributes:
- name - The fully qualified class-name of the entity.
Optional attributes:
- **table** - The Table-Name to be used for this entity. Otherwise the
Unqualified Class-Name is used by default.
- **repository-class** - The fully qualified class-name of an
alternative ``Doctrine\ORM\EntityRepository`` implementation to be
used with this entity.
- **inheritance-type** - The type of inheritance, defaults to none. A
more detailed description follows in the
*Defining Inheritance Mappings* section.
- **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.
Defining Fields
~~~~~~~~~~~~~~~
Each entity class can contain zero to infinite fields that are
managed by Doctrine. You can define them using the ``<field />``
element as a children to the ``<entity />`` element. The field
element is only used for primitive types that are not the ID of the
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" />
</entity>
Required attributes:
- name - The name of the Property/Field on the given Entity PHP
class.
Optional attributes:
- type - The ``Doctrine\DBAL\Types\Type`` name, defaults to
"string"
- column - Name of the column in the database, defaults to the
field name.
- length - The length of the given type, for use with strings
only.
- unique - Should this field contain a unique value across the
table? Defaults to false.
- nullable - Should this field allow NULL as a value? Defaults to
false.
- version - Should this field be used for optimistic locking? Only
works on fields with type integer or datetime.
- scale - Scale of a decimal type.
- precision - Precision of a decimal type.
- 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.
Defining Identity and Generator Strategies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An entity has to have at least one ``<id />`` element. For
composite keys you can specify more than one id-element, however
surrogate keys are recommended for use with Doctrine 2. The Id
field allows to define properties of the identifier and allows a
subset of the ``<field />`` element attributes:
.. code-block:: xml
<entity name="MyProject\User">
<id name="id" type="integer" column="user_id" />
</entity>
Required attributes:
- name - The name of the Property/Field on the given Entity PHP
class.
- type - The ``Doctrine\DBAL\Types\Type`` name, preferably
"string" or "integer".
Optional attributes:
- column - Name of the column in the database, defaults to the
field name.
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
``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 ``ASSIGNED`` strategy.
.. code-block:: xml
<entity name="MyProject\User">
<id name="id" type="integer" column="user_id">
<generator strategy="AUTO" />
</id>
</entity>
The following values are allowed for the ``<generator />`` strategy
attribute:
- AUTO - Automatic detection of the identifier strategy based on
the preferred solution of the database vendor.
- IDENTITY - Use of a IDENTIFY strategy such as Auto-Increment IDs
available to Doctrine AFTER the INSERT statement has been executed.
- SEQUENCE - Use of a database sequence to retrieve the
entity-ids. This is possible before the INSERT statement is
executed.
If you are using the SEQUENCE strategy you can define an additional
element to describe the sequence:
.. code-block:: xml
<entity name="MyProject\User">
<id name="id" type="integer" column="user_id">
<generator strategy="SEQUENCE" />
<sequence-generator sequence-name="user_seq" allocation-size="5" initial-value="1" />
</id>
</entity>
Required attributes for ``<sequence-generator />``:
- sequence-name - The name of the sequence
Optional attributes for ``<sequence-generator />``:
- allocation-size - By how much steps should the sequence be
incremented when a value is retrieved. Defaults to 1
- initial-value - What should the initial value of the sequence
be.
**NOTE**
If you want to implement a cross-vendor compatible application you
have to specify and additionally define the <sequence-generator />
element, if Doctrine chooses the sequence strategy for a
platform.
Defining a Mapped Superclass
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sometimes you want to define a class that multiple entities inherit
from, which itself is not an entity however. The chapter on
*Inheritance Mapping* describes a Mapped Superclass in detail. You
can define it in XML using the ``<mapped-superclass />`` tag.
.. code-block:: xml
<doctrine-mapping>
<mapped-superclass name="MyProject\BaseClass">
<field name="created" type="datetime" />
<field name="updated" type="datetime" />
</mapped-superclass>
</doctrine-mapping>
Required attributes:
- name - Class name of the mapped superclass.
You can nest any number of ``<field />`` and unidirectional
``<many-to-one />`` or ``<one-to-one />`` associations inside a
mapped superclass.
Defining Inheritance Mappings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are currently two inheritance persistence strategies that you
can choose from when defining entities that inherit from each
other. Single Table inheritance saves the fields of the complete
inheritance hierarchy in a single table, joined table inheritance
creates a table for each entity combining the fields using join
conditions.
You can specify the inheritance type in the ``<entity />`` element
and then use the ``<discriminator-column />`` and
``<discriminator-mapping />`` attributes.
.. code-block:: xml
<entity name="MyProject\Animal" inheritance-type="JOINED">
<discriminator-column name="discr" type="string" />
<discriminator-map>
<discriminator-mapping value="cat" class="MyProject\Cat" />
<discriminator-mapping value="dog" class="MyProject\Dog" />
<discriminator-mapping value="mouse" class="MyProject\Mouse" />
</discriminator-map>
</entity>
The allowed values for inheritance-type attribute are ``JOINED`` or
``SINGLE_TABLE``.
.. note::
All inheritance related definitions have to be defined on the root
entity of the hierarchy.
Defining Lifecycle Callbacks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can define the lifecycle callback methods on your entities
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>
</entity>
Defining One-To-One Relations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can define One-To-One Relations/Associations using the
``<one-to-one />`` element. The required and optional attributes
depend on the associations being on the inverse or owning side.
For the inverse side the mapping is as simple as:
.. code-block:: xml
<entity class="MyProject\User">
<one-to-one field="address" target-entity="Address" mapped-by="user" />
</entity>
Required attributes for inverse One-To-One:
- field - Name of the property/field on the entity's PHP class.
- target-entity - Name of the entity associated entity class. If
this is not qualified the namespace of the current class is
prepended. *IMPORTANT:* No leading backslash!
- mapped-by - Name of the field on the owning side (here Address
entity) that contains the owning side association.
For the owning side this mapping would look like:
.. code-block:: xml
<entity class="MyProject\Address">
<one-to-one field="user" target-entity="User" inversed-by="address" />
</entity>
Required attributes for owning One-to-One:
- field - Name of the property/field on the entity's PHP class.
- target-entity - Name of the entity associated entity class. If
this is not qualified the namespace of the current class is
prepended. *IMPORTANT:* No leading backslash!
Optional attributes for owning One-to-One:
- inversed-by - If the association is bidirectional the
inversed-by attribute has to be specified with the name of the
field on the inverse entity that contains the back-reference.
- orphan-removal - If true, the inverse side entity is always
deleted when the owning side entity is. Defaults to false.
- fetch - Either LAZY or EAGER, defaults to LAZY. This attribute
makes only sense on the owning side, the inverse side *ALWAYS* has
to use the ``FETCH`` strategy.
The definition for the owning side relies on a bunch of mapping
defaults for the join column names. Without the nested
``<join-column />`` element Doctrine assumes to foreign key to be
called ``user_id`` on the Address Entities table. This is because
the ``MyProject\Address`` entity is the owning side of this
association, which means it contains the foreign key.
The completed explicitly defined mapping is:
.. code-block:: xml
<entity class="MyProject\Address">
<one-to-one field="user" target-entity="User" inversed-by="address">
<join-column name="user_id" referenced-column-name="id" />
</one-to-one>
</entity>
Defining Many-To-One Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The many-to-one association is *ALWAYS* the owning side of any
bidirectional association. This simplifies the mapping compared to
the one-to-one case. The minimal mapping for this association looks
like:
.. code-block:: xml
<entity class="MyProject\Article">
<many-to-one field="author" target-entity="User" />
</entity>
Required attributes:
- field - Name of the property/field on the entity's PHP class.
- target-entity - Name of the entity associated entity class. If
this is not qualified the namespace of the current class is
prepended. *IMPORTANT:* No leading backslash!
Optional attributes:
- inversed-by - If the association is bidirectional the
inversed-by attribute has to be specified with the name of the
field on the inverse entity that contains the back-reference.
- orphan-removal - If true the entity on the inverse side is
always deleted when the owning side entity is and it is not
connected to any other owning side entity anymore. Defaults to
false.
- fetch - Either LAZY or EAGER, defaults to LAZY.
This definition relies on a bunch of mapping defaults with regards
to the naming of the join-column/foreign key. The explicitly
defined mapping includes a ``<join-column />`` tag nested inside
the many-to-one association tag:
.. code-block:: xml
<entity class="MyProject\Article">
<many-to-one field="author" target-entity="User">
<join-column name="author_id" referenced-column-name="id" />
</many-to-one>
</entity>
The join-column attribute ``name`` specifies the column name of the
foreign key and the ``referenced-column-name`` attribute specifies
the name of the primary key column on the User entity.
Defining One-To-Many Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The one-to-many association is *ALWAYS* the inverse side of any
association. There exists no such thing as a uni-directional
one-to-many association, which means this association only ever
exists for bi-directional associations.
.. code-block:: xml
<entity class="MyProject\User">
<one-to-many field="phonenumbers" target-entity="Phonenumber" mapped-by="user" />
</entity>
Required attributes:
- field - Name of the property/field on the entity's PHP class.
- target-entity - Name of the entity associated entity class. If
this is not qualified the namespace of the current class is
prepended. *IMPORTANT:* No leading backslash!
- mapped-by - Name of the field on the owning side (here
Phonenumber entity) that contains the owning side association.
Optional attributes:
- fetch - Either LAZY, EXTRA_LAZY or EAGER, defaults to LAZY.
- index-by: Index the collection by a field on the target entity.
Defining Many-To-Many Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
From all the associations the many-to-many has the most complex
definition. When you rely on the mapping defaults you can omit many
definitions and rely on their implicit values.
.. code-block:: xml
<entity class="MyProject\User">
<many-to-many field="groups" target-entity="Group" />
</entity>
Required attributes:
- field - Name of the property/field on the entity's PHP class.
- target-entity - Name of the entity associated entity class. If
this is not qualified the namespace of the current class is
prepended. *IMPORTANT:* No leading backslash!
Optional attributes:
- mapped-by - Name of the field on the owning side that contains
the owning side association if the defined many-to-many association
is on the inverse side.
- inversed-by - If the association is bidirectional the
inversed-by attribute has to be specified with the name of the
field on the inverse entity that contains the back-reference.
- fetch - Either LAZY, EXTRA_LAZY or EAGER, defaults to LAZY.
- index-by: Index the collection by a field on the target entity.
The mapping defaults would lead to a join-table with the name
"User\_Group" being created that contains two columns "user\_id"
and "group\_id". The explicit definition of this mapping would be:
.. code-block:: xml
<entity class="MyProject\User">
<many-to-many field="groups" target-entity="Group">
<join-table name="cms_users_groups">
<join-columns>
<join-column name="user_id" referenced-column-name="id"/>
</join-columns>
<inverse-join-columns>
<join-column name="group_id" referenced-column-name="id"/>
</inverse-join-columns>
</join-table>
</many-to-many>
</entity>
Here both the ``<join-columns>`` and ``<inverse-join-columns>``
tags are necessary to tell Doctrine for which side the specified
join-columns apply. These are nested inside a ``<join-table />``
attribute which allows to specify the table name of the
many-to-many join-table.
Cascade Element
~~~~~~~~~~~~~~~
Doctrine allows cascading of several UnitOfWork operations to
related entities. You can specify the cascade operations in the
``<cascade />`` element inside any of the association mapping
tags.
.. code-block:: xml
<entity class="MyProject\User">
<many-to-many field="groups" target-entity="Group">
<cascade>
<cascade-all/>
</cascade>
</many-to-many>
</entity>
Besides ``<cascade-all />`` the following operations can be
specified by their respective tags:
- ``<cascade-persist />``
- ``<cascade-merge />``
- ``<cascade-remove />``
- ``<cascade-refresh />``
Join Column Element
~~~~~~~~~~~~~~~~~~~
In any explicitly defined association mapping you will need the
``<join-column />`` tag. It defines how the foreign key and primary
key names are called that are used for joining two entities.
Required attributes:
- name - The column name of the foreign key.
- referenced-column-name - The column name of the associated
entities primary key
Optional attributes:
- unique - If the join column should contain a UNIQUE constraint.
This makes sense for Many-To-Many join-columns only to simulate a
one-to-many unidirectional using a join-table.
- nullable - should the join column be nullable, defaults to true.
- on-delete - Foreign Key Cascade action to perform when entity is
deleted, defaults to NO ACTION/RESTRICT but can be set to
"CASCADE".
Defining Order of To-Many Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can require one-to-many or many-to-many associations to be
retrieved using an additional ``ORDER BY``.
.. code-block:: xml
<entity class="MyProject\User">
<many-to-many field="groups" target-entity="Group">
<order-by>
<order-by-field name="name" direction="ASC" />
</order-by>
</many-to-many>
</entity>
Defining Indexes or Unique Constraints
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To define additional indexes or unique constraints on the entities
table you can use the ``<indexes />`` and
``<unique-constraints />`` elements:
.. 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>
</entity>
You have to specify the column and not the entity-class field names
in the index and unique-constraint definitions.
Derived Entities ID syntax
~~~~~~~~~~~~~~~~~~~~~~~~~~
If the primary key of an entity contains a foreign key to another entity we speak of a derived
entity relationship. You can define this in XML with the "association-key" attribute in the ``<id>`` tag.
.. code-block:: xml
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
<entity name="Application\Model\ArticleAttribute">
<id name="article" association-key="true" />
<id name="attribute" type="string" />
<field name="value" type="string" />
<many-to-one field="article" target-entity="Article" inversed-by="attributes" />
<entity>
</doctrine-mapping>

View File

@@ -0,0 +1,117 @@
YAML Mapping
============
The YAML mapping driver enables you to provide the ORM metadata in
form of YAML documents.
The YAML mapping document of a class is loaded on-demand the first
time it is requested and subsequently stored in the metadata cache.
In order to work, this requires certain conventions:
- Each entity/mapped superclass must get its own dedicated YAML
mapping document.
- The name of the mapping document must consist of the fully
qualified name of the class, where namespace separators are
replaced by dots (.).
- All mapping documents should get the extension ".dcm.yml" to
identify it as a Doctrine mapping file. This is more of a
convention and you are not forced to do this. You can change the
file extension easily enough.
.. code-block:: php
<?php
$driver->setFileExtension('.yml');
It is recommended to put all YAML mapping documents in a single
folder but you can spread the documents over several folders if you
want to. In order to tell the YamlDriver where to look for your
mapping documents, supply an array of paths as the first argument
of the constructor, like this:
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\Driver\YamlDriver;
// $config instanceof Doctrine\ORM\Configuration
$driver = new YamlDriver(array('/path/to/files'));
$config->setMetadataDriverImpl($driver);
Simplified YAML Driver
~~~~~~~~~~~~~~~~~~~~~~
The Symfony project sponsored a driver that simplifies usage of the YAML Driver.
The changes between the original driver are:
- File Extension is .orm.yml
- Filenames are shortened, "MyProject\\Entities\\User" will become User.orm.yml
- You can add a global file and add multiple entities in this file.
Configuration of this client works a little bit different:
.. code-block:: php
<?php
$namespaces = array(
'/path/to/files1' => 'MyProject\Entities',
'/path/to/files2' => 'OtherProject\Entities'
);
$driver = new \Doctrine\ORM\Mapping\Driver\SimplifiedYamlDriver($namespaces);
$driver->setGlobalBasename('global'); // global.orm.yml
Example
-------
As a quick start, here is a small example document that makes use
of several common elements:
.. code-block:: yaml
# Doctrine.Tests.ORM.Mapping.User.dcm.yml
Doctrine\Tests\ORM\Mapping\User:
type: entity
table: cms_users
indexes:
name_index:
columns: [ name ]
id:
id:
type: integer
generator:
strategy: AUTO
fields:
name:
type: string
length: 50
oneToOne:
address:
targetEntity: Address
joinColumn:
name: address_id
referencedColumnName: id
oneToMany:
phonenumbers:
targetEntity: Phonenumber
mappedBy: user
cascade: ["persist", "merge"]
manyToMany:
groups:
targetEntity: Group
joinTable:
name: cms_users_groups
joinColumns:
user_id:
referencedColumnName: id
inverseJoinColumns:
group_id:
referencedColumnName: id
lifecycleCallbacks:
prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ]
postPersist: [ doStuffOnPostPersist ]
Be aware that class-names specified in the YAML files should be
fully qualified.

80
docs/en/toc.rst Normal file
View File

@@ -0,0 +1,80 @@
Welcome to Doctrine 2 ORM's documentation!
==========================================
Tutorials
---------
.. toctree::
:maxdepth: 1
tutorials/getting-started
tutorials/working-with-indexed-associations
tutorials/extra-lazy-associations
tutorials/composite-primary-keys
tutorials/ordered-associations
tutorials/in-ten-quick-steps
tutorials/override-field-association-mappings-in-subclasses
Reference Guide
---------------
.. toctree::
:maxdepth: 1
:numbered:
reference/introduction
reference/architecture
reference/configuration
reference/faq
reference/basic-mapping
reference/association-mapping
reference/inheritance-mapping
reference/working-with-objects
reference/working-with-associations
reference/events
reference/unitofwork
reference/unitofwork-associations
reference/transactions-and-concurrency
reference/batch-processing
reference/dql-doctrine-query-language
reference/query-builder
reference/native-sql
reference/change-tracking-policies
reference/partial-objects
reference/xml-mapping
reference/yaml-mapping
reference/annotations-reference
reference/php-mapping
reference/caching
reference/improving-performance
reference/tools
reference/metadata-drivers
reference/best-practices
reference/limitations-and-known-issues
tutorials/pagination.rst
reference/filters.rst
reference/namingstrategy.rst
Cookbook
--------
.. toctree::
:maxdepth: 1
cookbook/aggregate-fields
cookbook/decorator-pattern
cookbook/dql-custom-walkers
cookbook/dql-user-defined-functions
cookbook/implementing-arrayaccess-for-domain-objects
cookbook/implementing-the-notify-changetracking-policy
cookbook/implementing-wakeup-or-clone
cookbook/integrating-with-codeigniter
cookbook/sql-table-prefixes
cookbook/strategy-cookbook-introduction
cookbook/validation-of-entities
cookbook/working-with-datetime
cookbook/mysql-enums
cookbook/advanced-field-value-conversion-using-custom-mapping-types
cookbook/entities-in-session

View File

@@ -0,0 +1,375 @@
Composite and Foreign Keys as Primary Key
=========================================
.. versionadded:: 2.1
Doctrine 2 supports composite primary keys natively. Composite keys are a very powerful relational database concept
and we took good care to make sure Doctrine 2 supports as many of the composite primary key use-cases.
For Doctrine 2.0 composite keys of primitive data-types are supported, for Doctrine 2.1 even foreign keys as
primary keys are supported.
This tutorial shows how the semantics of composite primary keys work and how they map to the database.
General Considerations
~~~~~~~~~~~~~~~~~~~~~~
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
~~~~~~~~~~~~~~~~~~~~
Even in version 2.0 you can have composite keys as long as they only consist of the primitive types
``integer`` and ``string``. Suppose you want to create a database of cars and use the model-name
and year of production as primary keys:
.. configuration-block::
.. code-block:: php
<?php
namespace VehicleCatalogue\Model;
/**
* @Entity
*/
class Car
{
/** @Id @Column(type="string") */
private $name;
/** @Id @Column(type="integer") */
private $year
public function __construct($name, $year)
{
$this->name = $name;
$this->year = $year;
}
public function getModelName()
{
return $this->name;
}
public function getYearOfProduction()
{
return $this->year;
}
}
.. 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
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="VehicleCatalogue\Model\Car">
<id field="name" type="string" />
<id field="year" type="integer" />
</entity>
</doctrine-mapping>
.. code-block:: yaml
VehicleCatalogue\Model\Car:
type: entity
id:
name:
type: string
year:
type: integer
Now you can use this entity:
.. code-block:: php
<?php
namespace VehicleCatalogue\Model;
// $em is the EntityManager
$car = new Car("Audi A8", 2010);
$em->persist($car);
$em->flush();
And for querying you can use arrays to both DQL and EntityRepositories:
.. code-block:: php
<?php
namespace VehicleCatalogue\Model;
// $em is the EntityManager
$audi = $em->find("VehicleCatalogue\Model\Car", array("name" => "Audi A8", "year" => 2010));
$dql = "SELECT c FROM VehicleCatalogue\Model\Car c WHERE c.id = ?1";
$audi = $em->createQuery($dql)
->setParameter(1, array("name" => "Audi A8", "year" => 2010))
->getSingleResult();
You can also use this entity in associations. Doctrine will then generate two foreign keys one for ``name``
and to ``year`` to the related entities.
.. note::
This example shows how you can nicely solve the requirement for existing
values before ``EntityManager#persist()``: By adding them as mandatory values for the constructor.
Identity through foreign Entities
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. note::
Identity through foreign entities is only supported with Doctrine 2.1
There are tons of use-cases where the identity of an Entity should be determined by the entity
of one or many parent entities.
- Dynamic Attributes of an Entity (for example Article). Each Article has many
attributes with primary key "article_id" and "attribute_name".
- Address object of a Person, the primary key of the address is "user_id". This is not a case of a composite primary
key, but the identity is derived through a foreign entity and a foreign key.
- Join Tables with metadata can be modelled as Entity, for example connections between two articles
with a little description and a score.
The semantics of mapping identity through foreign entities are easy:
- Only allowed on Many-To-One or One-To-One associations.
- Plug an ``@Id`` annotation onto every association.
- Set an attribute ``association-key`` with the field name of the association in XML.
- Set a key ``associationKey:`` with the field name of the association in YAML.
Use-Case 1: Dynamic Attributes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We keep up the example of an Article with arbitrary attributes, the mapping looks like this:
.. configuration-block::
.. code-block:: php
<?php
namespace Application\Model;
use Doctrine\Common\Collections\ArrayCollection;
/**
* @Entity
*/
class Article
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @Column(type="string") */
private $title;
/**
* @OneToMany(targetEntity="ArticleAttribute", mappedBy="article", cascade={"ALL"}, indexBy="attribute")
*/
private $attributes;
public function addAttribute($name, $value)
{
$this->attributes[$name] = new ArticleAttribute($name, $value, $this);
}
}
/**
* @Entity
*/
class ArticleAttribute
{
/** @Id @ManyToOne(targetEntity="Article", inversedBy="attributes") */
private $article;
/** @Id @Column(type="string") */
private $attribute;
/** @Column(type="string") */
private $value;
public function __construct($name, $value, $article)
{
$this->attribute = $name;
$this->value = $value;
$this->article = $article;
}
}
.. code-block:: xml
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Application\Model\ArticleAttribute">
<id name="article" association-key="true" />
<id name="attribute" type="string" />
<field name="value" type="string" />
<many-to-one field="article" target-entity="Article" inversed-by="attributes" />
<entity>
</doctrine-mapping>
.. code-block:: yaml
Application\Model\ArticleAttribute:
type: entity
id:
article:
associationKey: true
attribute:
type: string
fields:
value:
type: string
manyToOne:
article:
targetEntity: Article
inversedBy: attributes
Use-Case 2: Simple Derived Identity
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sometimes you have the requirement that two objects are related by a One-To-One association
and that the dependent class should re-use the primary key of the class it depends on.
One good example for this is a user-address relationship:
.. configuration-block::
.. code-block:: php
<?php
/**
* @Entity
*/
class User
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
}
/**
* @Entity
*/
class Address
{
/** @Id @OneToOne(targetEntity="User") */
private $user;
}
.. code-block:: yaml
User:
type: entity
id:
id:
type: integer
generator:
strategy: AUTO
Address:
type: entity
id:
user:
associationKey: true
oneToOne:
user:
targetEntity: User
Use-Case 3: Join-Table with Metadata
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In the classic order product shop example there is the concept of the order item
which contains references to order and product and additional data such as the amount
of products purchased and maybe even the current price.
.. code-block:: php
<?php
use Doctrine\Common\Collections\ArrayCollection;
/** @Entity */
class Order
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @ManyToOne(targetEntity="Customer") */
private $customer;
/** @OneToMany(targetEntity="OrderItem", mappedBy="order") */
private $items;
/** @Column(type="boolean") */
private $payed = false;
/** @Column(type="boolean") */
private $shipped = false;
/** @Column(type="datetime") */
private $created;
public function __construct(Customer $customer)
{
$this->customer = $customer;
$this->items = new ArrayCollection();
$this->created = new \DateTime("now");
}
}
/** @Entity */
class Product
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @Column(type="string") */
private $name;
/** @Column(type="decimal") */
private $currentPrice;
public function getCurrentPrice()
{
return $this->currentPrice;
}
}
/** @Entity */
class OrderItem
{
/** @Id @ManyToOne(targetEntity="Order") */
private $order;
/** @Id @ManyToOne(targetEntity="Product") */
private $product;
/** @Column(type="integer") */
private $amount = 1;
/** @Column(type="decimal") */
private $offeredPrice;
public function __construct(Order $order, Product $product, $amount = 1)
{
$this->order = $order;
$this->product = $product;
$this->offeredPrice = $product->getCurrentPrice();
}
}
Performance Considerations
~~~~~~~~~~~~~~~~~~~~~~~~~~
Using composite keys always comes with a performance hit compared to using entities with
a simple surrogate key. This performance impact is mostly due to additional PHP code that is
necessary to handle this kind of keys, most notably when using derived identifiers.
On the SQL side there is not much overhead as no additional or unexpected queries have to be
executed to manage entities with derived foreign keys.

View File

@@ -0,0 +1,84 @@
Extra Lazy Associations
=======================
.. versionadded:: 2.1
In many cases associations between entities can get pretty large. Even in a simple scenario like a blog.
where posts can be commented, you always have to assume that a post draws hundreds of comments.
In Doctrine 2.0 if you accessed an association it would always get loaded completely into memory. This
can lead to pretty serious performance problems, if your associations contain several hundreds or thousands
of entities.
With Doctrine 2.1 a feature called **Extra Lazy** is introduced for associations. Associations
are marked as **Lazy** by default, which means the whole collection object for an association is populated
the first time its accessed. If you mark an association as extra lazy the following methods on collections
can be called without triggering a full load of the collection:
- ``Collection#contains($entity)``
- ``Collection#count()``
- ``Collection#slice($offset, $length = null)``
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.
Additionally even with Doctrine 2.0 the following methods do not trigger the collection load:
- ``Collection#add($entity)``
- ``Collection#offsetSet($key, $entity)`` - ArrayAccess with no specific key ``$coll[] = $entity``, it does
not work when setting specific keys like ``$coll[0] = $entity``.
With extra lazy collections you can now not only add entities to large collections but also paginate them
easily using a combination of ``count`` and ``slice``.
Enabling Extra-Lazy Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The mapping configuration is simple. Instead of using the default value of ``fetch="LAZY"`` you have to
switch to extra lazy as shown in these examples:
.. configuration-block::
.. code-block:: php
<?php
namespace Doctrine\Tests\Models\CMS;
/**
* @Entity
*/
class CmsGroup
{
/**
* @ManyToMany(targetEntity="CmsUser", mappedBy="groups", fetch="EXTRA_LAZY")
*/
public $users;
}
.. 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
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\CMS\CmsGroup">
<!-- ... -->
<many-to-many field="users" target-entity="CmsUser" mapped-by="groups" fetch="EXTRA_LAZY" />
</entity>
</doctrine-mapping>
.. code-block:: yaml
Doctrine\Tests\Models\CMS\CmsGroup:
type: entity
# ...
manyToMany:
users:
targetEntity: CmsUser
mappedBy: groups
fetch: EXTRA_LAZY

View File

@@ -0,0 +1,27 @@
Getting Started: Database First
===============================
.. note:: *Development Workflows*
When you :doc:`Code First <getting-started>`, you
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 :doc:`Database First <getting-started-database>`, you already have a database schema
and generate the corresponding PHP code from it.
.. note::
This getting started guide is in development.
Development of new applications often starts with an existing database schema.
When the database schema is the starting point for your application, then
development is said to use the *Database First* approach to Doctrine.
In this workflow you would modify the database schema first and then
regenerate the PHP code to use with this schema. You need a flexible
code-generator for this task and up to Doctrine 2.2, the code generator hasn't
been flexible enough to achieve this.
We spinned off a subproject, Doctrine CodeGenerator, that will fill this gap and
allow you to do *Database First* development.

View File

@@ -0,0 +1,24 @@
Getting Started: Model First
============================
.. note:: *Development Workflows*
When you :doc:`Code First <getting-started>`, you
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 :doc:`Database First <getting-started-database>`, then you already have a database schema
and generate the corresponding PHP code from it.
.. note::
This getting started guide is in development.
There are applications when you start with a high-level description of the
model using modelling tools such as UML. Modelling tools could also be Excel,
XML or CSV files that describe the model in some structured way. If your
application is using a modelling tool, then the development workflow is said to
be a *Model First* approach to Doctrine2.
In this workflow you always change the model description and then regenerate
both PHP code and database schema from this model.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,110 @@
Ordering To-Many Associations
-----------------------------
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 an collection that specifies
an DQL snippet that is appended to all queries with this
collection.
Additional to any ``@OneToMany`` or ``@ManyToMany`` annotation you
can specify the ``@OrderBy`` in the following way:
.. configuration-block::
.. code-block:: php
<?php
/** @Entity **/
class User
{
// ...
/**
* @ManyToMany(targetEntity="Group")
* @OrderBy({"name" = "ASC"})
**/
private $groups;
}
.. code-block:: xml
<doctrine-mapping>
<entity name="User">
<many-to-many field="groups" target-entity="Group">
<order-by>
<order-by-field name="name" direction="ASC" />
</order-by>
</many-to-many>
</entity>
</doctrine-mapping>
.. code-block:: yaml
User:
type: entity
manyToMany:
groups:
orderBy: { 'name': 'ASC' }
targetEntity: Group
joinTable:
name: users_groups
joinColumns:
user_id:
referencedColumnName: id
inverseJoinColumns:
group_id:
referencedColumnName: id
The DQL Snippet in OrderBy is only allowed to consist of
unqualified, unquoted field names and of an optional ASC/DESC
positional statement. Multiple Fields are separated by a comma (,).
The referenced field names have to exist on the ``targetEntity``
class of the ``@ManyToMany`` or ``@OneToMany`` annotation.
The semantics of this feature can be described as follows.
- ``@OrderBy`` acts as an implicit ORDER BY clause for the given
fields, that is appended to all the explicitly given ORDER BY
items.
- All collections of the ordered type are always retrieved in an
ordered fashion.
- To keep the database impact low, these implicit ORDER BY items
are only added to an DQL Query if the collection is fetch joined in
the DQL query.
Given our previously defined example, the following would not add
ORDER BY, since g is not fetch joined:
.. code-block:: sql
SELECT u FROM User u JOIN u.groups g WHERE SIZE(g) > 10
However the following:
.. code-block:: sql
SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10
...would internally be rewritten to:
.. code-block:: sql
SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name ASC
You can reverse the order with an explicit DQL ORDER BY:
.. code-block:: sql
SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC
...is internally rewritten to:
.. code-block:: sql
SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC, g.name ASC

View File

@@ -0,0 +1,90 @@
Override Field Association Mappings In Subclasses
-------------------------------------------------
Sometimes there is a need to persist entities but override all or part of the
mapping metadata. Sometimes also the mapping to override comes from entities
using traits where the traits have mapping metadata.
This tutorial explains how to override mapping metadata,
i.e. attributes and associations metadata in particular. The example here shows
the overriding of a class that uses a trait but is similar when extending a base
class as shown at the end of this tutorial.
Suppose we have a class ExampleEntityWithOverride. This class uses trait ExampleTrait:
.. code-block:: php
<?php
/**
* @Entity
*
* @AttributeOverrides({
* @AttributeOverride(name="foo",
* column=@Column(
* name = "foo_overridden",
* type = "integer",
* length = 140,
* nullable = false,
* unique = false
* )
* )
* })
*
* @AssociationOverrides({
* @AssociationOverride(name="bar",
* joinColumns=@JoinColumn(
* name="example_entity_overridden_bar_id", referencedColumnName="id"
* )
* )
* })
*/
class ExampleEntityWithOverride
{
use ExampleTrait;
}
/**
* @Entity
*/
class Bar
{
/** @Id @Column(type="string") */
private $id;
}
The docblock is showing metadata override of the attribute and association type. It
basically changes the names of the columns mapped for a property ``foo`` and for
the association ``bar`` which relates to Bar class shown above. Here is the trait
which has mapping metadata that is overridden by the annotation above:
.. code-block:: php
/**
* Trait class
*/
trait ExampleTrait
{
/** @Id @Column(type="string") */
private $id;
/**
* @Column(name="trait_foo", type="integer", length=100, nullable=true, unique=true)
*/
protected $foo;
/**
* @OneToOne(targetEntity="Bar", cascade={"persist", "merge"})
* @JoinColumn(name="example_trait_bar_id", referencedColumnName="id")
*/
protected $bar;
}
The case for just extending a class would be just the same but:
.. code-block:: php
class ExampleEntityWithOverride extends BaseEntityWithSomeMapping
{
// ...
}
Overriding is also supported via XML and YAML.

View File

@@ -0,0 +1,43 @@
Pagination
==========
.. versionadded:: 2.2
Starting with version 2.2 Doctrine ships with a Paginator for DQL queries. It
has a very simple API and implements the SPL interfaces ``Countable`` and
``IteratorAggregate``.
.. code-block:: php
<?php
use Doctrine\ORM\Tools\Pagination\Paginator;
$dql = "SELECT p, c FROM BlogPost p JOIN p.comments c";
$query = $entityManager->createQuery($dql)
->setFirstResult(0)
->setMaxResults(100);
$paginator = new Paginator($query, $fetchJoinCollection = true);
$c = count($paginator);
foreach ($paginator as $post) {
echo $post->getHeadline() . "\n";
}
Paginating Doctrine queries is not as simple as you might think in the
beginning. If you have complex fetch-join scenarios with one-to-many or
many-to-many associations using the "default" LIMIT functionality of database
vendors is not sufficient to get the correct results.
By default the pagination extension does the following steps to compute the
correct result:
- Perform a Count query using `DISTINCT` keyword.
- Perform a Limit Subquery with `DISTINCT` to find all ids of the entity in from on the current page.
- Perform a WHERE IN query to get all results for the current page.
This behavior is only necessary if you actually fetch join a to-many
collection. You can disable this behavior by setting the
``$fetchJoinCollection`` flag to ``false``; in that case only 2 instead of the 3 queries
described are executed. We hope to automate the detection for this in
the future.

View File

@@ -0,0 +1,298 @@
Working with Indexed Associations
=================================
.. note::
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``
was used. Starting with Doctrine 2.1 you can index your collections by a value in the related entity.
This is a first step towards full ordered hashmap support through the Doctrine ORM.
The feature works like an implicit ``INDEX BY`` for the selected association but has several
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 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.
As an example we will design a simple stock exchange list view. The domain consists of the entity ``Stock``
and ``Market`` where each Stock has a symbol and is traded on a single market. Instead of having a numerical
list of stocks traded on a market they will be indexed by their symbol, which is unique across all markets.
Mapping Indexed Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can map indexed associations by adding:
* ``indexBy`` attribute to any ``@OneToMany`` or ``@ManyToMany`` annotation.
* ``index-by`` attribute to any ``<one-to-many />`` or ``<many-to-many />`` xml element.
* ``indexBy:`` key-value pair to any association defined in ``manyToMany:`` or ``oneToMany:`` YAML mapping files.
The code and mappings for the Market entity looks like this:
.. configuration-block::
.. code-block:: php
<?php
namespace Doctrine\Tests\Models\StockExchange;
use Doctrine\Common\Collections\ArrayCollection;
/**
* @Entity
* @Table(name="exchange_markets")
*/
class Market
{
/**
* @Id @Column(type="integer") @GeneratedValue
* @var int
*/
private $id;
/**
* @Column(type="string")
* @var string
*/
private $name;
/**
* @OneToMany(targetEntity="Stock", mappedBy="market", indexBy="symbol")
* @var Stock[]
*/
private $stocks;
public function __construct($name)
{
$this->name = $name;
$this->stocks = new ArrayCollection();
}
public function getId()
{
return $this->id;
}
public function getName()
{
return $this->name;
}
public function addStock(Stock $stock)
{
$this->stocks[$stock->getSymbol()] = $stock;
}
public function getStock($symbol)
{
if (!isset($this->stocks[$symbol])) {
throw new \InvalidArgumentException("Symbol is not traded on this market.");
}
return $this->stocks[$symbol];
}
public function getStocks()
{
return $this->stocks->toArray();
}
}
.. 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
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\StockExchange\Market">
<id name="id" type="integer">
<generator strategy="AUTO" />
</id>
<field name="name" type="string"/>
<one-to-many target-entity="Stock" mapped-by="market" field="stocks" index-by="symbol" />
</entity>
</doctrine-mapping>
.. code-block:: yaml
Doctrine\Tests\Models\StockExchange\Market:
type: entity
id:
id:
type: integer
generator:
strategy: AUTO
fields:
name:
type:string
oneToMany:
stocks:
targetEntity: Stock
mappedBy: market
indexBy: symbol
Inside the ``addStock()`` method you can see how we directly set the key of the association to the symbol,
so that we can work with the indexed association directly after invoking ``addStock()``. Inside ``getStock($symbol)``
we pick a stock traded on the particular market by symbol. If this stock doesn't exist an exception is thrown.
The ``Stock`` entity doesn't contain any special instructions that are new, but for completeness
here are the code and mappings for it:
.. configuration-block::
.. code-block:: php
<?php
namespace Doctrine\Tests\Models\StockExchange;
/**
* @Entity
* @Table(name="exchange_stocks")
*/
class Stock
{
/**
* @Id @GeneratedValue @Column(type="integer")
* @var int
*/
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;
/**
* @ManyToOne(targetEntity="Market", inversedBy="stocks")
* @var Market
*/
private $market;
public function __construct($symbol, Market $market)
{
$this->symbol = $symbol;
$this->market = $market;
$market->addStock($this);
}
public function getSymbol()
{
return $this->symbol;
}
}
.. 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
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\StockExchange\Stock">
<id name="id" type="integer">
<generator strategy="AUTO" />
</id>
<field name="symbol" type="string" unique="true" />
<many-to-one target-entity="Market" field="market" inversed-by="stocks" />
</entity>
</doctrine-mapping>
.. code-block:: yaml
Doctrine\Tests\Models\StockExchange\Stock:
type: entity
id:
id:
type: integer
generator:
strategy: AUTO
fields:
symbol:
type: string
manyToOne:
market:
targetEntity: Market
inversedBy: stocks
Querying indexed associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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:
.. code-block:: php
<?php
// $em is the EntityManager
$market = new Market("Some Exchange");
$stock1 = new Stock("AAPL", $market);
$stock2 = new Stock("GOOG", $market);
$em->persist($market);
$em->persist($stock1);
$em->persist($stock2);
$em->flush();
This code is not particular interesting since the indexing feature is not yet used. In a new request we could
now query for the market:
.. code-block:: php
<?php
// $em is the EntityManager
$marketId = 1;
$symbol = "AAPL";
$market = $em->find("Doctrine\Tests\Models\StockExchange\Market", $marketId);
// Access the stocks by symbol now:
$stock = $market->getStock($symbol);
echo $stock->getSymbol(); // will print "AAPL"
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.
.. code-block:: php
<?php
// $em is the EntityManager
$marketId = 1;
$symbol = "AAPL";
$dql = "SELECT m, s FROM Doctrine\Tests\Models\StockExchange\Market m JOIN m.stocks s WHERE m.id = ?1";
$market = $em->createQuery($dql)
->setParameter(1, $marketId)
->getSingleResult();
// Access the stocks by symbol now:
$stock = $market->getStock($symbol);
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, no matter if marked as
LAZY or EXTRA_LAZY.
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.

View File

@@ -37,6 +37,7 @@
<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"/>
@@ -51,6 +52,7 @@
<xs:enumeration value="preRemove"/>
<xs:enumeration value="postRemove"/>
<xs:enumeration value="postLoad"/>
<xs:enumeration value="preFlush"/>
</xs:restriction>
</xs:simpleType>
@@ -85,8 +87,11 @@
</xs:complexType>
<xs:complexType name="named-native-query">
<xs:sequence>
<xs:element name="query" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="query" type="xs:string" use="required"/>
<xs:attribute name="result-class" type="xs:string" />
<xs:attribute name="result-set-mapping" type="xs:string" />
</xs:complexType>
@@ -94,7 +99,21 @@
<xs:complexType name="named-native-queries">
<xs:sequence>
<xs:element name="named-native-query" type="orm:named-native-query" minOccurs="1" maxOccurs="unbounded" />
<xs:any minOccurs="1" maxOccurs="unbounded" namespace="##other"/>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="entity-listener">
<xs:sequence>
<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="xs:string"/>
</xs:complexType>
<xs:complexType name="entity-listeners">
<xs:sequence>
<xs:element name="entity-listener" type="orm:entity-listener" minOccurs="1" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
@@ -112,20 +131,24 @@
<xs:element name="field-result" type="orm:field-result" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="entity-class" type="xs:string" use="required" />
<xs:attribute name="discriminator-column" type="xs:string" use="optional" />
</xs:complexType>
<xs:complexType name="sql-result-set-mapping">
<xs:sequence>
<xs:element name="entity-result" type="orm:entity-result" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="column-result" type="orm:column-result" minOccurs="0" maxOccurs="unbounded" />
<xs:any minOccurs="1" maxOccurs="unbounded" namespace="##other"/>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="entity-result" type="orm:entity-result"/>
<xs:element name="column-result" type="orm:column-result"/>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:choice>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:string" use="required" />
</xs:complexType>
<xs:complexType name="sql-result-set-mappings">
<xs:sequence>
<xs:element name="sql-result-set-mapping" type="orm:sql-result-set-mapping" minOccurs="1" maxOccurs="unbounded" />
<xs:any minOccurs="1" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
</xs:complexType>
@@ -137,8 +160,10 @@
<xs:element name="discriminator-column" type="orm:discriminator-column" minOccurs="0"/>
<xs:element name="discriminator-map" type="orm:discriminator-map" minOccurs="0"/>
<xs:element name="lifecycle-callbacks" type="orm:lifecycle-callbacks" minOccurs="0" maxOccurs="1" />
<xs:element name="entity-listeners" type="orm:entity-listeners" minOccurs="0" maxOccurs="1" />
<xs:element name="named-queries" type="orm:named-queries" minOccurs="0" maxOccurs="1" />
<xs:element name="named-native-queries" type="orm:named-native-queries" minOccurs="0" maxOccurs="1" />
<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="one-to-one" type="orm:one-to-one" minOccurs="0" maxOccurs="unbounded"/>
@@ -205,6 +230,7 @@
<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"/>
@@ -324,11 +350,13 @@
<xs:element name="generator" type="orm:generator" minOccurs="0" />
<xs:element name="sequence-generator" type="orm:sequence-generator" minOccurs="0" maxOccurs="1" />
<xs:element name="custom-id-generator" type="orm:custom-id-generator" minOccurs="0" maxOccurs="1" />
<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="required" />
<xs:attribute name="type" type="xs:NMTOKEN" />
<xs:attribute name="column" type="xs:NMTOKEN" />
<xs:attribute name="length" type="xs:NMTOKEN" />
<xs:attribute name="association-key" type="xs:boolean" default="false" />
<xs:attribute name="column-definition" type="xs:string" />
<xs:anyAttribute namespace="##other"/>
@@ -363,7 +391,7 @@
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
<xs:attribute name="name" type="xs:NMTOKEN" use="optional" />
<xs:attribute name="referenced-column-name" type="xs:NMTOKEN" use="optional" default="id" />
<xs:attribute name="unique" type="xs:boolean" default="false" />
<xs:attribute name="nullable" type="xs:boolean" default="true" />
@@ -488,7 +516,7 @@
<xs:complexType name="association-overrides">
<xs:sequence>
<xs:element name="association-override" type="orm:association-override" minOccurs="1" maxOccurs="unbounded" />
<xs:any minOccurs="1" maxOccurs="unbounded" namespace="##other"/>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
</xs:complexType>
@@ -504,16 +532,33 @@
<xs:complexType name="attribute-overrides">
<xs:sequence>
<xs:element name="attribute-override" type="orm:attribute-override" minOccurs="1" maxOccurs="unbounded" />
<xs:any minOccurs="1" maxOccurs="unbounded" namespace="##other"/>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="attribute-override">
<xs:sequence>
<xs:element name="field" type="orm:field" minOccurs="1" />
<xs:any minOccurs="1" maxOccurs="unbounded" namespace="##other"/>
<xs:element name="field" type="orm:attribute-override-field" minOccurs="1" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
</xs:complexType>
<xs:complexType name="attribute-override-field">
<xs:sequence>
<xs:element name="options" type="orm:options" minOccurs="0" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="type" type="xs:NMTOKEN" default="string" />
<xs:attribute name="column" type="xs:NMTOKEN" />
<xs:attribute name="length" type="xs:NMTOKEN" />
<xs:attribute name="unique" type="xs:boolean" default="false" />
<xs:attribute name="nullable" type="xs:boolean" default="false" />
<xs:attribute name="version" type="xs:boolean" />
<xs:attribute name="column-definition" type="xs:string" />
<xs:attribute name="precision" type="xs:integer" use="optional" />
<xs:attribute name="scale" type="xs:integer" use="optional" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
</xs:schema>

View File

@@ -26,6 +26,7 @@ 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.
@@ -41,18 +42,22 @@ use Doctrine\ORM\Query\QueryException;
abstract class AbstractQuery
{
/* Hydration mode constants */
/**
* Hydrates an object graph. This is the default behavior.
*/
const HYDRATE_OBJECT = 1;
/**
* Hydrates an array graph.
*/
const HYDRATE_ARRAY = 2;
/**
* Hydrates a flat, rectangular result set with scalar values.
*/
const HYDRATE_SCALAR = 3;
/**
* Hydrates a single scalar value.
*/
@@ -64,27 +69,37 @@ abstract class AbstractQuery
const HYDRATE_SIMPLEOBJECT = 5;
/**
* @var \Doctrine\Common\Collections\ArrayCollection The parameter map of this query.
* The parameter map of this query.
*
* @var \Doctrine\Common\Collections\ArrayCollection
*/
protected $parameters;
/**
* @var ResultSetMapping The user-specified ResultSetMapping to use.
* The user-specified ResultSetMapping to use.
*
* @var \Doctrine\ORM\Query\ResultSetMapping
*/
protected $_resultSetMapping;
/**
* @var \Doctrine\ORM\EntityManager The entity manager used by this query object.
* The entity manager used by this query object.
*
* @var \Doctrine\ORM\EntityManager
*/
protected $_em;
/**
* @var array The map of query hints.
* The map of query hints.
*
* @var array
*/
protected $_hints = array();
/**
* @var integer The hydration mode.
* The hydration mode.
*
* @var integer
*/
protected $_hydrationMode = self::HYDRATE_OBJECT;
@@ -94,7 +109,9 @@ abstract class AbstractQuery
protected $_queryCacheProfile;
/**
* @var boolean Boolean value that indicates whether or not expire the result cache.
* Whether or not expire the result cache.
*
* @var boolean
*/
protected $_expireResultCache = false;
@@ -106,7 +123,7 @@ abstract class AbstractQuery
/**
* Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
*
* @param \Doctrine\ORM\EntityManager $entityManager
* @param \Doctrine\ORM\EntityManager $em
*/
public function __construct(EntityManager $em)
{
@@ -207,11 +224,11 @@ abstract class AbstractQuery
/**
* Sets a query parameter.
*
* @param string|integer $key The parameter position or name.
* @param mixed $value The parameter value.
* @param string $type The parameter type. If specified, the given value will be run through
* the type conversion of this type. This is usually not needed for
* strings and numeric types.
* @param string|int $key The parameter position or name.
* @param mixed $value The parameter value.
* @param string|null $type The parameter type. If specified, the given value will be run through
* the type conversion of this type. This is usually not needed for
* strings and numeric types.
*
* @return \Doctrine\ORM\AbstractQuery This query instance.
*/
@@ -240,51 +257,35 @@ abstract class AbstractQuery
}
/**
* Process an individual parameter value
* Processes an individual parameter value.
*
* @param mixed $value
*
* @return array
*
* @throws ORMInvalidArgumentException
*/
public function processParameterValue($value)
{
switch (true) {
case is_array($value):
foreach ($value as $key => $paramValue) {
$paramValue = $this->processParameterValue($paramValue);
$value[$key] = is_array($paramValue) ? $paramValue[key($paramValue)] : $paramValue;
}
if (is_array($value)) {
foreach ($value as $key => $paramValue) {
$paramValue = $this->processParameterValue($paramValue);
$value[$key] = is_array($paramValue) ? reset($paramValue) : $paramValue;
}
return $value;
case is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value)):
return $this->convertObjectParameterToScalarValue($value);
default:
return $value;
}
}
private function convertObjectParameterToScalarValue($value)
{
$class = $this->_em->getClassMetadata(get_class($value));
if ($class->isIdentifierComposite) {
throw new \InvalidArgumentException(
"Binding an entity with a composite primary key to a query is not supported. " .
"You should split the parameter into the explicit fields and bind them seperately."
);
return $value;
}
$values = ($this->_em->getUnitOfWork()->getEntityState($value) === UnitOfWork::STATE_MANAGED)
? $this->_em->getUnitOfWork()->getEntityIdentifier($value)
: $class->getIdentifierValues($value);
if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value))) {
$value = $this->_em->getUnitOfWork()->getSingleIdentifierValue($value);
$value = $values[$class->getSingleIdentifierFieldName()];
if ($value === null) {
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
}
}
if ( ! $value) {
throw new \InvalidArgumentException(
"Binding entities to query parameters only allowed for entities that have an identifier."
);
if ($value instanceof Mapping\ClassMetadata) {
return $value->name;
}
return $value;
@@ -293,16 +294,37 @@ abstract class AbstractQuery
/**
* Sets the ResultSetMapping that should be used for hydration.
*
* @param ResultSetMapping $rsm
* @param \Doctrine\ORM\Query\ResultSetMapping $rsm
*
* @return \Doctrine\ORM\AbstractQuery
*/
public function setResultSetMapping(Query\ResultSetMapping $rsm)
{
$this->translateNamespaces($rsm);
$this->_resultSetMapping = $rsm;
return $this;
}
/**
* Allows to translate entity namespaces to full qualified names.
*
* @param Query\ResultSetMapping $rsm
*
* @return void
*/
private function translateNamespaces(Query\ResultSetMapping $rsm)
{
$entityManager = $this->_em;
$translate = function ($alias) use ($entityManager) {
return $entityManager->getClassMetadata($alias)->getName();
};
$rsm->aliasMap = array_map($translate, $rsm->aliasMap);
$rsm->declaringClasses = array_map($translate, $rsm->declaringClasses);
}
/**
* Set a cache profile for hydration caching.
*
@@ -322,6 +344,7 @@ abstract class AbstractQuery
* $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey));
*
* @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
*
* @return \Doctrine\ORM\AbstractQuery
*/
public function setHydrationCacheProfile(QueryCacheProfile $profile = null)
@@ -351,6 +374,7 @@ abstract class AbstractQuery
* result cache driver is used from the configuration.
*
* @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
*
* @return \Doctrine\ORM\AbstractQuery
*/
public function setResultCacheProfile(QueryCacheProfile $profile = null)
@@ -366,10 +390,13 @@ abstract class AbstractQuery
}
/**
* Defines a cache driver to be used for caching result sets and implictly enables caching.
* Defines a cache driver to be used for caching result sets and implicitly enables caching.
*
* @param \Doctrine\Common\Cache\Cache|null $resultCacheDriver Cache driver
*
* @param \Doctrine\Common\Cache\Cache $driver Cache driver
* @return \Doctrine\ORM\AbstractQuery
*
* @throws ORMException
*/
public function setResultCacheDriver($resultCacheDriver = null)
{
@@ -388,6 +415,7 @@ abstract class AbstractQuery
* Returns the cache driver used for caching result sets.
*
* @deprecated
*
* @return \Doctrine\Common\Cache\Cache Cache driver
*/
public function getResultCacheDriver()
@@ -405,7 +433,8 @@ abstract class AbstractQuery
*
* @param boolean $bool
* @param integer $lifetime
* @param string $resultCacheId
* @param string $resultCacheId
*
* @return \Doctrine\ORM\AbstractQuery This query instance.
*/
public function useResultCache($bool, $lifetime = null, $resultCacheId = null)
@@ -426,6 +455,7 @@ abstract class AbstractQuery
* Defines how long the result cache will be active before expire.
*
* @param integer $lifetime How long the cache entry is valid.
*
* @return \Doctrine\ORM\AbstractQuery This query instance.
*/
public function setResultCacheLifetime($lifetime)
@@ -443,6 +473,7 @@ abstract class AbstractQuery
* Retrieves the lifetime of resultset cache.
*
* @deprecated
*
* @return integer
*/
public function getResultCacheLifetime()
@@ -454,6 +485,7 @@ abstract class AbstractQuery
* Defines if the result cache is active or not.
*
* @param boolean $expire Whether or not to force resultset cache expiration.
*
* @return \Doctrine\ORM\AbstractQuery This query instance.
*/
public function expireResultCache($expire = true)
@@ -486,9 +518,10 @@ abstract class AbstractQuery
*
* $fetchMode can be one of ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY
*
* @param string $class
* @param string $assocName
* @param int $fetchMode
* @param string $class
* @param string $assocName
* @param int $fetchMode
*
* @return AbstractQuery
*/
public function setFetchMode($class, $assocName, $fetchMode)
@@ -507,6 +540,7 @@ abstract class AbstractQuery
*
* @param integer $hydrationMode Doctrine processing mode to be used during hydration process.
* One of the Query::HYDRATE_* constants.
*
* @return \Doctrine\ORM\AbstractQuery This query instance.
*/
public function setHydrationMode($hydrationMode)
@@ -531,6 +565,8 @@ abstract class AbstractQuery
*
* Alias for execute(null, $hydrationMode = HYDRATE_OBJECT).
*
* @param int $hydrationMode
*
* @return array
*/
public function getResult($hydrationMode = self::HYDRATE_OBJECT)
@@ -565,9 +601,11 @@ abstract class AbstractQuery
/**
* Get exactly one result or null.
*
* @throws NonUniqueResultException
* @param int $hydrationMode
*
* @return mixed
*
* @throws NonUniqueResultException
*/
public function getOneOrNullResult($hydrationMode = null)
{
@@ -597,9 +635,11 @@ abstract class AbstractQuery
* If there is no result, a NoResultException is thrown.
*
* @param integer $hydrationMode
*
* @return mixed
*
* @throws NonUniqueResultException If the query result is not unique.
* @throws NoResultException If the query returned no result.
* @throws NoResultException If the query returned no result.
*/
public function getSingleResult($hydrationMode = null)
{
@@ -626,6 +666,7 @@ abstract class AbstractQuery
* Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
*
* @return mixed
*
* @throws QueryException If the query result is not unique.
*/
public function getSingleScalarResult()
@@ -636,8 +677,9 @@ abstract class AbstractQuery
/**
* Sets a query hint. If the hint name is not recognized, it is silently ignored.
*
* @param string $name The name of the hint.
* @param mixed $value The value of the hint.
* @param string $name The name of the hint.
* @param mixed $value The value of the hint.
*
* @return \Doctrine\ORM\AbstractQuery
*/
public function setHint($name, $value)
@@ -651,6 +693,7 @@ abstract class AbstractQuery
* Gets the value of a query hint. If the hint name is not recognized, FALSE is returned.
*
* @param string $name The name of the hint.
*
* @return mixed The value of the hint or FALSE, if the hint name is not recognized.
*/
public function getHint($name)
@@ -658,6 +701,18 @@ abstract class AbstractQuery
return isset($this->_hints[$name]) ? $this->_hints[$name] : false;
}
/**
* Check if the query has a hint
*
* @param string $name The name of the hint
*
* @return bool False if the query does not have any hint
*/
public function hasHint($name)
{
return isset($this->_hints[$name]);
}
/**
* Return the key value map of query hints that are currently set.
*
@@ -672,8 +727,9 @@ abstract class AbstractQuery
* Executes the query and returns an IterableResult that can be used to incrementally
* iterate over the result.
*
* @param \Doctrine\Common\Collections\ArrayCollection|array $parameters The query parameters.
* @param integer $hydrationMode The hydration mode to use.
* @param ArrayCollection|array|null $parameters The query parameters.
* @param integer|null $hydrationMode The hydration mode to use.
*
* @return \Doctrine\ORM\Internal\Hydration\IterableResult
*/
public function iterate($parameters = null, $hydrationMode = null)
@@ -696,8 +752,9 @@ abstract class AbstractQuery
/**
* Executes the query.
*
* @param \Doctrine\Common\Collections\ArrayCollection|array $parameters Query parameters.
* @param integer $hydrationMode Processing mode to be used during the hydration process.
* @param ArrayCollection|array|null $parameters Query parameters.
* @param integer|null $hydrationMode Processing mode to be used during the hydration process.
*
* @return mixed
*/
public function execute($parameters = null, $hydrationMode = null)
@@ -742,7 +799,7 @@ abstract class AbstractQuery
return $stmt;
}
$data = $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
$data = $this->_em->newHydrator($this->_hydrationMode)->hydrateAll(
$stmt, $this->_resultSetMapping, $this->_hints
);
@@ -782,6 +839,7 @@ abstract class AbstractQuery
* generated for you.
*
* @param string $id
*
* @return \Doctrine\ORM\AbstractQuery This query instance.
*/
public function setResultCacheId($id)
@@ -797,6 +855,7 @@ abstract class AbstractQuery
* Get the result cache id to use to store the result set cache entry if set.
*
* @deprecated
*
* @return string
*/
public function getResultCacheId()

View File

@@ -19,18 +19,22 @@
namespace Doctrine\ORM;
use Doctrine\Common\Cache\Cache,
Doctrine\Common\Cache\ArrayCache,
Doctrine\Common\Annotations\AnnotationRegistry,
Doctrine\Common\Annotations\AnnotationReader,
Doctrine\Common\Persistence\Mapping\Driver\MappingDriver,
Doctrine\ORM\Mapping\Driver\AnnotationDriver,
Doctrine\ORM\Mapping\QuoteStrategy,
Doctrine\ORM\Mapping\DefaultQuoteStrategy,
Doctrine\ORM\Mapping\NamingStrategy,
Doctrine\ORM\Mapping\DefaultNamingStrategy,
Doctrine\Common\Annotations\SimpleAnnotationReader,
Doctrine\Common\Annotations\CachedReader;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Doctrine\Common\Annotations\CachedReader;
use Doctrine\Common\Annotations\SimpleAnnotationReader;
use Doctrine\Common\Cache\ArrayCache;
use Doctrine\Common\Cache\Cache;
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;
use Doctrine\ORM\Mapping\DefaultEntityListenerResolver;
use Doctrine\ORM\Mapping\DefaultNamingStrategy;
use Doctrine\ORM\Mapping\DefaultQuoteStrategy;
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
use Doctrine\ORM\Mapping\EntityListenerResolver;
use Doctrine\ORM\Mapping\NamingStrategy;
use Doctrine\ORM\Mapping\QuoteStrategy;
use Doctrine\ORM\Repository\DefaultRepositoryFactory;
use Doctrine\ORM\Repository\RepositoryFactory;
/**
* Configuration container for all configuration options of Doctrine.
@@ -49,6 +53,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Sets the directory where Doctrine generates any necessary proxy class files.
*
* @param string $dir
*
* @return void
*/
public function setProxyDir($dir)
{
@@ -58,7 +64,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Gets the directory where Doctrine generates any necessary proxy class files.
*
* @return string
* @return string|null
*/
public function getProxyDir()
{
@@ -85,6 +91,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
* during each script execution.
*
* @param boolean $bool
*
* @return void
*/
public function setAutoGenerateProxyClasses($bool)
{
@@ -94,7 +102,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Gets the namespace where proxy classes reside.
*
* @return string
* @return string|null
*/
public function getProxyNamespace()
{
@@ -107,6 +115,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Sets the namespace where proxy classes reside.
*
* @param string $ns
*
* @return void
*/
public function setProxyNamespace($ns)
{
@@ -117,6 +127,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Sets the cache driver implementation that is used for metadata caching.
*
* @param MappingDriver $driverImpl
*
* @return void
*
* @todo Force parameter to be a Closure to ensure lazy evaluation
* (as soon as a metadata cache is in effect, the driver never needs to initialize).
*/
@@ -126,11 +139,12 @@ class Configuration extends \Doctrine\DBAL\Configuration
}
/**
* Add a new default annotation driver with a correctly configured annotation reader. If $useSimpleAnnotationReader
* Adds a new default annotation driver with a correctly configured annotation reader. If $useSimpleAnnotationReader
* is true, the notation `@Entity` will work, otherwise, the notation `@ORM\Entity` will be supported.
*
* @param array $paths
* @param bool $useSimpleAnnotationReader
* @param bool $useSimpleAnnotationReader
*
* @return AnnotationDriver
*/
public function newDefaultAnnotationDriver($paths = array(), $useSimpleAnnotationReader = true)
@@ -157,6 +171,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* @param string $alias
* @param string $namespace
*
* @return void
*/
public function addEntityNamespace($alias, $namespace)
{
@@ -167,8 +183,10 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Resolves a registered namespace alias to the full namespace.
*
* @param string $entityNamespaceAlias
* @throws ORMException
*
* @return string
*
* @throws ORMException
*/
public function getEntityNamespace($entityNamespaceAlias)
{
@@ -180,9 +198,11 @@ class Configuration extends \Doctrine\DBAL\Configuration
}
/**
* Set the entity alias map
* Sets the entity alias map.
*
* @param array $entityNamespaces
*
* @return void
*/
public function setEntityNamespaces(array $entityNamespaces)
{
@@ -202,8 +222,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Gets the cache driver implementation that is used for the mapping metadata.
*
* @return MappingDriver|null
*
* @throws ORMException
* @return MappingDriver
*/
public function getMetadataDriverImpl()
{
@@ -215,7 +236,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Gets the cache driver implementation that is used for the query cache (SQL cache).
*
* @return \Doctrine\Common\Cache\Cache
* @return \Doctrine\Common\Cache\Cache|null
*/
public function getQueryCacheImpl()
{
@@ -228,6 +249,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Sets the cache driver implementation that is used for the query cache (SQL cache).
*
* @param \Doctrine\Common\Cache\Cache $cacheImpl
*
* @return void
*/
public function setQueryCacheImpl(Cache $cacheImpl)
{
@@ -237,7 +260,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Gets the cache driver implementation that is used for the hydration cache (SQL cache).
*
* @return \Doctrine\Common\Cache\Cache
* @return \Doctrine\Common\Cache\Cache|null
*/
public function getHydrationCacheImpl()
{
@@ -250,6 +273,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Sets the cache driver implementation that is used for the hydration cache (SQL cache).
*
* @param \Doctrine\Common\Cache\Cache $cacheImpl
*
* @return void
*/
public function setHydrationCacheImpl(Cache $cacheImpl)
{
@@ -259,7 +284,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Gets the cache driver implementation that is used for metadata caching.
*
* @return \Doctrine\Common\Cache\Cache
* @return \Doctrine\Common\Cache\Cache|null
*/
public function getMetadataCacheImpl()
{
@@ -272,6 +297,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Sets the cache driver implementation that is used for metadata caching.
*
* @param \Doctrine\Common\Cache\Cache $cacheImpl
*
* @return void
*/
public function setMetadataCacheImpl(Cache $cacheImpl)
{
@@ -282,7 +309,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Adds a named DQL query to the configuration.
*
* @param string $name The name of the query.
* @param string $dql The DQL query string.
* @param string $dql The DQL query string.
*
* @return void
*/
public function addNamedQuery($name, $dql)
{
@@ -293,8 +322,10 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Gets a previously registered named DQL query.
*
* @param string $name The name of the query.
* @throws ORMException
*
* @return string The DQL query.
*
* @throws ORMException
*/
public function getNamedQuery($name)
{
@@ -311,6 +342,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
* @param string $name The name of the query.
* @param string $sql The native SQL query string.
* @param Query\ResultSetMapping $rsm The ResultSetMapping used for the results of the SQL query.
*
* @return void
*/
public function addNamedNativeQuery($name, $sql, Query\ResultSetMapping $rsm)
{
@@ -320,10 +353,12 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Gets the components of a previously registered named native query.
*
* @param string $name The name of the query.
* @param string $name The name of the query.
*
* @return array A tuple with the first element being the SQL string and the second
* element being the ResultSetMapping.
*
* @throws ORMException
* @return array A tuple with the first element being the SQL string and the second
* element being the ResultSetMapping.
*/
public function getNamedNativeQuery($name)
{
@@ -338,6 +373,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Ensures that this Configuration instance contains settings that are
* suitable for a production environment.
*
* @return void
*
* @throws ORMException If a configuration setting has a value that is not
* suitable for a production environment.
*/
@@ -365,6 +402,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* @param string $name
* @param string $className
*
* @return void
*
* @throws ORMException
*/
public function addCustomStringFunction($name, $className)
@@ -380,7 +420,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Gets the implementation class name of a registered custom string DQL function.
*
* @param string $name
* @return string
*
* @return string|null
*/
public function getCustomStringFunction($name)
{
@@ -400,6 +441,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Any previously added string functions are discarded.
*
* @param array $functions The map of custom DQL string functions.
*
* @return void
*/
public function setCustomStringFunctions(array $functions)
{
@@ -417,6 +460,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* @param string $name
* @param string $className
*
* @return void
*
* @throws ORMException
*/
public function addCustomNumericFunction($name, $className)
@@ -432,7 +478,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Gets the implementation class name of a registered custom numeric DQL function.
*
* @param string $name
* @return string
*
* @return string|null
*/
public function getCustomNumericFunction($name)
{
@@ -452,6 +499,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Any previously added numeric functions are discarded.
*
* @param array $functions The map of custom DQL numeric functions.
*
* @return void
*/
public function setCustomNumericFunctions(array $functions)
{
@@ -469,6 +518,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* @param string $name
* @param string $className
*
* @return void
*
* @throws ORMException
*/
public function addCustomDatetimeFunction($name, $className)
@@ -484,7 +536,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Gets the implementation class name of a registered custom date/time DQL function.
*
* @param string $name
* @return string
*
* @return string|null
*/
public function getCustomDatetimeFunction($name)
{
@@ -504,6 +557,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Any previously added date/time functions are discarded.
*
* @param array $functions The map of custom DQL date/time functions.
*
* @return void
*/
public function setCustomDatetimeFunctions(array $functions)
{
@@ -513,9 +568,11 @@ class Configuration extends \Doctrine\DBAL\Configuration
}
/**
* Set the custom hydrator modes in one pass.
* Sets the custom hydrator modes in one pass.
*
* @param array An array of ($modeName => $hydrator)
* @param array $modes An array of ($modeName => $hydrator).
*
* @return void
*/
public function setCustomHydrationModes($modes)
{
@@ -527,10 +584,11 @@ class Configuration extends \Doctrine\DBAL\Configuration
}
/**
* Get the hydrator class for the given hydration mode name.
* Gets the hydrator class for the given hydration mode name.
*
* @param string $modeName The hydration mode name.
* @return string $hydrator The hydrator class name.
*
* @return string|null The hydrator class name.
*/
public function getCustomHydrationMode($modeName)
{
@@ -540,10 +598,12 @@ class Configuration extends \Doctrine\DBAL\Configuration
}
/**
* Add a custom hydration mode.
* Adds a custom hydration mode.
*
* @param string $modeName The hydration mode name.
* @param string $hydrator The hydrator class name.
*
* @return void
*/
public function addCustomHydrationMode($modeName, $hydrator)
{
@@ -551,9 +611,11 @@ class Configuration extends \Doctrine\DBAL\Configuration
}
/**
* Set a class metadata factory.
* Sets a class metadata factory.
*
* @param string $cmfName
*
* @return void
*/
public function setClassMetadataFactoryName($cmfName)
{
@@ -573,9 +635,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
}
/**
* Add a filter to the list of possible filters.
* Adds a filter to the list of possible filters.
*
* @param string $name The name of the filter.
* @param string $name The name of the filter.
* @param string $className The class name of the filter.
*/
public function addFilter($name, $className)
@@ -599,10 +661,14 @@ class Configuration extends \Doctrine\DBAL\Configuration
}
/**
* Set default repository class.
* Sets default repository class.
*
* @since 2.2
*
* @param string $className
*
* @return void
*
* @throws ORMException If not is a \Doctrine\Common\Persistence\ObjectRepository
*/
public function setDefaultRepositoryClassName($className)
@@ -620,6 +686,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Get default repository class.
*
* @since 2.2
*
* @return string
*/
public function getDefaultRepositoryClassName()
@@ -630,10 +697,13 @@ class Configuration extends \Doctrine\DBAL\Configuration
}
/**
* Set naming strategy.
* Sets naming strategy.
*
* @since 2.3
*
* @param NamingStrategy $namingStrategy
*
* @return void
*/
public function setNamingStrategy(NamingStrategy $namingStrategy)
{
@@ -641,9 +711,10 @@ class Configuration extends \Doctrine\DBAL\Configuration
}
/**
* Get naming strategy..
* Gets naming strategy..
*
* @since 2.3
*
* @return NamingStrategy
*/
public function getNamingStrategy()
@@ -656,10 +727,13 @@ class Configuration extends \Doctrine\DBAL\Configuration
}
/**
* Set quote strategy.
* Sets quote strategy.
*
* @since 2.3
* @param Doctrine\ORM\Mapping\QuoteStrategy $quoteStrategy
*
* @param \Doctrine\ORM\Mapping\QuoteStrategy $quoteStrategy
*
* @return void
*/
public function setQuoteStrategy(QuoteStrategy $quoteStrategy)
{
@@ -667,10 +741,11 @@ class Configuration extends \Doctrine\DBAL\Configuration
}
/**
* Get quote strategy.
* Gets quote strategy.
*
* @since 2.3
* @return Doctrine\ORM\Mapping\QuoteStrategy
*
* @return \Doctrine\ORM\Mapping\QuoteStrategy
*/
public function getQuoteStrategy()
{
@@ -680,4 +755,54 @@ class Configuration extends \Doctrine\DBAL\Configuration
return $this->_attributes['quoteStrategy'];
}
/**
* Set the entity listener resolver.
*
* @since 2.4
* @param \Doctrine\ORM\Mapping\EntityListenerResolver $resolver
*/
public function setEntityListenerResolver(EntityListenerResolver $resolver)
{
$this->_attributes['entityListenerResolver'] = $resolver;
}
/**
* Get the entity listener resolver.
*
* @since 2.4
* @return \Doctrine\ORM\Mapping\EntityListenerResolver
*/
public function getEntityListenerResolver()
{
if ( ! isset($this->_attributes['entityListenerResolver'])) {
$this->_attributes['entityListenerResolver'] = new DefaultEntityListenerResolver();
}
return $this->_attributes['entityListenerResolver'];
}
/**
* Set the entity repository factory.
*
* @since 2.4
* @param \Doctrine\ORM\Repository\RepositoryFactory $repositoryFactory
*/
public function setRepositoryFactory(RepositoryFactory $repositoryFactory)
{
$this->_attributes['repositoryFactory'] = $repositoryFactory;
}
/**
* Get the entity repository factory.
*
* @since 2.4
* @return \Doctrine\ORM\Repository\RepositoryFactory
*/
public function getRepositoryFactory()
{
return isset($this->_attributes['repositoryFactory'])
? $this->_attributes['repositoryFactory']
: new DefaultRepositoryFactory();
}
}

View File

@@ -0,0 +1,271 @@
<?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\Decorator;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Common\Persistence\ObjectManagerDecorator;
/**
* Base class for EntityManager decorators
*
* @since 2.4
* @author Lars Strojny <lars@strojny.net
*/
abstract class EntityManagerDecorator extends ObjectManagerDecorator implements EntityManagerInterface
{
/**
* @var EntityManagerInterface
*/
protected $wrapped;
/**
* @param EntityManagerInterface $wrapped
*/
public function __construct(EntityManagerInterface $wrapped)
{
$this->wrapped = $wrapped;
}
/**
* {@inheritdoc}
*/
public function getConnection()
{
return $this->wrapped->getConnection();
}
/**
* {@inheritdoc}
*/
public function getExpressionBuilder()
{
return $this->wrapped->getExpressionBuilder();
}
/**
* {@inheritdoc}
*/
public function beginTransaction()
{
return $this->wrapped->beginTransaction();
}
/**
* {@inheritdoc}
*/
public function transactional($func)
{
return $this->wrapped->transactional($func);
}
/**
* {@inheritdoc}
*/
public function commit()
{
return $this->wrapped->commit();
}
/**
* {@inheritdoc}
*/
public function rollback()
{
return $this->wrapped->rollback();
}
/**
* {@inheritdoc}
*/
public function createQuery($dql = '')
{
return $this->wrapped->createQuery($dql);
}
/**
* {@inheritdoc}
*/
public function createNamedQuery($name)
{
return $this->wrapped->createNamedQuery($name);
}
/**
* {@inheritdoc}
*/
public function createNativeQuery($sql, ResultSetMapping $rsm)
{
return $this->wrapped->createNativeQuery($sql, $rsm);
}
/**
* {@inheritdoc}
*/
public function createNamedNativeQuery($name)
{
return $this->wrapped->createNamedNativeQuery($name);
}
/**
* {@inheritdoc}
*/
public function createQueryBuilder()
{
return $this->wrapped->createQueryBuilder();
}
/**
* {@inheritdoc}
*/
public function getReference($entityName, $id)
{
return $this->wrapped->getReference($entityName, $id);
}
/**
* {@inheritdoc}
*/
public function getPartialReference($entityName, $identifier)
{
return $this->wrapped->getPartialReference($entityName, $identifier);
}
/**
* {@inheritdoc}
*/
public function close()
{
return $this->wrapped->close();
}
/**
* {@inheritdoc}
*/
public function copy($entity, $deep = false)
{
return $this->wrapped->copy($entity, $deep);
}
/**
* {@inheritdoc}
*/
public function lock($entity, $lockMode, $lockVersion = null)
{
return $this->wrapped->lock($entity, $lockMode, $lockVersion);
}
/**
* {@inheritdoc}
*/
public function find($entityName, $id, $lockMode = LockMode::NONE, $lockVersion = null)
{
return $this->wrapped->find($entityName, $id, $lockMode, $lockVersion);
}
/**
* {@inheritdoc}
*/
public function flush($entity = null)
{
return $this->wrapped->flush($entity);
}
/**
* {@inheritdoc}
*/
public function getEventManager()
{
return $this->wrapped->getEventManager();
}
/**
* {@inheritdoc}
*/
public function getConfiguration()
{
return $this->wrapped->getConfiguration();
}
/**
* {@inheritdoc}
*/
public function isOpen()
{
return $this->wrapped->isOpen();
}
/**
* {@inheritdoc}
*/
public function getUnitOfWork()
{
return $this->wrapped->getUnitOfWork();
}
/**
* {@inheritdoc}
*/
public function getHydrator($hydrationMode)
{
return $this->wrapped->getHydrator($hydrationMode);
}
/**
* {@inheritdoc}
*/
public function newHydrator($hydrationMode)
{
return $this->wrapped->newHydrator($hydrationMode);
}
/**
* {@inheritdoc}
*/
public function getProxyFactory()
{
return $this->wrapped->getProxyFactory();
}
/**
* {@inheritdoc}
*/
public function getFilters()
{
return $this->wrapped->getFilters();
}
/**
* {@inheritdoc}
*/
public function isFiltersStateClean()
{
return $this->wrapped->isFiltersStateClean();
}
/**
* {@inheritdoc}
*/
public function hasFilters()
{
return $this->wrapped->hasFilters();
}
}

View File

@@ -19,27 +19,50 @@
namespace Doctrine\ORM;
use Exception,
Doctrine\Common\EventManager,
Doctrine\Common\Persistence\ObjectManager,
Doctrine\DBAL\Connection,
Doctrine\DBAL\LockMode,
Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\ORM\Mapping\ClassMetadataFactory,
Doctrine\ORM\Query\ResultSetMapping,
Doctrine\ORM\Proxy\ProxyFactory,
Doctrine\ORM\Query\FilterCollection;
use Exception;
use Doctrine\Common\EventManager;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\Query\FilterCollection;
use Doctrine\Common\Util\ClassUtils;
/**
* The EntityManager is the central access point to ORM functionality.
*
* It is a facade to all different ORM subsystems such as UnitOfWork,
* Query Language and Repository API. Instantiation is done through
* the static create() method. The quickest way to obtain a fully
* configured EntityManager is:
*
* use Doctrine\ORM\Tools\Setup;
* use Doctrine\ORM\EntityManager;
*
* $paths = array('/path/to/entity/mapping/files');
*
* $config = Setup::createAnnotationMetadataConfiguration($paths);
* $dbParams = array('driver' => 'pdo_sqlite', 'memory' => true);
* $entityManager = EntityManager::create($dbParams, $config);
*
* For more information see
* {@link http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/configuration.html}
*
* You should never attempt to inherit from the EntityManager: Inheritance
* is not a valid extension point for the EntityManager. Instead you
* should take a look at the {@see \Doctrine\ORM\Decorator\EntityManagerDecorator}
* and wrap your entity manager in a decorator.
*
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class EntityManager implements ObjectManager
/* final */class EntityManager implements EntityManagerInterface
{
/**
* The used Configuration.
@@ -62,13 +85,6 @@ class EntityManager implements ObjectManager
*/
private $metadataFactory;
/**
* The EntityRepository instances.
*
* @var array
*/
private $repositories = array();
/**
* The UnitOfWork used to coordinate object-level transactions.
*
@@ -83,13 +99,6 @@ class EntityManager implements ObjectManager
*/
private $eventManager;
/**
* The maintained (cached) hydrators. One instance per type.
*
* @var array
*/
private $hydrators = array();
/**
* The proxy factory used to create dynamic proxies.
*
@@ -97,6 +106,13 @@ class EntityManager implements ObjectManager
*/
private $proxyFactory;
/**
* The repository factory used to create dynamic repositories.
*
* @var \Doctrine\ORM\Repository\RepositoryFactory
*/
private $repositoryFactory;
/**
* The expression builder instance used to generate query expressions.
*
@@ -114,7 +130,7 @@ class EntityManager implements ObjectManager
/**
* Collection of query filters.
*
* @var Doctrine\ORM\Query\FilterCollection
* @var \Doctrine\ORM\Query\FilterCollection
*/
private $filterCollection;
@@ -122,15 +138,15 @@ class EntityManager implements ObjectManager
* Creates a new EntityManager that operates on the given database connection
* and uses the given Configuration and EventManager implementations.
*
* @param \Doctrine\DBAL\Connection $conn
* @param \Doctrine\ORM\Configuration $config
* @param \Doctrine\DBAL\Connection $conn
* @param \Doctrine\ORM\Configuration $config
* @param \Doctrine\Common\EventManager $eventManager
*/
protected function __construct(Connection $conn, Configuration $config, EventManager $eventManager)
{
$this->conn = $conn;
$this->config = $config;
$this->eventManager = $eventManager;
$this->conn = $conn;
$this->config = $config;
$this->eventManager = $eventManager;
$metadataFactoryClassName = $config->getClassMetadataFactoryName();
@@ -138,8 +154,9 @@ class EntityManager implements ObjectManager
$this->metadataFactory->setEntityManager($this);
$this->metadataFactory->setCacheDriver($this->config->getMetadataCacheImpl());
$this->unitOfWork = new UnitOfWork($this);
$this->proxyFactory = new ProxyFactory(
$this->repositoryFactory = $config->getRepositoryFactory();
$this->unitOfWork = new UnitOfWork($this);
$this->proxyFactory = new ProxyFactory(
$this,
$config->getProxyDir(),
$config->getProxyNamespace(),
@@ -192,6 +209,8 @@ class EntityManager implements ObjectManager
/**
* Starts a transaction on the underlying database connection.
*
* @return void
*/
public function beginTransaction()
{
@@ -209,7 +228,8 @@ class EntityManager implements ObjectManager
* the transaction is rolled back, the EntityManager closed and the exception re-thrown.
*
* @param callable $func The function to execute transactionally.
* @return mixed Returns the non-empty value returned from the closure or true instead
*
* @return mixed The non-empty value returned from the closure or true instead.
*/
public function transactional($func)
{
@@ -236,6 +256,8 @@ class EntityManager implements ObjectManager
/**
* Commits a transaction on the underlying database connection.
*
* @return void
*/
public function commit()
{
@@ -244,6 +266,8 @@ class EntityManager implements ObjectManager
/**
* Performs a rollback on the underlying database connection.
*
* @return void
*/
public function rollback()
{
@@ -260,7 +284,10 @@ class EntityManager implements ObjectManager
* MyProject\Domain\User
* sales:PriceRequest
*
* @param string $className
*
* @return \Doctrine\ORM\Mapping\ClassMetadata
*
* @internal Performance-sensitive method.
*/
public function getClassMetadata($className)
@@ -272,9 +299,10 @@ class EntityManager implements ObjectManager
* Creates a new Query object.
*
* @param string $dql The DQL string.
*
* @return \Doctrine\ORM\Query
*/
public function createQuery($dql = "")
public function createQuery($dql = '')
{
$query = new Query($this);
@@ -289,6 +317,7 @@ class EntityManager implements ObjectManager
* Creates a Query from a named query.
*
* @param string $name
*
* @return \Doctrine\ORM\Query
*/
public function createNamedQuery($name)
@@ -299,8 +328,9 @@ class EntityManager implements ObjectManager
/**
* Creates a native SQL query.
*
* @param string $sql
* @param string $sql
* @param ResultSetMapping $rsm The ResultSetMapping to use.
*
* @return NativeQuery
*/
public function createNativeQuery($sql, ResultSetMapping $rsm)
@@ -317,6 +347,7 @@ class EntityManager implements ObjectManager
* Creates a NativeQuery from a named native query.
*
* @param string $name
*
* @return \Doctrine\ORM\NativeQuery
*/
public function createNamedNativeQuery($name)
@@ -329,7 +360,7 @@ class EntityManager implements ObjectManager
/**
* Create a QueryBuilder instance
*
* @return QueryBuilder $qb
* @return QueryBuilder
*/
public function createQueryBuilder()
{
@@ -344,7 +375,10 @@ class EntityManager implements ObjectManager
* If an entity is explicitly passed to this method only this entity and
* the cascade-persist semantics + scheduled inserts/removals are synchronized.
*
* @param object $entity
* @param null|object|array $entity
*
* @return void
*
* @throws \Doctrine\ORM\OptimisticLockException If a version check on an entity that
* makes use of optimistic locking fails.
*/
@@ -354,21 +388,34 @@ class EntityManager implements ObjectManager
$this->unitOfWork->commit($entity);
}
/**
* Finds an Entity by its identifier.
*
* @param string $entityName
* @param mixed $id
* @param integer $lockMode
* @param integer $lockVersion
* @param string $entityName
* @param mixed $id
* @param integer $lockMode
* @param integer|null $lockVersion
*
* @return object
* @return object|null The entity instance or NULL if the entity can not be found.
*
* @throws OptimisticLockException
* @throws ORMInvalidArgumentException
* @throws TransactionRequiredException
* @throws ORMException
*/
public function find($entityName, $id, $lockMode = LockMode::NONE, $lockVersion = null)
{
$class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
if (is_object($id) && $this->metadataFactory->hasMetadataFor(ClassUtils::getClass($id))) {
$id = $this->unitOfWork->getSingleIdentifierValue($id);
if ($id === null) {
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
}
}
if ( ! is_array($id)) {
$id = array($class->identifier[0] => $id);
}
@@ -437,8 +484,11 @@ class EntityManager implements ObjectManager
* without actually loading it, if the entity is not yet loaded.
*
* @param string $entityName The name of the entity type.
* @param mixed $id The entity identifier.
* @param mixed $id The entity identifier.
*
* @return object The entity reference.
*
* @throws ORMException
*/
public function getReference($entityName, $id)
{
@@ -494,7 +544,8 @@ class EntityManager implements ObjectManager
* never be loaded in the first place.
*
* @param string $entityName The name of the entity type.
* @param mixed $identifier The entity identifier.
* @param mixed $identifier The entity identifier.
*
* @return object The (partial) entity reference.
*/
public function getPartialReference($entityName, $identifier)
@@ -524,7 +575,9 @@ class EntityManager implements ObjectManager
* Clears the EntityManager. All entities that are currently managed
* by this EntityManager become detached.
*
* @param string $entityName if given, only entities of this type will get detached
* @param string|null $entityName if given, only entities of this type will get detached
*
* @return void
*/
public function clear($entityName = null)
{
@@ -535,6 +588,8 @@ class EntityManager implements ObjectManager
* Closes the EntityManager. All entities that are currently managed
* by this EntityManager become detached. The EntityManager may no longer
* be used after it is closed.
*
* @return void
*/
public function close()
{
@@ -552,7 +607,11 @@ class EntityManager implements ObjectManager
* NOTE: The persist operation always considers entities that are not yet known to
* this EntityManager as NEW. Do not pass detached entities to the persist operation.
*
* @param object $object The instance to make managed and persistent.
* @param object $entity The instance to make managed and persistent.
*
* @return void
*
* @throws ORMInvalidArgumentException
*/
public function persist($entity)
{
@@ -572,6 +631,10 @@ class EntityManager implements ObjectManager
* or as a result of the flush operation.
*
* @param object $entity The entity instance to remove.
*
* @return void
*
* @throws ORMInvalidArgumentException
*/
public function remove($entity)
{
@@ -589,6 +652,10 @@ class EntityManager implements ObjectManager
* overriding any local changes that have not yet been persisted.
*
* @param object $entity The entity to refresh.
*
* @return void
*
* @throws ORMInvalidArgumentException
*/
public function refresh($entity)
{
@@ -609,6 +676,10 @@ class EntityManager implements ObjectManager
* reference it.
*
* @param object $entity The entity to detach.
*
* @return void
*
* @throws ORMInvalidArgumentException
*/
public function detach($entity)
{
@@ -625,7 +696,10 @@ class EntityManager implements ObjectManager
* The entity passed to merge will not become associated/managed with this EntityManager.
*
* @param object $entity The detached entity to merge into the persistence context.
*
* @return object The managed copy of the entity.
*
* @throws ORMInvalidArgumentException
*/
public function merge($entity)
{
@@ -641,8 +715,13 @@ class EntityManager implements ObjectManager
/**
* Creates a copy of the given entity. Can create a shallow or a deep copy.
*
* @param object $entity The entity to copy.
* @return object The new entity.
* @param object $entity The entity to copy.
* @param boolean $deep FALSE for a shallow copy, TRUE for a deep copy.
*
* @return object The new entity.
*
* @throws \BadMethodCallException
*
* @todo Implementation need. This is necessary since $e2 = clone $e1; throws an E_FATAL when access anything on $e:
* Fatal error: Maximum function nesting level of '100' reached, aborting!
*/
@@ -654,9 +733,12 @@ class EntityManager implements ObjectManager
/**
* Acquire a lock on the given entity.
*
* @param object $entity
* @param int $lockMode
* @param int $lockVersion
* @param object $entity
* @param int $lockMode
* @param int|null $lockVersion
*
* @return void
*
* @throws OptimisticLockException
* @throws PessimisticLockException
*/
@@ -669,34 +751,19 @@ class EntityManager implements ObjectManager
* Gets the repository for an entity class.
*
* @param string $entityName The name of the entity.
* @return EntityRepository The repository class.
*
* @return \Doctrine\ORM\EntityRepository The repository class.
*/
public function getRepository($entityName)
{
$entityName = ltrim($entityName, '\\');
if (isset($this->repositories[$entityName])) {
return $this->repositories[$entityName];
}
$metadata = $this->getClassMetadata($entityName);
$repositoryClassName = $metadata->customRepositoryClassName;
if ($repositoryClassName === null) {
$repositoryClassName = $this->config->getDefaultRepositoryClassName();
}
$repository = new $repositoryClassName($this, $metadata);
$this->repositories[$entityName] = $repository;
return $repository;
return $this->repositoryFactory->getRepository($this, $entityName);
}
/**
* Determines whether an entity instance is managed in this EntityManager.
*
* @param object $entity
*
* @return boolean TRUE if this EntityManager currently manages the given entity, FALSE otherwise.
*/
public function contains($entity)
@@ -729,6 +796,8 @@ class EntityManager implements ObjectManager
/**
* Throws an exception if the EntityManager is closed or currently not active.
*
* @return void
*
* @throws ORMException If the EntityManager is closed.
*/
private function errorIfClosed()
@@ -764,23 +833,25 @@ class EntityManager implements ObjectManager
* This method caches the hydrator instances which is used for all queries that don't
* selectively iterate over the result.
*
* @deprecated
*
* @param int $hydrationMode
*
* @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator
*/
public function getHydrator($hydrationMode)
{
if ( ! isset($this->hydrators[$hydrationMode])) {
$this->hydrators[$hydrationMode] = $this->newHydrator($hydrationMode);
}
return $this->hydrators[$hydrationMode];
return $this->newHydrator($hydrationMode);
}
/**
* Create a new instance for the given hydration mode.
*
* @param int $hydrationMode
* @param int $hydrationMode
*
* @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator
*
* @throws ORMException
*/
public function newHydrator($hydrationMode)
{
@@ -825,6 +896,8 @@ class EntityManager implements ObjectManager
* This method is a no-op for other objects
*
* @param object $obj
*
* @return void
*/
public function initializeObject($obj)
{
@@ -834,11 +907,14 @@ class EntityManager implements ObjectManager
/**
* Factory method to create EntityManager instances.
*
* @param mixed $conn An array with the connection parameters or an existing
* Connection instance.
* @param Configuration $config The Configuration instance to use.
* @param EventManager $eventManager The EventManager instance to use.
* @param mixed $conn An array with the connection parameters or an existing Connection instance.
* @param Configuration $config The Configuration instance to use.
* @param EventManager $eventManager The EventManager instance to use.
*
* @return EntityManager The created EntityManager.
*
* @throws \InvalidArgumentException
* @throws ORMException
*/
public static function create($conn, Configuration $config, EventManager $eventManager = null)
{
@@ -893,7 +969,7 @@ class EntityManager implements ObjectManager
/**
* Checks whether the Entity Manager has filters.
*
* @return True, if the EM has a filter collection.
* @return boolean True, if the EM has a filter collection.
*/
public function hasFilters()
{

View File

@@ -0,0 +1,60 @@
<?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;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Query\ResultSetMapping;
/**
* EntityManager interface
*
* @since 2.4
* @author Lars Strojny <lars@strojny.net
*/
interface EntityManagerInterface extends ObjectManager
{
public function getConnection();
public function getExpressionBuilder();
public function beginTransaction();
public function transactional($func);
public function commit();
public function rollback();
public function createQuery($dql = '');
public function createNamedQuery($name);
public function createNativeQuery($sql, ResultSetMapping $rsm);
public function createNamedNativeQuery($name);
public function createQueryBuilder();
public function getReference($entityName, $id);
public function getPartialReference($entityName, $identifier);
public function close();
public function copy($entity, $deep = false);
public function lock($entity, $lockMode, $lockVersion = null);
public function getEventManager();
public function getConfiguration();
public function isOpen();
public function getUnitOfWork();
public function getHydrator($hydrationMode);
public function newHydrator($hydrationMode);
public function getProxyFactory();
public function getFilters();
public function isFiltersStateClean();
public function hasFilters();
}

View File

@@ -27,6 +27,9 @@ namespace Doctrine\ORM;
*/
class EntityNotFoundException extends ORMException
{
/**
* Constructor.
*/
public function __construct()
{
parent::__construct('Entity was not found.');

View File

@@ -19,6 +19,8 @@
namespace Doctrine\ORM;
use Doctrine\ORM\Query\ResultSetMappingBuilder;
use Doctrine\DBAL\LockMode;
use Doctrine\Common\Persistence\ObjectRepository;
@@ -59,8 +61,8 @@ class EntityRepository implements ObjectRepository, Selectable
/**
* Initializes a new <tt>EntityRepository</tt>.
*
* @param EntityManager $em The EntityManager to use.
* @param ClassMetadata $classMetadata The class descriptor.
* @param EntityManager $em The EntityManager to use.
* @param Mapping\ClassMetadata $class The class descriptor.
*/
public function __construct($em, Mapping\ClassMetadata $class)
{
@@ -70,10 +72,11 @@ class EntityRepository implements ObjectRepository, Selectable
}
/**
* Create a new QueryBuilder instance that is prepopulated for this entity name
* Creates a new QueryBuilder instance that is prepopulated for this entity name.
*
* @param string $alias
* @return QueryBuilder $qb
*
* @return QueryBuilder
*/
public function createQueryBuilder($alias)
{
@@ -83,9 +86,27 @@ class EntityRepository implements ObjectRepository, Selectable
}
/**
* Create a new Query instance based on a predefined metadata named query.
* Creates a new result set mapping builder for this entity.
*
* The column naming strategy is "INCREMENT".
*
* @param string $alias
*
* @return ResultSetMappingBuilder
*/
public function createResultSetMappingBuilder($alias)
{
$rsm = new ResultSetMappingBuilder($this->_em, ResultSetMappingBuilder::COLUMN_RENAMING_INCREMENT);
$rsm->addRootEntityFromClassMetadata($this->_entityName, $alias);
return $rsm;
}
/**
* Creates a new Query instance based on a predefined metadata named query.
*
* @param string $queryName
*
* @return Query
*/
public function createNamedQuery($queryName)
@@ -97,6 +118,7 @@ class EntityRepository implements ObjectRepository, Selectable
* Creates a native SQL query.
*
* @param string $queryName
*
* @return NativeQuery
*/
public function createNativeNamedQuery($queryName)
@@ -110,6 +132,8 @@ class EntityRepository implements ObjectRepository, Selectable
/**
* Clears the repository, causing all managed entities to become detached.
*
* @return void
*/
public function clear()
{
@@ -119,11 +143,11 @@ class EntityRepository implements ObjectRepository, Selectable
/**
* Finds an entity by its primary key / identifier.
*
* @param mixed $id The identifier.
* @param integer $lockMode
* @param integer $lockVersion
* @param mixed $id The identifier.
* @param int $lockMode The lock mode.
* @param int|null $lockVersion The lock version.
*
* @return object The entity.
* @return object|null The entity instance or NULL if the entity can not be found.
*/
public function find($id, $lockMode = LockMode::NONE, $lockVersion = null)
{
@@ -143,10 +167,11 @@ class EntityRepository implements ObjectRepository, Selectable
/**
* Finds entities by a set of criteria.
*
* @param array $criteria
* @param array $criteria
* @param array|null $orderBy
* @param int|null $limit
* @param int|null $offset
* @param int|null $limit
* @param int|null $offset
*
* @return array The objects.
*/
public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
@@ -160,20 +185,27 @@ class EntityRepository implements ObjectRepository, Selectable
* Finds a single entity by a set of criteria.
*
* @param array $criteria
* @return object
* @param array|null $orderBy
*
* @return object|null The entity instance or NULL if the entity can not be found.
*/
public function findOneBy(array $criteria)
public function findOneBy(array $criteria, array $orderBy = null)
{
$persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
return $persister->load($criteria, null, null, array(), 0, 1);
return $persister->load($criteria, null, null, array(), 0, 1, $orderBy);
}
/**
* Adds support for magic finders.
*
* @param string $method
* @param array $arguments
*
* @return array|object The found entity/entities.
* @throws BadMethodCallException If the method called is an invalid find* method
*
* @throws ORMException
* @throws \BadMethodCallException If the method called is an invalid find* method
* or no find* method at all and therefore an invalid
* method call.
*/
@@ -214,7 +246,7 @@ class EntityRepository implements ObjectRepository, Selectable
case 3:
return $this->$method(array($fieldName => $arguments[0]), $arguments[1], $arguments[2]);
case 4;
case 4:
return $this->$method(array($fieldName => $arguments[0]), $arguments[1], $arguments[2], $arguments[3]);
default:
@@ -272,4 +304,3 @@ class EntityRepository implements ObjectRepository, Selectable
return new ArrayCollection($persister->loadCriteria($criteria));
}
}

View File

@@ -19,8 +19,8 @@
namespace Doctrine\ORM\Event;
use Doctrine\Common\EventArgs;
use Doctrine\ORM\EntityManager;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs as BaseLifecycleEventArgs;
/**
* Lifecycle Events are triggered by the UnitOfWork during lifecycle transitions
@@ -31,47 +31,25 @@ use Doctrine\ORM\EntityManager;
* @author Roman Borschel <roman@code-factory.de>
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class LifecycleEventArgs extends EventArgs
class LifecycleEventArgs extends BaseLifecycleEventArgs
{
/**
* @var \Doctrine\ORM\EntityManager
*/
private $em;
/**
* @var object
*/
private $entity;
/**
* Constructor
*
* @param object $entity
* @param \Doctrine\ORM\EntityManager $em
*/
public function __construct($entity, EntityManager $em)
{
$this->entity = $entity;
$this->em = $em;
}
/**
* Retrieve associated Entity.
* Retrieves associated Entity.
*
* @return object
*/
public function getEntity()
{
return $this->entity;
return $this->getObject();
}
/**
* Retrieve associated EntityManager.
* Retrieves associated EntityManager.
*
* @return \Doctrine\ORM\EntityManager
*/
public function getEntityManager()
{
return $this->em;
return $this->getObjectManager();
}
}

View File

@@ -0,0 +1,120 @@
<?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\Event;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\EntityManager;
use Doctrine\Common\EventArgs;
/**
* A method invoker based on entity lifecycle.
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @since 2.4
*/
class ListenersInvoker
{
const INVOKE_NONE = 0;
const INVOKE_LISTENERS = 1;
const INVOKE_CALLBACKS = 2;
const INVOKE_MANAGER = 4;
/**
* @var \Doctrine\ORM\Mapping\EntityListenerResolver The Entity listener resolver.
*/
private $resolver;
/**
* The EventManager used for dispatching events.
*
* @var \Doctrine\Common\EventManager
*/
private $eventManager;
/**
* Initializes a new ListenersInvoker instance.
*
* @param \Doctrine\ORM\EntityManager $em
*/
public function __construct(EntityManager $em)
{
$this->eventManager = $em->getEventManager();
$this->resolver = $em->getConfiguration()->getEntityListenerResolver();
}
/**
* Get the subscribed event systems
*
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param string $eventName The entity lifecycle event.
*
* @return integer Bitmask of subscribed event systems.
*/
public function getSubscribedSystems(ClassMetadata $metadata, $eventName)
{
$invoke = self::INVOKE_NONE;
if (isset($metadata->lifecycleCallbacks[$eventName])) {
$invoke |= self::INVOKE_CALLBACKS;
}
if (isset($metadata->entityListeners[$eventName])) {
$invoke |= self::INVOKE_LISTENERS;
}
if ($this->eventManager->hasListeners($eventName)) {
$invoke |= self::INVOKE_MANAGER;
}
return $invoke;
}
/**
* Dispatches the lifecycle event of the given entity.
*
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param string $eventName The entity lifecycle event.
* @param object $entity The Entity on which the event occurred.
* @param \Doctrine\Common\EventArgs $event The Event args.
* @param integer $invoke Bitmask to invoke listeners.
*/
public function invoke(ClassMetadata $metadata, $eventName, $entity, EventArgs $event, $invoke)
{
if($invoke & self::INVOKE_CALLBACKS) {
foreach ($metadata->lifecycleCallbacks[$eventName] as $callback) {
$entity->$callback($event);
}
}
if($invoke & self::INVOKE_LISTENERS) {
foreach ($metadata->entityListeners[$eventName] as $listener) {
$class = $listener['class'];
$method = $listener['method'];
$instance = $this->resolver->resolve($class);
$instance->$method($entity, $event);
}
}
if($invoke & self::INVOKE_MANAGER) {
$this->eventManager->dispatchEvent($eventName, $event);
}
}
}

View File

@@ -22,6 +22,7 @@ namespace Doctrine\ORM\Event;
use Doctrine\Common\EventArgs;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\EntityManager;
use Doctrine\Common\Persistence\Event\LoadClassMetadataEventArgs as BaseLoadClassMetadataEventArgs;
/**
* Class that holds event arguments for a loadMetadata event.
@@ -29,40 +30,8 @@ use Doctrine\ORM\EntityManager;
* @author Jonathan H. Wage <jonwage@gmail.com>
* @since 2.0
*/
class LoadClassMetadataEventArgs extends EventArgs
class LoadClassMetadataEventArgs extends BaseLoadClassMetadataEventArgs
{
/**
* @var \Doctrine\ORM\Mapping\ClassMetadata
*/
private $classMetadata;
/**
* @var \Doctrine\ORM\EntityManager
*/
private $em;
/**
* Constructor.
*
* @param \Doctrine\ORM\Mapping\ClassMetadataInfo $classMetadata
* @param \Doctrine\ORM\EntityManager $em
*/
public function __construct(ClassMetadataInfo $classMetadata, EntityManager $em)
{
$this->classMetadata = $classMetadata;
$this->em = $em;
}
/**
* Retrieve associated ClassMetadata.
*
* @return \Doctrine\ORM\Mapping\ClassMetadataInfo
*/
public function getClassMetadata()
{
return $this->classMetadata;
}
/**
* Retrieve associated EntityManager.
*
@@ -70,7 +39,6 @@ class LoadClassMetadataEventArgs extends EventArgs
*/
public function getEntityManager()
{
return $this->em;
return $this->getObjectManager();
}
}

View File

@@ -19,6 +19,8 @@
namespace Doctrine\ORM\Event;
use Doctrine\ORM\EntityManager;
/**
* Provides event arguments for the onClear event.
*
@@ -44,16 +46,16 @@ class OnClearEventArgs extends \Doctrine\Common\EventArgs
* Constructor.
*
* @param \Doctrine\ORM\EntityManager $em
* @param string $entityClass Optional entity class
* @param string|null $entityClass Optional entity class.
*/
public function __construct($em, $entityClass = null)
public function __construct(EntityManager $em, $entityClass = null)
{
$this->em = $em;
$this->entityClass = $entityClass;
}
/**
* Retrieve associated EntityManager.
* Retrieves associated EntityManager.
*
* @return \Doctrine\ORM\EntityManager
*/
@@ -65,7 +67,7 @@ class OnClearEventArgs extends \Doctrine\Common\EventArgs
/**
* Name of the entity class that is cleared, or empty if all are cleared.
*
* @return string
* @return string|null
*/
public function getEntityClass()
{
@@ -73,7 +75,7 @@ class OnClearEventArgs extends \Doctrine\Common\EventArgs
}
/**
* Check if event clears all entities.
* Checks if event clears all entities.
*
* @return bool
*/

View File

@@ -19,6 +19,7 @@
namespace Doctrine\ORM\Event;
use Doctrine\Common\EventArgs;
use Doctrine\ORM\EntityManager;
/**
@@ -30,16 +31,13 @@ use Doctrine\ORM\EntityManager;
* @author Roman Borschel <roman@code-factory.de>
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class OnFlushEventArgs extends \Doctrine\Common\EventArgs
class OnFlushEventArgs extends EventArgs
{
/**
* @var Doctirne\ORM\EntityManager
* @var \Doctrine\ORM\EntityManager
*/
private $em;
//private $entitiesToPersist = array();
//private $entitiesToRemove = array();
/**
* Constructor.
*
@@ -60,25 +58,4 @@ class OnFlushEventArgs extends \Doctrine\Common\EventArgs
return $this->em;
}
/*
public function addEntityToPersist($entity)
{
}
public function addEntityToRemove($entity)
{
}
public function addEntityToUpdate($entity)
{
}
public function getEntitiesToPersist()
{
return $this->_entitiesToPersist;
}
*/
}

View File

@@ -47,7 +47,7 @@ class PostFlushEventArgs extends EventArgs
}
/**
* Retrieve associated EntityManager.
* Retrieves associated EntityManager.
*
* @return \Doctrine\ORM\EntityManager
*/

View File

@@ -19,6 +19,9 @@
namespace Doctrine\ORM\Event;
use Doctrine\Common\EventArgs;
use Doctrine\ORM\EntityManager;
/**
* Provides event arguments for the preFlush event.
*
@@ -28,23 +31,28 @@ namespace Doctrine\ORM\Event;
* @author Roman Borschel <roman@code-factory.de>
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class PreFlushEventArgs extends \Doctrine\Common\EventArgs
class PreFlushEventArgs extends EventArgs
{
/**
* @var EntityManager
* @var \Doctrine\ORM\EntityManager
*/
private $_em;
private $em;
public function __construct($em)
/**
* Constructor.
*
* @param \Doctrine\ORM\EntityManager $em
*/
public function __construct(EntityManager $em)
{
$this->_em = $em;
$this->em = $em;
}
/**
* @return EntityManager
* @return \Doctrine\ORM\EntityManager
*/
public function getEntityManager()
{
return $this->_em;
return $this->em;
}
}

View File

@@ -19,8 +19,8 @@
namespace Doctrine\ORM\Event;
use Doctrine\Common\EventArgs,
Doctrine\ORM\EntityManager;
use Doctrine\Common\EventArgs;
use Doctrine\ORM\EntityManager;
/**
* Class that holds event arguments for a preInsert/preUpdate event.
@@ -40,9 +40,9 @@ class PreUpdateEventArgs extends LifecycleEventArgs
/**
* Constructor.
*
* @param object $entity
* @param \Doctrine\ORM\EntityManager $em
* @param array $changeSet
* @param object $entity
* @param EntityManager $em
* @param array $changeSet
*/
public function __construct($entity, EntityManager $em, array &$changeSet)
{
@@ -52,7 +52,7 @@ class PreUpdateEventArgs extends LifecycleEventArgs
}
/**
* Retrieve entity changeset.
* Retrieves entity changeset.
*
* @return array
*/
@@ -62,7 +62,9 @@ class PreUpdateEventArgs extends LifecycleEventArgs
}
/**
* Check if field has a changeset.
* Checks if field has a changeset.
*
* @param string $field
*
* @return boolean
*/
@@ -72,9 +74,10 @@ class PreUpdateEventArgs extends LifecycleEventArgs
}
/**
* Get the old value of the changeset of the changed field.
* Gets the old value of the changeset of the changed field.
*
* @param string $field
*
* @param string $field
* @return mixed
*/
public function getOldValue($field)
@@ -85,9 +88,10 @@ class PreUpdateEventArgs extends LifecycleEventArgs
}
/**
* Get the new value of the changeset of the changed field.
* Gets the new value of the changeset of the changed field.
*
* @param string $field
*
* @param string $field
* @return mixed
*/
public function getNewValue($field)
@@ -98,10 +102,12 @@ class PreUpdateEventArgs extends LifecycleEventArgs
}
/**
* Set the new value of this field.
* Sets the new value of this field.
*
* @param string $field
* @param mixed $value
* @param mixed $value
*
* @return void
*/
public function setNewValue($field, $value)
{
@@ -111,9 +117,13 @@ class PreUpdateEventArgs extends LifecycleEventArgs
}
/**
* Assert the field exists in changeset.
* Asserts the field exists in changeset.
*
* @param string $field
*
* @return void
*
* @throws \InvalidArgumentException
*/
private function assertValidField($field)
{
@@ -126,4 +136,3 @@ class PreUpdateEventArgs extends LifecycleEventArgs
}
}
}

View File

@@ -29,7 +29,13 @@ namespace Doctrine\ORM;
*/
final class Events
{
private function __construct() {}
/**
* Private constructor. This class is not meant to be instantiated.
*/
private function __construct()
{
}
/**
* The preRemove event occurs for a given entity before the respective
* EntityManager remove operation for that entity is executed.
@@ -39,6 +45,7 @@ final class Events
* @var string
*/
const preRemove = 'preRemove';
/**
* The postRemove event occurs for an entity after the entity has
* been deleted. It will be invoked after the database delete operations.
@@ -48,6 +55,7 @@ final class Events
* @var string
*/
const postRemove = 'postRemove';
/**
* The prePersist event occurs for a given entity before the respective
* EntityManager persist operation for that entity is executed.
@@ -57,6 +65,7 @@ final class Events
* @var string
*/
const prePersist = 'prePersist';
/**
* The postPersist event occurs for an entity after the entity has
* been made persistent. It will be invoked after the database insert operations.
@@ -67,6 +76,7 @@ final class Events
* @var string
*/
const postPersist = 'postPersist';
/**
* The preUpdate event occurs before the database update operations to
* entity data.
@@ -76,6 +86,7 @@ final class Events
* @var string
*/
const preUpdate = 'preUpdate';
/**
* The postUpdate event occurs after the database update operations to
* entity data.
@@ -85,6 +96,7 @@ final class Events
* @var string
*/
const postUpdate = 'postUpdate';
/**
* The postLoad event occurs for an entity after the entity has been loaded
* into the current EntityManager from the database or after the refresh operation
@@ -99,6 +111,7 @@ final class Events
* @var string
*/
const postLoad = 'postLoad';
/**
* The loadClassMetadata event occurs after the mapping metadata for a class
* has been loaded from a mapping source (annotations/xml/yaml).
@@ -109,7 +122,7 @@ final class Events
/**
* The preFlush event occurs when the EntityManager#flush() operation is invoked,
* but before any changes to managed entites have been calculated. This event is
* but before any changes to managed entities have been calculated. This event is
* always raised right after EntityManager#flush() call.
*/
const preFlush = 'preFlush';

View File

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

View File

@@ -23,7 +23,7 @@ use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMException;
/**
* Special generator for application-assigned identifiers (doesnt really generate anything).
* Special generator for application-assigned identifiers (doesn't really generate anything).
*
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
@@ -36,8 +36,13 @@ class AssignedGenerator extends AbstractIdGenerator
/**
* Returns the identifier assigned to the given entity.
*
* @param object $entity
* @param EntityManager $em
* @param object $entity
*
* @return mixed
*
* @throws \Doctrine\ORM\ORMException
*
* @override
*/
public function generate(EntityManager $em, $entity)
@@ -47,7 +52,7 @@ class AssignedGenerator extends AbstractIdGenerator
$identifier = array();
foreach ($idFields as $idField) {
$value = $class->reflFields[$idField]->getValue($entity);
$value = $class->getFieldValue($entity, $idField);
if ( ! isset($value)) {
throw ORMException::entityMissingAssignedIdForField($entity, $idField);

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