Compare commits

..

238 Commits

Author SHA1 Message Date
Marco Pivetta 22546a3811 Correcting connection existence in tearDown operations 2017-08-11 23:28:20 +02:00
Marco Pivetta efa058bd8f If no connection was enstablished, skip the tearDown operations 2017-08-11 22:56:02 +02:00
Marco Pivetta 767577cec6 Removing ::class pseudo-constant usage 2017-08-11 22:49:46 +02:00
Marco Pivetta c0f593e422 Removing ::class syntax to make dinosaurs run against this codebase too 2017-08-11 22:22:50 +02:00
Marco Pivetta d89d238594 Merge branch 'fix/#6464-#6475-correct-SQL-generated-with-JTI-and-WITH-condition-2.5' into 2.5
Backport #6464
Backport #6475
2017-08-11 21:43:05 +02:00
Marco Pivetta 2337b7aedd #6464 #6475 cleaning up test - removed invalid fetch join, CS 2017-08-11 21:41:05 +02:00
Stefan Siegl 9e6f061bfb #6464 code review updates 2017-08-11 21:40:53 +02:00
Stefan Siegl bf1188127e generate nested join sql for CTIs, closes #6464 2017-08-11 21:40:16 +02:00
Stefan Siegl c73ec2aa76 #6464 add test 2017-08-11 21:39:45 +02:00
Marco Pivetta 095611c4b6 Merge branch 'fix/#6614-clean-modified-collection-causing-double-dirty-object-persistence-2.5' into 2.5
Backport #6613
Backport #6614
Backport #6616
2017-08-11 21:25:27 +02:00
Marco Pivetta 96c6f4cf1d #6613 #6614 #6616 removed unused import 2017-08-11 21:25:10 +02:00
Marco Pivetta 5cacb6e14f #6613 #6614 #6616 minor performance optimisations around the new restoreNewObjectsInDirtyCollection implementation 2017-08-11 21:23:58 +02:00
Marco Pivetta ab63628960 #6613 #6614 #6616 removing DDC6613 test, which was fully ported to unit tests 2017-08-11 21:23:55 +02:00
Marco Pivetta 15731c7bde #6613 #6614 #6616 ensuring that the collection is marked as non-dirty if all new items are contained in the initialized ones 2017-08-11 21:23:51 +02:00
Andreas Braun abb429a0c9 Add failing test for dirty flag 2017-08-11 21:23:47 +02:00
Marco Pivetta 61cb03bf30 #6613 #6614 #6616 removing repeated PersistentCollectionTest chunks of code 2017-08-11 21:23:40 +02:00
Marco Pivetta d6bcb5b1f8 #6613 #6614 #6616 initializing a dirty collection that has new items that are also coming from initialization data de-duplicates new and persisted items 2017-08-11 21:22:59 +02:00
Marco Pivetta bdae362777 #6613 #6614 #6616 moved integration test basics to a unit test that verifies basic dirty collection initialization semantics 2017-08-11 21:22:54 +02:00
Marco Pivetta 59c5574554 #6613 #6614 correcting broken test that isn't using objects against a PersistentCollection 2017-08-11 21:22:43 +02:00
Marco Pivetta 9545bf9d8c #6613 #6614 correcting broken test that isn't using objects against a PersistentCollection 2017-08-11 21:22:40 +02:00
Marco Pivetta 5521d1f325 #6613 #6614 ensuring that only newly added items that weren't loaded are restored in the dirty state of the collection 2017-08-11 21:22:34 +02:00
Marco Pivetta 49694dc335 #6613 #6614 after initialization, the collection should be dirty anyway 2017-08-11 21:21:43 +02:00
Marco Pivetta 3155d970d3 #6613 #6614 adding assertions about collection initialization and dirty status 2017-08-11 21:21:40 +02:00
Marco Pivetta 09189fc021 #6613 #6614 removing IDE-generated header 2017-08-11 21:21:36 +02:00
Marco Pivetta 5a0d3e5fb8 #6613 #6614 removing phone/user specifics, using ORM naming for associations 2017-08-11 21:21:27 +02:00
Marco Pivetta b9ba4e3207 #6613 #6614 correcting column mapping (was integer, should be string), segregating phone creation away 2017-08-11 21:21:24 +02:00
Marco Pivetta d7919678e5 #6613 #6614 remove superfluous mappings 2017-08-11 21:21:21 +02:00
Marco Pivetta 8b185eb822 #6613 #6614 rewrote test logic to be less magic-constant-dependent 2017-08-11 21:21:17 +02:00
Marco Pivetta 693a0546d3 #6613 #6614 CS - applying @group annotation to the test 2017-08-11 21:21:14 +02:00
Marco Pivetta 5c5c8fc487 #6613 #6614 removing dedicated DDC6613 model directory 2017-08-11 21:21:10 +02:00
Marco Pivetta 85dc707cc8 #6613 #6614 smashing entity definitions into the test 2017-08-11 21:21:03 +02:00
Marco Pivetta 64a1251b61 #6613 #6614 better test specification - removing useless assertions 2017-08-11 21:20:42 +02:00
Marco Pivetta 65ed6a2c2f #6613 #6614 simplifying entity definition - using auto-assigned string identifiers to reduce moving parts 2017-08-11 21:20:34 +02:00
Marco Pivetta d27a9fce7a Merge pull request #6580 from Tobion/patch-1
Allow DBAL 2.6 and common 2.8 to be installed
2017-07-25 05:02:26 +02:00
Tobias Schultze 11659f5cfe Allow common 2.8 to be installed 2017-07-24 16:38:01 +02:00
Tobias Schultze 68dad26482 Allow DBAL 2.6 to be installed
DBAL 2.6 is released but currently dependencies can't be resolved as ORM 2.5 does not allow DBAL 2.6 and ORM 2.6 is not relased yet.
2017-07-24 16:27:53 +02:00
Marco Pivetta b3ceef0fb6 Merge branch 'fix/#6550-correct-return-value-of-extra-lazy-removeElement-calls' into 2.5
Backport #6550 to 2.5
2017-07-22 09:27:30 +02:00
Andreas Braun 095b365146 Add test for removing element not in collection 2017-07-22 09:27:13 +02:00
Andreas Braun 7c1ebd99bc Fix return of removeElement on collections
Fixes #5745
2017-07-22 09:27:01 +02:00
Marco Pivetta c06f19db8d Merge branch 'fix/#1515-clean-hydrator-listeners-on-hydration-end-2.5' into 2.5
Close #1515

This is a backport of the original PR - the same patch should land in `master` too, after a cleanup
2017-06-24 03:23:19 +02:00
Emiel Nijpels 0be9be4e24 DDC-3146 remove event listener from event listener in abstract hydrator in cleanup function 2017-06-24 03:23:01 +02:00
Luís Cobucci b2bf5ee92e Leave PHP 7.1 and nightly to master 2017-06-22 07:57:54 +02:00
Luís Cobucci 698bd813a2 Remove HHVM from build 2017-06-22 07:46:45 +02:00
Jáchym Toušek b2ac8fdfd7 Fix CountOutputWalker for queries with GROUP BY 2017-06-22 07:15:35 +02:00
Marco Pivetta 9c2b54b748 Adding classes required by the SchemaToolTest that exist in 'master', but not in '2.5' 2017-06-21 07:27:41 +02:00
Marco Pivetta 910784213f Corrected duplicate import statements due to cherry picking 2017-06-21 06:49:45 +02:00
Marco Pivetta 1d1de7de80 Merge branch 'fix/#5798-undefined-schema-tool-index-2.5' into 2.5
Close #5798
2017-06-21 06:32:00 +02:00
Sergey Fedotov 741da6eed7 Fix undefined index for discriminator column in SchemaTool 2017-06-21 06:31:23 +02:00
Marco Pivetta ad5397b581 Merge branch 'fix/#5715-fix-metadata-filtering-in-cli-tools-2.5' into 2.5
Close #5715
2017-06-21 06:15:42 +02:00
Guilliam Xavier 57bb46ca9d Add regex tests for MetadataFilter (PR #507) 2017-06-21 06:15:25 +02:00
Guilliam Xavier 0416d5e036 Add more basic tests for MetadataFilter 2017-06-21 06:15:14 +02:00
Guilliam Xavier a14432117a Fix MetadataFilter not testing filters after first 2017-06-21 06:15:01 +02:00
Guilliam Xavier 824f62d3bb Add failing test for #5715 (unit test for MetadataFilter) 2017-06-21 06:14:48 +02:00
Marco Pivetta c0f0fe060f Merge branch 'fix/#1541-minor-docblock-correction-in-resultset-mapping-builder' into 2.5
Backport #1541 to 2.5
2017-05-20 16:45:59 +02:00
aleeeftw caffbe04a2 Minor docblock correction
The documentation for the method ‘addJoinedEntityFromClassMetadata’ is
wrong. As we can see currently says you need to pass an object and that
is wrong. The $relation variable is passed to ‘addJoinedEntityResult’
which is using it as a ‘string’.
2017-05-20 16:45:25 +02:00
Marco Pivetta d2c805b071 Correcting PHP 5.4 compliance by removing ::class usage (moving to real constants) 2017-05-02 09:33:48 +02:00
Marco Pivetta 04fc7a9a1c Merge branch 'fix/#6367-#6362-inheritance-alias-hydration' into 2.5
Close #6367
Close #6362
2017-05-02 09:26:54 +02:00
Timothy Clissold 149b8f4e09 Fix inheritance join alias 2017-05-02 09:26:44 +02:00
Marco Pivetta 48e8c02cb8 Merge pull request #6381 from ElisDN/ElisDN-phpdoc
Fixed PHPDoc
2017-04-03 09:04:13 +02:00
Елисеев Дмитрий e218866a69 Fixed PHPDoc 2017-04-02 20:29:46 +03:00
Marco Pivetta e6c434196c Merge pull request #6178 from doctrine/fix/#6174-#5570-merging-new-entities-should-also-trigger-prepersist-lifecycle-callbacks-2.5
Backport #6177 - fix #6174 #5570: merging new entities should also trigger prepersist lifecycle callbacks with the merged data
2016-12-18 16:42:34 +01:00
Marco Pivetta d52dbe62ac #6174 #5570 switching ::class to string constants for PHP 5.4 compat (still supported in ORM 2.5.x) 2016-12-18 16:24:42 +01:00
Marco Pivetta b0ede40f47 #6174 #5570 removed modifications applied to the CompanyContractListener, since UnitOfWorkTest now completely encapsulates the scenarios being covered 2016-12-18 16:13:11 +01:00
Marco Pivetta 3645a9c44d #6174 #5570 removed unused imports 2016-12-18 16:13:04 +01:00
Marco Pivetta 39ce6f96a0 #6174 #5570 renamed entity for better fitting the use-cases it's in 2016-12-18 16:12:57 +01:00
Marco Pivetta e43f5304ef #6174 #5570 removed unused test class 2016-12-18 16:12:46 +01:00
Marco Pivetta 67724eb7ae #6174 #5570 adding group annotations to newly introduced test 2016-12-18 16:12:36 +01:00
Marco Pivetta 8d4bc0638d #6174 #5570 prePersist listeners should never be called when entities are merged, but are already in the UoW 2016-12-18 16:12:29 +01:00
Marco Pivetta 81186105b6 #6174 #5570 started moving tests around prePersist event subscriber triggering on UnitOfWork into the UnitOfWorkTest 2016-12-18 16:12:03 +01:00
Marco Pivetta beef8acdf5 #6174 #5570 CS fixes around the EntityListenersOnMergeTest 2016-12-18 16:10:17 +01:00
Marco Pivetta 26fc8d60e6 #6174 #5570 adding group annotation to newly introduced tests 2016-12-18 16:10:08 +01:00
Marco Pivetta 12e8ab216a #6174 #5570 CS - spacing/variable naming 2016-12-18 16:09:59 +01:00
Marco Pivetta dac1a16964 #6174 #5570 removed unused/dead code 2016-12-18 16:09:52 +01:00
Marco Pivetta d9821d3fda #6174 #5570 CS - spacing 2016-12-18 16:09:45 +01:00
Marco Pivetta 576a4d7e31 #6174 #5570 CS - spacing 2016-12-18 16:09:38 +01:00
Marco Pivetta eaee924180 #6174 #5570 flattened nested conditionals 2016-12-18 16:09:32 +01:00
Marco Pivetta cf941ce54f #6174 #5570 documenting thrown exception types 2016-12-18 16:09:25 +01:00
Marco Pivetta cfb7461f51 #6174 #5570 CS - alignment 2016-12-18 16:09:18 +01:00
bilouwan 569c08ce55 Rename test 2016-12-18 16:09:11 +01:00
bilouwan 295523cdca Cherry pick unit test from PR #5570 (Fix PrePersist EventListener when using merge instead of persist) 2016-12-18 16:09:00 +01:00
bilouwan 25efabdb74 doMerge will mergeEntityStateIntoManagedCopy BEFORE persistNew to let lifecyle events changes be persisted 2016-12-18 16:08:15 +01:00
bilouwan 1d96178097 Create failing test to reveal the issue 2016-12-18 16:08:05 +01:00
Marco Pivetta 20cb50451d Merge pull request #6159 from nicolas-cajelli/backport-fix-relation-cache-#1551-to-2.5
#5821 Backport #1551 - Fixed support for inverse side second level cache
2016-12-12 08:34:27 +01:00
Guilherme Blanco 0ff512ba8f Fixed support for inverse side second level cache 2016-12-05 14:36:33 -03:00
Marco Pivetta 5e9014fd99 Merge pull request #6156 from Slamdunk/patch-2
Allow doctrine/common 2.7
2016-12-04 06:53:07 +01:00
Filippo Tessarotto a90cd9dfe8 Allow doctrine/common 2.7 2016-12-02 09:00:50 +01:00
Marco Pivetta 4d699789a2 Merge branch 'fix/#6110-collection-clear-should-also-clear-keys-2.5' into 2.5
Close #6110
2016-11-26 06:06:11 +01:00
Marco Pivetta 1486c8f8e2 split test into multiple sub-scenarios involving PersistentCollection key checking #6110 2016-11-26 06:05:31 +01:00
Steevan BARBOYON 3dadfa49d5 Clear $this->collection even when empty, to reset indexes 2016-11-26 06:04:35 +01:00
Marco Pivetta 9b36947a48 Merge branch 'fix/#6028-l2c-inheritance-query-cache-use-parent-entity-name-2.5' into 2.5
Backport #6028 to 2.5.x
2016-11-23 18:06:28 +01:00
Marco Pivetta 2122297fdb #6028 removed specific ::class usage, since 2.5.x still supports PHP 5.4.x 2016-11-23 18:06:14 +01:00
Marco Pivetta af99cba28c #6028 removed specific ::class usage, since 2.5.x still supports PHP 5.4.x 2016-11-23 18:02:15 +01:00
Luís Cobucci 9bcee455ca Make child entity share the timestamp region with parent class 2016-11-23 17:58:05 +01:00
Marco Pivetta 73e4be7c7b Merge branch 'fix/#5768-#5755-clone-proxy-private-properties-in-multi-level-inheritances-2.5' into 2.5
Close #5768
Close #5755
2016-09-10 20:51:13 +02:00
Ed Hartwell Goose d7026c46ec Fixes #5755, uses '->getReflectionProperties()' instead of '->getReflectionClass()->getProperties()' to ensure all fields are copied, and adds test to confirm behaviour 2016-09-10 20:48:12 +02:00
Marco Pivetta b7bfbb6adb Merge branch 'fix/#5689-avoid-object-hash-conflicts-due-to-merge-operations-2.5' into 2.5
Close #5689
2016-09-10 20:22:23 +02:00
Marco Pivetta c9161fcd6f #5689 removed unused reflection access 2016-09-10 20:19:29 +02:00
Marco Pivetta 147f8fffff #5689 removed OidReuseTest, which was moved to UnitOfWork tests 2016-09-10 20:18:10 +02:00
Marco Pivetta e73428a051 #5689 moved OidReuseTest contents into the UnitOfWork tests 2016-09-10 20:17:59 +02:00
Mathieu De Zutter a3d93afc4f Additional assertion to check that unreferenced objects are not in UOW. 2016-09-10 20:16:38 +02:00
Mathieu De Zutter b0e4e3eda4 Remove old code in comments. 2016-09-10 20:16:33 +02:00
Mathieu De Zutter 95dcf51ad5 Avoid conflicts due to spl_object_hash().
When merging an entity with a to-many association, it will store the
original entity data using the object hash of the to-be-merged entity
instead of the managed entity. Since this to-be-merged entity is not
managed by Doctrine, it can disappear from the memory. A new object
can reuse the same memory location and thus have the same object hash.
When one tries to persist this object as new, Doctrine will refuse it
because it thinks that the entity is managed+dirty.

This patch is a very naive fix: it just disables storing the original
entity data in case of to-many associations. It may not be the ideal
or even a good solution at all, but it solves the problem of object
hash reuse.

The test case relies on the immediate reusing of memory locations by
PHP. The variable $user has twice the same object hash, though referring
a different object. Tested on PHP 5.6.17

Without the fix, the test fails on the last line with:
A managed+dirty entity Doctrine\Tests\Models\CMS\CmsUser@[...] can not
be scheduled for insertion.
2016-09-10 20:16:28 +02:00
Marco Pivetta 5365a418e9 Removed non-existing CLASSNAME reference 2016-09-09 23:25:54 +02:00
Marco Pivetta 47ce079b64 Removing ::class usage (PHP 5.5 is not yet required on ORM 2.5) 2016-09-09 22:52:54 +02:00
Marco Pivetta 8e95672f49 Removing ::class usage (PHP 5.5 is not yet required on ORM 2.5) 2016-09-09 22:51:42 +02:00
Marco Pivetta e16de704a1 Merge branch 'fix/#6001-second-level-cache-query-cache-timestamp-from-region-2.5' into 2.5
Close #6001
2016-09-08 14:02:07 +02:00
Marco Pivetta 3e57c46afd #6001 adding orderBy, limit and offset variables (defined in master, not in 2.5) 2016-09-08 14:01:56 +02:00
Marco Pivetta 5e702ad524 #6001 documenting minor BC break in QueryCacheEntry#time type - specific version used 2016-09-08 13:58:30 +02:00
Luís Cobucci 9e9864c684 The timestamp verification is now done by the validator
So it's useless to keep it here too.
2016-09-08 13:58:24 +02:00
Luís Cobucci 88567ea395 Evict query cache when entities are updated 2016-09-08 13:56:46 +02:00
Luís Cobucci bf18aac62d Add timestamp key to QueryCacheKey 2016-09-08 13:54:47 +02:00
Luís Cobucci 4d16e30a16 Use microtime to have more precision on cache time 2016-09-08 13:54:03 +02:00
Marco Pivetta d592c14a6c Merge branch 'fix/#6004-#5989-fix-hydration-in-a-joined-inheritance-with-simple-array-or-json-array-2.5' into 2.5
Close #6004
Close #5989
2016-09-08 13:38:57 +02:00
Carl Vuorinen da41161d73 Add unit test for SimpleObjectHydrator 2016-09-08 13:38:34 +02:00
Carl Vuorinen 33e23cdddb PR fixes (public properties & correct letter case in annotations) 2016-09-08 13:38:31 +02:00
Carl Vuorinen c47c23a101 Use yoda condition in the null check 2016-09-08 13:38:27 +02:00
Carl Vuorinen 7352b97b14 Fix hydration in a joined inheritance with simple array or json array
SimpleArrayType and JsonArrayType convert NULL value to an empty array, which fails the null check that is used to prevent overwrite
Fixes issue #5989
2016-09-08 13:38:23 +02:00
Carl Vuorinen dce0aeaa15 Create a failing test for issue #5989
Field with type=simple_array in a joined inheritance gets overridden by empty array in the hydrator
2016-09-08 13:38:18 +02:00
Marco Pivetta a2c23fb9cb Merge branch 'fix/#5975-fix_hydrating_fetch_join_with_composite_pk-2.5' into 2.5
Close #5975
Close #5762
Close #5776
2016-09-07 23:22:01 +02:00
Marco Pivetta aa6dc9695d #5975 minor test cleanups 2016-09-07 23:20:47 +02:00
Marco Pivetta c4a2a348f4 #5975 short array syntax 2016-09-07 23:20:43 +02:00
Alexander Kurilo 6ab27993fc Use ::class const instead of FQCN string (#5762) 2016-09-07 23:20:27 +02:00
Alexander Kurilo 5eedccc22a Remove irrelevant accessors (#5762) 2016-09-07 23:20:22 +02:00
John Keller 592122fbcb add functional test and bug fix for issue #5762 2016-09-07 23:20:17 +02:00
Marco Pivetta aa5820309e Merge branch 'fix/#5867-allow-embeddable-usage-in-inheritance-2.5' into 2.5
Close #5867
Close #4097
Close #4277
2016-06-19 12:48:28 +02:00
Marco Pivetta b183818fa8 #5867 s/::class/::CLASSNAME for PHP 5.4 compat 2016-06-19 12:48:15 +02:00
Marco Pivetta 2af84c6025 #5867 @group annotations, describing scenario 2016-06-19 12:47:05 +02:00
Marco Pivetta f181cf6c6b #5867 simplifying test case by inlining all required models into the test case 2016-06-19 12:45:54 +02:00
Luís Cobucci 62431ae477 Allow the usage of embedded objects on parent classes.
The `ClassMetadataInfo` was always using the "current class" to
fetch the reflection of a property even when a field is declared
on the parent class (which causes `ReflectionProperty` to throw
an exception).
2016-06-19 12:45:47 +02:00
Marco Pivetta 0d93461e66 Merge branch 'fix/#5858-yaml-exporter-should-only-introspect-join-column-on-owning-association-side-2.5' into 2.5
Close #5858
2016-06-08 13:34:23 +02:00
Thomas Ploch a5eb0f2e82 Exporters should only inspect joinColumns for owning side in bi-directional OneToOne
rebased commits:

- Added test case for bi-directional OneToOne in YamlExporter
- Only inspect joinColumns for owning side in bi-directional OneToOne in YamlExporter
- Adding bi-directional test case without joinColumn to XmlExporter test
- Same testcase also applied to PhpExporter
- Fixing bi-directional issue in PhpExporter when inspecting joinColumns index
- Implemented @Ocramius suggestions
2016-06-08 13:34:10 +02:00
Marco Pivetta 9a393ccba7 Removed reliance on ::class meta-constant (only available in PHP 5.5+) 2016-06-06 01:48:32 +02:00
Marco Pivetta 224ac9725e Removed annotation reader constructor argument (incorrect argument used) 2016-06-06 01:34:20 +02:00
Marco Pivetta 0af9ee0140 Merge branch 'fix/#5850-clearing-specific-entity-name-should-clear-also-its-entity-insertions-2.5' into 2.5
Close #5850
Close #5849
2016-06-06 00:34:54 +02:00
Marco Pivetta fecadf059c #5849 #5850 renamed clearEntityInsertions to clearEntityInsertionsForEntityName, for clarity 2016-06-06 00:32:01 +02:00
Marco Pivetta 800215040a #5849 #5850 refactored clearIdentityMapForEntityName to remove useless looping 2016-06-06 00:31:56 +02:00
Marco Pivetta ec4dd4ab44 #5849 #5850 renamed clearIdentityMap to clearIdentityMapForEntityName, for clarity 2016-06-06 00:31:51 +02:00
Marco Pivetta 7378035f68 #5849 #5850 correcting test scenario: identity map could not be built with auto-generated identities+persist 2016-06-06 00:31:23 +02:00
Marco Pivetta 7fbcbfa271 #5849 #5850 adding group annotations to the newly introduced test case 2016-06-06 00:30:46 +02:00
Rico Humme 4a38c96ec5 Correct naming convention of function. Was confusing otherwise 2016-06-06 00:30:42 +02:00
Rico Humme 110d771883 Split of functionality in separate functions 2016-06-06 00:30:37 +02:00
Rico Humme 996c5048ab Test Case for Clear entityInsertions for specific entityName 2016-06-06 00:30:33 +02:00
Rico Humme cd746beae2 Clear entityInsertions for specific entityName 2016-06-06 00:30:28 +02:00
Marco Pivetta dfbc6bbea3 Merge branch 'fix/#5599-having-regression-fix-2.5' into 2.5
Backport #5599 into 2.5
2016-01-16 10:29:29 -06:00
Bill Schaller c4209b4654 Fix issue were identifier operands in /,* arithmetic terms were not checked to see if they're query components 2016-01-16 10:28:52 -06:00
Alessandro Lai 6279c80e05 Regression test: HAVING clause does not translate variable name when used with * and / math operators 2016-01-16 10:28:43 -06:00
Marco Pivetta d05aa6a5e0 Bumping to development version 2.5.5-DEV 2016-01-05 22:36:06 +01:00
Marco Pivetta bc4ddbfb01 Release 2.5.4 2016-01-05 22:34:58 +01:00
Marco Pivetta 14499f5021 Removing coveralls installation/reporting from 2.5: not required 2016-01-05 22:25:57 +01:00
Marco Pivetta aae43cbb77 Merge branch 'hotfix/#1568-identifier-type-cached-incorrectly-backport-2-5' into 2.5
Backport #1568 to 2.5
2016-01-05 22:14:40 +01:00
Jan Langer db6cb8dedc Second level cache stores identifier with correct type even if findById is called with wrong identifier type 2016-01-05 22:13:03 +01:00
Guido Contreras Woda 5092da074a Test that reflects the issue described in http://www.doctrine-project.org/jira/browse/DDC-3967 2016-01-05 22:12:55 +01:00
Marco Pivetta 1c6524db55 Bumping to development version 2.5.4-DEV 2015-12-25 16:50:31 +01:00
Marco Pivetta d9fc5388f1 2.5.3 release 2015-12-25 16:50:05 +01:00
Marco Pivetta d2e51eacff Reverting incorrect DBAL 2.6 bump 2015-12-25 16:49:48 +01:00
Marco Pivetta 5a6ae4686f Allowing doctrine/common 2.6 2015-12-25 15:58:40 +01:00
Marco Pivetta 8b8a1cbe81 Merge branch 'hotfix/common-2.6-upgrade-compat-2.5' into 2.5 2015-12-25 15:26:15 +01:00
Marco Pivetta 27a5284899 doctrine/common 2.6.0 compat
Less strict assertion - no need to check the exact file name
2015-12-25 15:24:37 +01:00
Marco Pivetta 0086d17afe Common 2.6 compatibility
Internal structure of the ArrayCache has changed, therefore we should fix the tests depending on it instead
2015-12-25 15:24:32 +01:00
Marco Pivetta ab62167c8a Merge branch 'hotfix/#4884-support-proxy-php7-hints-generation-2.5' into 2.5
Close #4884
2015-12-25 14:47:12 +01:00
Marco Pivetta 6d43195669 #4884 - allow installation of doctrine/common 2.6.x, which allows generating type-hints on proxies 2015-12-25 14:46:59 +01:00
Marco Pivetta 7065ff0ac9 Merge branch 'hotfix/#1572-target-entity-resolver-dql-with-interfaces-support' into 2.5
Close #1572
2015-12-11 21:35:18 +01:00
Marco Pivetta aa61328e90 #1572 - test coverage - interfaces should also resolve to target entities when in DQL 2015-12-11 21:30:19 +01:00
Marco Pivetta 62719f2a97 Merge branch 'hotfix/#1573-merge-associated-versioned-entity-2.5.x' into 2.5
Close #1573
2015-12-11 20:19:10 +01:00
Marco Pivetta 66770c5bfe #1573 - correcting test asset namespace, removing unused properties and bi-directional association 2015-12-11 20:18:53 +01:00
Marco Pivetta 42691c21b4 Removing empty newline 2015-12-11 20:18:48 +01:00
Marco Pivetta 596e895763 #1573 - correcting docblock arguments/description 2015-12-11 20:18:42 +01:00
Marco Pivetta d5c82094df #1573 removing unused API 2015-12-11 20:18:37 +01:00
bilouwan 4148220f9c Refactor testing Proxy not initilized 2015-12-11 20:18:31 +01:00
bilouwan e173c930ec Fix superflous whitespaces & empty lines 2015-12-11 20:18:25 +01:00
bilouwan 7071984559 Fix compatibility with php5.4 2015-12-11 20:18:19 +01:00
bilouwan 216c466233 Unit test & fix for merge versionned entity 2015-12-11 20:18:12 +01:00
Marco Pivetta 65f5777e60 Merge branch 'hotfix/php7-xdebug-incompatibility-fixes-2.5.x' into 2.5
Close #5547
2015-12-11 19:35:42 +01:00
Marco Pivetta 6e3ce26429 Correcting minor test case incompatibility with XDebug 2.4.x
In PHP 5.x + XDebug < 2.4, the output would be "string:..."
In PHP 7.x + XDebug >= 2.4, the output would be "the/file/name.php:11:string:..."

This is an improvement in XDebug that is quite annoying for our purposes, but is actually welcome to most users anyway.

This commit simply fixes that incompatibility
2015-12-11 19:35:28 +01:00
oprokidnev 752d4f9eac Target entity resolver for DQL
Since we have target entity resolver in doctrine this class check is not enought.
To gain interface resolution it is better to add interface check in addition to class_check here.
2015-11-27 16:00:56 +05:00
Marco Pivetta 2983081a60 Bumping current dev version to 2.5.3-DEV 2015-11-23 13:44:56 +01:00
Marco Pivetta 464b5fdbfb Release 2.5.2 2015-11-23 13:44:25 +01:00
Guilherme Blanco 04254a8e34 Merge pull request #1512 from neoglez/2.5
Backport of "LimitSubqueryOutputWalker: fix aliasing of property in OrderBy from MappedSuperclass"
2015-11-15 22:08:59 -05:00
Guilherme Blanco 1d213e6733 Merge pull request #1543 from nicolas-grekas/dep-30
Allow symfony 3.0 components on 2.5
2015-11-09 14:54:44 -05:00
Klein Thomas bc82e94afc Move to 2.5 section 2015-11-09 03:56:16 +00:00
Klein Thomas b9af1c8fa5 Update Upgrade.md after minor bc break in 2.5.1
The introduction of the second parameter in EntityRepository#createQueryBuilder generates a runtime notice if you have a sub-class of EntityRepository that has a second parameter in the createQueryBuilder method
2015-11-09 03:56:07 +00:00
Pantel d606efd4eb [DDC-3711] add Test that check if the association key are composite 2015-11-09 03:46:06 +00:00
Pantel 581e1638a2 [DDC-3711] add Tests that check if the association key are composite 2015-11-09 03:45:56 +00:00
Pantel 17ae8d1b2d [DDC-3711] Correct Error on manyToMany with composite primary key 2015-11-09 03:45:46 +00:00
Guilherme Blanco 3a058f8522 Merge branch 'hotfix/#1375-prevent-duplicate-unique-index' into 2.5
Backported #1375 to 2.5
2015-11-07 16:47:26 +00:00
Michał Bundyra 567220ef71 prevent duplicate unique index 2015-11-07 16:46:22 +00:00
Marco Pivetta 39098ce415 Merge branch 'hotfix/#1507-fixed-wrong-property-name-in-resultset-mapping-builder' into 2.5
Backport merge #1507 into 2.5.x
Close #1507
2015-11-07 10:35:30 -05:00
François-Xavier de Guillebon 1eb9c8a7f6 Added test 2015-11-07 10:35:08 -05:00
François-Xavier de Guillebon f2f53ba9dc Fixed wrong variable used as array key 2015-11-07 10:35:02 -05:00
François-Xavier de Guillebon ebbc443ec3 Fixed wrong property name 2015-11-07 10:34:55 -05:00
Nicolas Grekas 16802d2614 Allow symfony 3.0 components
Tests should tell if any deprecated interfaces of Symfony are used. If not, then the bundle is defacto compatible with 3.0
2015-11-05 11:43:20 +01:00
neoglez ef73249bc7 Entity to test a mapped superclass
Backport of https://github.com/doctrine/doctrine2/pull/1377/files to branch 2.5
2015-09-21 09:32:54 +02:00
neoglez ed637e51b9 Backport of "fix aliasing of property in OrderBy from MappedSuperclass"
Backport of "LimitSubqueryOutputWalker: fix aliasing of property in OrderBy from MappedSuperclass" ( https://github.com/doctrine/doctrine2/commit/e501137d1afff2c08963828b61b0b8b6668edd83 )
See my comment on https://github.com/neoglez/doctrine2/commit/a3ece3b419fa7bfe50ccf2a48d83545aa007fbe8
2015-09-21 08:58:30 +02:00
neoglez a3ece3b419 UnitTest backport of "Failing test case for broken paginator case"
UnitTest backport of "Failing test case for broken paginator case" ( https://github.com/doctrine/doctrine2/commit/192da148428e62cea53fa2b918daf14f85cd7286 ).
The branch 2.x is very important because it's related to ZF2 doctrine module (see https://github.com/doctrine/DoctrineORMModule/blob/master/composer.json ) and specially this issue affects the use case of extending the ZF2 user entity defined in ZfcUser ( https://github.com/ZF-Commons/ZfcUser ).
This test is meant to show the need of the backport of https://github.com/doctrine/doctrine2/commit/e501137d1afff2c08963828b61b0b8b6668edd83
2015-09-21 08:52:46 +02:00
Marco Pivetta 2d1bc78749 Merge branch 'hotfix/#1510-DDC-3908-fix-cache-related-tests-in-2.5' into 2.5
Close #1510
2015-09-19 10:45:24 +02:00
Matthias Pigulla eaf8b1c7ca Fix tests related to caches, as per doctrine/cache 1.5.0 changes
Backports #1510
Fixes DDC-3908

https://github.com/doctrine/cache/commit/dd47003641aa5425820c0ec8a6f4a85e7412ffcd removes the 'DoctrineNamespaceCacheKey[]' entry from the cache. Thus, all tests counting cache entries were off by one.
2015-09-19 10:44:47 +02:00
Benjamin Eberlei 8070b50150 Bump version to 2.5.2 2015-08-31 14:59:39 +02:00
Benjamin Eberlei e6a83bedbe Release 2.5.1 2015-08-31 14:59:39 +02:00
Benjamin Eberlei b6e5464b98 Fix version 2015-08-31 14:59:36 +02:00
Benjamin Eberlei 6366d190d7 [DCOM-293] Fix security misconfiguration vulnerability allowing local remote arbitrary code execution. 2015-08-31 14:58:12 +02:00
Bill Schaller 89eed31e79 Merge pull request #1463 from ehimen/paginate-order-by-subselect
Fixed issue when paginator orders by a subselect expression
Conflicts:
	tests/Doctrine/Tests/ORM/Tools/Pagination/LimitSubqueryOutputWalkerTest.php
2015-08-04 14:31:08 -04:00
Marco Pivetta 4ca00f7a9d Merge branch 'hotfix/#1387-DDC-3699-do-not-merge-managed-uninitialized-entities-2.5' into 2.5
Close #1387
2015-07-15 21:52:41 +01:00
Marco Pivetta 6bc405455e DDC-3699 - #1387 - leveraging the OrmFunctionalTestCase API 2015-07-15 21:52:25 +01:00
Marco Pivetta 173729e560 DDC-3699 - #1387 - catching specific exceptions 2015-07-15 21:52:20 +01:00
Marco Pivetta 86abbb0e78 DDC-3699 - #1387 - simpifying tests, clarifying on test method names 2015-07-15 21:52:16 +01:00
Lenard Palko 69ef75ff2d Added test cases for both one-to-one and one-to-many cases. 2015-07-15 21:52:08 +01:00
Lenard Palko c68edec0c2 Fix skipping properties if they are listed after a not loaded relation. 2015-07-15 21:51:55 +01:00
Marco Pivetta f9bbd953a7 Merge branch 'hotfix/#1381-wakeup-reflection-with-embeddable-and-staticreflection-serialization-fix-2.5' into 2.5
Close #1381
2015-07-15 20:50:28 +01:00
Nico Vogelaar 9097014c3d Fixes ClassMetadata wakeupReflection with embeddable and StaticReflectionService 2015-07-15 20:50:05 +01:00
Marco Pivetta 12d178777a Merge branch 'hotfix/#1380-non-cache-persister-bug-2.5' into 2.5
Close #1380
2015-07-15 20:45:38 +01:00
Marco Pivetta ed1c4de2b6 DDC-3683 - #1380 - reverting BC break, annotating correct types, cs fixes 2015-07-15 20:45:02 +01:00
Darien Hager fff56c7f3f Remove runtime assertion 2015-07-15 20:44:55 +01:00
Darien Hager 97e90ddefc Clarify state-changes, replace array_key_exists() with isset() for speed 2015-07-15 20:44:50 +01:00
Darien Hager d5adda954d Whitespace formatting tweaks 2015-07-15 20:44:39 +01:00
Darien Hager c507b52f20 Remove now-superfluous EntityManager check 2015-07-15 20:44:33 +01:00
Darien Hager 08be905fc3 Refactor LoadClassMetadataEventArgs to ensure it contains an EntityManager 2015-07-15 20:44:24 +01:00
Darien Hager d29cc3660f Change the test listener than layers on second-level-caching so that it is more conservative, only turning on caching-associations when it knows the target entity is cache-able. 2015-07-15 20:44:10 +01:00
Darien Hager 768c291cd1 Stumbled across a bug where signatures didn't match, but also the current persister-type didn't support getCacheRegion(). Unsure of exact mechanism, but clearly the constructor doesn't take the second argument anyway, may be old code. 2015-07-15 20:43:51 +01:00
Bill Schaller 10ed690d99 Backport Merge pull request #1430 from michael-lavaveshkul/master
"INSTANCE OF" example doesn't match description.
2015-06-18 10:41:56 -04:00
Benjamin Eberlei e4e59d8064 Merge branch 'DDC-3756' into 2.5 2015-06-16 21:44:14 +02:00
Restless-ET 0e208f7538 [2.5][Bug] Fix ConvertDoctrine1Schema->getMetadata
This bug was introduced at #1205 while resolving #1200.
2015-06-16 21:43:54 +02:00
Guilherme Blanco 3e6c6af845 Merge pull request #1382 from holtkamp/patch-second-level-cache-association-hydration
Patch second level cache association hydration
2015-04-14 11:57:54 -04:00
Marco Pivetta 584345397b Merge branch 'hotfix/#1374-fix-ddc-767-test-php7-pg94-2.5' into 2.5 2015-04-06 04:23:17 +01:00
Matteo Beccati 786791b8fe Fix DDC767Test failing on php7 + pg94
The failure happens when running the full suite or even just:

phpunit tests/Doctrine/Tests/ORM/Functional/Ticket
2015-04-06 04:23:03 +01:00
Marco Pivetta 724dfa0de3 Merge branch 'hotfix/#1361-persistent-collection-collection-hint-fix' into 2.5 2015-04-05 01:30:32 +01:00
Marco Pivetta 39592ba59c Correcting ObjectHydrator logic: if an array is a default value for a collection-valued property, it should be cast to a Collection 2015-04-05 01:29:59 +01:00
Marco Pivetta 58a6013d15 Correcting static introspection issue in cache specific tests (null was being passed to a PersistentCollection) 2015-04-05 01:29:52 +01:00
Marco Pivetta 4580429616 Removing irrelevant tests (as per discussion with @guilhermeblanco and @stof 2015-04-05 01:29:42 +01:00
Marco Pivetta 6e563a313e a PersistentCollection should only allow another collection as a wrapped collection 2015-04-05 01:29:35 +01:00
Marco Pivetta e8c9cb2f23 Reverting BC break: PersistentConnection#__construct() now accepts null|array|Collection again 2015-04-05 01:29:28 +01:00
Marco Pivetta 4792b4f974 FQCN reference (class was not imported correctly) 2015-04-05 01:29:19 +01:00
Marco Pivetta c2f6b09ee0 PersistentCollection should still accept null and array as constructor argument, as it did before 2015-04-05 01:29:08 +01:00
Marco Pivetta cb3179865b Minor docblock correction (discovered during testing) 2015-04-05 01:29:00 +01:00
Marco Pivetta a1602bd91f Hydration of fetch-joined results fails when an entity has a default value of array for the collection property 2015-04-05 01:28:53 +01:00
Marco Pivetta 34696126e0 Merge branch 'hotfix/#1365-query-dql-function-test-determinism-2.5' into 2.5 2015-04-05 00:10:25 +01:00
Bill Schaller aa4b2e59ce fix rare query test failures due to nondeterminism without order by clause 2015-04-05 00:10:11 +01:00
Marco Pivetta 2cd86deeb4 Merge branch 'hotfix/#1360-docs-fix-misleading-embeddable-documentation-prefix' into 2.5 2015-04-02 23:25:11 +01:00
Marco Pivetta 52288abf48 Merge branch 'docs/#1359-correcting-mapping-in-working-with-objects-reference' into 2.5 2015-04-02 21:56:03 +01:00
2712 changed files with 167877 additions and 172835 deletions
+4
View File
@@ -0,0 +1,4 @@
# for php-coveralls
service_name: travis-ci
src_dir: lib
coverage_clover: build/logs/clover*.xml
-117
View File
@@ -1,117 +0,0 @@
{
"active": true,
"name": "Object Relational Mapper",
"shortName": "ORM",
"slug": "orm",
"docsSlug": "doctrine-orm",
"versions": [
{
"name": "4.0",
"branchName": "4.0.x",
"slug": "latest",
"upcoming": true
},
{
"name": "3.4",
"branchName": "3.4.x",
"slug": "3.4",
"upcoming": true
},
{
"name": "3.3",
"branchName": "3.3.x",
"slug": "3.3",
"current": true
},
{
"name": "3.2",
"branchName": "3.2.x",
"slug": "3.2",
"maintained": false
},
{
"name": "3.1",
"branchName": "3.1.x",
"slug": "3.1",
"maintained": false
},
{
"name": "3.0",
"branchName": "3.0.x",
"slug": "3.0",
"maintained": false
},
{
"name": "2.21",
"branchName": "2.21.x",
"slug": "2.21",
"upcoming": true
},
{
"name": "2.20",
"branchName": "2.20.x",
"slug": "2.20",
"maintained": true
},
{
"name": "2.19",
"branchName": "2.19.x",
"slug": "2.19",
"maintained": false
},
{
"name": "2.18",
"branchName": "2.18.x",
"slug": "2.18",
"maintained": false
},
{
"name": "2.17",
"branchName": "2.17.x",
"slug": "2.17",
"maintained": false
},
{
"name": "2.16",
"branchName": "2.16.x",
"slug": "2.16",
"maintained": false
},
{
"name": "2.15",
"branchName": "2.15.x",
"slug": "2.15",
"maintained": false
},
{
"name": "2.14",
"branchName": "2.14.x",
"slug": "2.14",
"maintained": false
},
{
"name": "2.13",
"branchName": "2.13.x",
"slug": "2.13",
"maintained": false
},
{
"name": "2.12",
"branchName": "2.12.x",
"slug": "2.12",
"maintained": false
},
{
"name": "2.11",
"branchName": "2.11.x",
"slug": "2.11",
"maintained": false
},
{
"name": "2.10",
"branchName": "2.10.x",
"slug": "2.10",
"maintained": false
}
]
}
+2 -11
View File
@@ -1,21 +1,12 @@
/.github export-ignore
/ci export-ignore
/docs export-ignore
/tests export-ignore
/tools export-ignore
.doctrine-project.json 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
phpcs.xml.dist export-ignore
phpbench.json export-ignore
phpstan.neon export-ignore
phpstan-baseline.neon export-ignore
phpstan-dbal2.neon export-ignore
phpstan-params.neon export-ignore
phpstan-persistence2.neon export-ignore
@@ -1,19 +0,0 @@
---
name: 🐞 Failing Test
about: You found a bug and have a failing Unit or Functional test? 🔨
---
### Failing Test
<!-- Fill in the relevant information below to help triage your issue. -->
| Q | A
|------------ | ------
| BC Break | yes/no
| Version | x.y.z
#### Summary
<!-- Provide a summary of the failing scenario. -->
@@ -1,18 +0,0 @@
---
name: ⚙ Improvement
about: You have some improvement to make Doctrine better? 🎁
---
### Improvement
<!-- Fill in the relevant information below to help triage your issue. -->
| Q | A
|------------ | ------
| New Feature | yes
| RFC | yes/no
| BC Break | yes/no
#### Summary
<!-- Provide a summary of the improvement you are submitting. -->
@@ -1,26 +0,0 @@
---
name: 🎉 New Feature
about: You have implemented some neat idea that you want to make part of Doctrine? 🎩
---
<!--
Thank you for submitting new feature!
Pick the target branch based according to these criteria:
* submitting a bugfix: target the lowest active stable branch: 2.9.x
* submitting a new feature: target the next minor branch: 2.10.x
* submitting a BC-breaking change: target the next major branch: 3.0.x
-->
### New Feature
<!-- Fill in the relevant information below to help triage your issue. -->
| Q | A
|------------ | ------
| New Feature | yes
| RFC | yes/no
| BC Break | yes/no
#### Summary
<!-- Provide a summary of the feature you have implemented. -->
-9
View File
@@ -1,9 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
labels:
- "CI"
target-branch: "2.20.x"
-27
View File
@@ -1,27 +0,0 @@
name: "Coding Standards"
on:
pull_request:
branches:
- "*.x"
paths:
- .github/workflows/coding-standards.yml
- bin/**
- composer.*
- src/**
- phpcs.xml.dist
- tests/**
push:
branches:
- "*.x"
paths:
- .github/workflows/coding-standards.yml
- bin/**
- composer.*
- src/**
- phpcs.xml.dist
- tests/**
jobs:
coding-standards:
uses: "doctrine/.github/.github/workflows/coding-standards.yml@7.2.1"
@@ -1,350 +0,0 @@
name: "Continuous Integration"
on:
pull_request:
branches:
- "*.x"
paths:
- .github/workflows/continuous-integration.yml
- ci/**
- composer.*
- src/**
- phpunit.xml.dist
- tests/**
push:
branches:
- "*.x"
paths:
- .github/workflows/continuous-integration.yml
- ci/**
- composer.*
- src/**
- phpunit.xml.dist
- tests/**
env:
fail-fast: true
jobs:
phpunit-smoke-check:
name: "PHPUnit with SQLite"
runs-on: "ubuntu-22.04"
strategy:
matrix:
php-version:
- "8.1"
- "8.2"
- "8.3"
- "8.4"
dbal-version:
- "default"
- "3.7"
extension:
- "sqlite3"
- "pdo_sqlite"
deps:
- "highest"
include:
- php-version: "8.2"
dbal-version: "4@dev"
extension: "pdo_sqlite"
- php-version: "8.2"
dbal-version: "4@dev"
extension: "sqlite3"
- php-version: "8.1"
dbal-version: "default"
deps: "lowest"
extension: "pdo_sqlite"
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
with:
fetch-depth: 2
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
extensions: "apcu, pdo, ${{ matrix.extension }}"
coverage: "pcov"
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
with:
composer-options: "--ignore-platform-req=php+"
dependency-versions: "${{ matrix.deps }}"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage-no-cache.xml"
env:
ENABLE_SECOND_LEVEL_CACHE: 0
- name: "Run PHPUnit with Second Level Cache"
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --exclude-group performance,non-cacheable,locking_functional --coverage-clover=coverage-cache.xml"
env:
ENABLE_SECOND_LEVEL_CACHE: 1
- name: "Upload coverage file"
uses: "actions/upload-artifact@v4"
with:
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.deps }}-coverage"
path: "coverage*.xml"
phpunit-postgres:
name: "PHPUnit with PostgreSQL"
runs-on: "ubuntu-22.04"
needs: "phpunit-smoke-check"
strategy:
matrix:
php-version:
- "8.2"
- "8.3"
- "8.4"
dbal-version:
- "default"
- "3.7"
postgres-version:
- "17"
extension:
- pdo_pgsql
- pgsql
include:
- php-version: "8.2"
dbal-version: "4@dev"
postgres-version: "14"
extension: pdo_pgsql
- php-version: "8.2"
dbal-version: "3.7"
postgres-version: "9.6"
extension: pdo_pgsql
services:
postgres:
image: "postgres:${{ matrix.postgres-version }}"
env:
POSTGRES_PASSWORD: "postgres"
options: >-
--health-cmd "pg_isready"
ports:
- "5432:5432"
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
with:
fetch-depth: 2
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
extensions: "pgsql pdo_pgsql"
coverage: "pcov"
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
with:
composer-options: "--ignore-platform-req=php+"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_pgsql.xml --coverage-clover=coverage.xml"
- name: "Upload coverage file"
uses: "actions/upload-artifact@v4"
with:
name: "${{ github.job }}-${{ matrix.postgres-version }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.extension }}-coverage"
path: "coverage.xml"
phpunit-mariadb:
name: "PHPUnit with MariaDB"
runs-on: "ubuntu-22.04"
needs: "phpunit-smoke-check"
strategy:
matrix:
php-version:
- "8.2"
- "8.3"
- "8.4"
dbal-version:
- "default"
- "3.7"
- "4@dev"
mariadb-version:
- "11.4"
extension:
- "mysqli"
- "pdo_mysql"
services:
mariadb:
image: "mariadb:${{ matrix.mariadb-version }}"
env:
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: yes
MARIADB_DATABASE: "doctrine_tests"
options: >-
--health-cmd "healthcheck.sh --connect --innodb_initialized"
ports:
- "3306:3306"
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
with:
fetch-depth: 2
- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
coverage: "pcov"
ini-values: "zend.assertions=1, apc.enable_cli=1"
extensions: "${{ matrix.extension }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
with:
composer-options: "--ignore-platform-req=php+"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage.xml"
- name: "Upload coverage file"
uses: "actions/upload-artifact@v4"
with:
name: "${{ github.job }}-${{ matrix.mariadb-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage.xml"
phpunit-mysql:
name: "PHPUnit with MySQL"
runs-on: "ubuntu-22.04"
needs: "phpunit-smoke-check"
strategy:
matrix:
php-version:
- "8.2"
- "8.3"
- "8.4"
dbal-version:
- "default"
- "3.7"
mysql-version:
- "5.7"
- "8.0"
extension:
- "mysqli"
- "pdo_mysql"
include:
- php-version: "8.2"
dbal-version: "4@dev"
mysql-version: "8.0"
extension: "mysqli"
- php-version: "8.2"
dbal-version: "4@dev"
mysql-version: "8.0"
extension: "pdo_mysql"
services:
mysql:
image: "mysql:${{ matrix.mysql-version }}"
options: >-
--health-cmd "mysqladmin ping --silent"
-e MYSQL_ALLOW_EMPTY_PASSWORD=yes
-e MYSQL_DATABASE=doctrine_tests
ports:
- "3306:3306"
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
with:
fetch-depth: 2
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
coverage: "pcov"
ini-values: "zend.assertions=1, apc.enable_cli=1"
extensions: "${{ matrix.extension }}"
- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
with:
composer-options: "--ignore-platform-req=php+"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage-no-cache.xml"
env:
ENABLE_SECOND_LEVEL_CACHE: 0
- name: "Run PHPUnit with Second Level Cache"
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --exclude-group performance,non-cacheable,locking_functional --coverage-clover=coverage-no-cache.xml"
env:
ENABLE_SECOND_LEVEL_CACHE: 1
- name: "Upload coverage files"
uses: "actions/upload-artifact@v4"
with:
name: "${{ github.job }}-${{ matrix.mysql-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage*.xml"
upload_coverage:
name: "Upload coverage to Codecov"
runs-on: "ubuntu-22.04"
# Only run on PRs from forks
if: "github.event.pull_request.head.repo.full_name != github.repository"
needs:
- "phpunit-smoke-check"
- "phpunit-postgres"
- "phpunit-mariadb"
- "phpunit-mysql"
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
with:
fetch-depth: 2
- name: "Download coverage files"
uses: "actions/download-artifact@v4"
with:
path: "reports"
- name: "Upload to Codecov"
uses: "codecov/codecov-action@v5"
with:
directory: reports
env:
CODECOV_TOKEN: "${{ secrets.CODECOV_TOKEN }}"
-20
View File
@@ -1,20 +0,0 @@
name: "Documentation"
on:
pull_request:
branches:
- "*.x"
paths:
- ".github/workflows/documentation.yml"
- "docs/**"
push:
branches:
- "*.x"
paths:
- ".github/workflows/documentation.yml"
- "docs/**"
jobs:
documentation:
name: "Documentation"
uses: "doctrine/.github/.github/workflows/documentation.yml@7.2.1"
-54
View File
@@ -1,54 +0,0 @@
name: "Performance benchmark"
on:
pull_request:
branches:
- "*.x"
paths:
- .github/workflows/phpbench.yml
- composer.*
- src/**
- phpbench.json
- tests/**
push:
branches:
- "*.x"
paths:
- .github/workflows/phpbench.yml
- composer.*
- src/**
- phpbench.json
- tests/**
env:
fail-fast: true
jobs:
phpbench:
name: "PHPBench"
runs-on: "ubuntu-22.04"
strategy:
matrix:
php-version:
- "8.1"
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
with:
fetch-depth: 2
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
coverage: "pcov"
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
- name: "Run PHPBench"
run: "vendor/bin/phpbench run --report=default"
@@ -1,15 +0,0 @@
name: "Automatic Releases"
on:
milestone:
types:
- "closed"
jobs:
release:
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@7.2.1"
secrets:
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}
ORGANIZATION_ADMIN_TOKEN: ${{ secrets.ORGANIZATION_ADMIN_TOKEN }}
SIGNING_SECRET_KEY: ${{ secrets.SIGNING_SECRET_KEY }}
-24
View File
@@ -1,24 +0,0 @@
name: 'Close stale pull requests'
on:
schedule:
- cron: '0 3 * * *'
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
stale-pr-message: >
There hasn't been any activity on this pull request in the past 90 days, so
it has been marked as stale and it will be closed automatically if no
further activity occurs in the next 7 days.
If you want to continue working on it, please leave a comment.
close-pr-message: >
This pull request was closed due to inactivity.
days-before-stale: -1
days-before-pr-stale: 90
days-before-pr-close: 7
-56
View File
@@ -1,56 +0,0 @@
name: "Static Analysis"
on:
pull_request:
branches:
- "*.x"
paths:
- .github/workflows/static-analysis.yml
- composer.*
- src/**
- phpstan*
- tests/StaticAnalysis/**
push:
branches:
- "*.x"
paths:
- .github/workflows/static-analysis.yml
- composer.*
- src/**
- phpstan*
- tests/StaticAnalysis/**
jobs:
static-analysis-phpstan:
name: Static Analysis with PHPStan
runs-on: ubuntu-22.04
strategy:
matrix:
include:
- dbal-version: default
config: phpstan.neon
- dbal-version: 3.8.2
config: phpstan-dbal3.neon
steps:
- name: "Checkout code"
uses: "actions/checkout@v4"
- name: Install PHP
uses: shivammathur/setup-php@v2
with:
coverage: none
php-version: "8.4"
tools: cs2pr
- name: Require specific DBAL version
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: Install dependencies with Composer
uses: ramsey/composer-install@v2
- name: Run static analysis with phpstan/phpstan
run: "vendor/bin/phpstan analyse -c ${{ matrix.config }} --error-format=checkstyle | cs2pr"
-21
View File
@@ -1,21 +0,0 @@
name: "Website config validation"
on:
pull_request:
branches:
- "*.x"
paths:
- ".doctrine-project.json"
- ".github/workflows/website-schema.yml"
push:
branches:
- "*.x"
paths:
- ".doctrine-project.json"
- ".github/workflows/website-schema.yml"
jobs:
json-validate:
name: "Validate JSON schema"
uses: "doctrine/.github/.github/workflows/website-schema.yml@7.1.0"
+3 -6
View File
@@ -3,15 +3,12 @@ logs/
reports/
dist/
download/
lib/api/
lib/Doctrine/Common
lib/Doctrine/DBAL
/.settings/
.buildpath
.project
.idea
*.iml
vendor/
/tests/Doctrine/Performance/history.db
/.phpcs-cache
composer.lock
.phpunit.cache
.phpunit.result.cache
/*.phpunit.xml
+6
View File
@@ -0,0 +1,6 @@
[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
+31
View File
@@ -0,0 +1,31 @@
language: php
php:
- 5.4
- 5.5
- 5.6
- 7.0
env:
- DB=mysql
- DB=pgsql
- DB=sqlite
before_script:
- if [[ $TRAVIS_PHP_VERSION = '5.6' && $DB = 'sqlite' ]]; then PHPUNIT_FLAGS="--coverage-clover ./build/logs/clover.xml"; else PHPUNIT_FLAGS=""; fi
- if [[ $PHPUNIT_FLAGS == "" ]]; then phpenv config-rm xdebug.ini; fi
- composer self-update
- composer install --prefer-source --dev
script:
- ENABLE_SECOND_LEVEL_CACHE=0 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml $PHPUNIT_FLAGS
- ENABLE_SECOND_LEVEL_CACHE=1 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml --exclude-group performance,non-cacheable,locking_functional
matrix:
fast_finish: true
allow_failures:
- php: 7.0
cache:
directories:
- $HOME/.composer/cache
+38 -29
View File
@@ -6,60 +6,69 @@ Before we can merge your Pull-Request here are some guidelines that you need to
These guidelines exist not to annoy you, but to keep the code base clean,
unified and future proof.
Doctrine has [general contributing guidelines][contributor workflow], make
sure you follow them.
## We only accept PRs to "master"
[contributor workflow]: https://www.doctrine-project.org/contribute/index.html
Our branching strategy is "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
This project follows [`doctrine/coding-standard`][coding standard homepage].
You may fix many some of the issues with `vendor/bin/phpcbf`.
We use PSR-1 and PSR-2:
[coding standard homepage]: https://github.com/doctrine/coding-standard
* 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
Please try to 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/Tests/ORM/Functional/Ticket`` with the name of the ticket,
``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 ``vendor/bin/phpunit`` from the root of the project.
You can run the unit-tests by calling ``phpunit`` from the root of the project.
It will run all the tests with an in memory SQLite database.
In order to do that, you will need a fresh copy of the ORM, and you
will have to run a composer installation in the project:
```sh
git clone git@github.com:doctrine/orm.git
cd orm
composer install
```
You will also need to enable the PHP extension that provides the SQLite driver
for PDO: `pdo_sqlite`. How to do so depends on your system, but checking that it
is enabled can universally be done with `php -m`: that command should list the
extension.
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 ``ci/github/phpunit`` directory for some examples. Then run:
take a look at the ``tests/travis`` folder for some examples. Then run:
vendor/bin/phpunit -c mysql.phpunit.xml
phpunit -c mysql.phpunit.xml
If you do not provide these parameters, the test suite will use an in-memory
sqlite database.
Tips for creating unit tests:
Tips for creating unittests:
1. If you put a test into the `Ticket` namespace as described above, put the testcase and all entities into the same class.
See `https://github.com/doctrine/orm/tree/3.0.x/tests/Tests/ORM/Functional/Ticket/DDC2306Test.php` for an
See `https://github.com/doctrine/doctrine2/tree/master/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2306Test.php` for an
example.
## 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
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) Doctrine Project
Copyright (c) 2006-2012 Doctrine Project
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
+30
View File
@@ -0,0 +1,30 @@
| [Master][Master] | [2.4][2.4] | [2.3][2.3] | [2.2][2.2] | [2.1][2.1] |
|:----------------:|:----------:|:----------:|:----------:|:----------:|
| [![Build status][Master image]][Master] | [![Build status][2.4 image]][2.4] | [![Build status][2.3 image]][2.3] | [![Build status][2.2 image]][2.2] | [![Build status][2.1 image]][2.1] |
| [![Coverage Status][Master coverage image]][Master coverage] |
Doctrine 2 is an object-relational mapper (ORM) for PHP 5.4+ that provides transparent persistence
for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features
is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL),
inspired by Hibernate's HQL. This provides developers with a powerful alternative to SQL that maintains flexibility
without requiring unnecessary code duplication.
## More resources:
* [Website](http://www.doctrine-project.org)
* [Documentation](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/index.html)
* [Issue Tracker](http://www.doctrine-project.org/jira/browse/DDC)
* [Downloads](http://github.com/doctrine/doctrine2/downloads)
[Master image]: https://travis-ci.org/doctrine/doctrine2.svg?branch=master
[Master]: https://travis-ci.org/doctrine/doctrine2
[Master coverage image]: https://coveralls.io/repos/doctrine/doctrine2/badge.png?branch=master
[Master coverage]: https://coveralls.io/r/doctrine/doctrine2?branch=master
[2.4 image]: https://travis-ci.org/doctrine/doctrine2.svg?branch=2.4
[2.4]: https://github.com/doctrine/doctrine2/tree/2.4
[2.3 image]: https://travis-ci.org/doctrine/doctrine2.svg?branch=2.3
[2.3]: https://github.com/doctrine/doctrine2/tree/2.3
[2.2 image]: https://travis-ci.org/doctrine/doctrine2.svg?branch=2.2
[2.2]: https://github.com/doctrine/doctrine2/tree/2.2
[2.1 image]: https://travis-ci.org/doctrine/doctrine2.svg?branch=2.1.x
[2.1]: https://github.com/doctrine/doctrine2/tree/2.1.x
-38
View File
@@ -1,38 +0,0 @@
| [4.0.x][4.0] | [3.4.x][3.4] | [3.3.x][3.3] | [2.21.x][2.21] | [2.20.x][2.20] |
|:------------------------------------------------------:|:------------------------------------------------------:|:------------------------------------------------------:|:--------------------------------------------------------:|:--------------------------------------------------------:|
| [![Build status][4.0 image]][4.0] | [![Build status][3.4 image]][3.4] | [![Build status][3.3 image]][3.3] | [![Build status][2.21 image]][2.21] | [![Build status][2.20 image]][2.20] |
| [![Coverage Status][4.0 coverage image]][4.0 coverage] | [![Coverage Status][3.4 coverage image]][3.4 coverage] | [![Coverage Status][3.3 coverage image]][3.3 coverage] | [![Coverage Status][2.21 coverage image]][2.21 coverage] | [![Coverage Status][2.20 coverage image]][2.20 coverage] |
Doctrine ORM is an object-relational mapper for PHP 8.1+ that provides transparent persistence
for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features
is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL),
inspired by Hibernate's HQL. This provides developers with a powerful alternative to SQL that maintains flexibility
without requiring unnecessary code duplication.
## More resources:
* [Website](http://www.doctrine-project.org)
* [Documentation](https://www.doctrine-project.org/projects/doctrine-orm/en/stable/index.html)
[4.0 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=4.0.x
[4.0]: https://github.com/doctrine/orm/tree/4.0.x
[4.0 coverage image]: https://codecov.io/gh/doctrine/orm/branch/4.0.x/graph/badge.svg
[4.0 coverage]: https://codecov.io/gh/doctrine/orm/branch/4.0.x
[3.4 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.4.x
[3.4]: https://github.com/doctrine/orm/tree/3.4.x
[3.4 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.4.x/graph/badge.svg
[3.4 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.4.x
[3.3 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.3.x
[3.3]: https://github.com/doctrine/orm/tree/3.3.x
[3.3 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.3.x/graph/badge.svg
[3.3 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.3.x
[2.21 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.21.x
[2.21]: https://github.com/doctrine/orm/tree/2.21.x
[2.21 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.21.x/graph/badge.svg
[2.21 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.21.x
[2.20 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.20.x
[2.20]: https://github.com/doctrine/orm/tree/2.20.x
[2.20 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.20.x/graph/badge.svg
[2.20 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.20.x
+5 -4
View File
@@ -10,8 +10,9 @@ we cannot protect you from SQL injection.
Please read the documentation chapter on Security in Doctrine DBAL and ORM to
understand the assumptions we make.
- [DBAL Security Page](https://www.doctrine-project.org/projects/doctrine-dbal/en/stable/reference/security.html)
- [ORM Security Page](https://www.doctrine-project.org/projects/doctrine-orm/en/stable/reference/security.html)
- [DBAL Security Page](https://github.com/doctrine/dbal/blob/master/docs/en/reference/security.rst)
- [ORM Security Page](https://github.com/doctrine/doctrine2/blob/master/docs/en/reference/security.rst)
If you find a Security bug in Doctrine, please follow our
[Security reporting guidelines](https://www.doctrine-project.org/policies/security.html#reporting).
If you find a Security bug in Doctrine, please report it on Jira and change the
Security Level to "Security Issues". It will be visible to Doctrine Core
developers and you only.
+11 -1722
View File
File diff suppressed because it is too large Load Diff
Executable
+4
View File
@@ -0,0 +1,4 @@
#!/usr/bin/env php
<?php
include('doctrine.php');
+50
View File
@@ -0,0 +1,50 @@
<?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 LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
require_once 'Doctrine/Common/ClassLoader.php';
$classLoader = new \Doctrine\Common\ClassLoader('Doctrine');
$classLoader->register();
$classLoader = new \Doctrine\Common\ClassLoader('Symfony');
$classLoader->register();
$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
);
}
require $configFile;
foreach ($GLOBALS as $helperSetCandidate) {
if ($helperSetCandidate instanceof \Symfony\Component\Console\Helper\HelperSet) {
$helperSet = $helperSetCandidate;
break;
}
}
}
$helperSet = ($helperSet) ?: new \Symfony\Component\Console\Helper\HelperSet();
\Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet);
+9
View File
@@ -0,0 +1,9 @@
@echo off
if "%PHPBIN%" == "" set PHPBIN=@php_bin@
if not exist "%PHPBIN%" if "%PHP_PEAR_PHP_BIN%" neq "" goto USE_PEAR_PATH
GOTO RUN
:USE_PEAR_PATH
set PHPBIN=%PHP_PEAR_PHP_BIN%
:RUN
"%PHPBIN%" "@bin_dir@\doctrine" %*
+66
View File
@@ -0,0 +1,66 @@
<?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>.
*/
use Symfony\Component\Console\Helper\HelperSet;
use Doctrine\ORM\Tools\Console\ConsoleRunner;
$autoloadFiles = array(__DIR__ . '/../vendor/autoload.php',
__DIR__ . '/../../../autoload.php');
foreach ($autoloadFiles as $autoloadFile) {
if (file_exists($autoloadFile)) {
require_once $autoloadFile;
}
}
$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;
}
}
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 HelperSet) {
$helperSet = $helperSetCandidate;
break;
}
}
}
\Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet, $commands);
+3
View File
@@ -0,0 +1,3 @@
# Version class and file
project.version_class = Doctrine\\ORM\\Version
project.version_file = lib/Doctrine/ORM/Version.php
+16
View File
@@ -0,0 +1,16 @@
version=2.0.0BETA2
dependencies.common=2.0.0BETA4
dependencies.dbal=2.0.0BETA4
stability=beta
build.dir=build
dist.dir=dist
report.dir=reports
log.archive.dir=logs
project.pirum_dir=
project.download_dir=
project.xsd_dir=
test.phpunit_configuration_file=
test.phpunit_generate_coverage=0
test.pmd_reports=0
test.pdepend_exec=
test.phpmd_exec=
+101
View File
@@ -0,0 +1,101 @@
<?xml version="1.0"?>
<project name="DoctrineORM" default="build" basedir=".">
<property file="build.properties" />
<target name="php">
<exec executable="which" outputproperty="php_executable">
<arg value="php" />
</exec>
</target>
<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>
-42
View File
@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
failOnRisky="true"
cacheDirectory=".phpunit.cache"
>
<php>
<ini name="error_reporting" value="-1" />
<var name="db_driver" value="mysqli"/>
<var name="db_host" value="127.0.0.1" />
<var name="db_port" value="3306"/>
<var name="db_user" value="root" />
<var name="db_dbname" value="doctrine_tests" />
<var name="db_default_table_option_charset" value="utf8mb4" />
<var name="db_default_table_option_collation" value="utf8mb4_unicode_ci" />
<var name="db_default_table_option_engine" value="InnoDB" />
<!-- necessary change for some CLI/console output test assertions -->
<env name="COLUMNS" value="120"/>
</php>
<testsuites>
<testsuite name="Doctrine DBAL Test Suite">
<directory>../../../tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">../../../src</directory>
</include>
</source>
<groups>
<exclude>
<group>performance</group>
<group>locking_functional</group>
</exclude>
</groups>
</phpunit>
-42
View File
@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
failOnRisky="true"
cacheDirectory=".phpunit.cache"
>
<php>
<ini name="error_reporting" value="-1" />
<var name="db_driver" value="pdo_mysql"/>
<var name="db_host" value="127.0.0.1" />
<var name="db_port" value="3306"/>
<var name="db_user" value="root" />
<var name="db_dbname" value="doctrine_tests" />
<var name="db_default_table_option_charset" value="utf8mb4" />
<var name="db_default_table_option_collation" value="utf8mb4_unicode_ci" />
<var name="db_default_table_option_engine" value="InnoDB" />
<!-- necessary change for some CLI/console output test assertions -->
<env name="COLUMNS" value="120"/>
</php>
<testsuites>
<testsuite name="Doctrine DBAL Test Suite">
<directory>../../../tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">../../../src</directory>
</include>
</source>
<groups>
<exclude>
<group>performance</group>
<group>locking_functional</group>
</exclude>
</groups>
</phpunit>
-39
View File
@@ -1,39 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
failOnRisky="true"
cacheDirectory=".phpunit.cache"
>
<php>
<ini name="error_reporting" value="-1" />
<var name="db_driver" value="pdo_pgsql"/>
<var name="db_host" value="localhost" />
<var name="db_user" value="postgres" />
<var name="db_password" value="postgres" />
<var name="db_dbname" value="doctrine_tests" />
<!-- necessary change for some CLI/console output test assertions -->
<env name="COLUMNS" value="120"/>
</php>
<testsuites>
<testsuite name="Doctrine DBAL Test Suite">
<directory>../../../tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">../../../src</directory>
</include>
</source>
<groups>
<exclude>
<group>performance</group>
<group>locking_functional</group>
</exclude>
</groups>
</phpunit>
-37
View File
@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
failOnRisky="true"
cacheDirectory=".phpunit.cache"
>
<php>
<ini name="error_reporting" value="-1" />
<!-- use an in-memory sqlite database -->
<var name="db_driver" value="pdo_sqlite"/>
<var name="db_memory" value="true"/>
<!-- necessary change for some CLI/console output test assertions -->
<env name="COLUMNS" value="120"/>
</php>
<testsuites>
<testsuite name="Doctrine DBAL Test Suite">
<directory>../../../tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">../../../src</directory>
</include>
</source>
<groups>
<exclude>
<group>performance</group>
<group>locking_functional</group>
</exclude>
</groups>
</phpunit>
-39
View File
@@ -1,39 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
failOnRisky="true"
cacheDirectory=".phpunit.cache"
>
<php>
<ini name="error_reporting" value="-1" />
<var name="db_driver" value="pgsql"/>
<var name="db_host" value="localhost" />
<var name="db_user" value="postgres" />
<var name="db_password" value="postgres" />
<var name="db_dbname" value="doctrine_tests" />
<!-- necessary change for some CLI/console output test assertions -->
<env name="COLUMNS" value="120"/>
</php>
<testsuites>
<testsuite name="Doctrine DBAL Test Suite">
<directory>../../../tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">../../../src</directory>
</include>
</source>
<groups>
<exclude>
<group>performance</group>
<group>locking_functional</group>
</exclude>
</groups>
</phpunit>
-37
View File
@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
failOnRisky="true"
cacheDirectory=".phpunit.cache"
>
<php>
<ini name="error_reporting" value="-1" />
<!-- use an in-memory sqlite database -->
<var name="db_driver" value="sqlite3"/>
<var name="db_memory" value="true"/>
<!-- necessary change for some CLI/console output test assertions -->
<env name="COLUMNS" value="120"/>
</php>
<testsuites>
<testsuite name="Doctrine DBAL Test Suite">
<directory>../../../tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">../../../src</directory>
</include>
</source>
<groups>
<exclude>
<group>performance</group>
<group>locking_functional</group>
</exclude>
</groups>
</phpunit>
+22 -43
View File
@@ -3,66 +3,45 @@
"type": "library",
"description": "Object-Relational-Mapper for PHP",
"keywords": ["orm", "database"],
"homepage": "https://www.doctrine-project.org/projects/orm.html",
"homepage": "http://www.doctrine-project.org",
"license": "MIT",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"},
{"name": "Marco Pivetta", "email": "ocramius@gmail.com"}
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"}
],
"config": {
"allow-plugins": {
"composer/package-versions-deprecated": true,
"dealerdirect/phpcodesniffer-composer-installer": true,
"phpstan/extension-installer": true
},
"sort-packages": true
},
"minimum-stability": "dev",
"require": {
"php": "^8.1",
"composer-runtime-api": "^2",
"ext-ctype": "*",
"doctrine/collections": "^2.2",
"doctrine/dbal": "^3.8.2 || ^4",
"doctrine/deprecations": "^0.5.3 || ^1",
"doctrine/event-manager": "^1.2 || ^2",
"doctrine/inflector": "^1.4 || ^2.0",
"doctrine/instantiator": "^1.3 || ^2",
"doctrine/lexer": "^3",
"doctrine/persistence": "^3.3.1 || ^4",
"psr/cache": "^1 || ^2 || ^3",
"symfony/console": "^5.4 || ^6.0 || ^7.0",
"symfony/var-exporter": "^6.3.9 || ^7.0"
"php": ">=5.4",
"ext-pdo": "*",
"doctrine/collections": "~1.2",
"doctrine/dbal": ">=2.5-dev,<2.7-dev",
"doctrine/instantiator": "~1.0.1",
"doctrine/common": ">=2.5-dev,<2.9-dev",
"doctrine/cache": "~1.4",
"symfony/console": "~2.5|~3.0"
},
"require-dev": {
"doctrine/coding-standard": "^12.0",
"phpbench/phpbench": "^1.0",
"phpdocumentor/guides-cli": "^1.4",
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan": "2.0.3",
"phpstan/phpstan-deprecation-rules": "^2",
"phpunit/phpunit": "^10.4.0",
"psr/log": "^1 || ^2 || ^3",
"squizlabs/php_codesniffer": "3.7.2",
"symfony/cache": "^5.4 || ^6.2 || ^7.0"
"symfony/yaml": "~2.3|~3.0",
"phpunit/phpunit": "~4.0"
},
"suggest": {
"ext-dom": "Provides support for XSD validation for XML mapping files",
"symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0"
"symfony/yaml": "If you want to use YAML Metadata Mapping Driver"
},
"autoload": {
"psr-4": { "Doctrine\\ORM\\": "src" }
"psr-0": { "Doctrine\\ORM\\": "lib/" }
},
"autoload-dev": {
"psr-4": {
"Doctrine\\Tests\\": "tests/Tests",
"Doctrine\\StaticAnalysis\\": "tests/StaticAnalysis",
"Doctrine\\Performance\\": "tests/Performance"
"psr-0": { "Doctrine\\Tests\\": "tests/" }
},
"bin": ["bin/doctrine", "bin/doctrine.php"],
"extra": {
"branch-alias": {
"dev-master": "2.6.x-dev"
}
},
"archive": {
"exclude": ["!vendor", "tests", "*phpunit.xml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp"]
"exclude": ["!vendor", "tests", "*phpunit.xml", ".travis.yml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp", "*coveralls.yml"]
}
}
+4
View File
@@ -0,0 +1,4 @@
en/_exts/configurationblock.pyc
build
en/_build
.idea
+3
View File
@@ -0,0 +1,3 @@
[submodule "en/_theme"]
path = en/_theme
url = https://github.com/doctrine/doctrine-sphinx-theme.git
+3 -2
View File
@@ -1,4 +1,4 @@
The Doctrine ORM documentation is licensed under [CC BY-NC-SA 3.0](https://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US)
The Doctrine2 documentation is licensed under [CC BY-NC-SA 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US)
Creative Commons Legal Code
@@ -359,4 +359,5 @@ Creative Commons Notice
available upon request from time to time. For the avoidance of doubt,
this trademark restriction does not form part of this License.
Creative Commons may be contacted at https://creativecommons.org/.
Creative Commons may be contacted at http://creativecommons.org/.
+2 -12
View File
@@ -1,18 +1,8 @@
# Doctrine ORM Documentation
## How to Generate:
Using Ubuntu 14.04 LTS:
## How to Generate
1. Run ./bin/install-dependencies.sh
2. Run ./bin/generate-docs.sh
It will generate the documentation into the build directory of the checkout.
## Theme issues
If you get a "Theme error", check if the `en/_theme` subdirectory is empty,
in which case you will need to run:
1. git submodule init
2. git submodule update
It will generate the documentation into the build directory of the checkout.
+1 -1
View File
@@ -7,4 +7,4 @@ rm build -Rf
sphinx-build en build
sphinx-build -b latex en build/pdf
rubber --into build/pdf --pdf build/pdf/Doctrine2ORM.tex
rubber --into build/pdf --pdf build/pdf/Doctrine2ORM.tex
+3 -1
View File
@@ -1,2 +1,4 @@
#!/bin/bash
sudo apt-get update && sudo apt-get install -y python2.7 python-sphinx python-pygments
sudo apt-get install python25 python25-dev texlive-full rubber
sudo easy_install pygments
sudo easy_install sphinx
+93
View File
@@ -0,0 +1,93 @@
#Copyright (c) 2010 Fabien Potencier
#
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is furnished
#to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in all
#copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#THE SOFTWARE.
from docutils.parsers.rst import Directive, directives
from docutils import nodes
from string import upper
class configurationblock(nodes.General, nodes.Element):
pass
class ConfigurationBlock(Directive):
has_content = True
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = True
option_spec = {}
formats = {
'html': 'HTML',
'xml': 'XML',
'php': 'PHP',
'yaml': 'YAML',
'jinja': 'Twig',
'html+jinja': 'Twig',
'jinja+html': 'Twig',
'php+html': 'PHP',
'html+php': 'PHP',
'ini': 'INI',
'php-annotations': 'Annotations',
}
def run(self):
env = self.state.document.settings.env
node = nodes.Element()
node.document = self.state.document
self.state.nested_parse(self.content, self.content_offset, node)
entries = []
for i, child in enumerate(node):
if isinstance(child, nodes.literal_block):
# add a title (the language name) before each block
#targetid = "configuration-block-%d" % env.new_serialno('configuration-block')
#targetnode = nodes.target('', '', ids=[targetid])
#targetnode.append(child)
innernode = nodes.emphasis(self.formats[child['language']], self.formats[child['language']])
para = nodes.paragraph()
para += [innernode, child]
entry = nodes.list_item('')
entry.append(para)
entries.append(entry)
resultnode = configurationblock()
resultnode.append(nodes.bullet_list('', *entries))
return [resultnode]
def visit_configurationblock_html(self, node):
self.body.append(self.starttag(node, 'div', CLASS='configuration-block'))
def depart_configurationblock_html(self, node):
self.body.append('</div>\n')
def visit_configurationblock_latex(self, node):
pass
def depart_configurationblock_latex(self, node):
pass
def setup(app):
app.add_node(configurationblock,
html=(visit_configurationblock_html, depart_configurationblock_html),
latex=(visit_configurationblock_latex, depart_configurationblock_latex))
app.add_directive('configuration-block', ConfigurationBlock)
+1
Submodule docs/en/_theme added at dc294be1db
+710
View File
@@ -0,0 +1,710 @@
What is new in Doctrine ORM 2.5?
================================
This document describes changes between Doctrine ORM 2.4 and 2.5 (currently in
Beta). It contains a description of all the new features and sections
about behavioral changes and potential backwards compatibility breaks.
Please review this document carefully when updating to Doctrine 2.5.
First note, that with the ORM 2.5 release we are dropping support
for PHP 5.3. We are enforcing this with Composer, servers without
at least PHP 5.4 will not allow installing Doctrine 2.5.
New Features and Improvements
-----------------------------
Events: PostLoad now triggered after associations are loaded
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Before Doctrine 2.5 if you had an entity with a ``@PostLoad`` event
defined then Doctrine would trigger listeners after the fields were
loaded, but before assocations are available.
- `DDC-54 <http://doctrine-project.org/jira/browse/DDC-54>`_
- `Commit <https://github.com/doctrine/doctrine2/commit/a906295c65f1516737458fbee2f6fa96254f27a5>`_
Events: Add API to programatically add event listeners to Entity
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When developing third party libraries or decoupled applications
it can be interesting to develop an entity listener without knowing
the entities that require this listener.
You can now attach entity listeners to entities using the
``AttachEntityListenersListener`` class, which is listening to the
``loadMetadata`` event that is fired once for every entity during
metadata generation:
.. code-block:: php
<?php
use Doctrine\ORM\Tools\AttachEntityListenersListener;
use Doctrine\ORM\Events;
$listener = new AttachEntityListenersListener();
$listener->addEntityListener(
'MyProject\Entity\User', 'MyProject\Listener\TimestampableListener',
Events::prePersist, 'onPrePersist'
);
$evm->addEventListener(Events::loadClassMetadata, $listener);
class TimestampableListener
{
public function onPrePersist($event)
{
$entity = $event->getEntity();
$entity->setCreated(new \DateTime('now'));
}
}
Embeddedable Objects
~~~~~~~~~~~~~~~~~~~~
Doctrine now supports creating multiple PHP objects from one database table
implementing a feature called "Embeddedable Objects". Next to an ``@Entity``
class you can now define a class that is embeddable into a database table of an
entity using the ``@Embeddable`` annotation. Embeddable objects can never be
saved, updated or deleted on their own, only as part of an entity (called
"root-entity" or "aggregate"). Consequently embeddables don't have a primary
key, they are identified only by their values.
Example of defining and using embeddables classes:
.. code-block:: php
<?php
/** @Entity */
class Product
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @Embedded(class = "Money") */
private $price;
}
/** @Embeddable */
class Money
{
/** @Column(type = "decimal") */
private $value;
/** @Column(type = "string") */
private $currency = 'EUR';
}
You can read more on the features of Embeddables objects `in the documentation
<http://docs.doctrine-project.org/en/latest/tutorials/embeddables.html>`_.
This feature was developed by external contributor `Johannes Schmitt
<https://twitter.com/schmittjoh>`_
- `DDC-93 <http://doctrine-project.org/jira/browse/DDC-93>`_
- `Pull Request <https://github.com/doctrine/doctrine2/pull/835>`_
Second-Level-Cache
~~~~~~~~~~~~~~~~~~
Since version 2.0 of Doctrine, fetching the same object twice by primary key
would result in just one query. This was achieved by the identity map pattern
(first-level-cache) that kept entities in memory.
The newly introduced second-level-cache works a bit differently. Instead
of saving objects in memory, it saves them in a fast in-memory cache such
as Memcache, Redis, Riak or MongoDB. Additionally it allows saving the result
of more complex queries than by primary key. Summarized this feature works
like the existing Query result cache, but it is much more powerful.
As an example lets cache an entity Country that is a relation to the User
entity. We always want to display the country, but avoid the additional
query to this table.
.. code-block:: php
<?php
/**
* @Entity
* @Cache(usage="READ_ONLY", region="country_region")
*/
class Country
{
/**
* @Id
* @GeneratedValue
* @Column(type="integer")
*/
protected $id;
/**
* @Column(unique=true)
*/
protected $name;
}
In this example we have specified a caching region name called
``country_region``, which we have to configure now on the EntityManager:
.. code-block:: php
$config = new \Doctrine\ORM\Configuration();
$config->setSecondLevelCacheEnabled();
$cacheConfig = $config->getSecondLevelCacheConfiguration();
$regionConfig = $cacheConfig->getRegionsConfiguration();
$regionConfig->setLifetime('country_region', 3600);
Now Doctrine will first check for the data of any country in the cache
instead of the database.
- `Documentation
<http://docs.doctrine-project.org/en/latest/reference/second-level-cache.html>`_
- `Pull Request <https://github.com/doctrine/doctrine2/pull/808>`_
Criteria API: Support for ManyToMany assocations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We introduced support for querying collections using the `Criteria API
<http://docs.doctrine-project.org/en/latest/reference/working-with-associations.html#filtering-collections>`_
in 2.4. This only worked efficently for One-To-Many assocations, not for
Many-To-Many. With the start of 2.5 also Many-To-Many associations get queried
instead of loading them into memory.
Criteria API: Add new contains() expression
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is now possible to use the Criteria API to check for string contains needle
using ``contains()``. This translates to using a ``column LIKE '%needle%'`` SQL
condition.
.. code-block:: php
<?php
use \Doctrine\Common\Collections\Criteria;
$criteria = Criteria::create()
->where(Criteria::expr()->contains('name', 'Benjamin'));
$users = $repository->matching($criteria);
Criteria API: Support for EXTRA_LAZY
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A collection that is marked as ``fetch="EXTRA_LAZY"`` will now return another
lazy collection when using ``Collection::matching($criteria)``:
.. code-block:: php
<?php
class Post
{
/** @OneToMany(targetEntity="Comment", fetch="EXTRA_LAZY") */
private $comments;
}
$criteria = Criteria::create()
->where(Criteria->expr()->eq("published", 1));
$publishedComments = $post->getComments()->matching($criteria);
echo count($publishedComments);
The lazy criteria currently supports the ``count()`` and ``contains()``
functionality lazily. All other operations of the ``Collection`` interface
trigger a full load of the collection.
This feature was contributed by `Michaël Gallego <https://github.com/bakura10>`_.
- `Pull Request #1 <https://github.com/doctrine/doctrine2/pull/882>`_
- `Pull Request #2 <https://github.com/doctrine/doctrine2/pull/1032>`_
Mapping: Allow configuring Index flags
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is now possible to control the index flags in the DBAL
schema abstraction from the ORM using metadata. This was possible
only with a schema event listener before.
.. code-block:: php
<?php
/**
* @Table(name="product", indexes={@Index(columns={"description"},flags={"fulltext"})})
*/
class Product
{
private $description;
}
This feature was contributed by `Adrian Olek <https://github.com/adrianolek>`_.
- `Pull Request <https://github.com/doctrine/doctrine2/pull/973>`_
SQLFilter API: Check if a parameter is set
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can now check in your SQLFilter if a parameter was set. This allows
to more easily control which features of a filter to enable or disable.
Extending on the locale example of the documentation:
.. code-block:: php
<?php
class MyLocaleFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
{
if (!$targetEntity->reflClass->implementsInterface('LocaleAware')) {
return "";
}
if (!$this->hasParameter('locale')) {
return "";
}
return $targetTableAlias.'.locale = ' . $this->getParameter('locale');
}
}
This feature was contributed by `Miroslav Demovic <https://github.com/mdemo>`_
- `Pull Request <https://github.com/doctrine/doctrine2/pull/963>`_
EXTRA_LAZY Improvements
~~~~~~~~~~~~~~~~~~~~~~~
1. Efficient query when using EXTRA_LAZY and containsKey
When calling ``Collection::containsKey($key)`` on one-to-many and many-to-many
collections using ``indexBy`` and ``EXTRA_LAZY`` a query is now executed to check
for the existance for the item. Prevoiusly this operation was performed in memory
by loading all entities of the collection.
.. code-block:: php
<?php
class User
{
/** @OneToMany(targetEntity="Group", indexBy="id") */
private $groups;
}
if ($user->getGroups()->containsKey($groupId)) {
echo "User is in group $groupId\n";
}
This feature was contributed by `Asmir Mustafic <https://github.com/goetas>`_
- `Pull Request <https://github.com/doctrine/doctrine2/pull/937>`_
2. Add EXTRA_LAZY Support for get() for owning and inverse many-to-many
This was contributed by `Sander Marechal <https://github.com/sandermarechal>`_.
Improve efficiency of One-To-Many EAGER
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When marking a one-to-many association with ``fetch="EAGER"`` it will now
execute one query less than before and work correctly in combination with
``indexBy``.
Better support for EntityManagerInterface
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Many of the locations where previously only the ``Doctrine\ORM\EntityManager``
was allowed are now changed to accept the ``EntityManagerInterface`` that was
introduced in 2.4. This allows you to more easily use the decorator pattern
to extend the EntityManager if you need. It's still not replaced everywhere,
so you still have to be careful.
DQL Improvements
~~~~~~~~~~~~~~~~
1. It is now possible to add functions to the ``ORDER BY`` clause in DQL statements:
.. code-block:: php
<?php
$dql = "SELECT u FROM User u ORDER BY CONCAT(u.username, u.name)";
2. Support for functions in ``IS NULL`` expressions:
.. code-block:: php
<?php
$dql = "SELECT u.name FROM User u WHERE MAX(u.name) IS NULL";
3. A ``LIKE`` expression is now suported in ``HAVING`` clause.
4. Subselects are now supported inside a ``NEW()`` expression:
.. code-block:: php
<?php
$dql = "SELECT new UserDTO(u.name, SELECT count(g.id) FROM Group g WHERE g.id = u.id) FROM User u";
5. ``MEMBER OF`` expression now allows to filter for more than one result:
.. code-block:: php
<?php
$dql = "SELECT u FROM User u WHERE :groups MEMBER OF u.groups";
$query = $entityManager->createQuery($dql);
$query->setParameter('groups', array(1, 2, 3));
$users = $query->getResult();
6. Expressions inside ``COUNT()`` now allowed
.. code-block:: php
<?php
$dql = "SELECT COUNT(DISTINCT CONCAT(u.name, u.lastname)) FROM User u";
7. Add support for ``HOUR`` in ``DATE_ADD()``/``DATE_SUB()`` functions
Custom DQL Functions: Add support for factories
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Previously custom DQL functions could only be provided with their
full-qualified class-name, preventing runtime configuration through
dependency injection.
A simplistic approach has been contributed by `Matthieu Napoli
<https://github.com/mnapoli>`_ to pass a callback instead that resolves
the function:
.. code-block:: php
<?php
$config = new \Doctrine\ORM\Configuration();
$config->addCustomNumericFunction(
'IS_PUBLISHED', function($funcName) use ($currentSiteId) {
return new IsPublishedFunction($currentSiteId);
}
);
Query API: WHERE IN Query using a Collection as parameter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When performing a ``WHERE IN`` query for a collection of entities you can
now pass the array collection of entities as a parameter value to the query
object:
.. code-block:: php
<?php
$categories = $rootCategory->getChildren();
$queryBuilder
->select('p')
->from('Product', 'p')
->where('p.category IN (:categories)')
->setParameter('categories', $categories)
;
This feature was contributed by `Michael Perrin
<https://github.com/michaelperrin>`_.
- `Pull Request <https://github.com/doctrine/doctrine2/pull/590>`_
- `DDC-2319 <http://doctrine-project.org/jira/browse/DDC-2319>`_
Query API: Add suport for default Query Hints
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To configure multiple different features such as custom AST Walker, fetch modes,
locking and other features affecting DQL generation we have had a feature
called "query hints" since version 2.0.
It is now possible to add query hints that are always enabled for every Query:
.. code-block:: php
<?php
$config = new \Doctrine\ORM\Configuration();
$config->setDefaultQueryHints(
'doctrine.customOutputWalker' => 'MyProject\CustomOutputWalker'
);
This feature was contributed by `Artur Eshenbrener
<https://github.com/Strate>`_.
- `Pull Request <https://github.com/doctrine/doctrine2/pull/863>`_
ResultSetMappingBuilder: Add support for Single-Table Inheritance
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Before 2.5 the ResultSetMappingBuilder did not work with entities
that are using Single-Table-Inheritance. This restriction was lifted
by adding the missing support.
YAML Mapping: Many-To-Many doesnt require join column definition
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In Annotations and XML it was not necessary using conventions for naming
the many-to-many join column names, in YAML it was not possible however.
A many-to-many definition in YAML is now possible using this minimal
definition:
.. code-block:: yaml
manyToMany:
groups:
targetEntity: Group
joinTable:
name: users_groups
Schema Validator Command: Allow to skip sub-checks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The Schema Validator command executes two independent checks
for validity of the mappings and if the schema is synchronized
correctly. It is now possible to skip any of the two steps
when executing the command:
::
$ php vendor/bin/doctrine orm:validate-schema --skip-mapping
$ php vendor/bin/doctrine orm:validate-schema --skip-sync
This allows you to write more specialized continuous integration and automation
checks. When no changes are found the command returns the exit code 0
and 1, 2 or 3 when failing because of mapping, sync or both.
EntityGenerator Command: Avoid backups
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When calling the EntityGenerator for an existing entity, Doctrine would
create a backup file every time to avoid loosing changes to the code.
You can now skip generating the backup file by passing the ``--no-backup``
flag:
::
$ php vendor/bin/doctrine orm:generate-entities src/ --no-backup
Support for Objects as Identifiers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is now possible to use Objects as identifiers for Entities
as long as they implement the magic method ``__toString()``.
.. code-block:: php
<?php
class UserId
{
private $value;
public function __construct($value)
{
$this->value = $value;
}
public function __toString()
{
return (string)$this->value;
}
}
class User
{
/** @Id @Column(type="userid") */
private $id;
public function __construct(UserId $id)
{
$this->id = $id;
}
}
class UserIdType extends \Doctrine\DBAL\Types\Type
{
// ...
}
Doctrine\DBAL\Types\Type::addType('userid', 'MyProject\UserIdType');
Behavioral Changes (BC Breaks)
------------------------------
NamingStrategy interface changed
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``Doctrine\ORM\Mapping\NamingStrategyInterface`` changed slightly
to pass the Class Name of the entity into the join column name generation:
::
- function joinColumnName($propertyName);
+ function joinColumnName($propertyName, $className = null);
It also received a new method for supporting embeddables:
::
public function embeddedFieldToColumnName($propertyName, $embeddedColumnName);
Minor BC BREAK: EntityManagerInterface instead of EntityManager in type-hints
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As of 2.5, classes requiring the ``EntityManager`` in any method signature will now require
an ``EntityManagerInterface`` instead.
If you are extending any of the following classes, then you need to check following
signatures:
- ``Doctrine\ORM\Tools\DebugUnitOfWorkListener#dumpIdentityMap(EntityManagerInterface $em)``
- ``Doctrine\ORM\Mapping\ClassMetadataFactory#setEntityManager(EntityManagerInterface $em)``
Minor BC BREAK: Custom Hydrators API change
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As of 2.5, ``AbstractHydrator`` does not enforce the usage of cache as part of
API, and now provides you a clean API for column information through the method
``hydrateColumnInfo($column)``.
Cache variable being passed around by reference is no longer needed since
Hydrators are per query instantiated since Doctrine 2.4.
- `DDC-3060 <http://doctrine-project.org/jira/browse/DDC-3060>`_
Minor BC BREAK: All non-transient classes in an inheritance must be part of the inheritance map
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As of 2.5, classes, if you define an inheritance map for an inheritance tree, you are required
to map all non-transient classes in that inheritance, including the root of the inheritance.
So far, the root of the inheritance was allowed to be skipped in the inheritance map: this is
not possible anymore, and if you don't plan to persist instances of that class, then you should
either:
- make that class as ``abstract``
- add that class to your inheritance map
If you fail to do so, then a ``Doctrine\ORM\Mapping\MappingException`` will be thrown.
- `DDC-3300 <http://doctrine-project.org/jira/browse/DDC-3300>`_
- `DDC-3503 <http://doctrine-project.org/jira/browse/DDC-3503>`_
Minor BC BREAK: Entity based EntityManager#clear() calls follow cascade detach
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Whenever ``EntityManager#clear()`` method gets called with a given entity class
name, until 2.4, it was only detaching the specific requested entity.
As of 2.5, ``EntityManager`` will follow configured cascades, providing a better
memory management since associations will be garbage collected, optimizing
resources consumption on long running jobs.
Updates on entities scheduled for deletion are no longer processed
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In Doctrine 2.4, if you modified properties of an entity scheduled for deletion, UnitOfWork would
produce an ``UPDATE`` statement to be executed right before the ``DELETE`` statement. The entity in question
was therefore present in ``UnitOfWork#entityUpdates``, which means that ``preUpdate`` and ``postUpdate``
listeners were (quite pointlessly) called. In ``preFlush`` listeners, it used to be possible to undo
the scheduled deletion for updated entities (by calling ``persist()`` if the entity was found in both
``entityUpdates`` and ``entityDeletions``). This does not work any longer, because the entire changeset
calculation logic is optimized away.
Minor BC BREAK: Default lock mode changed from LockMode::NONE to null in method signatures
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A misconception concerning default lock mode values in method signatures lead to unexpected behaviour
in SQL statements on SQL Server. With a default lock mode of ``LockMode::NONE`` throughout the
method signatures in ORM, the table lock hint ``WITH (NOLOCK)`` was appended to all locking related
queries by default. This could result in unpredictable results because an explicit ``WITH (NOLOCK)``
table hint tells SQL Server to run a specific query in transaction isolation level READ UNCOMMITTED
instead of the default READ COMMITTED transaction isolation level.
Therefore there now is a distinction between ``LockMode::NONE`` and ``null`` to be able to tell
Doctrine whether to add table lock hints to queries by intention or not. To achieve this, the following
method signatures have been changed to declare ``$lockMode = null`` instead of ``$lockMode = LockMode::NONE``:
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#getSelectSQL()``
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#load()``
- ``Doctrine\ORM\Cache\Persister\AbstractEntityPersister#refresh()``
- ``Doctrine\ORM\Decorator\EntityManagerDecorator#find()``
- ``Doctrine\ORM\EntityManager#find()``
- ``Doctrine\ORM\EntityRepository#find()``
- ``Doctrine\ORM\Persisters\BasicEntityPersister#getSelectSQL()``
- ``Doctrine\ORM\Persisters\BasicEntityPersister#load()``
- ``Doctrine\ORM\Persisters\BasicEntityPersister#refresh()``
- ``Doctrine\ORM\Persisters\EntityPersister#getSelectSQL()``
- ``Doctrine\ORM\Persisters\EntityPersister#load()``
- ``Doctrine\ORM\Persisters\EntityPersister#refresh()``
- ``Doctrine\ORM\Persisters\JoinedSubclassPersister#getSelectSQL()``
You should update signatures for these methods if you have subclassed one of the above classes.
Please also check the calling code of these methods in your application and update if necessary.
.. note::
This in fact is really a minor BC BREAK and should not have any affect on database vendors
other than SQL Server because it is the only one that supports and therefore cares about
``LockMode::NONE``. It's really just a FIX for SQL Server environments using ORM.
Minor BC BREAK: __clone method not called anymore when entities are instantiated via metadata API
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As of PHP 5.6, instantiation of new entities is deferred to the
`doctrine/instantiator <https://github.com/doctrine/instantiator>`_ library, which will avoid calling ``__clone``
or any public API on instantiated objects.
BC BREAK: DefaultRepositoryFactory is now final
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Please implement the ``Doctrine\ORM\Repository\RepositoryFactory`` interface instead of extending
the ``Doctrine\ORM\Repository\DefaultRepositoryFactory``.
BC BREAK: New object expression DQL queries now respects user provided aliasing and not return consumed fields
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When executing DQL queries with new object expressions, instead of returning
DTOs numerically indexes, it will now respect user provided aliases. Consider
the following query:
::
SELECT new UserDTO(u.id,u.name) as user,new AddressDTO(a.street,a.postalCode) as address, a.id as addressId
FROM User u INNER JOIN u.addresses a WITH a.isPrimary = true
Previously, your result would be similar to this:
::
array(
0=>array(
0=>{UserDTO object},
1=>{AddressDTO object},
2=>{u.id scalar},
3=>{u.name scalar},
4=>{a.street scalar},
5=>{a.postalCode scalar},
'addressId'=>{a.id scalar},
),
...
)
From now on, the resultset will look like this:
::
array(
0=>array(
'user'=>{UserDTO object},
'address'=>{AddressDTO object},
'addressId'=>{a.id scalar}
),
...
)
+201
View File
@@ -0,0 +1,201 @@
# -*- coding: utf-8 -*-
#
# Doctrine 2 ORM documentation build configuration file, created by
# sphinx-quickstart on Fri Dec 3 18:10:24 2010.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys, os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.append(os.path.abspath('_exts'))
# -- General configuration -----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['configurationblock']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Doctrine 2 ORM'
copyright = u'2010-12, Doctrine Project Team'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '2'
# The full version, including alpha/beta/rc tags.
release = '2'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
language = 'en'
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of documents that shouldn't be included in the build.
#unused_docs = []
# List of directories, relative to source directory, that shouldn't be searched
# for source files.
exclude_trees = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
show_authors = True
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
html_theme = 'doctrine'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = ['_theme']
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_use_modindex = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = ''
# Output file base name for HTML help builder.
htmlhelp_basename = 'Doctrine2ORMdoc'
# -- Options for LaTeX output --------------------------------------------------
# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'Doctrine2ORM.tex', u'Doctrine 2 ORM Documentation',
u'Doctrine Project Team', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# Additional stuff for the LaTeX preamble.
#latex_preamble = ''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_use_modindex = True
primary_domain = "dcorm"
def linkcode_resolve(domain, info):
if domain == 'dcorm':
return 'http://'
return None
@@ -4,18 +4,18 @@ Advanced field value conversion using custom mapping types
.. sectionauthor:: Jan Sorgalla <jsorgalla@googlemail.com>
When creating entities, you sometimes have the need to transform field values
before they are saved to the database. In Doctrine you can use Custom Mapping
before they are saved to the database. In Doctrine you can use Custom Mapping
Types to solve this (see: :ref:`reference-basic-mapping-custom-mapping-types`).
There are several ways to achieve this: converting the value inside the Type
class, converting the value on the database-level or a combination of both.
This article describes the third way by implementing the MySQL specific column
type `Point <https://dev.mysql.com/doc/refman/8.0/en/gis-class-point.html>`_.
type `Point <http://dev.mysql.com/doc/refman/5.5/en/gis-class-point.html>`_.
The ``Point`` type is part of the `Spatial extension <https://dev.mysql.com/doc/refman/8.0/en/spatial-extensions.html>`_
The ``Point`` type is part of the `Spatial extension <http://dev.mysql.com/doc/refman/5.5/en/spatial-extensions.html>`_
of MySQL and enables you to store a single location in a coordinate space by
using x and y coordinates. You can use the Point type to store a
using x and y coordinates. You can use the Point type to store a
longitude/latitude pair to represent a geographic location.
The entity
@@ -29,42 +29,62 @@ The entity class:
.. code-block:: php
<?php
namespace Geo\Entity;
use Geo\ValueObject\Point;
#[Entity]
/**
* @Entity
*/
class Location
{
#[Column(type: 'point')]
private Point $point;
/**
* @Column(type="point")
*
* @var \Geo\ValueObject\Point
*/
private $point;
#[Column]
private string $address;
/**
* @Column(type="string")
*
* @var string
*/
private $address;
public function setPoint(Point $point): void
/**
* @param \Geo\ValueObject\Point $point
*/
public function setPoint(\Geo\ValueObject\Point $point)
{
$this->point = $point;
}
public function getPoint(): Point
/**
* @return \Geo\ValueObject\Point
*/
public function getPoint()
{
return $this->point;
}
public function setAddress(string $address): void
/**
* @param string $address
*/
public function setAddress($address)
{
$this->address = $address;
}
public function getAddress(): string
/**
* @return string
*/
public function getAddress()
{
return $this->address;
}
}
We use the custom type ``point`` in the ``#[Column]`` attribute of the
We use the custom type ``point`` in the ``@Column`` docblock annotation of the
``$point`` field. We will create this custom mapping type in the next chapter.
The point class:
@@ -72,23 +92,34 @@ The point class:
.. code-block:: php
<?php
namespace Geo\ValueObject;
class Point
{
public function __construct(
private float $latitude,
private float $longitude,
) {
/**
* @param float $latitude
* @param float $longitude
*/
public function __construct($latitude, $longitude)
{
$this->latitude = $latitude;
$this->longitude = $longitude;
}
public function getLatitude(): float
/**
* @return float
*/
public function getLatitude()
{
return $this->latitude;
}
public function getLongitude(): float
/**
* @return float
*/
public function getLongitude()
{
return $this->longitude;
}
@@ -140,6 +171,11 @@ Now we're going to create the ``point`` type and implement all required methods.
return $value;
}
public function canRequireSQLConversion()
{
return true;
}
public function convertToPHPValueSQL($sqlExpr, AbstractPlatform $platform)
{
return sprintf('AsText(%s)', $sqlExpr);
@@ -156,11 +192,11 @@ object into a string representation before saving to the database (in the
``convertToDatabaseValue`` method) and back into an object after fetching the
value from the database (in the ``convertToPHPValue`` method).
The format of the string representation format is called
`Well-known text (WKT) <https://en.wikipedia.org/wiki/Well-known_text>`_.
The advantage of this format is, that it is both human readable and parsable by MySQL.
The format of the string representation format is called `Well-known text (WKT)
<http://en.wikipedia.org/wiki/Well-known_text>`_. The advantage of this format
is, that it is both human readable and parsable by MySQL.
Internally, MySQL stores geometry values in a binary format that is not
Internally, MySQL stores geometry values in a binary format that is not
identical to the WKT format. So, we need to let MySQL transform the WKT
representation into its internal format.
@@ -168,19 +204,19 @@ This is where the ``convertToPHPValueSQL`` and ``convertToDatabaseValueSQL``
methods come into play.
This methods wrap a sql expression (the WKT representation of the Point) into
MySQL functions `ST_PointFromText <https://dev.mysql.com/doc/refman/8.0/en/gis-wkt-functions.html#function_st-pointfromtext>`_
and `ST_AsText <https://dev.mysql.com/doc/refman/8.0/en/gis-format-conversion-functions.html#function_st-astext>`_
MySQL functions `PointFromText <http://dev.mysql.com/doc/refman/5.5/en/creating-spatial-values.html#function_pointfromtext>`_
and `AsText <http://dev.mysql.com/doc/refman/5.5/en/functions-to-convert-geometries-between-formats.html#function_astext>`_
which convert WKT strings to and from the internal format of MySQL.
.. note::
When using DQL queries, the ``convertToPHPValueSQL`` and
When using DQL queries, the ``convertToPHPValueSQL`` and
``convertToDatabaseValueSQL`` methods only apply to identification variables
and path expressions in SELECT clauses. Expressions in WHERE clauses are
and path expressions in SELECT clauses. Expressions in WHERE clauses are
**not** wrapped!
If you want to use Point values in WHERE clauses, you have to implement a
:doc:`user defined function <dql-user-defined-functions>` for
:doc:`user defined function <dql-user-defined-functions>` for
``PointFromText``.
Example usage
@@ -191,7 +227,7 @@ Example usage
<?php
// Bootstrapping stuff...
// $em = new \Doctrine\ORM\EntityManager($connection, $config);
// $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config);
// Setup custom mapping type
use Doctrine\DBAL\Types\Type;
@@ -213,8 +249,8 @@ Example usage
$em->clear();
// Fetch the Location object
$query = $em->createQuery("SELECT l FROM Geo\Entity\Location l WHERE l.address = '1600 Amphitheatre Parkway, Mountain View, CA'");
$query = $em->createQuery("SELECT l FROM Geo\Entity\Location WHERE l.address = '1600 Amphitheatre Parkway, Mountain View, CA'");
$location = $query->getSingleResult();
/** @var Geo\ValueObject\Point */
/* @var Geo\ValueObject\Point */
$point = $location->getPoint();
+89 -88
View File
@@ -6,7 +6,7 @@ Aggregate Fields
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 ORM offers several ways to get access to
traditionally. Doctrine 2 offers several ways to get access to
these values and this article will describe all of them from
different perspectives.
@@ -22,7 +22,7 @@ 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 where money is composed of
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.
@@ -32,55 +32,63 @@ Our entities look like:
.. code-block:: php
<?php
namespace Bank\Entities;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
#[ORM\Entity]
/**
* @Entity
*/
class Account
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private ?int $id;
#[ORM\OneToMany(targetEntity: Entry::class, mappedBy: 'account', cascade: ['persist'])]
private Collection $entries;
public function __construct(
#[ORM\Column(type: 'string', unique: true)]
private string $no,
#[ORM\Column(type: 'integer')]
private int $maxCredit = 0,
) {
$this->entries = new ArrayCollection();
/** @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();
}
}
#[ORM\Entity]
/**
* @Entity
*/
class Entry
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private ?int $id;
public function __construct(
#[ORM\ManyToOne(targetEntity: Account::class, inversedBy: 'entries')]
private Account $account,
#[ORM\Column(type: 'integer')]
private int $amount,
) {
/** @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(): Amount
public function getAmount()
{
return $this->amount;
}
@@ -138,14 +146,12 @@ collection, which means we can compute this value at runtime:
class Account
{
// .. previous code
public function getBalance(): int
public function getBalance()
{
$balance = 0;
foreach ($this->entries as $entry) {
$balance += $entry->getAmount();
}
return $balance;
}
}
@@ -169,11 +175,13 @@ relation with this method:
<?php
class Account
{
public function addEntry(int $amount): void
public function addEntry($amount)
{
$this->assertAcceptEntryAllowed($amount);
$this->entries[] = new Entry($this, $amount);
$e = new Entry($this, $amount);
$this->entries[] = $e;
return $e;
}
}
@@ -182,28 +190,25 @@ Now look at the following test-code for our entities:
.. code-block:: php
<?php
use PHPUnit\Framework\TestCase;
class AccountTest extends TestCase
class AccountTest extends \PHPUnit_Framework_TestCase
{
public function testAddEntry()
{
$account = new Account("123456", maxCredit: 200);
$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->expectException(Exception::class);
$account = new Account("123456", $maxCredit = 200);
$this->setExpectedException("Exception");
$account->addEntry(-1000);
}
}
@@ -214,12 +219,9 @@ To enforce our rule we can now implement the assertion in
.. code-block:: php
<?php
class Account
{
// .. previous code
private function assertAcceptEntryAllowed(int $amount): void
private function assertAcceptEntryAllowed($amount)
{
$futureBalance = $this->getBalance() + $amount;
$allowedMinimalBalance = ($this->maxCredit * -1);
@@ -263,20 +265,24 @@ entries collection) we want to add an aggregate field called
<?php
class Account
{
#[ORM\Column(type: 'integer')]
private int $balance = 0;
public function getBalance(): int
/**
* @Column(type="integer")
*/
private $balance = 0;
public function getBalance()
{
return $this->balance;
}
public function addEntry(int $amount): void
public function addEntry($amount)
{
$this->assertAcceptEntryAllowed($amount);
$this->entries[] = new Entry($this, $amount);
$e = new Entry($this, $amount);
$this->entries[] = $e;
$this->balance += $amount;
return $e;
}
}
@@ -300,26 +306,23 @@ potentially lead to inconsistent state. See this example:
.. code-block:: php
<?php
use Bank\Entities\Account;
// The Account $accId has a balance of 0 and a max credit limit of 200:
// request 1 account
$account1 = $em->find(Account::class, $accId);
$account1 = $em->find('Bank\Entities\Account', $accId);
// request 2 account
$account2 = $em->find(Account::class, $accId);
$account2 = $em->find('Bank\Entities\Account', $accId);
$account1->addEntry(-200);
$account2->addEntry(-200);
// now request 1 and 2 both flush the changes.
The aggregate field ``Account::$balance`` is now -200, however the
SUM over all entries amounts yields -400. A violation of our max
credit rule.
You can use both optimistic or pessimistic locking to safe-guard
You can use both optimistic or pessimistic locking to save-guard
your aggregate fields against this kind of race-conditions. Reading
Eric Evans DDD carefully he mentions that the "Aggregate Root"
(Account in our example) needs a locking mechanism.
@@ -329,12 +332,10 @@ Optimistic locking is as easy as adding a version column:
.. code-block:: php
<?php
class Account
{
#[ORM\Column(type: 'integer')]
#[ORM\Version]
private int $version;
/** @Column(type="integer") @Version */
private $version;
}
The previous example would then throw an exception in the face of
@@ -348,11 +349,9 @@ the database using a FOR UPDATE.
.. code-block:: php
<?php
use Bank\Entities\Account;
use Doctrine\DBAL\LockMode;
$account = $em->find(Account::class, $accId, LockMode::PESSIMISTIC_WRITE);
$account = $em->find('Bank\Entities\Account', $accId, LockMode::PESSIMISTIC_READ);
Keeping Updates and Deletes in Sync
-----------------------------------
@@ -373,3 +372,5 @@ 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.
@@ -49,6 +49,8 @@ you wish. Here is an example skeleton of such a custom type class:
The following assumptions are applied to mapping types by the ORM:
- If the value of the field is *NULL* the method
``convertToDatabaseValue()`` is not called.
- The ``UnitOfWork`` never passes values to the database convert
method that did not change in the request.
- The ``UnitOfWork`` internally assumes that entity identifiers are
+137 -103
View File
@@ -3,82 +3,98 @@ 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 ORM to persist an implementation of the
`Decorator Pattern <https://en.wikipedia.org/wiki/Decorator_pattern>`_
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``.
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' => Component\ConcreteComponent::class,
'cd' => Decorator\ConcreteDecorator::class])]
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"cc" = "Test\Component\ConcreteComponent",
"cd" = "Test\Decorator\ConcreteDecorator"})
*/
abstract class Component
{
#[Id, Column]
#[GeneratedValue(strategy: 'AUTO')]
protected int|null $id = null;
#[Column(type: 'string', nullable: true)]
/**
* @Id @Column(type="integer")
* @GeneratedValue(strategy="AUTO")
*/
protected $id;
/** @Column(type="string", nullable=true) */
protected $name;
public function getId(): int|null
/**
* Get id
* @return integer $id
*/
public function getId()
{
return $this->id;
}
public function setName(string $name): void
/**
* Set name
* @param string $name
*/
public function setName($name)
{
$this->name = $name;
}
public function getName(): string
/**
* 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
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]
/** @Entity */
class ConcreteComponent extends Component
{}
Decorator
---------
The ``Decorator`` class doesn't need to be persisted, but it does
need to define an association with a persisted ``Entity``. We can
The ``Decorator`` class doesn't need to be persisted, but it does
need to define an association with a persisted ``Entity``. We can
use a ``MappedSuperclass`` for this.
.. code-block:: php
@@ -86,14 +102,17 @@ use a ``MappedSuperclass`` for this.
<?php
namespace Test;
#[MappedSuperclass]
/** @MappedSuperclass */
abstract class Decorator extends Component
{
#[OneToOne(targetEntity: Component::class, cascade: ['all'])]
#[JoinColumn(name: 'decorates', referencedColumnName: 'id')]
/**
* @OneToOne(targetEntity="Test\Component", cascade={"all"})
* @JoinColumn(name="decorates", referencedColumnName="id")
*/
protected $decorates;
/**
* initialize the decorator
* @param Component $c
@@ -102,138 +121,153 @@ use a ``MappedSuperclass`` for this.
{
$this->setDecorates($c);
}
/**
* (non-PHPdoc)
* @see Test.Component::getName()
*/
public function getName(): string
public function getName()
{
return 'Decorated ' . $this->getDecorates()->getName();
}
/** the component being decorated */
protected function getDecorates(): Component
/**
* the component being decorated
* @return Component
*/
protected function getDecorates()
{
return $this->decorates;
}
/** sets the component being decorated */
protected function setDecorates(Component $c): void
/**
* 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
All operations on the ``Decorator`` (i.e. persist, remove, etc) will
cascade from the ``Decorator`` to the ``Component``. This means that
when we persist a ``Decorator``, Doctrine will take care of
persisting the chain of decorated objects for us. A ``Decorator`` can
be treated exactly as a ``Component`` when it comes time to
persisting it.
The ``Decorator's`` constructor accepts an instance of a
``Component``, as defined by the ``Decorator`` pattern. The
setDecorates/getDecorates methods have been defined as protected to
hide the fact that a ``Decorator`` is decorating a ``Component`` and
keeps the ``Component`` interface and the ``Decorator`` interface
The ``Decorator's`` constructor accepts an instance of a
``Component``, as defined by the ``Decorator`` pattern. The
setDecorates/getDecorates methods have been defined as protected to
hide the fact that a ``Decorator`` is decorating a ``Component`` and
keeps the ``Component`` interface and the ``Decorator`` interface
identical.
To illustrate the intended result of the ``Decorator`` pattern, the
getName() method has been overridden to append a string to the
To illustrate the intended result of the ``Decorator`` pattern, the
getName() method has been overridden to append a string to the
``Component's`` getName() method.
ConcreteDecorator
-----------------
The final class required to complete a simple implementation of the
Decorator pattern is the ``ConcreteDecorator``. In order to further
illustrate how the ``Decorator`` can alter data as it moves through
the chain of decoration, a new field, "special", has been added to
this class. The getName() has been overridden and appends the value
of the getSpecial() method to its return value.
The final class required to complete a simple implementation of the
Decorator pattern is the ``ConcreteDecorator``. In order to further
illustrate how the ``Decorator`` can alter data as it moves through
the chain of decoration, a new field, "special", has been added to
this class. The getName() has been overridden and appends the value
of the getSpecial() method to its return value.
.. code-block:: php
<?php
namespace Test\Decorator;
use Test\Decorator;
#[Entity]
/** @Entity */
class ConcreteDecorator extends Decorator
{
#[Column(type: 'string', nullable: true)]
protected string|null $special = null;
public function setSpecial(string|null $special): void
/** @Column(type="string", nullable=true) */
protected $special;
/**
* Set special
* @param string $special
*/
public function setSpecial($special)
{
$this->special = $special;
}
public function getSpecial(): string|null
/**
* Get special
* @return string $special
*/
public function getSpecial()
{
return $this->special;
}
/**
* (non-PHPdoc)
* @see Test.Component::getName()
*/
public function getName(): string
public function getName()
{
return '[' . $this->getSpecial()
. '] ' . parent::getName();
. '] ' . parent::getName();
}
}
Examples
--------
Here is an example of how to persist and retrieve your decorated
Here is an example of how to persist and retrieve your decorated
objects
.. code-block:: php
<?php
use Test\Component\ConcreteComponent,
Test\Decorator\ConcreteDecorator;
// assumes Doctrine ORM is configured and an instance of
// assumes Doctrine 2 is configured and an instance of
// an EntityManager is available as $em
// create a new concrete component
$c = new ConcreteComponent();
$c->setName('Test Component 1');
$em->persist($c); // assigned unique ID = 1
// create a new concrete decorator
$c = new ConcreteComponent();
$c->setName('Test Component 2');
$d = new ConcreteDecorator($c);
$d->setSpecial('Really');
$em->persist($d);
$em->persist($d);
// assigns c as unique ID = 2, and d as unique ID = 3
$em->flush();
$c = $em->find('Test\Component', 1);
$d = $em->find('Test\Component', 3);
echo get_class($c);
// prints: Test\Component\ConcreteComponent
echo $c->getName();
// prints: Test Component 1
echo get_class($d)
// prints: Test Component 1
echo get_class($d)
// prints: Test\Component\ConcreteDecorator
echo $d->getName();
// prints: [Really] Decorated Test Component 2
+18 -18
View File
@@ -1,4 +1,4 @@
Extending DQL in Doctrine ORM: Custom AST Walkers
Extending DQL in Doctrine 2: Custom AST Walkers
===============================================
.. sectionauthor:: Benjamin Eberlei <kontakt@beberlei.de>
@@ -12,9 +12,9 @@ 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 ORM in contrast has a real parser for the DQL language,
Doctrine 2 in contrast has a real parser for the DQL language,
which transforms the DQL statement into an
`Abstract Syntax Tree <https://en.wikipedia.org/wiki/Abstract_syntax_tree>`_
`Abstract Syntax Tree <http://en.wikipedia.org/wiki/Abstract_syntax_tree>`_
and generates the appropriate SQL statement for it. Since this
process is deterministic Doctrine heavily caches the SQL that is
generated from any given DQL query, which reduces the performance
@@ -33,8 +33,8 @@ the DQL parser:
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.
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
@@ -50,7 +50,7 @@ example:
- Modify the Output walker to pretty print the SQL for debugging
purposes.
In this cookbook-entry I will show examples of the first two
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
@@ -64,7 +64,7 @@ like:
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 it's the
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.
@@ -79,7 +79,7 @@ query for pagination would look like:
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. Let's see how the
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
@@ -88,7 +88,7 @@ API would look for this use-case:
$pageNum = 1;
$query = $em->createQuery($dql);
$query->setFirstResult( ($pageNum-1) * 20)->setMaxResults(20);
$totalResults = Paginate::count($query);
$results = $query->getResult();
@@ -101,12 +101,12 @@ The ``Paginate::count(Query $query)`` looks like:
{
static public function count(Query $query)
{
/** @var Query $countQuery */
/* @var $countQuery Query */
$countQuery = clone $query;
$countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('DoctrineExtensions\Paginate\CountSqlWalker'));
$countQuery->setFirstResult(null)->setMaxResults(null);
return $countQuery->getSingleScalarResult();
}
}
@@ -137,13 +137,13 @@ implementation is:
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
@@ -167,7 +167,7 @@ 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.
SQL\_NO\_CACHE query hint.
.. code-block:: php
@@ -180,7 +180,7 @@ We will implement a custom Output Walker that allows to specify the
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:
SQL\_NO\_CACHE on those queries that need it:
.. code-block:: php
@@ -196,7 +196,7 @@ modify the generation of the SELECT clause, adding the
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);
@@ -204,7 +204,7 @@ modify the generation of the SELECT clause, adding the
$sql = str_replace('SELECT', 'SELECT SQL_NO_CACHE', $sql);
}
}
return $sql;
}
}
+33 -58
View File
@@ -10,7 +10,7 @@ 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 ORM also allows you to handwrite
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
@@ -21,7 +21,7 @@ the :doc:`Native Query <../reference/native-sql>` chapter.
The DQL Parser has hooks to register functions that can then be
used in your DQL queries and transformed into SQL, allowing to
extend Doctrines Query capabilities to the vendors strength. This
post explains the User-Defined Functions API (UDF) of the Dql
post explains the Used-Defined Functions API (UDF) of the Dql
Parser and shows some examples to give you some hints how you would
extend DQL.
@@ -45,8 +45,8 @@ configuration:
$config->addCustomStringFunction($name, $class);
$config->addCustomNumericFunction($name, $class);
$config->addCustomDatetimeFunction($name, $class);
$em = new EntityManager($connection, $config);
$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
@@ -70,7 +70,7 @@ methods, which are quite handy in my opinion:
Date Diff
---------
`Mysql's DateDiff function <https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_datediff>`_
`Mysql's DateDiff function <http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_datediff>`_
takes two dates as argument and calculates the difference in days
with ``date1-date2``.
@@ -96,17 +96,17 @@ discuss it step by step:
// (1)
public $firstDateExpression = null;
public $secondDateExpression = null;
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(TokenType::T_IDENTIFIER); // (2)
$parser->match(TokenType::T_OPEN_PARENTHESIS); // (3)
$parser->match(Lexer::T_IDENTIFIER); // (2)
$parser->match(Lexer::T_OPEN_PARENTHESIS); // (3)
$this->firstDateExpression = $parser->ArithmeticPrimary(); // (4)
$parser->match(TokenType::T_COMMA); // (5)
$parser->match(Lexer::T_COMMA); // (5)
$this->secondDateExpression = $parser->ArithmeticPrimary(); // (6)
$parser->match(TokenType::T_CLOSE_PARENTHESIS); // (3)
$parser->match(Lexer::T_CLOSE_PARENTHESIS); // (3)
}
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
return 'DATEDIFF(' .
@@ -131,8 +131,8 @@ 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 :ref:`DQL EBNF grammar
<dql_ebnf_grammar>`
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
@@ -164,7 +164,7 @@ Date Add
Often useful it the ability to do some simple date calculations in
your DQL query using
`MySql's DATE_ADD function <https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_date-add>`_.
`MySql's DATE\_ADD function <http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date-add>`_.
I'll skip the blah and show the code for this function:
@@ -180,28 +180,28 @@ I'll skip the blah and show the code for this function:
public $firstDateExpression = null;
public $intervalExpression = null;
public $unit = null;
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->firstDateExpression = $parser->ArithmeticPrimary();
$parser->match(TokenType::T_COMMA);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(Lexer::T_COMMA);
$parser->match(Lexer::T_IDENTIFIER);
$this->intervalExpression = $parser->ArithmeticPrimary();
$parser->match(TokenType::T_IDENTIFIER);
/** @var Lexer $lexer */
$parser->match(Lexer::T_IDENTIFIER);
/* @var $lexer Lexer */
$lexer = $parser->getLexer();
$this->unit = $lexer->token['value'];
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
return 'DATE_ADD(' .
@@ -232,33 +232,6 @@ 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.
Typed functions
---------------
By default, result of custom functions is fetched as-is from the database driver.
If you want to be sure that the type is always the same, then your custom function needs to
implement ``Doctrine\ORM\Query\AST\TypedExpression``. Then, the result is wired
through ``Doctrine\DBAL\Types\Type::convertToPhpValue()`` of the ``Type`` returned in ``getReturnType()``.
.. code-block:: php
<?php
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\AST\TypedExpression;
class DateDiff extends FunctionNode implements TypedExpression
{
// ...
public function getReturnType(): Type
{
return Type::getType(Types::INTEGER);
}
}
Conclusion
----------
@@ -267,10 +240,12 @@ 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 ORM we will come with the current set of functions, however for
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 the GitHub DoctrineExtensions repository <https://github.com/beberlei/DoctrineExtensions>`_.
`in my Github DoctrineExtensions repository <http://github.com/beberlei/DoctrineExtensions>`_.
+27 -52
View File
@@ -3,91 +3,66 @@ Entities in the Session
There are several use-cases to save entities in the session, for example:
1. User data
1. User object
2. Multi-step forms
To achieve this with Doctrine you have to pay attention to some details to get
this working.
Updating an entity
------------------
Merging entity into an EntityManager
------------------------------------
In Doctrine an entity objects has to be "managed" by an EntityManager to be
updatable. Entities saved into the session are not managed in the next request
anymore. This means that you have to update the entities with the stored session
data after you fetch the entities from the EntityManager again.
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 data from the session into a
managed Doctrine object can look like these examples:
Working with scalars
~~~~~~~~~~~~~~~~~~~~
In simpler applications there is no need to work with objects in sessions and you can use
separate session elements.
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['userId']) && is_int($_SESSION['userId'])) {
$userId = $_SESSION['userId'];
$em = GetEntityManager(); // creates an EntityManager
$user = $em->find(User::class, $userId);
$user->setValue($_SESSION['storedValue']);
$em->flush();
if (isset($_SESSION['user']) && $_SESSION['user'] instanceof User) {
$user = $_SESSION['user'];
$user = $em->merge($user);
}
Working with custom data transfer objects
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. note::
If objects are needed, we discourage the storage of entity objects in the session. It's
preferable to use a `DTO (data transfer object) <https://en.wikipedia.org/wiki/Data_transfer_object>`_
instead and merge the DTO data later with the entity.
.. code-block:: php
<?php
require_once 'bootstrap.php';
session_start();
if (isset($_SESSION['user']) && $_SESSION['user'] instanceof UserDto) {
$userDto = $_SESSION['user'];
$em = GetEntityManager(); // creates an EntityManager
$userEntity = $em->find(User::class, $userDto->getId());
$userEntity->populateFromDto($userDto);
$em->flush();
}
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 their
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 shouldn't serialize an entity and use
only the needed values of it. This can happen with the help of a DTO.
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);
$userDto = new UserDto($user->getId(), $user->getFirstName(), $user->getLastName());
// or "UserDto::createFrom($user);", but don't store an entity in a property. Only its values without relations.
$em->detach($user);
$_SESSION['user'] = $user;
$_SESSION['user'] = $userDto;
.. 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.
@@ -1,12 +1,12 @@
Implementing ArrayAccess for Domain Objects
===========================================
.. sectionauthor:: Roman Borschel <roman@code-factory.org>
.. 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 <https://martinfowler.com/eaaCatalog/layerSupertype.html>`_
`Layer Supertype <http://martinfowler.com/eaaCatalog/layerSupertype.html>`_
for all our domain objects.
Option 1
@@ -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.
@@ -0,0 +1,78 @@
Implementing Wakeup or Clone
============================
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
As explained in the
`restrictions for entity classes in the manual <http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities>`_,
it is usually not allowed for an entity to implement ``__wakeup``
or ``__clone``, because Doctrine makes special use of them.
However, it is quite easy to make use of these methods in a safe
way by guarding the custom wakeup or clone code with an entity
identity check, as demonstrated in the following sections.
Safely implementing \_\_wakeup
------------------------------
To safely implement ``__wakeup``, simply enclose your
implementation code in an identity check as follows:
.. code-block:: php
<?php
class MyEntity
{
private $id; // This is the identifier of the entity.
//...
public function __wakeup()
{
// If the entity has an identity, proceed as normal.
if ($this->id) {
// ... Your code here as normal ...
}
// otherwise do nothing, do NOT throw an exception!
}
//...
}
Safely implementing \_\_clone
-----------------------------
Safely implementing ``__clone`` is pretty much the same:
.. code-block:: php
<?php
class MyEntity
{
private $id; // This is the identifier of the entity.
//...
public function __clone()
{
// If the entity has an identity, proceed as normal.
if ($this->id) {
// ... Your code here as normal ...
}
// otherwise do nothing, do NOT throw an exception!
}
//...
}
Summary
-------
As you have seen, it is quite easy to safely make use of
``__wakeup`` and ``__clone`` in your entities without adding any
really Doctrine-specific or Doctrine-dependant code.
These implementations are possible and safe because when Doctrine
invokes these methods, the entities never have an identity (yet).
Furthermore, it is possibly a good idea to check for the identity
in your code anyway, since it's rarely the case that you want to
unserialize or clone an entity with no identity.
@@ -0,0 +1,140 @@
Integrating with CodeIgniter
============================
This is recipe for using Doctrine 2 in your
`CodeIgniter <http://www.codeigniter.com>`_ framework.
.. note::
This might not work for all CodeIgniter versions and may require
slight adjustments.
Here is how to set it up:
Make a CodeIgniter library that is both a wrapper and a bootstrap
for Doctrine 2.
Setting up the file structure
-----------------------------
Here are the steps:
- Add a php file to your system/application/libraries folder
called Doctrine.php. This is going to be your wrapper/bootstrap for
the D2 entity manager.
- Put the Doctrine folder (the one that contains Common, DBAL, and
ORM) inside that same libraries folder.
- Your system/application/libraries folder now looks like this:
system/applications/libraries -Doctrine -Doctrine.php -index.html
- If you want, open your config/autoload.php file and autoload
your Doctrine library.
<?php $autoload['libraries'] = array('doctrine');
Creating your Doctrine CodeIgniter library
------------------------------------------
Now, here is what your Doctrine.php file should look like.
Customize it to your needs.
.. code-block:: php
<?php
use Doctrine\Common\ClassLoader,
Doctrine\ORM\Configuration,
Doctrine\ORM\EntityManager,
Doctrine\Common\Cache\ArrayCache,
Doctrine\DBAL\Logging\EchoSQLLogger;
class Doctrine {
public $em = null;
public function __construct()
{
// load database configuration from CodeIgniter
require_once APPPATH.'config/database.php';
// Set up class loading. You could use different autoloaders, provided by your favorite framework,
// if you want to.
require_once APPPATH.'libraries/Doctrine/Common/ClassLoader.php';
$doctrineClassLoader = new ClassLoader('Doctrine', APPPATH.'libraries');
$doctrineClassLoader->register();
$entitiesClassLoader = new ClassLoader('models', rtrim(APPPATH, "/" ));
$entitiesClassLoader->register();
$proxiesClassLoader = new ClassLoader('Proxies', APPPATH.'models/proxies');
$proxiesClassLoader->register();
// Set up caches
$config = new Configuration;
$cache = new ArrayCache;
$config->setMetadataCacheImpl($cache);
$driverImpl = $config->newDefaultAnnotationDriver(array(APPPATH.'models/Entities'));
$config->setMetadataDriverImpl($driverImpl);
$config->setQueryCacheImpl($cache);
$config->setQueryCacheImpl($cache);
// Proxy configuration
$config->setProxyDir(APPPATH.'/models/proxies');
$config->setProxyNamespace('Proxies');
// Set up logger
$logger = new EchoSQLLogger;
$config->setSQLLogger($logger);
$config->setAutoGenerateProxyClasses( TRUE );
// Database connection information
$connectionOptions = array(
'driver' => 'pdo_mysql',
'user' => $db['default']['username'],
'password' => $db['default']['password'],
'host' => $db['default']['hostname'],
'dbname' => $db['default']['database']
);
// Create EntityManager
$this->em = EntityManager::create($connectionOptions, $config);
}
}
Please note that this is a development configuration; for a
production system you'll want to use a real caching system like
APC, get rid of EchoSqlLogger, and turn off
autoGenerateProxyClasses.
For more details, consult the
`Doctrine 2 Configuration documentation <http://www.doctrine-project.org/documentation/manual/2_0/en/configuration#configuration-options>`_.
Now to use it
-------------
Whenever you need a reference to the entity manager inside one of
your controllers, views, or models you can do this:
.. code-block:: php
<?php
$em = $this->doctrine->em;
That's all there is to it. Once you get the reference to your
EntityManager do your Doctrine 2.0 voodoo as normal.
Note: If you do not choose to autoload the Doctrine library, you
will need to put this line before you get a reference to it:
.. code-block:: php
<?php
$this->load->library('doctrine');
Good luck!
+11 -20
View File
@@ -1,12 +1,12 @@
Mysql Enums
===========
The type system of Doctrine ORM consists of flyweights, which means there is only
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 ORM application you will get
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
@@ -43,13 +43,13 @@ entities:
.. code-block:: php
<?php
#[Entity]
/** @Entity */
class Article
{
const STATUS_VISIBLE = 'visible';
const STATUS_INVISIBLE = 'invisible';
#[Column(type: "string")]
/** @Column(type="string") */
private $status;
public function setStatus($status)
@@ -67,10 +67,10 @@ the **columnDefinition** attribute.
.. code-block:: php
<?php
#[Entity]
/** @Entity */
class Article
{
#[Column(type: "string", columnDefinition: "ENUM('visible', 'invisible')")]
/** @Column(type="string", columnDefinition="ENUM('visible', 'invisible')") */
private $status;
}
@@ -98,7 +98,7 @@ For example for the previous enum type:
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
return "ENUM('visible', 'invisible')";
return "ENUM('visible', 'invisible') COMMENT '(DC2Type:enumvisibility)'";
}
public function convertToPHPValue($value, AbstractPlatform $platform)
@@ -118,11 +118,6 @@ For example for the previous enum type:
{
return self::ENUM_VISIBILITY;
}
public function requiresSQLCommentHint(AbstractPlatform $platform)
{
return true;
}
}
You can register this type with ``Type::addType('enumvisibility', 'MyProject\DBAL\EnumVisibilityType');``.
@@ -131,10 +126,10 @@ Then in your entity you can just use this type:
.. code-block:: php
<?php
#[Entity]
/** @Entity */
class Article
{
#[Column(type: "enumvisibility")]
/** @Column(type="enumvisibility") */
private $status;
}
@@ -157,7 +152,7 @@ You can generalize this approach easily to create a base class for enums:
{
$values = array_map(function($val) { return "'".$val."'"; }, $this->values);
return "ENUM(".implode(", ", $values).")";
return "ENUM(".implode(", ", $values).") COMMENT '(DC2Type:".$this->name.")'";
}
public function convertToPHPValue($value, AbstractPlatform $platform)
@@ -177,11 +172,6 @@ You can generalize this approach easily to create a base class for enums:
{
return $this->name;
}
public function requiresSQLCommentHint(AbstractPlatform $platform)
{
return true;
}
}
With this base class you can define an enum as easily as:
@@ -196,3 +186,4 @@ With this base class you can define an enum as easily as:
protected $name = 'enumvisibility';
protected $values = array('visible', 'invisible');
}
@@ -1,11 +1,13 @@
Keeping your Modules independent
=================================
.. versionadded:: 2.2
One of the goals of using modules is to create discrete 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 ORM includes a new utility called the ``ResolveTargetEntityListener``,
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
@@ -47,8 +49,10 @@ A Customer entity
use Acme\CustomerModule\Entity\Customer as BaseCustomer;
use Acme\InvoiceModule\Model\InvoiceSubjectInterface;
#[ORM\Entity]
#[ORM\Table(name: 'customer')]
/**
* @ORM\Entity
* @ORM\Table(name="customer")
*/
class Customer extends BaseCustomer implements InvoiceSubjectInterface
{
// In our example, any methods defined in the InvoiceSubjectInterface
@@ -67,12 +71,19 @@ An Invoice entity
use Doctrine\ORM\Mapping AS ORM;
use Acme\InvoiceModule\Model\InvoiceSubjectInterface;
#[ORM\Entity]
#[ORM\Table(name: 'invoice')]
/**
* Represents an Invoice.
*
* @ORM\Entity
* @ORM\Table(name="invoice")
*/
class Invoice
{
#[ORM\ManyToOne(targetEntity: InvoiceSubjectInterface::class)]
protected InvoiceSubjectInterface $subject;
/**
* @ORM\ManyToOne(targetEntity="Acme\InvoiceModule\Model\InvoiceSubjectInterface")
* @var InvoiceSubjectInterface
*/
protected $subject;
}
An InvoiceSubjectInterface
@@ -118,8 +129,7 @@ the targetEntity resolution will occur reliably:
// Add the ResolveTargetEntityListener
$evm->addEventListener(Doctrine\ORM\Events::loadClassMetadata, $rtel);
$connection = \Doctrine\DBAL\DriverManager::getConnection($connectionOptions, $config, $evm);
$em = new \Doctrine\ORM\EntityManager($connection, $config, $evm);
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);
Final Thoughts
--------------
@@ -128,3 +138,5 @@ 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.
+5 -5
View File
@@ -41,13 +41,11 @@ appropriate autoloaders.
$classMetadata = $eventArgs->getClassMetadata();
if (!$classMetadata->isInheritanceTypeSingleTable() || $classMetadata->getName() === $classMetadata->rootEntityName) {
$classMetadata->setPrimaryTable([
'name' => $this->prefix . $classMetadata->getTableName()
]);
$classMetadata->setTableName($this->prefix . $classMetadata->getTableName());
}
foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) {
if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadata::MANY_TO_MANY && $mapping['isOwningSide']) {
if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY && $mapping['isOwningSide']) {
$mappedTableName = $mapping['joinTable']['name'];
$classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix . $mappedTableName;
}
@@ -81,4 +79,6 @@ before the prefix has been set.
$tablePrefix = new \DoctrineExtensions\TablePrefix('prefix_');
$evm->addEventListener(\Doctrine\ORM\Events::loadClassMetadata, $tablePrefix);
$em = new \Doctrine\ORM\EntityManager($connection, $config, $evm);
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);
@@ -3,7 +3,7 @@ Strategy-Pattern
This recipe will give you a short introduction on how to design
similar entities without using expensive (i.e. slow) inheritance
but with not more than *the well-known strategy pattern* event
but with not more than \* the well-known strategy pattern \* event
listeners
Scenario / Problem
@@ -30,7 +30,7 @@ highly uncomfortable because of the following:
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 ORM would do the rest for you, i.e.
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
@@ -87,12 +87,12 @@ Such an interface could look like this:
* @return \Zend_View_Helper_Interface
*/
public function setView(\Zend_View_Interface $view);
/**
* @return \Zend_View_Interface
*/
public function getView();
/**
* Renders this strategy. This method will be called when the user
* displays the site.
@@ -100,7 +100,7 @@ Such an interface could look like this:
* @return string
*/
public function renderFrontend();
/**
* Renders the backend of this block. This method will be called when
* a user tries to reconfigure this block instance.
@@ -118,21 +118,21 @@ Such an interface could look like this:
* @return array
*/
public function getRequiredPanelTypes();
/**
* Determines whether a Block is able to use a given type or not
* @param string $typeName The typename
* @return boolean
*/
public function canUsePanelType($typeName);
public function setBlockEntity(AbstractBlock $block);
public function getBlockEntity();
}
As you can see, we have a method "setBlockEntity" which ties a potential strategy to an object of type AbstractBlock. This type will simply define the basic behaviour of our blocks and could potentially look something like this:
.. code-block:: php
<?php
@@ -152,16 +152,16 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg
/**
* This var contains the classname of the strategy
* that is used for this blockitem. (This string (!) value will be persisted by Doctrine ORM)
* 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 a
#[Column] attribute or setup your xml files correctly
* 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 ORM.
* This var contains an instance of $this->blockStrategy. Will not be persisted by Doctrine 2.
*
* @var BlockStrategyInterface
*/
@@ -177,7 +177,7 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg
public function getStrategyClassName() {
return $this->strategyClassName;
}
/**
* Returns the instantiated strategy
*
@@ -186,7 +186,7 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg
public function getStrategyInstance() {
return $this->strategyInstance;
}
/**
* Sets the strategy this block / panel should work as. Make sure that you've used
* this method before persisting the block!
@@ -199,7 +199,7 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg
$strategy->setBlockEntity($this);
}
Now, the important point is that $strategyClassName is a Doctrine ORM
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!
@@ -213,29 +213,28 @@ This might look like this:
.. code-block:: php
<?php
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Events;
use \Doctrine\ORM,
\Doctrine\Common;
/**
* The BlockStrategyEventListener will initialize a strategy after the
* block itself was loaded.
*/
class BlockStrategyEventListener implements EventSubscriber {
class BlockStrategyEventListener implements Common\EventSubscriber {
protected $view;
public function __construct(\Zend_View_Interface $view) {
$this->view = $view;
}
public function getSubscribedEvents() {
return array(Events::postLoad);
return array(ORM\Events::postLoad);
}
public function postLoad(LifecycleEventArgs $args) {
$blockItem = $args->getObject();
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();
@@ -251,3 +250,5 @@ This might look like this:
In this example, even some variables are set - like a view object
or a specific configuration object.
+24 -20
View File
@@ -3,7 +3,7 @@ Validation of Entities
.. sectionauthor:: Benjamin Eberlei <kontakt@beberlei.de>
Doctrine ORM does not ship with any internal validators, the reason
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.
@@ -11,7 +11,7 @@ What we offer are hooks to execute any kind of validation.
.. note::
You don't need to validate your entities in the lifecycle
events. It is only one of many options. Of course you can also
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.
@@ -25,8 +25,8 @@ 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 they
are allowed to:
never want to allow any customer to order for a larger sum than he
is allowed to:
.. code-block:: php
@@ -36,12 +36,12 @@ are allowed to:
public function assertCustomerAllowedBuying()
{
$orderLimit = $this->customer->getOrderLimit();
$amount = 0;
foreach ($this->orderLines as $line) {
$amount += $line->getAmount();
}
if ($amount > $orderLimit) {
throw new CustomerOrderLimitExceededException();
}
@@ -53,21 +53,20 @@ 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 Attributes:
First Annotations:
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\HasLifecycleCallbacks;
use Doctrine\ORM\Mapping\PrePersist;
use Doctrine\ORM\Mapping\PreUpdate;
#[Entity]
#[HasLifecycleCallbacks]
/**
* @Entity
* @HasLifecycleCallbacks
*/
class Order
{
#[PrePersist, PreUpdate]
/**
* @PrePersist @PreUpdate
*/
public function assertCustomerAllowedBuying() {}
}
@@ -84,10 +83,13 @@ In XML Mappings:
</entity>
</doctrine-mapping>
YAML needs some little change yet, to allow multiple lifecycle
events for one method, this will happen before Beta 1 though.
Now validation is performed whenever you call
``EntityManager#persist($order)`` or when you call
``EntityManager#flush()`` and an order is about to be updated. Any
Exception that happens in the lifecycle callbacks will be caught by
Exception that happens in the lifecycle callbacks will be cached by
the EntityManager and the current transaction is rolled back.
Of course you can do any type of primitive checks, not null,
@@ -99,17 +101,19 @@ validation callbacks.
<?php
class Order
{
#[PrePersist, PreUpdate]
/**
* @PrePersist @PreUpdate
*/
public function validate()
{
if (!($this->plannedShipDate instanceof DateTime)) {
throw new ValidateException();
}
if ($this->plannedShipDate->format('U') < time()) {
throw new ValidateException();
}
if ($this->customer == null) {
throw new OrderRequiresCustomerException();
}
@@ -130,4 +134,4 @@ instances. This was already discussed in the previous blog post on
the Versionable extension, which requires another type of event
called "onFlush".
Further readings: :ref:`reference-events-lifecycle-events`
Further readings: :doc:`Lifecycle Events <../reference/events>`
+33 -63
View File
@@ -1,9 +1,9 @@
Working with DateTime Instances
===============================
There are many nitty gritty details when working with PHPs DateTime instances. You have to know their inner
There are many nitty gritty details when working with PHPs DateTime instances. You have know their inner
workings pretty well not to make mistakes with date handling. This cookbook entry holds several
interesting pieces of information on how to work with PHP DateTime instances in ORM.
interesting pieces of information on how to work with PHP DateTime instances in Doctrine 2.
DateTime changes are detected by Reference
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -15,16 +15,13 @@ these comparisons are always made **BY REFERENCE**. That means the following cha
.. code-block:: php
<?php
use DateTime;
#[Entity]
/** @Entity */
class Article
{
#[Column(type: 'datetime')]
private DateTime $updated;
/** @Column(type="datetime") */
private $updated;
public function setUpdated(): void
public function setUpdated()
{
// will NOT be saved in the database
$this->updated->modify("now");
@@ -36,14 +33,12 @@ The way to go would be:
.. code-block:: php
<?php
use DateTime;
class Article
{
public function setUpdated(): void
public function setUpdated()
{
// WILL be saved in the database
$this->updated = new DateTime("now");
$this->updated = new \DateTime("now");
}
}
@@ -54,25 +49,24 @@ By default Doctrine assumes that you are working with a default timezone. Each D
is created by Doctrine will be assigned the timezone that is currently the default, either through
the ``date.timezone`` ini setting or by calling ``date_default_timezone_set()``.
This is very important to handle correctly if your application runs on different servers or is moved from one to another server
This is very important to handle correctly if your application runs on different serves or is moved from one to another server
(with different timezone settings). You have to make sure that the timezone is the correct one
on all this systems.
Handling different Timezones with the DateTime Type
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you first come across the requirement to save different timezones you may be still optimistic about how
to manage this mess,
however let me crush your expectations fast. There is not a single database out there (supported by Doctrine ORM)
If you first come across the requirement to save different you are still optimistic to manage this mess,
however let me crush your expectations fast. There is not a single database out there (supported by Doctrine 2)
that supports timezones correctly. Correctly here means that you can cover all the use-cases that
can come up with timezones. If you don't believe me you should read up on `Storing DateTime
in Databases <https://derickrethans.nl/storing-date-time-in-database.html>`_.
in Databases <http://derickrethans.nl/storing-date-time-in-database.html>`_.
The problem is simple. Not a single database vendor saves the timezone, only the differences to UTC.
However with frequent daylight saving and political timezone changes you can have a UTC offset that moves
in different offset directions depending on the real location.
The solution for this dilemma is simple. Don't use timezones with DateTime and Doctrine ORM. However there is a workaround
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.
@@ -89,71 +83,45 @@ the UTC time at the time of the booking and the timezone the event happened in.
namespace DoctrineExtensions\DBAL\Types;
use DateTimeZone;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\ConversionException;
use Doctrine\DBAL\Types\DateTimeType;
class UTCDateTimeType extends DateTimeType
{
private static DateTimeZone $utc;
static private $utc = null;
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
if ($value instanceof \DateTime) {
$value->setTimezone(self::getUtc());
if ($value === null) {
return null;
}
return parent::convertToDatabaseValue($value, $platform);
return $value->format($platform->getDateTimeFormatString(),
(self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone('UTC'))
);
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
if (null === $value || $value instanceof \DateTime) {
return $value;
if ($value === null) {
return null;
}
$converted = \DateTime::createFromFormat(
$val = \DateTime::createFromFormat(
$platform->getDateTimeFormatString(),
$value,
self::getUtc()
(self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone('UTC'))
);
if (! $converted) {
throw ConversionException::conversionFailedFormat(
$value,
$this->getName(),
$platform->getDateTimeFormatString()
);
if (!$val) {
throw ConversionException::conversionFailed($value, $this->getName());
}
return $converted;
}
private static function getUtc(): DateTimeZone
{
return self::$utc ??= new DateTimeZone('UTC');
return $val;
}
}
This database type makes sure that every DateTime instance is always saved in UTC, relative
to the current timezone that the passed DateTime instance has.
To actually use this new type instead of the default ``datetime`` type, you need to run following
code before bootstrapping the ORM:
.. code-block:: php
<?php
use Doctrine\DBAL\Types\Type;
use DoctrineExtensions\DBAL\Types\UTCDateTimeType;
Type::overrideType('datetime', UTCDateTimeType::class);
Type::overrideType('datetimetz', UTCDateTimeType::class);
To be able to transform these values
to the current timezone that the passed DateTime instance has. To be able to transform these values
back into their real timezone you have to save the timezone in a separate field of the entity
requiring timezoned datetimes:
@@ -162,13 +130,15 @@ requiring timezoned datetimes:
<?php
namespace Shipping;
#[Entity]
/**
* @Entity
*/
class Event
{
#[Column(type: 'datetime')]
/** @Column(type="datetime") */
private $created;
#[Column(type: 'string')]
/** @Column(type="string") */
private $timezone;
/**
+37 -30
View File
@@ -1,4 +1,4 @@
Welcome to Doctrine ORM's documentation!
Welcome to Doctrine 2 ORM's documentation!
==========================================
The Doctrine documentation is comprised of tutorials, a reference section and
@@ -13,13 +13,14 @@ If this documentation is not helping to answer questions you have about
Doctrine ORM don't panic. You can get help from different sources:
- There is a :doc:`FAQ <reference/faq>` with answers to frequent questions.
- The `Doctrine Mailing List <https://groups.google.com/group/doctrine-user>`_
- Slack chat room `#orm <https://www.doctrine-project.org/slack>`_
- Report a bug on `GitHub <https://github.com/doctrine/orm/issues>`_.
- On `StackOverflow <https://stackoverflow.com/questions/tagged/doctrine-orm>`_
- The `Doctrine Mailing List <http://groups.google.com/group/doctrine-user>`_
- Internet Relay Chat (IRC) in #doctrine on Freenode
- Report a bug on `JIRA <http://www.doctrine-project.org/jira>`_.
- On `Twitter <https://twitter.com/search/%23doctrine2>`_ with ``#doctrine2``
- On `StackOverflow <http://stackoverflow.com/questions/tagged/doctrine2>`_
If you need more structure over the different topics you can browse the table
of contents.
If you need more structure over the different topics you can browse the :doc:`table
of contents <toc>`.
Getting Started
---------------
@@ -34,30 +35,31 @@ Mapping Objects onto a Database
-------------------------------
* **Mapping**:
:doc:`Objects <reference/basic-mapping>` \|
:doc:`Associations <reference/association-mapping>` \|
:doc:`Objects <reference/basic-mapping>` |
:doc:`Associations <reference/association-mapping>` |
:doc:`Inheritance <reference/inheritance-mapping>`
* **Drivers**:
:doc:`Attributes <reference/attributes-reference>` \|
:doc:`XML <reference/xml-mapping>` \|
: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:`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:`DQL <reference/dql-doctrine-query-language>` |
:doc:`QueryBuilder <reference/query-builder>` |
:doc:`Native SQL <reference/native-sql>`
* **Internals**:
:doc:`Internals explained <reference/unitofwork>` \|
:doc:`Internals explained <reference/unitofwork>` |
:doc:`Associations <reference/unitofwork-associations>`
Advanced Topics
@@ -70,11 +72,9 @@ Advanced Topics
* :doc:`Transactions and Concurrency <reference/transactions-and-concurrency>`
* :doc:`Filters <reference/filters>`
* :doc:`NamingStrategy <reference/namingstrategy>`
* :doc:`TypedFieldMapper <reference/typedfieldmapper>`
* :doc:`Improving Performance <reference/improving-performance>`
* :doc:`Caching <reference/caching>`
* :doc:`Partial Hydration <reference/partial-hydration>`
* :doc:`Partial Objects <reference/partial-objects>`
* :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>`
@@ -95,30 +95,37 @@ Tutorials
Changelogs
----------
* `Upgrade <https://github.com/doctrine/orm/blob/HEAD/UPGRADE.md>`_
* :doc:`Migration to 2.5 <changelog/migration_2_5>`
Cookbook
--------
* **Patterns**:
:doc:`Aggregate Fields <cookbook/aggregate-fields>` \|
:doc:`Decorator Pattern <cookbook/decorator-pattern>` \|
:doc:`Strategy Pattern <cookbook/strategy-cookbook-introduction>`
: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 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:`Working with DateTime <cookbook/working-with-datetime>` \|
:doc:`Validation <cookbook/validation-of-entities>` \|
:doc:`Entities in the Session <cookbook/entities-in-session>` \|
: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>`
.. include:: toc.rst
+113
View File
@@ -0,0 +1,113 @@
@ECHO OFF
REM Command file for Sphinx documentation
set SPHINXBUILD=sphinx-build
set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^<target^>` where ^<target^> is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. changes to make an overview over all changed/added/deprecated items
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Doctrine2ORM.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Doctrine2ORM.ghc
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
:end
+122 -131
View File
@@ -9,61 +9,51 @@ steps of configuration.
.. code-block:: php
<?php
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\ORM\ORMSetup;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
use Doctrine\ORM\EntityManager,
Doctrine\ORM\Configuration;
// ...
if ($applicationMode == "development") {
$queryCache = new ArrayAdapter();
$metadataCache = new ArrayAdapter();
$cache = new \Doctrine\Common\Cache\ArrayCache;
} else {
$queryCache = new PhpFilesAdapter('doctrine_queries');
$metadataCache = new PhpFilesAdapter('doctrine_metadata');
$cache = new \Doctrine\Common\Cache\ApcCache;
}
$config = new Configuration;
$config->setMetadataCache($metadataCache);
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities'], true);
$config->setMetadataCacheImpl($cache);
$driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
$config->setMetadataDriverImpl($driverImpl);
$config->setQueryCache($queryCache);
$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);
}
$connection = DriverManager::getConnection([
$connectionOptions = array(
'driver' => 'pdo_sqlite',
'path' => 'database.sqlite',
], $config);
$em = new EntityManager($connection, $config);
Doctrine and Caching
--------------------
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.
Doctrine does not bundle its own cache implementation anymore. Instead,
the PSR-6 standard interfaces are used to access the cache. In the examples
in this documentation, Symfony Cache is used as a reference implementation.
'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
---------------------
@@ -111,28 +101,29 @@ Gets or sets the metadata driver implementation that is used by
Doctrine to acquire the object-relational metadata for your
classes.
There are currently 3 available implementations:
There are currently 4 available implementations:
- ``Doctrine\ORM\Mapping\Driver\AttributeDriver``
- ``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 AttributeDriver is
used in the examples. For information on the usage of the
XmlDriver please refer to the dedicated chapter ``XML Mapping``.
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 attribute driver can be injected in the ``Doctrine\ORM\Configuration``:
The annotation driver can be configured with a factory method on
the ``Doctrine\ORM\Configuration``:
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities'], true);
$driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
$config->setMetadataDriverImpl($driverImpl);
The path information to the entities is required for the attribute
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
@@ -145,21 +136,29 @@ Metadata Cache (***RECOMMENDED***)
.. code-block:: php
<?php
$config->setMetadataCache($cache);
$config->getMetadataCache();
$config->setMetadataCacheImpl($cache);
$config->getMetadataCacheImpl();
Gets or sets the cache adapter to use for caching metadata
information, that is, all the information you supply via attributes,
xml, 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 PSR-6
``Psr\Cache\CacheItemPoolInterface`` interface.
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.
For development you should use an array cache like
``Symfony\Component\Cache\Adapter\ArrayAdapter``
which only caches data on a per-request basis.
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***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -167,8 +166,8 @@ Query Cache (***RECOMMENDED***)
.. code-block:: php
<?php
$config->setQueryCache($cache);
$config->getQueryCache();
$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
@@ -180,9 +179,17 @@ minimal memory usage in your cache).
Usage of a query cache is highly recommended.
For development you should use an array cache like
``Symfony\Component\Cache\Adapter\ArrayAdapter``
which only caches data on a per-request basis.
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***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -195,7 +202,10 @@ SQL Logger (***Optional***)
Gets or sets the logger to use for logging all SQL statements
executed by Doctrine. The logger class must implement the
deprecated ``Doctrine\DBAL\Logging\SQLLogger`` interface.
``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***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -211,7 +221,7 @@ option that controls this behavior is:
Possible values for ``$mode`` are:
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_NEVER``
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_NEVER``
Never autogenerate a proxy. You will need to generate the proxies
manually, for this use the Doctrine Console like so:
@@ -227,17 +237,17 @@ methods were added to the entity class that are not yet in the proxy class.
In such a case, simply use the Doctrine Console to (re)generate the
proxy classes.
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_ALWAYS``
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_ALWAYS``
Always generates a new proxy in every request and writes it to disk.
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``
Generate the proxy class when the proxy file does not exist.
This strategy causes a file exists call whenever any proxy is
used the first time in a request.
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_EVAL``
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_EVAL``
Generate the proxy classes and evaluate them on the fly via eval(),
avoiding writing the proxies to disk.
@@ -247,7 +257,7 @@ In a production environment, it is highly recommended to use
AUTOGENERATE_NEVER to allow for optimal performances. The other
options are interesting in development environment.
``setAutoGenerateProxyClasses`` can accept a boolean
Before v2.4, ``setAutoGenerateProxyClasses`` would accept a boolean
value. This is still possible, ``FALSE`` being equivalent to
AUTOGENERATE_NEVER and ``TRUE`` to AUTOGENERATE_ALWAYS.
@@ -256,10 +266,10 @@ Development vs Production Configuration
You should code your Doctrine2 bootstrapping with two different
runtime models in mind. There are some serious benefits of using
APCu or Memcache in production. In development however this will
APC or Memcache in production. In development however this will
frequently give you fatal errors, when you change your entities and
the cache still keeps the outdated metadata. That is why we
recommend an array cache for development.
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
@@ -271,21 +281,23 @@ proxy sets an exclusive file lock which can cause serious
performance bottlenecks in systems with regular concurrent
requests.
Connection
----------
Connection Options
------------------
The ``$connection`` passed as the first argument to he constructor of
``EntityManager`` has to be an instance of ``Doctrine\DBAL\Connection``.
You can use the factory ``Doctrine\DBAL\DriverManager::getConnection()``
to create such a connection. The DBAL configuration is explained in the
`DBAL section <https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/configuration.html>`_.
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 <./../../../../../projects/doctrine-dbal/en/latest/reference/configuration.html>`_.
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 ORM,
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.
@@ -295,7 +307,7 @@ 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 ORM implements a variant of the proxy pattern where it
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
@@ -306,12 +318,10 @@ Reference Proxies
The method ``EntityManager#getReference($entityName, $identifier)``
lets you obtain a reference to an entity for which the identifier
is known, without necessarily 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.
Consider the following example:
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
@@ -321,33 +331,14 @@ Consider the following example:
$item = $em->getReference('MyProject\Model\Item', $itemId);
$cart->addItem($item);
Whether the object being returned from ``EntityManager#getReference()``
is a proxy or a direct instance of the entity class may depend on different
factors, including whether the entity has already been loaded into memory
or entity inheritance being used. But your code does not need to care
and in fact it **should not care**. Proxy objects should be transparent to your
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.
When using the ``EntityManager#getReference()`` method, you need to be aware
of a few peculiarities.
At the best case, the ORM can avoid querying the database at all. But, that
also means that this method will not throw an exception when an invalid value
for the ``$identifier`` parameter is passed. ``$identifier`` values are
not checked and there is no guarantee that the requested entity instance even
exists the method will still return a proxy object.
Its only when the proxy has to be fully initialized or associations cannot
be written to the database that invalid ``$identifier`` values may lead to
exceptions.
The ``EntityManager#getReference()`` is mostly useful when you only
need a reference to some entity to make an association, like in the example
above. In that case, it can save you from loading data from the database
that you don't need. But remember as soon as you read any property values
besides those making up the ID, a database request will be made to initialize
all fields.
Association proxies
~~~~~~~~~~~~~~~~~~~
@@ -418,19 +409,19 @@ be found.
Multiple Metadata Sources
-------------------------
When using different components using Doctrine ORM you may end up
When using different components using Doctrine 2 you may end up
with them using two different metadata drivers, for example XML and
PHP. You can use the MappingDriverChain Metadata implementations to
YAML. You can use the DriverChain Metadata implementations to
aggregate these drivers based on namespaces:
.. code-block:: php
<?php
use Doctrine\Persistence\Mapping\Driver\MappingDriverChain;
use Doctrine\ORM\Mapping\Driver\DriverChain;
$chain = new MappingDriverChain();
$chain = new DriverChain();
$chain->addDriver($xmlDriver, 'Doctrine\Tests\Models\Company');
$chain->addDriver($phpDriver, 'Doctrine\Tests\ORM\Mapping');
$chain->addDriver($yamlDriver, 'Doctrine\Tests\ORM\Mapping');
Based on the namespace of the entity the loading of entities is
delegated to the appropriate driver. The chain semantics come from
@@ -456,22 +447,22 @@ That will be available for all entities without a custom repository class.
The default value is ``Doctrine\ORM\EntityRepository``.
Any repository class must be a subclass of EntityRepository otherwise you got an ORMException
Ignoring entities (***OPTIONAL***)
-----------------------------------
Specifies the Entity FQCNs to ignore.
SchemaTool will then skip these (e.g. when comparing schemas).
.. code-block:: php
<?php
$config->setSchemaIgnoreClasses([$fqcn]);
$config->getSchemaIgnoreClasses();
Setting up the Console
----------------------
Doctrine uses the Symfony Console component for generating the command
line interface. You can take a look at the
:doc:`tools chapter <../reference/tools>` for inspiration how to setup the cli.
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
+51 -60
View File
@@ -2,55 +2,50 @@ Architecture
============
This chapter gives an overview of the overall architecture,
terminology and constraints of Doctrine ORM. It is recommended to
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 ORM aims to simplify the
As the term ORM already hints at, Doctrine 2 aims to simplify the
translation between database rows and the PHP object model. The
primary use case for Doctrine are therefore applications that
utilize the Object-Oriented Programming Paradigm. For applications
that do not primarily work with objects Doctrine ORM is not suited very
that do not primarily work with objects Doctrine 2 is not suited very
well.
Requirements
------------
Doctrine ORM requires a minimum of PHP 8.1. For greatly improved
Doctrine 2 requires a minimum of PHP 5.4. For greatly improved
performance it is also recommended that you use APC with PHP.
Doctrine ORM Packages
Doctrine 2 Packages
-------------------
Doctrine ORM is divided into four main packages.
Doctrine 2 is divided into three main packages.
- `Collections <https://www.doctrine-project.org/projects/doctrine-collections/en/stable/index.html>`_
- `Event Manager <https://www.doctrine-project.org/projects/doctrine-event-manager/en/stable/index.html>`_
- `Persistence <https://www.doctrine-project.org/projects/doctrine-persistence/en/stable/index.html>`_
- `DBAL <https://www.doctrine-project.org/projects/doctrine-dbal/en/stable/index.html>`_
- ORM (depends on DBAL+Persistence+Collections)
- Common
- DBAL (includes Common)
- ORM (includes DBAL+Common)
This manual mainly covers the ORM package, sometimes touching parts
of the underlying DBAL and Persistence packages. The Doctrine codebase
is split into these packages for a few reasons:
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...
- to make things more maintainable and decoupled
- to allow you to use the code in Doctrine Persistence and Collections without the ORM or DBAL
- to allow you to use the DBAL without the ORM
- ...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
Collection, Event Manager and Persistence
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The Common Package
~~~~~~~~~~~~~~~~~~
The Collection, Event Manager and Persistence packages contain highly
reusable components that have no dependencies beyond the packages
themselves (and PHP, of course). The root namespace of the Persistence
package is ``Doctrine\Persistence``. The root namespace of the
Collection package is ``Doctrine\Common\Collections``, for historical
reasons. The root namespace of the Event Manager package is just
``Doctrine\Common``, also for historical reasons.
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
~~~~~~~~~~~~~~~~
@@ -71,21 +66,33 @@ The root namespace of the ORM package is ``Doctrine\ORM``.
Terminology
-----------
.. _terminology_entities:
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 nor read-only but
it may contain final methods or read-only properties.
- 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://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
@@ -99,25 +106,6 @@ classes, and non-entity classes may extend entity classes.
never calls entity constructors, thus you are free to use them as
you wish and even have it require arguments of any type.
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.
Mapped superclasses are explained in greater detail in the chapter
on :doc:`inheritance mapping </reference/inheritance-mapping>`.
Transient Classes
~~~~~~~~~~~~~~~~~
The term "transient class" appears in some places in the mapping
drivers as well as the code dealing with metadata handling.
A transient class is a class that is neither an entity nor a mapped
superclass. From the ORM's point of view, these classes can be
completely ignored, and no class metadata is loaded for them at all.
Entity states
~~~~~~~~~~~~~
@@ -164,19 +152,22 @@ 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.
By default, serializing proxy objects does not initialize them. On
unserialization, resulting objects are detached from the entity
manager and cannot be initialiazed anymore. You can implement the
``__serialize()`` method if you want to change that behavior, but
then you need to ensure that you won't generate large serialized
object graphs and take care of circular associations.
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
functionality provided by Doctrine ORM. The ``EntityManager`` API is
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.
@@ -193,14 +184,14 @@ 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.
.. _unit-of-work:
The Unit of Work
~~~~~~~~~~~~~~~~
Internally an ``EntityManager`` uses a ``UnitOfWork``, which is a
typical implementation of the
`Unit of Work pattern <https://martinfowler.com/eaaCatalog/unitOfWork.html>`_,
`Unit of Work pattern <http://martinfowler.com/eaaCatalog/unitOfWork.html>`_,
to keep track of all the things that need to be done the next time
``flush`` is invoked. You usually do not directly interact with a
``UnitOfWork`` but with the ``EntityManager`` instead.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+193 -153
View File
@@ -14,11 +14,17 @@ After working through this guide you should know:
Mapping of associations will be covered in the next chapter on
:doc:`Association Mapping <association-mapping>`.
Guide Assumptions
-----------------
You should have already :doc:`installed and configure <configuration>`
Doctrine.
Creating Classes for the Database
---------------------------------
Every PHP object that you want to save in the database using Doctrine
is called an *Entity*. The term "Entity" describes objects
is called an "Entity". The term "Entity" describes objects
that have an identity over many independent requests. This identity is
usually achieved by assigning a unique identifier to an entity.
In this tutorial the following ``Message`` PHP class will serve as the
@@ -44,18 +50,19 @@ that describes your entity.
Doctrine provides several different ways to specify object-relational
mapping metadata:
- :doc:`Attributes <attributes-reference>`
- :doc:`Docblock Annotations <annotations-reference>`
- :doc:`XML <xml-mapping>`
- :doc:`YAML <yaml-mapping>`
- :doc:`PHP code <php-mapping>`
This manual will usually show mapping metadata via attributes, though
many examples also show the equivalent configuration in XML.
This manual will usually show mapping metadata via docblock annotations, though
many examples also show the equivalent configuration in YAML and XML.
.. note::
All metadata drivers perform equally. Once the metadata of a class has been
read from the source (attributes, XML, etc.) it is stored in an instance
of the ``Doctrine\ORM\Mapping\ClassMetadata`` class which are
read from the source (annotations, xml or yaml) it is stored in an instance
of the ``Doctrine\ORM\Mapping\ClassMetadata`` class and these instances are
stored in the metadata cache. If you're not using a metadata cache (not
recommended!) then the XML driver is the fastest.
@@ -63,15 +70,13 @@ Marking our ``Message`` class as an entity for Doctrine is straightforward:
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\Entity;
#[Entity]
/** @Entity */
class Message
{
// ...
//...
}
.. code-block:: xml
@@ -82,23 +87,28 @@ Marking our ``Message`` class as an entity for Doctrine is straightforward:
</entity>
</doctrine-mapping>
.. code-block:: yaml
Message:
type: entity
# ...
With no additional information, Doctrine expects the entity to be saved
into a table with the same name as the class in our case ``Message``.
You can change this by configuring information about the table:
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
#[Entity]
#[Table(name: 'message')]
/**
* @Entity
* @Table(name="message")
*/
class Message
{
// ...
//...
}
.. code-block:: xml
@@ -109,34 +119,39 @@ You can change this by configuring information about the table:
</entity>
</doctrine-mapping>
.. code-block:: yaml
Message:
type: entity
table: message
# ...
Now the class ``Message`` will be saved and fetched from the table ``message``.
Property Mapping
----------------
The next step is mapping its properties to columns in the table.
The next step after marking a PHP class as an entity is mapping its properties
to columns in a table.
To configure a property use the ``Column`` attribute. The ``type``
argument specifies the :ref:`Doctrine Mapping Type
<reference-mapping-types>` to use for the field. If the type is not
specified, ``string`` is used as the default.
To configure a property use the ``@Column`` docblock annotation. The ``type``
attribute specifies the :ref:`Doctrine Mapping Type <reference-mapping-types>`
to use for the field. If the type is not specified, ``string`` is used as the
default.
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\Column;
use Doctrine\DBAL\Types\Types;
#[Entity]
/** @Entity */
class Message
{
#[Column(type: Types::INTEGER)]
/** @Column(type="integer") */
private $id;
#[Column(length: 140)]
/** @Column(length=140) */
private $text;
#[Column(name: 'posted_at', type: Types::DATETIME)]
/** @Column(type="datetime", name="posted_at") */
private $postedAt;
}
@@ -150,91 +165,101 @@ specified, ``string`` is used as the default.
</entity>
</doctrine-mapping>
.. code-block:: yaml
Message:
type: entity
fields:
id:
type: integer
text:
length: 140
postedAt:
type: datetime
column: posted_at
When we don't explicitly specify a column name via the ``name`` option, Doctrine
assumes the field name is also the column name. So in this example:
assumes the field name is also the column name. This means that:
* the ``id`` property will map to the column ``id`` using the type ``integer``;
* the ``text`` property will map to the column ``text`` with the default mapping type ``string``;
* the ``postedAt`` property will map to the ``posted_at`` column with the ``datetime`` type.
Here is a complete list of ``Column``s attributes (all optional):
The Column annotation has some more attributes. Here is a complete
list:
- ``type`` (default: 'string'): The mapping type to use for the column.
- ``name`` (default: name of property): The name of the column in the database.
- ``length`` (default: 255): The length of the column in the database.
Applies only if a string-valued column is used.
- ``unique`` (default: ``false``): Whether the column is a unique key.
- ``nullable`` (default: ``false``): Whether the column is nullable.
- ``insertable`` (default: ``true``): Whether the column should be inserted.
- ``updatable`` (default: ``true``): Whether the column should be updated.
- ``generated`` (default: ``null``): Whether the generated strategy should be ``'NEVER'``, ``'INSERT'`` and ``ALWAYS``.
- ``enumType`` (requires PHP 8.1 and ``doctrine/orm`` 2.11): The PHP enum class name to convert the database value into.
- ``precision`` (default: 0): The precision for a decimal (exact numeric) column
(applies only for decimal column),
- ``type``: (optional, defaults to 'string') The mapping type to
use for the column.
- ``name``: (optional, defaults to field name) The name of the
column in the database.
- ``length``: (optional, default 255) The length of the column in
the database. (Applies only if a string-valued column is used).
- ``unique``: (optional, default FALSE) Whether the column is a
unique key.
- ``nullable``: (optional, default FALSE) Whether the database
column is nullable.
- ``precision``: (optional, default 0) The precision for a decimal
(exact numeric) column (applies only for decimal column),
which is the maximum number of digits that are stored for the values.
- ``scale`` (default: 0): The scale for a decimal (exact
- ``scale``: (optional, default 0) The scale for a decimal (exact
numeric) column (applies only for decimal column), which represents
the number of digits to the right of the decimal point and must
not be greater than ``precision``.
- ``columnDefinition``: Allows to define a custom
not be greater than *precision*.
- ``columnDefinition``: (optional) Allows to define a custom
DDL snippet that is used to create the column. Warning: This normally
confuses the :doc:`SchemaTool <tools>` to always detect the column as changed.
- ``options``: Key-value pairs of options that get passed
confuses the SchemaTool to always detect the column as changed.
- ``options``: (optional) Key-value pairs of options that get passed
to the underlying database platform when generating DDL statements.
.. _reference-php-mapping-types:
PHP Types Mapping
_________________
.. versionadded:: 2.9
The column types can be inferred automatically from PHP's property types.
However, when the property type is nullable this has no effect on the ``nullable`` Column attribute.
These are the "automatic" mapping rules:
+-----------------------+-------------------------------+
| PHP property type | Doctrine column type |
+=======================+===============================+
| ``DateInterval`` | ``Types::DATEINTERVAL`` |
+-----------------------+-------------------------------+
| ``DateTime`` | ``Types::DATETIME_MUTABLE`` |
+-----------------------+-------------------------------+
| ``DateTimeImmutable`` | ``Types::DATETIME_IMMUTABLE`` |
+-----------------------+-------------------------------+
| ``array`` | ``Types::JSON`` |
+-----------------------+-------------------------------+
| ``bool`` | ``Types::BOOLEAN`` |
+-----------------------+-------------------------------+
| ``float`` | ``Types::FLOAT`` |
+-----------------------+-------------------------------+
| ``int`` | ``Types::INTEGER`` |
+-----------------------+-------------------------------+
| Any other type | ``Types::STRING`` |
+-----------------------+-------------------------------+
As of version 2.11 Doctrine can also automatically map typed properties using a
PHP 8.1 enum to set the right ``type`` and ``enumType``.
.. versionadded:: 2.14
Since version 2.14 you can specify custom typed field mapping between PHP type and DBAL type using ``Configuration``
and a custom ``Doctrine\ORM\Mapping\TypedFieldMapper`` implementation.
:doc:`Read more about TypedFieldMapper <typedfieldmapper>`.
.. _reference-mapping-types:
Doctrine Mapping Types
----------------------
The ``type`` option used in the ``@Column`` accepts any of the
`existing Doctrine DBAL types <https://docs.doctrine-project.org/projects/doctrine-dbal/en/stable/reference/types.html#reference>`_
or :doc:`your own custom mapping types
<../cookbook/custom-mapping-types>`. A Doctrine type defines
The ``type`` option used in the ``@Column`` accepts any of the existing
Doctrine types or even your own custom types. A Doctrine type defines
the conversion between PHP and SQL types, independent from the database vendor
you are using.
you are using. All Mapping Types that ship with Doctrine are fully portable
between the supported database systems.
As an example, the Doctrine Mapping Type ``string`` defines the
mapping from a PHP string to a SQL VARCHAR (or VARCHAR2 etc.
depending on the RDBMS brand). Here is a quick overview of the
built-in mapping types:
- ``string``: Type that maps a SQL VARCHAR to a PHP string.
- ``integer``: Type that maps a SQL INT to a PHP integer.
- ``smallint``: Type that maps a database SMALLINT to a PHP
integer.
- ``bigint``: Type that maps a database BIGINT to a PHP string.
- ``boolean``: Type that maps a SQL boolean or equivalent (TINYINT) to a PHP boolean.
- ``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
A cookbook article shows how to define :doc:`your own custom mapping types
<../cookbook/custom-mapping-types>`.
.. note::
@@ -245,7 +270,7 @@ you are using.
.. warning::
All Date types assume that you are exclusively using the default timezone
set by `date_default_timezone_set() <https://php.net/manual/en/function.date-default-timezone-set.php>`_
set by `date_default_timezone_set() <http://docs.php.net/manual/en/function.date-default-timezone-set.php>`_
or by the php.ini configuration ``date.timezone``. Working with
different timezones will cause troubles and unexpected behavior.
@@ -259,20 +284,22 @@ Identifiers / Primary Keys
--------------------------
Every entity class must have an identifier/primary key. You can select
the field that serves as the identifier with the ``#[Id]`` attribute.
the field that serves as the identifier with the ``@Id``
annotation.
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
class Message
{
#[Id]
#[Column(type: 'integer')]
#[GeneratedValue]
private int|null $id = null;
// ...
/**
* @Id @Column(type="integer")
* @GeneratedValue
*/
private $id;
//...
}
.. code-block:: xml
@@ -286,29 +313,22 @@ the field that serves as the identifier with the ``#[Id]`` attribute.
</entity>
</doctrine-mapping>
In most cases using the automatic generator strategy (``#[GeneratedValue]``) is
what you want, but for backwards-compatibility reasons it might not. It
defaults to the identifier generation mechanism your current database
vendor preferred at the time that strategy was introduced:
``AUTO_INCREMENT`` with MySQL, sequences with PostgreSQL and Oracle and
so on.
If you are using `doctrine/dbal` 4, we now recommend using ``IDENTITY``
for PostgreSQL, and ``AUTO`` resolves to it because of that.
You can stick with ``SEQUENCE`` while still using the ``AUTO``
strategy, by configuring what it defaults to.
.. code-block:: yaml
.. code-block:: php
Message:
type: entity
id:
id:
type: integer
generator:
strategy: AUTO
fields:
# fields here
<?php
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\ORM\Configuration;
$config = new Configuration();
$config->setIdentityGenerationPreferences([
PostgreSQLPlatform::class => ClassMetadata::GENERATOR_TYPE_SEQUENCE,
]);
.. _identifier-generation-strategies:
In most cases using the automatic generator strategy (``@GeneratedValue``) is
what you want. It defaults to the identifier generation mechanism your current
database vendor prefers: AUTO_INCREMENT with MySQL, SERIAL with PostgreSQL,
Sequences with Oracle and so on.
Identifier Generation Strategies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -323,24 +343,24 @@ Here is the list of possible generation strategies:
- ``AUTO`` (default): Tells Doctrine to pick the strategy that is
preferred by the used database platform. The preferred strategies
are ``IDENTITY`` for MySQL, SQLite, MsSQL and SQL Anywhere and, for
historical reasons, ``SEQUENCE`` for Oracle and PostgreSQL. This
strategy provides full portability.
- ``IDENTITY``: Tells Doctrine to use special identity columns in
the database that generate a value on insertion of a row. This
strategy does currently not provide full portability and is
supported by the following platforms: MySQL/SQLite/SQL Anywhere
(``AUTO_INCREMENT``), MSSQL (``IDENTITY``) and PostgreSQL (``SERIAL``).
are IDENTITY for MySQL, SQLite, MsSQL and SQL Anywhere and SEQUENCE
for Oracle and PostgreSQL. This strategy provides full portability.
- ``SEQUENCE``: Tells Doctrine to use a database sequence for ID
generation. This strategy does currently not provide full
portability. Sequences are supported by Oracle, PostgreSql and
SQL Anywhere.
- ``IDENTITY``: Tells Doctrine to use special identity columns in
the database that generate a value on insertion of a row. This
strategy does currently not provide full portability and is
supported by the following platforms: MySQL/SQLite/SQL Anywhere
(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.
- ``CUSTOM``: With this option, you can use the ``#[CustomIdGenerator]`` attribute.
It will allow you to pass a :ref:`class of your own to generate the identifiers. <attrref_customidgenerator>`
same as leaving off the @GeneratedValue entirely.
Sequence Generator
^^^^^^^^^^^^^^^^^^
@@ -351,16 +371,18 @@ besides specifying the sequence's name:
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
class Message
{
#[Id]
#[GeneratedValue(strategy: 'SEQUENCE')]
#[SequenceGenerator(sequenceName: 'message_seq', initialValue: 1, allocationSize: 100)]
protected int|null $id = null;
// ...
/**
* @Id
* @GeneratedValue(strategy="SEQUENCE")
* @SequenceGenerator(sequenceName="message_seq", initialValue=1, allocationSize=100)
*/
protected $id = null;
//...
}
.. code-block:: xml
@@ -374,6 +396,20 @@ besides specifying the sequence's name:
</entity>
</doctrine-mapping>
.. code-block:: yaml
Message:
type: entity
id:
id:
type: integer
generator:
strategy: SEQUENCE
sequenceGenerator:
sequenceName: message_seq
allocationSize: 100
initialValue: 1
The initial value specifies at which value the sequence should
start.
@@ -382,10 +418,12 @@ 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 ORM would only
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
@@ -408,12 +446,11 @@ need to access the sequence once to generate the identifiers for
Composite Keys
~~~~~~~~~~~~~~
With Doctrine ORM you can use composite primary keys, using ``#[Id]`` on
more than one column. Some restrictions exist opposed to using a single
identifier in this case: The use of the ``#[GeneratedValue]`` attribute
is not supported, which means you can only use composite keys if you
generate the primary key values yourself before calling
``EntityManager#persist()`` on the entity.
with Doctrine 2 you can use composite primary keys, using ``@Id`` on more then
one column. Some restrictions exist opposed to using a single identifier in
this case: The use of the ``@GeneratedValue`` annotation is not supported,
which means you can only use composite keys if you generate the primary key
values yourself before calling ``EntityManager#persist()`` on the entity.
More details on composite primary keys are discussed in a :doc:`dedicated tutorial
<../tutorials/composite-primary-keys>`.
@@ -429,8 +466,7 @@ needs to be done explicitly using ticks in the definition.
.. code-block:: php
<?php
#[Column(name: '`number`', type: 'integer')]
/** @Column(name="`number`", type="integer") */
private $number;
Doctrine will then quote this column name in all SQL statements
@@ -443,11 +479,15 @@ according to the used database platform.
.. _reference-basic-mapping-custom-mapping-types:
.. versionadded: 2.3
For more control over column quoting the ``Doctrine\ORM\Mapping\QuoteStrategy`` interface
was introduced in ORM. It is invoked for every column, table, alias and other
was introduced in 2.3. It is invoked for every column, table, alias and other
SQL names. You can implement the QuoteStrategy and set it by calling
``Doctrine\ORM\Configuration#setQuoteStrategy()``.
.. versionadded: 2.4
The ANSI Quote Strategy was added, which assumes quoting is not necessary for any SQL name.
You can use it with the following code:
+23 -32
View File
@@ -16,23 +16,6 @@ especially what the strategies presented here provide help with.
operations.
.. note::
Having an SQL logger enabled when processing batches can have a
serious impact on performance and resource usage.
To avoid that, you should use a PSR logger implementation that can be
disabled at runtime.
For example, with Monolog, you can use ``Logger::pushHandler()``
to push a ``NullHandler`` to the logger instance, and then pop it
when you need to enable logging again.
With DBAL 2, you can disable the SQL logger like below:
.. code-block:: php
<?php
$em->getConnection()->getConfiguration()->setSQLLogger(null);
Bulk Inserts
------------
@@ -59,7 +42,7 @@ internally but also mean more work during ``flush``.
$em->clear(); // Detaches all objects from Doctrine!
}
}
$em->flush(); // Persist objects that did not make up an entire batch
$em->flush(); //Persist objects that did not make up an entire batch
$em->clear();
Bulk Updates
@@ -83,7 +66,7 @@ Iterating results
~~~~~~~~~~~~~~~~~
An alternative solution for bulk updates is to use the
``Query#toIterable()`` facility to iterate over the query results step
``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:
@@ -94,14 +77,16 @@ with the batching strategy that was already used for bulk inserts:
$batchSize = 20;
$i = 0;
$q = $em->createQuery('select u from MyProject\Model\User u');
foreach ($q->toIterable() as $user) {
$iterableResult = $q->iterate();
foreach ($iterableResult as $row) {
$user = $row[0];
$user->increaseCredit();
$user->calculateNewBonuses();
++$i;
if (($i % $batchSize) === 0) {
$em->flush(); // Executes all updates.
$em->clear(); // Detaches all objects from Doctrine!
}
++$i;
}
$em->flush();
@@ -115,7 +100,7 @@ with the batching strategy that was already used for bulk inserts:
Results may be fully buffered by the database client/ connection allocating
additional memory not visible to the PHP process. For large sets this
may easily kill the process for no apparent reason.
may easily kill the process for no apparant reason.
Bulk Deletes
@@ -143,7 +128,7 @@ Iterating results
~~~~~~~~~~~~~~~~~
An alternative solution for bulk deletes is to use the
``Query#toIterable()`` facility to iterate over the query results step
``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:
@@ -153,13 +138,14 @@ The following example shows how to do this:
$batchSize = 20;
$i = 0;
$q = $em->createQuery('select u from MyProject\Model\User u');
foreach($q->toIterable() as $row) {
$em->remove($row);
++$i;
$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();
@@ -173,18 +159,20 @@ The following example shows how to do this:
Iterating Large Results for Data-Processing
-------------------------------------------
You can use the ``toIterable()`` method just to iterate over a large
result and no UPDATE or DELETE intention. ``$query->toIterable()`` returns ``iterable``
so you can process a large result without memory
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');
foreach ($q->toIterable() as $row) {
// do stuff with the data in the row
$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]);
}
@@ -194,3 +182,6 @@ problems using the following approach:
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.
+6 -8
View File
@@ -43,7 +43,7 @@ should use events judiciously.
Use cascades judiciously
------------------------
Automatic cascades of the persist/remove/etc. operations are
Automatic cascades of the persist/remove/merge/etc. operations are
very handy but should be used wisely. Do NOT simply add all
cascades to all associations. Think about which cascades actually
do make sense for you for a particular association, given the
@@ -54,7 +54,7 @@ Don't use special characters
Avoid using any non-ASCII characters in class, field, table or
column names. Doctrine itself is not unicode-safe in many places
and will not be until PHP itself is fully unicode-aware.
and will not be until PHP itself is fully unicode-aware (PHP6).
Don't use identifier quoting
----------------------------
@@ -74,13 +74,11 @@ collections in entities in the constructor. Example:
<?php
namespace MyProject\Model;
use Doctrine\Common\Collections\ArrayCollection;
class User {
/** @var Collection<int, Address> */
private Collection $addresses;
/** @var Collection<int, Article> */
private Collection $articles;
private $addresses;
private $articles;
public function __construct() {
$this->addresses = new ArrayCollection;
$this->articles = new ArrayCollection;
+280 -62
View File
@@ -1,14 +1,272 @@
Caching
=======
The Doctrine ORM package can leverage cache adapters implementing the PSR-6
standard to allow you to improve the performance of various aspects of
Doctrine by simply making some additional configurations and method calls.
Doctrine provides cache drivers in the ``Common`` package for some
of the most popular caching implementations such as APC, Memcache
and Xcache. We also provide an ``ArrayCache`` driver which stores
the data in a PHP array. Obviously, when using ``ArrayCache``, the
cache does not persist between requests, but this is useful for
testing in a development environment.
.. _types-of-caches:
Cache Drivers
-------------
Types of Caches
---------------
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
this interface.
The interface defines the following public methods for you to implement:
- fetch($id) - Fetches an entry from the cache
- contains($id) - Test if an entry exists in the cache
- save($id, $data, $lifeTime = false) - Puts data into the cache
- 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. use the
above protected methods which are implemented by the drivers. The
code is organized this way so that the protected methods in the
drivers do the raw interaction with the cache implementation and
the ``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://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');
Memcached
~~~~~~~~
Memcached is a more recent and complete alternative extension to
Memcache.
In order to use the Memcached cache driver you must have it compiled
and enabled in your php.ini. You can read about Memcached
`on the PHP website <http://php.net/memcached>`_. It will
give you a little background information about what it is and how
you can use it as well as how to install it.
Below is a simple example of how you could use the Memcached cache
driver by itself.
.. code-block:: php
<?php
$memcached = new Memcached();
$memcached->addServer('memcache_host', 11211);
$cacheDriver = new \Doctrine\Common\Cache\MemcachedCache();
$cacheDriver->setMemcached($memcached);
$cacheDriver->save('cache_id', 'my_data');
Xcache
~~~~~~
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 Redis is
`from here <http://redis.io/>`_. Also check
`A PHP extension for Redis <https://github.com/nicolasff/phpredis/>`_ for how you can use
and install the Redis PHP extension.
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 data to a cache, check if some cached data
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
~~~~~~
Saving some data to the cache driver 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 cached data 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 again 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. You can delete by an individual ID, 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 use 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 the performance of various aspects of
Doctrine by simply making some additional configurations and
method calls.
Query Cache
~~~~~~~~~~~
@@ -24,27 +282,21 @@ use on your ORM configuration.
.. code-block:: php
<?php
$cache = new \Symfony\Component\Cache\Adapter\PhpFilesAdapter('doctrine_queries');
$config = new \Doctrine\ORM\Configuration();
$config->setQueryCache($cache);
$config->setQueryCacheImpl(new \Doctrine\Common\Cache\ApcCache());
Result Cache
~~~~~~~~~~~~
The result cache can be used to cache the results of your queries
so that we don't have to query the database again after the first time.
You just need to configure the result cache implementation.
so that we don't have to query the database or hydrate the data
again after the first time. You just need to configure the result
cache implementation.
.. code-block:: php
<?php
$cache = new \Symfony\Component\Cache\Adapter\PhpFilesAdapter(
'doctrine_results',
0,
'/path/to/writable/directory'
);
$config = new \Doctrine\ORM\Configuration();
$config->setResultCache($cache);
$config->setResultCacheImpl(new \Doctrine\Common\Cache\ApcCache());
Now when you're executing DQL queries you can configure them to use
the result cache.
@@ -53,7 +305,7 @@ the result cache.
<?php
$query = $em->createQuery('select u from \Entities\User u');
$query->enableResultCache();
$query->useResultCache(true);
You can also configure an individual query to use a different
result cache driver.
@@ -61,23 +313,18 @@ result cache driver.
.. code-block:: php
<?php
$cache = new \Symfony\Component\Cache\Adapter\PhpFilesAdapter(
'doctrine_results',
0,
'/path/to/writable/directory'
);
$query->setResultCache($cache);
$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 use ``disableResultCache()``.
disable it pass false to ``useResultCache()``.
::
<?php
$query->disableResultCache();
$query->useResultCache(false);
If you want to set the time the cache has to live you can use the
@@ -98,20 +345,19 @@ yourself with the ``setResultCacheId()`` method.
$query->setResultCacheId('my_custom_id');
You can also set the lifetime and cache ID by passing the values as
the first and second argument to ``enableResultCache()``.
the second and third argument to ``useResultCache()``.
.. code-block:: php
<?php
$query->enableResultCache(3600, 'my_custom_id');
$query->useResultCache(true, 3600, 'my_custom_id');
Metadata Cache
~~~~~~~~~~~~~~
Your class metadata can be parsed from a few different sources like
XML, Attributes, etc. Instead of parsing this
information on each request we should cache it using one of the cache
drivers.
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.
@@ -119,13 +365,7 @@ first.
.. code-block:: php
<?php
$cache = \Symfony\Component\Cache\Adapter\PhpFilesAdapter(
'doctrine_metadata',
0,
'/path/to/writable/directory'
);
$config = new \Doctrine\ORM\Configuration();
$config->setMetadataCache($cache);
$config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcCache());
Now the metadata information will only be parsed once and stored in
the cache driver.
@@ -161,30 +401,6 @@ To clear the result cache use the ``orm:clear-cache:result`` task.
All these tasks accept a ``--flush`` option to flush the entire
contents of the cache instead of invalidating the entries.
.. note::
None of these tasks will work with APC, APCu, or XCache drivers
because the memory that the cache is stored in is only accessible
to the webserver.
Cache Chaining
--------------
A common pattern is to use a static cache to store data that is
requested many times in a single PHP request. Even though this data
may be stored in a fast memory cache, often that cache is over a
network link leading to sizable network traffic.
A chain cache class allows multiple caches to be registered at once.
For example, a per-request array cache can be used first, followed by
a (relatively) slower Memcached cache if the array cache misses.
The chain cache automatically handles pushing data up to faster caches in
the chain and clearing data in the entire stack when it is deleted.
Symfony Cache provides such a chain cache. To find out how to use it,
please have a look at the
`Symfony Documentation <https://symfony.com/doc/current/components/cache/adapters/chain_adapter.html>`_.
Cache Slams
-----------
@@ -200,3 +416,5 @@ 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>`_.
+98 -5
View File
@@ -5,7 +5,7 @@ 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 2 different change tracking policies, each having
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).
@@ -30,7 +30,7 @@ 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 ORM only
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
@@ -49,10 +49,103 @@ This policy can be configured as follows:
.. code-block:: php
<?php
#[Entity]
#[ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
/**
* @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.
+71 -28
View File
@@ -1,7 +1,9 @@
Installation and Configuration
==============================
Doctrine can be installed with `Composer <https://getcomposer.org>`_.
Doctrine can be installed with `Composer <http://www.getcomposer.org>`_. For
older versions we still have `PEAR packages
<http://pear.doctrine-project.org>`_.
Define the following requirement in your ``composer.json`` file:
@@ -14,7 +16,8 @@ Define the following requirement in your ``composer.json`` file:
}
Then call ``composer install`` from your command line. If you don't know
how Composer works, check out their `Getting Started <https://getcomposer.org/doc/00-intro.md>`_ to set up.
how Composer works, check out their `Getting Started
<http://getcomposer.org/doc/00-intro.md>`_ to set up.
Class loading
-------------
@@ -41,58 +44,98 @@ access point to ORM functionality provided by Doctrine.
// bootstrap.php
require_once "vendor/autoload.php";
use Doctrine\DBAL\DriverManager;
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMSetup;
$paths = ['/path/to/entity-files'];
$paths = array("/path/to/entity-files");
$isDevMode = false;
// the connection configuration
$dbParams = [
$dbParams = array(
'driver' => 'pdo_mysql',
'user' => 'root',
'password' => '',
'dbname' => 'foo',
];
);
$config = ORMSetup::createAttributeMetadataConfiguration($paths, $isDevMode);
$connection = DriverManager::getConnection($dbParams, $config);
$entityManager = new EntityManager($connection, $config);
$config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);
Or if you prefer XML:
.. code-block:: php
<?php
$paths = ['/path/to/xml-mappings'];
$config = ORMSetup::createXMLMetadataConfiguration($paths, $isDevMode);
$connection = DriverManager::getConnection($dbParams, $config);
$entityManager = new EntityManager($connection, $config);
$paths = array("/path/to/xml-mappings");
$config = Setup::createXMLMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);
Inside the ``ORMSetup`` methods several assumptions are made:
Or if you prefer YAML:
- If ``$isDevMode`` is true caching is done in memory with the ``ArrayAdapter``. Proxy objects are recreated on every request.
- If ``$isDevMode`` is false, check for Caches in the order APCu, Redis (127.0.0.1:6379), Memcache (127.0.0.1:11211) unless `$cache` is passed as fourth argument.
- If ``$isDevMode`` is false, set then proxy classes have to be explicitly created through the command line.
.. code-block:: php
<?php
$paths = array("/path/to/yml-mappings");
$config = Setup::createYAMLMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);
Inside the ``Setup`` methods several assumptions are made:
- If `$isDevMode` is true caching is done in memory with the ``ArrayCache``. Proxy objects are recreated on every request.
- If `$isDevMode` is false, check for Caches in the order APC, Xcache, Memcache (127.0.0.1:11211), Redis (127.0.0.1:6379) unless `$cache` is passed as fourth argument.
- If `$isDevMode` is false, set then proxy classes have to be explicitly created through the command line.
- If third argument `$proxyDir` is not set, use the systems temporary directory.
.. note::
In order to have ``ORMSetup`` configure the cache automatically, the library ``symfony/cache``
has to be installed as a dependency.
If you want to configure Doctrine in more detail, take a look at the :doc:`Advanced Configuration </reference/advanced-configuration>` section.
If you want to configure Doctrine in more detail, take a look at the :doc:`Advanced
Configuration <reference/advanced-configuration>` section.
.. note::
You can learn more about the database connection configuration in the
`Doctrine DBAL connection configuration reference <https://docs.doctrine-project.org/projects/doctrine-dbal/en/stable/reference/configuration.html>`_.
`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. In order to make use of them, create an executable PHP
script in your project as described in the
:doc:`tools chapter <../reference/tools>`.
during development. You can call this command from the Composer binary
directory:
.. code-block:: sh
$ 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
File diff suppressed because it is too large Load Diff
+23 -33
View File
@@ -13,14 +13,20 @@ Database Schema
How do I set the charset and collation for MySQL tables?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In your mapping configuration, the column definition (for example, the
``#[Column]`` attribute) has an ``options`` parameter where you can specify
the ``charset`` and ``collation``. The default values are ``utf8`` and
``utf8_unicode_ci``, respectively.
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?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -32,12 +38,11 @@ upon insert:
class User
{
private const STATUS_DISABLED = 0;
private const STATUS_ENABLED = 1;
const STATUS_DISABLED = 0;
const STATUS_ENABLED = 1;
private string $algorithm = "sha1";
/** @var self::STATUS_* */
private int $status = self::STATUS_DISABLED;
private $algorithm = "sha1";
private $status = self:STATUS_DISABLED;
}
.
@@ -53,7 +58,7 @@ or adding entities to a collection twice. You have to check for both conditions
in the code before calling ``$em->flush()`` if you know that unique constraint failures
can occur.
In `Symfony2 <https://www.symfony.com>`_ for example there is a Unique Entity Validator
In `Symfony2 <http://www.symfony.com>`_ for example there is a Unique Entity Validator
to achieve this task.
For collections you can check with ``$collection->contains($entity)`` if an entity is already
@@ -81,7 +86,7 @@ You can solve this exception by:
How can I filter an association?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You should use DQL queries to query for the filtered set of entities.
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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -99,9 +104,9 @@ 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 ORM.
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>`.
See :doc:`the tutorial on composite primary keys for more information<../tutorials/composite-primary-keys>`.
How can i paginate fetch-joined collections?
@@ -113,8 +118,8 @@ over this collection using a LIMIT statement (or vendor equivalent).
Doctrine does not offer a solution for this out of the box but there are several extensions
that do:
* `DoctrineExtensions <https://github.com/beberlei/DoctrineExtensions>`_
* `Pagerfanta <https://github.com/whiteoctober/pagerfanta>`_
* `DoctrineExtensions <http://github.com/beberlei/DoctrineExtensions>`_
* `Pagerfanta <http://github.com/whiteoctober/pagerfanta>`_
Why does pagination not work correctly with fetch joins?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -129,10 +134,10 @@ See the previous question for a solution to this task.
Inheritance
-----------
Can I use Inheritance with Doctrine ORM?
Can I use Inheritance with Doctrine 2?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Yes, you can use Single- or Joined-Table Inheritance in ORM.
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.
@@ -199,21 +204,6 @@ No, it is not supported to sort by function in DQL. If you need this functionali
use a native-query or come up with another solution. As a side note: Sorting with ORDER BY RAND() is painfully slow
starting with 1000 rows.
Is it better to write DQL or to generate it with the query builder?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The purpose of the ``QueryBuilder`` is to generate DQL dynamically,
which is useful when you have optional filters, conditional joins, etc.
But the ``QueryBuilder`` is not an alternative to DQL, it actually generates DQL
queries at runtime, which are then interpreted by Doctrine. This means that
using the ``QueryBuilder`` to build and run a query is actually always slower
than only running the corresponding DQL query.
So if you only need to generate a query and bind parameters to it,
you should use plain DQL, as this is a simpler and much more readable solution.
You should only use the ``QueryBuilder`` when you can't achieve what you want to do with a DQL query.
A Query fails, how can I debug it?
----------------------------------
+9 -42
View File
@@ -1,7 +1,9 @@
Filters
=======
Doctrine ORM features a filter system that allows the developer to add SQL to
.. 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).
@@ -28,22 +30,21 @@ table alias of the SQL table of the entity.
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.
For the filter to correctly function, the following rules must be followed. Failure to do so will lead to unexpected results from the query cache.
1. Parameters for the query should be set on the filter object by ``SQLFilter#setParameter()`` before the filter is used by the ORM ( i.e. do not set parameters inside ``SQLFilter#addFilterConstraint()`` function ).
2. The filter must be deterministic. Don't change the values base on external inputs.
The ``SQLFilter#getParameter()`` function takes care of the proper quoting of parameters.
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,
use Doctrine\ORM\Mapping\ClassMetaData,
Doctrine\ORM\Query\Filter\SQLFilter;
class MyLocaleFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias): string
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
{
// Check if the entity implements the LocalAware interface
if (!$targetEntity->reflClass->implementsInterface('LocaleAware')) {
@@ -54,9 +55,6 @@ The ``SQLFilter#getParameter()`` function takes care of the proper quoting of pa
}
}
If the parameter is an array and should be quoted as a list of values for an IN query
this is possible with the alternative ``SQLFilter#setParameterList()`` and
``SQLFilter#getParameterList()`` functions.
Configuration
-------------
@@ -93,34 +91,3 @@ object.
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.
Suspending/Restoring Filters
----------------------------
When a filter is disabled, the instance is fully deleted and all the filter
parameters previously set are lost. Then, if you enable it again, a new filter
is created without the previous filter parameters. If you want to keep a filter
(in order to use it later) but temporary disable it, you'll need to use the
``FilterCollection#suspend($name)`` and ``FilterCollection#restore($name)``
methods instead.
.. code-block:: php
<?php
$filter = $em->getFilters()->enable("locale");
$filter->setParameter('locale', 'en');
// Temporary suspend the filter
$filter = $em->getFilters()->suspend("locale");
// Do things
// Then restore it, the locale parameter will still be set
$filter = $em->getFilters()->restore("locale");
.. warning::
If you enable a previously disabled filter, doctrine will create a new
one without keeping any of the previously parameter set with
``SQLFilter#setParameter()`` or ``SQLFilter#getParameterList()``. If you
want to restore the previously disabled filter instead, you must use the
``FilterCollection#restore($name)`` method.
+11 -42
View File
@@ -4,14 +4,14 @@ Improving Performance
Bytecode Cache
--------------
It is highly recommended to make use of a bytecode cache like OPcache.
It is highly recommended to make use of a bytecode cache like APC.
A bytecode cache removes the need for parsing PHP code on every
request and can greatly improve performance.
"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*
@@ -20,21 +20,12 @@ 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.
Operating Doctrine without these caches means
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.
The preferred cache adapter for metadata and query caches is a PHP file
cache like Symfony's
`PHP files adapter <https://symfony.com/doc/current/components/cache/adapters/php_files_adapter.html>`_.
This kind of cache serializes cache items and writes them to a file.
This allows for opcode caching to be used and provides high performance in most scenarios.
See :ref:`types-of-caches`
Alternative Query Result Formats
--------------------------------
@@ -45,32 +36,11 @@ in scenarios where data is loaded for read-only purposes.
Read-Only Entities
------------------
You can mark entities as read only. For details, see :ref:`attrref_entity`
This means that the entity marked as read only is never considered for updates.
During 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.
You can also explicitly mark individual entities read only directly on the
UnitOfWork via a call to ``markReadOnly()``:
.. code-block:: php
$user = $entityManager->find(User::class, $id);
$entityManager->getUnitOfWork()->markReadOnly($user);
Or you can set all objects that are the result of a query hydration to be
marked as read only with the following query hint:
.. code-block:: php
$query = $entityManager->createQuery('SELECT u FROM App\\Entity\\User u');
$query->setHint(Query::HINT_READ_ONLY, true);
$users = $query->getResult();
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
----------------------
@@ -82,7 +52,7 @@ for more information on how this fetch mode works.
Temporarily change fetch mode in DQL
------------------------------------
See :ref:`dql-temporarily-change-fetch-mode`
See :ref:`Doctrine Query Language chapter <dql-temporarily-change-fetch-mode>`
Apply Best Practices
@@ -91,9 +61,8 @@ Apply Best Practices
A lot of the points mentioned in the Best Practices chapter will
also positively affect the performance of Doctrine.
See :doc:`Best Practices </reference/best-practices>`
Change Tracking policies
------------------------
See: :doc:`Change Tracking Policies <change-tracking-policies>`
See: :doc:`Change Tracking Policies <reference/change-tracking-policies>`
+289 -252
View File
@@ -1,9 +1,6 @@
Inheritance Mapping
===================
This chapter explains the available options for mapping class
hierarchies.
Mapped Superclasses
-------------------
@@ -15,96 +12,49 @@ 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). They
are not query-able, and do not require an ``#[Id]`` property.
No database table will be created for a mapped superclass itself,
only for entity classes inheriting from it. That implies that a
mapped superclass cannot be the ``targetEntity`` in associations.
In other words, a mapped superclass can use unidirectional One-To-One
and Many-To-One associations where it is the owning side.
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.
(through Single Table Inheritance or Class Table Inheritance).
.. note::
One-To-Many associations are not generally possible on a mapped
superclass, since they require the "many" side to hold the foreign
key.
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.
It is, however, possible to use the :doc:`ResolveTargetEntityListener </cookbook/resolve-target-entity-listener>`
to replace references to a mapped superclass with an entity class at runtime.
As long as there is only one entity subclass inheriting from the mapped
superclass and all references to the mapped superclass are resolved to that
entity class at runtime, the mapped superclass *can* use One-To-Many associations
and be named as the ``targetEntity`` on the owning sides.
.. warning::
At least when using attributes or annotations to specify your mapping,
it *seems* as if you could inherit from a base class that is neither
an entity nor a mapped superclass, but has properties with mapping configuration
on them that would also be used in the inheriting class.
This, however, is due to how the corresponding mapping
drivers work and what the PHP reflection API reports for inherited fields.
Such a configuration is explicitly not supported. To give just one example,
it will break for ``private`` properties.
.. note::
You may be tempted to use traits to mix mapped fields or relationships
into your entity classes to circumvent some of the limitations of
mapped superclasses. Before doing that, please read the section on traits
in the :doc:`Limitations and Known Issues </reference/limitations-and-known-issues>` chapter.
Example:
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\JoinColumn;
use Doctrine\ORM\Mapping\OneToOne;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\MappedSuperclass;
use Doctrine\ORM\Mapping\Entity;
#[MappedSuperclass]
class Person
/** @MappedSuperclass */
class MappedSuperclassBase
{
#[Column(type: 'integer')]
protected int $mapped1;
#[Column(type: 'string')]
protected string $mapped2;
#[OneToOne(targetEntity: Toothbrush::class)]
#[JoinColumn(name: 'toothbrush_id', referencedColumnName: 'id')]
protected Toothbrush|null $toothbrush = null;
/** @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 Employee extends Person
/** @Entity */
class EntitySubClass extends MappedSuperclassBase
{
#[Id, Column(type: 'integer')]
private int|null $id = null;
#[Column(type: 'string')]
private string $name;
// ... more fields and methods
}
#[Entity]
class Toothbrush
{
#[Id, Column(type: 'integer')]
private int|null $id = null;
/** @Id @Column(type="integer") */
private $id;
/** @Column(type="string") */
private $name;
// ... more fields and methods
}
@@ -113,105 +63,86 @@ like this (this is for SQLite):
.. code-block:: sql
CREATE TABLE Employee (mapped1 INTEGER NOT NULL, mapped2 TEXT NOT NULL, id INTEGER NOT NULL, name TEXT NOT NULL, toothbrush_id INTEGER DEFAULT NULL, PRIMARY KEY(id))
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.
Entity Inheritance
------------------
As soon as one entity class inherits from another entity class, either
directly, with a mapped superclass or other unmapped (also called
"transient") classes in between, these entities form an inheritance
hierarchy. The topmost entity class in this hierarchy is called the
root entity, and the hierarchy includes all entities that are
descendants of this root entity.
On the root entity class, ``#[InheritanceType]``,
``#[DiscriminatorColumn]`` and ``#[DiscriminatorMap]`` must be specified.
``#[InheritanceType]`` specifies one of the two available inheritance
mapping strategies that are explained in the following sections.
``#[DiscriminatorColumn]`` designates the so-called discriminator column.
This is an extra column in the table that keeps information about which
type from the hierarchy applies for a particular database row.
``#[DiscriminatorMap]`` declares the possible values for the discriminator
column and maps them to class names in the hierarchy. This discriminator map
has to declare all non-abstract entity classes that exist in that particular
inheritance hierarchy. That includes the root as well as any intermediate
entity classes, given they are not abstract.
The names of the classes in the discriminator map do not need to be fully
qualified if the classes are contained in the same namespace as the entity
class on which the discriminator map is applied.
If no discriminator map is provided, then the map is generated
automatically. The automatically generated discriminator map contains the
lowercase short name of each class as key.
.. note::
Automatically generating the discriminator map is very expensive
computation-wise. The mapping driver has to provide all classes
for which mapping configuration exists, and those have to be
loaded and checked against the current inheritance hierarchy
to see if they are part of it. The resulting map, however, can be kept
in the metadata cache.
Performance impact on to-one associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There is a general performance consideration when using entity inheritance:
If the target-entity of a many-to-one or one-to-one association is part of
an inheritance hierarchy, it is preferable for performance reasons that it
be a leaf entity (ie. have no subclasses).
Otherwise, the ORM is currently unable to tell beforehand which entity class
will have to be used, and so no appropriate proxy instance can be created.
That means the referred-to entities will *always* be loaded eagerly, which
might even propagate to relationships of these entities (in the case of
self-referencing associations).
Single Table Inheritance
------------------------
`Single Table Inheritance <https://martinfowler.com/eaaCatalog/singleTableInheritance.html>`_
is an inheritance mapping strategy where all classes of a hierarchy are
mapped to a single database table.
`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:
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
namespace MyProject\Model;
#[Entity]
#[InheritanceType('SINGLE_TABLE')]
#[DiscriminatorColumn(name: 'discr', type: 'string')]
#[DiscriminatorMap(['person' => Person::class, 'employee' => Employee::class])]
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
class Person
{
// ...
}
#[Entity]
/**
* @Entity
*/
class Employee extends Person
{
// ...
}
.. code-block:: yaml
MyProject\Model\Person:
type: entity
inheritanceType: SINGLE_TABLE
discriminatorColumn:
name: discr
type: string
discriminatorMap:
person: Person
employee: Employee
MyProject\Model\Employee:
type: entity
Things to note:
In this example, the ``#[DiscriminatorMap]`` specifies that in the
discriminator column, a value of "person" identifies a row as being of type
``Person`` and employee" identifies a row as being of type ``Employee``.
- The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap
must be specified on the topmost class that is part of the mapped
entity hierarchy.
- The @DiscriminatorMap specifies which values of the
discriminator column identify a row as being of a certain type. In
the case above a value of "person" identifies a row as being of
type ``Person`` and "employee" identifies a row as being of type
``Employee``.
- All entity classes that is part of the mapped entity hierarchy
(including the topmost class) should be specified in the
@DiscriminatorMap. In the case above Person class included.
- The names of the classes in the discriminator map do not need to
be fully qualified if the classes are contained in the same
namespace as the entity class on which the discriminator map is
applied.
- If no discriminator map is provided, then the map is generated
automatically. The automatically generated discriminator map
contains the lowercase short name of each class as key.
Design-time considerations
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -227,9 +158,16 @@ 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,
only a WHERE clause listing the type identifiers. In particular,
relationships involving types that employ this mapping strategy are
very performing.
very performant.
There is a general performance consideration with Single Table
Inheritance: If the target-entity of a many-to-one or one-to-one
association is an STI entity, it is preferable for performance reasons that it
be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
Otherwise Doctrine *CANNOT* create proxy instances
of this entity and will *ALWAYS* load the entity eagerly.
SQL Schema considerations
~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -237,22 +175,21 @@ SQL Schema considerations
For Single-Table-Inheritance to work in scenarios where you are
using either a legacy database schema or a self-written database
schema you have to make sure that all columns that are not in the
root entity but in any of the different sub-entities has to allow
null values. Columns that have ``NOT NULL`` constraints have to be on
root entity but in any of the different sub-entities has to allows
null values. Columns that have NOT NULL constraints have to be on
the root entity of the single-table inheritance hierarchy.
Class Table Inheritance
-----------------------
`Class Table Inheritance <https://martinfowler.com/eaaCatalog/classTableInheritance.html>`_
`Class Table Inheritance <http://martinfowler.com/eaaCatalog/classTableInheritance.html>`_
is an inheritance mapping strategy where each class in a hierarchy
is mapped to several tables: its own table and the tables of all
parent classes. The table of a child class is linked to the table
of a parent class through a foreign key constraint.
The discriminator column is placed in the topmost table of the hierarchy,
because this is the easiest way to achieve polymorphic queries with Class
Table Inheritance.
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:
@@ -260,25 +197,42 @@ Example:
<?php
namespace MyProject\Model;
#[Entity]
#[InheritanceType('JOINED')]
#[DiscriminatorColumn(name: 'discr', type: 'string')]
#[DiscriminatorMap(['person' => Person::class, 'employee' => Employee::class])]
/**
* @Entity
* @InheritanceType("JOINED")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
class Person
{
// ...
}
#[Entity]
/** @Entity */
class Employee extends Person
{
// ...
}
As before, the ``#[DiscriminatorMap]`` specifies that in the
discriminator column, a value of "person" identifies a row as being of type
``Person`` and "employee" identifies a row as being of type ``Employee``.
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.
- If no discriminator map is provided, then the map is generated
automatically. The automatically generated discriminator map
contains the lowercase short name of each class as key.
.. note::
@@ -309,13 +263,17 @@ 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 JOIN``ed which can increase
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 also another important performance consideration that it is *not possible*
to query for the base entity without any ``LEFT JOIN``s to the sub-types.
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
~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -332,17 +290,9 @@ column and cascading on delete.
Overrides
---------
Overrides can only be applied to entities that extend a mapped superclass or
use traits. They are used to override a mapping for an entity field or
relationship defined in that mapped superclass or trait.
It is not supported to use overrides in entity inheritance scenarios.
.. note::
When using traits, make sure not to miss the warnings given in the
:doc:`Limitations and Known Issues </reference/limitations-and-known-issues>` chapter.
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
@@ -356,47 +306,53 @@ Example:
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
// user mapping
namespace MyProject\Model;
#[MappedSuperclass]
/**
* @MappedSuperclass
*/
class User
{
// other fields mapping
//other fields mapping
/** @var Collection<int, Group> */
#[JoinTable(name: 'users_groups')]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')]
#[ManyToMany(targetEntity: 'Group', inversedBy: 'users')]
protected Collection $groups;
/**
* @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|null $address = null;
/**
* @ManyToOne(targetEntity="Address")
* @JoinColumn(name="address_id", referencedColumnName="id")
*/
protected $address;
}
// admin mapping
namespace MyProject\Model;
#[Entity]
#[AssociationOverrides([
new AssociationOverride(
name: 'groups',
joinTable: new JoinTable(
name: 'users_admingroups',
),
joinColumns: [new JoinColumn(name: 'adminuser_id')],
inverseJoinColumns: [new JoinColumn(name: 'admingroup_id')]
),
new AssociationOverride(
name: 'address',
joinColumns: [new JoinColumn(name: 'adminaddress_id', referencedColumnName: 'id')]
)
])]
/**
* @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
{
}
@@ -410,6 +366,7 @@ Example:
<many-to-many field="groups" target-entity="Group" inversed-by="users">
<cascade>
<cascade-persist/>
<cascade-merge/>
<cascade-detach/>
</cascade>
<join-table name="users_groups">
@@ -446,16 +403,58 @@ Example:
</association-overrides>
</entity>
</doctrine-mapping>
.. code-block:: yaml
# user mapping
MyProject\Model\User:
type: mappedSuperclass
# other fields mapping
manyToOne:
address:
targetEntity: Address
joinColumn:
name: address_id
referencedColumnName: id
cascade: [ persist, merge ]
manyToMany:
groups:
targetEntity: Group
joinTable:
name: users_groups
joinColumns:
user_id:
referencedColumnName: id
inverseJoinColumns:
group_id:
referencedColumnName: id
cascade: [ persist, merge, detach ]
# admin mapping
MyProject\Model\Admin:
type: entity
associationOverride:
address:
joinColumn:
adminaddress_id:
name: adminaddress_id
referencedColumnName: id
groups:
joinTable:
name: users_admingroups
joinColumns:
adminuser_id:
referencedColumnName: id
inverseJoinColumns:
admingroup_id:
referencedColumnName: id
Things to note:
- The "association override" specifies the overrides based 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.
- The override could redefine ``inversedBy`` to reference more than one extended entity.
- The override could redefine fetch to modify the fetch strategy of the extended entity.
- 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
~~~~~~~~~~~~~~~~~~~~
@@ -465,46 +464,47 @@ Could be used by an entity that extends a mapped superclass to override a field
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
// user mapping
namespace MyProject\Model;
#[MappedSuperclass]
/**
* @MappedSuperclass
*/
class User
{
#[Id, GeneratedValue, Column(type: 'integer', name: 'user_id', length: 150)]
protected int|null $id = null;
/** @Id @GeneratedValue @Column(type="integer", name="user_id", length=150) */
protected $id;
#[Column(name: 'user_name', nullable: true, unique: false, length: 250)]
protected string $name;
/** @Column(name="user_name", nullable=true, unique=false, length=250) */
protected $name;
// other fields mapping
}
// guest mapping
namespace MyProject\Model;
#[Entity]
#[AttributeOverrides([
new AttributeOverride(
name: 'id',
column: new Column(
name: 'guest_id',
type: 'integer',
length: 140
)
),
new AttributeOverride(
name: 'name',
column: new Column(
name: 'guest_name',
nullable: false,
unique: true,
length: 240
)
)
])]
/**
* @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
{
}
@@ -521,6 +521,7 @@ Could be used by an entity that extends a mapped superclass to override a field
<many-to-one field="address" target-entity="Address">
<cascade>
<cascade-persist/>
<cascade-merge/>
</cascade>
<join-column name="address_id" referenced-column-name="id"/>
</many-to-one>
@@ -541,12 +542,48 @@ Could be used by an entity that extends a mapped superclass to override a field
</attribute-overrides>
</entity>
</doctrine-mapping>
.. code-block:: yaml
# user mapping
MyProject\Model\User:
type: mappedSuperclass
id:
id:
type: integer
column: user_id
length: 150
generator:
strategy: AUTO
fields:
name:
type: string
column: user_name
length: 250
nullable: true
unique: false
#other fields mapping
# guest mapping
MyProject\Model\Guest:
type: entity
attributeOverride:
id:
column: guest_id
type: integer
length: 140
name:
column: guest_name
type: string
length: 240
nullable: false
unique: true
Things to note:
- The "attribute override" specifies the overrides based on the property name.
- The column type *cannot* be changed. If the column type is not equal, you get a ``MappingException``.
- The override can redefine all the attributes except the type.
- The "attribute override" specifies the overrides base on the property name.
- The column type *CANNOT* be changed. If the column type is not equal you get a ``MappingException``
- The override can redefine all the columns except the type.
Query the Type
--------------
+2 -3
View File
@@ -1,6 +1,5 @@
:orphan:
Installation
============
The installation chapter has moved to :doc:`Installation and Configuration </reference/configuration>`.
The installation chapter has moved to `Installation and Configuration
<reference/configuration>`_.
@@ -1,12 +1,12 @@
Limitations and Known Issues
============================
We try to make using Doctrine ORM a very pleasant experience.
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 the ORM is not perfect and far from feature complete.
software Doctrine2 is not perfect and far from feature complete.
This section should give you an overview of current limitations of
Doctrine ORM as well as critical known issues that you should know
Doctrine 2 as well as critical known issues that you should know
about.
Current Limitations
@@ -63,7 +63,16 @@ Where the ``attribute_name`` column contains the key and
``$attributes``.
The feature request for persistence of primitive value arrays
`is described in the DDC-298 ticket <https://github.com/doctrine/orm/issues/3743>`_.
`is described in the DDC-298 ticket <http://www.doctrine-project.org/jira/browse/DDC-298>`_.
Cascade Merge with Bi-directional Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are two bugs now that concern the use of cascade merge in combination with bi-directional associations.
Make sure to study the behavior of cascade merge if you are using it:
- `DDC-875 <http://www.doctrine-project.org/jira/browse/DDC-875>`_ Merge can sometimes add the same entity twice into a collection
- `DDC-763 <http://www.doctrine-project.org/jira/browse/DDC-763>`_ Cascade merge on associated entities can insert too many rows through "Persistence by Reachability"
Custom Persisters
~~~~~~~~~~~~~~~~~
@@ -74,8 +83,10 @@ Currently there is no way to overwrite the persister implementation
for a given entity, however there are several use-cases that can
benefit from custom persister implementations:
- `Add Upsert Support <https://github.com/doctrine/orm/issues/5178>`_
- `Evaluate possible ways in which stored-procedures can be used <https://github.com/doctrine/orm/issues/4946>`_
- `Add Upsert Support <http://www.doctrine-project.org/jira/browse/DDC-668>`_
- `Evaluate possible ways in which stored-procedures can be used <http://www.doctrine-project.org/jira/browse/DDC-445>`_
- The previous Filter Rules Feature Request
Persist Keys of Collections
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -85,7 +96,7 @@ PHP Arrays are ordered hash-maps and so should be the
evaluate a feature that optionally persists and hydrates the keys
of a Collection instance.
`Ticket DDC-213 <https://github.com/doctrine/orm/issues/2817>`_
`Ticket DDC-213 <http://www.doctrine-project.org/jira/browse/DDC-213>`_
Mapping many tables to one entity
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -98,17 +109,18 @@ to the same entity.
Behaviors
~~~~~~~~~
Doctrine ORM will **never** include a behavior system like Doctrine 1
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 <https://www.doctrine-project.org/2010/02/17/doctrine2-behaviours-nutshell.html>`_
- `A re-usable Versionable behavior for Doctrine2 <https://www.doctrine-project.org/2010/02/24/doctrine2-versionable.html>`_
- `Write your own ORM on top of Doctrine2 <https://www.doctrine-project.org/2010/07/19/your-own-orm-doctrine2.html>`_
- `Doctrine ORM Behavioral Extensions <https://www.doctrine-project.org/2010/11/18/doctrine2-behavioral-extensions.html>`_
- `Doctrine2 "Behaviors" in a Nutshell <http://www.doctrine-project.org/blog/doctrine2-behaviours-nutshell>`_
- `A re-usable Versionable behavior for Doctrine2 <http://www.doctrine-project.org/blog/doctrine2-versionable>`_
- `Write your own ORM on top of Doctrine2 <http://www.doctrine-project.org/blog/your-own-orm-doctrine2>`_
- `Doctrine 2 Behavioral Extensions <http://www.doctrine-project.org/blog/doctrine2-behavioral-extensions>`_
- `Doctrator <https://github.com/pablodip/doctrator`>_
Doctrine ORM has enough hooks and extension points so that **you** can
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.
@@ -117,66 +129,13 @@ Nested Set
~~~~~~~~~~
NestedSet was offered as a behavior in Doctrine 1 and will not be
included in the core of Doctrine ORM. However there are already two
included in the core of Doctrine 2. However there are already two
extensions out there that offer support for Nested Set with
ORM:
Doctrine 2:
- `Doctrine2 Hierarchical-Structural Behavior <https://github.com/guilhermeblanco/Doctrine2-Hierarchical-Structural-Behavior>`_
- `Doctrine2 NestedSet <https://github.com/blt04/doctrine2-nestedset>`_
Using Traits in Entity Classes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The use of traits in entity or mapped superclasses, at least when they
include mapping configuration or mapped fields, is currently not
endorsed by the Doctrine project. The reasons for this are as follows.
Traits were added in PHP 5.4 more than 10 years ago, but at the same time
more than two years after the initial Doctrine 2 release and the time where
core components were designed.
In fact, this documentation mentions traits only in the context of
:doc:`overriding field association mappings in subclasses </tutorials/override-field-association-mappings-in-subclasses>`.
Coverage of traits in test cases is practically nonexistent.
Thus, you should at least be aware that when using traits in your entity and
mapped superclasses, you will be in uncharted terrain.
.. warning::
There be dragons.
From a more technical point of view, traits basically work at the language level
as if the code contained in them had been copied into the class where the trait
is used, and even private fields are accessible by the using class. In addition to
that, some precedence and conflict resolution rules apply.
When it comes to loading mapping configuration, the annotation and attribute drivers
rely on PHP reflection to inspect class properties including their docblocks.
As long as the results are consistent with what a solution *without* traits would
have produced, this is probably fine.
However, to mention known limitations, it is currently not possible to use "class"
level `annotations <https://github.com/doctrine/orm/pull/1517>`_ or
`attributes <https://github.com/doctrine/orm/issues/8868>`_ on traits, and attempts to
improve parser support for traits as `here <https://github.com/doctrine/annotations/pull/102>`_
or `there <https://github.com/doctrine/annotations/pull/63>`_ have been abandoned
due to complexity.
XML mapping configuration probably needs to completely re-configure or otherwise
copy-and-paste configuration for fields used from traits.
Mapping multiple private fields of the same name
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When two classes, say a mapped superclass and an entity inheriting from it,
both contain a ``private`` field of the same name, this will lead to a ``MappingException``.
Since the fields are ``private``, both are technically separate and can contain
different values at the same time. However, the ``ClassMetadata`` configuration used
internally by the ORM currently refers to fields by their name only, without taking the
class containing the field into consideration. This makes it impossible to keep separate
mapping configuration for both fields.
- `Doctrine2 Hierarchical-Structural Behavior <http://github.com/guilhermeblanco/Doctrine2-Hierarchical-Structural-Behavior>`_
- `Doctrine2 NestedSet <http://github.com/blt04/doctrine2-nestedset>`_
Known Issues
------------
@@ -187,15 +146,17 @@ backwards compatibility issues or where no simple fix exists (yet).
We don't plan to add every bug in the tracker there, just those
issues that can potentially cause nightmares or pain of any sort.
See bugs, improvement and feature requests on `Github issues <https://github.com/doctrine/orm/issues>`_.
See the Open Bugs on Jira for more details on `bugs, improvement and feature
requests
<http://www.doctrine-project.org/jira/secure/IssueNavigator.jspa?reset=true&mode=hide&pid=10032&resolution=-1&sorter/field=updated&sorter/order=DESC>`_.
Identifier Quoting and Legacy Databases
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For compatibility reasons between all the supported vendors and
edge case problems Doctrine ORM does **NOT** do automatic identifier
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 ORM.
legacy-databases to work with Doctrine 2.
- You can quote column-names as described in the
+55 -55
View File
@@ -13,7 +13,8 @@ metadata:
- **XML files** (XmlDriver)
- **Attributes** (AttributeDriver)
- **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
@@ -34,10 +35,12 @@ an entity.
.. code-block:: php
<?php
$em->getConfiguration()->setMetadataCacheImpl(new ApcuCache());
$em->getConfiguration()->setMetadataCacheImpl(new ApcCache());
All the drivers are in the ``Doctrine\ORM\Mapping\Driver`` namespace:
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
@@ -50,83 +53,69 @@ 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 ``MappingDriver`` interface:
implements the ``Driver`` interface:
.. code-block:: php
<?php
declare(strict_types=1);
namespace Doctrine\Persistence\Mapping\Driver;
use Doctrine\Persistence\Mapping\ClassMetadata;
/**
* Contract for metadata drivers.
*/
interface MappingDriver
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
interface Driver
{
/**
* Loads the metadata for the specified class into the provided container.
*
* @param class-string<T> $className
* @param ClassMetadata<T> $metadata
*
* @return void
*
* @template T of object
*
* @param string $className
* @param ClassMetadataInfo $metadata
*/
public function loadMetadataForClass(string $className, ClassMetadata $metadata);
function loadMetadataForClass($className, ClassMetadataInfo $metadata);
/**
* Gets the names of all mapped classes known to this driver.
*
* @return list<class-string> The names of all mapped classes known to this driver.
*
* @return array The names of all mapped classes known to this driver.
*/
public function getAllClassNames();
function getAllClassNames();
/**
* Returns 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.
* 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 class-string $className
*
* @return bool
* @param string $className
* @return boolean
*/
public function isTransient(string $className);
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 ``FileDriver`` implementation for you to extend from:
the ``AbstractFileDriver`` implementation for you to extend from:
.. code-block:: php
<?php
use Doctrine\Persistence\Mapping\ClassMetadata;
use Doctrine\Persistence\Mapping\Driver\FileDriver;
class MyMetadataDriver extends FileDriver
class MyMetadataDriver extends AbstractFileDriver
{
/**
* {@inheritDoc}
* {@inheritdoc}
*/
protected $_fileExtension = '.dcm.ext';
/**
* {@inheritDoc}
* {@inheritdoc}
*/
public function loadMetadataForClass($className, ClassMetadata $metadata)
public function loadMetadataForClass($className, ClassMetadataInfo $metadata)
{
$data = $this->_loadMappingFile($file);
// populate ClassMetadata instance from $data
// populate ClassMetadataInfo instance from $data
}
/**
* {@inheritDoc}
* {@inheritdoc}
*/
protected function _loadMappingFile($file)
{
@@ -136,12 +125,13 @@ the ``FileDriver`` implementation for you to extend from:
.. note::
When using the ``FileDriver`` 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.
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
@@ -157,13 +147,21 @@ ClassMetadata
-------------
The last piece you need to know and understand about metadata in
Doctrine ORM is the API of the ``ClassMetadata`` classes. You need to
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.
@@ -192,3 +190,5 @@ iterate over them:
foreach ($class->fieldMappings as $fieldMapping) {
echo $fieldMapping['fieldName'] . "\n";
}
+35 -24
View File
@@ -1,47 +1,50 @@
Implementing a NamingStrategy
==============================
Using a naming strategy you can provide rules for generating database identifiers,
column or table names. This feature helps
reduce the verbosity of the mapping document, eliminating repetitive noise (eg: ``TABLE_``).
.. versionadded:: 2.3
.. warning
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_``).
The naming strategy is always overridden by entity mapping such as the `Table` attribute.
Configuring a naming strategy
-----------------------------
The default strategy used by Doctrine is quite minimal.
By default the ``Doctrine\ORM\Mapping\DefaultNamingStrategy``
uses the simple class name and the attribute names to generate tables and columns.
uses the simple class name and the attributes names to generate tables and columns
You can specify a different strategy by calling ``Doctrine\ORM\Configuration#setNamingStrategy()``:
You can specify a different strategy by calling ``Doctrine\ORM\Configuration#setNamingStrategy()`` :
.. code-block:: php
<?php
$namingStrategy = new MyNamingStrategy();
$configuration->setNamingStrategy($namingStrategy);
$configuration()->setNamingStrategy($namingStrategy);
Underscore naming strategy
---------------------------
``\Doctrine\ORM\Mapping\UnderscoreNamingStrategy`` is a built-in strategy.
``\Doctrine\ORM\Mapping\UnderscoreNamingStrategy`` is a built-in strategy
that might be a useful if you want to use a underlying convention.
.. code-block:: php
<?php
$namingStrategy = new \Doctrine\ORM\Mapping\UnderscoreNamingStrategy(CASE_UPPER);
$configuration->setNamingStrategy($namingStrategy);
$configuration()->setNamingStrategy($namingStrategy);
Then SomeEntityName will generate the table SOME_ENTITY_NAME when CASE_UPPER
or some_entity_name using CASE_LOWER is given.
For SomeEntityName the strategy will generate the table SOME_ENTITY_NAME with the
``CASE_UPPER`` option, or some_entity_name with the ``CASE_LOWER`` option.
Naming strategy interface
-------------------------
The interface ``Doctrine\ORM\Mapping\NamingStrategy`` allows you to specify
a naming strategy for database tables and columns.
a "naming standard" for database tables and columns.
.. code-block:: php
@@ -98,11 +101,10 @@ a naming strategy for database tables and columns.
Implementing a naming strategy
-------------------------------
If you have database naming standards, like all table names should be prefixed
by the application prefix, all column names should be lower case, you can easily
achieve such standards by implementing a naming strategy.
You need to create a class which implements ``Doctrine\ORM\Mapping\NamingStrategy``.
If you have database naming standards like all tables names should be prefixed
by the application prefix, all column names should be upper case,
you can easily achieve such standards by implementing a naming strategy.
You need to implements NamingStrategy first. Following is an example
.. code-block:: php
@@ -110,30 +112,39 @@ You need to create a class which implements ``Doctrine\ORM\Mapping\NamingStrateg
<?php
class MyAppNamingStrategy implements NamingStrategy
{
public function classToTableName(string $className): string
public function classToTableName($className)
{
return 'MyApp_' . substr($className, strrpos($className, '\\') + 1);
}
public function propertyToColumnName(string $propertyName): string
public function propertyToColumnName($propertyName)
{
return $propertyName;
}
public function referenceColumnName(): string
public function referenceColumnName()
{
return 'id';
}
public function joinColumnName(string $propertyName, ?string $className = null): string
public function joinColumnName($propertyName, $className = null)
{
return $propertyName . '_' . $this->referenceColumnName();
}
public function joinTableName(string $sourceEntity, string $targetEntity, string $propertyName): string
public function joinTableName($sourceEntity, $targetEntity, $propertyName = null)
{
return strtolower($this->classToTableName($sourceEntity) . '_' .
$this->classToTableName($targetEntity));
}
public function joinKeyColumnName(string $entityName, ?string $referencedColumnName): string
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);
+485 -47
View File
@@ -63,7 +63,7 @@ This has several benefits:
- The API is much simpler than the usual ``ResultSetMapping`` API.
One downside is that the builder API does not yet support entities
with inheritance hierarchies.
with inheritance hierachies.
.. code-block:: php
@@ -71,7 +71,7 @@ with inheritance hierarchies.
use Doctrine\ORM\Query\ResultSetMappingBuilder;
$sql = "SELECT u.id, u.name, a.id AS address_id, a.street, a.city " .
$sql = "SELECT u.id, u.name, a.id AS address_id, a.street, a.city " .
"FROM users u INNER JOIN address a ON u.address_id = a.id";
$rsm = new ResultSetMappingBuilder($entityManager);
@@ -80,7 +80,9 @@ with inheritance hierarchies.
The builder extends the ``ResultSetMapping`` class and as such has all the functionality of it as well.
The ``SELECT`` clause can be generated
.. 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
@@ -90,7 +92,7 @@ a mapping from DQL alias (key) to SQL alias (value)
<?php
$selectClause = $rsm->generateSelectClause(array(
$selectClause = $builder->generateSelectClause(array(
'u' => 't1',
'g' => 't2'
));
@@ -250,40 +252,6 @@ 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.
Special case: DTOs
...................
You can also use ``ResultSetMapping`` to map the results of a native SQL
query to a DTO (Data Transfer Object). This is done by adding scalar
results for each argument of the DTO's constructor, then filling the
``newObjectMappings`` property of the ``ResultSetMapping`` with
information about where to map each scalar result:
.. code-block:: php
<?php
$rsm = new ResultSetMapping();
$rsm->addScalarResult('name', 1, 'string');
$rsm->addScalarResult('email', 2, 'string');
$rsm->addScalarResult('city', 3, 'string');
$rsm->newObjectMappings['name'] = [
'className' => CmsUserDTO::class,
'objIndex' => 0, // a result can contain many DTOs, this is the index of the DTO to map to
'argIndex' => 0, // each scalar result can be mapped to a different argument of the DTO constructor
];
$rsm->newObjectMappings['email'] = [
'className' => CmsUserDTO::class,
'objIndex' => 0,
'argIndex' => 1,
];
$rsm->newObjectMappings['city'] = [
'className' => CmsUserDTO::class,
'objIndex' => 0,
'argIndex' => 2,
];
Meta results
~~~~~~~~~~~~
@@ -299,7 +267,7 @@ detail:
<?php
/**
* Adds a meta column (foreign key or discriminator column) to the result set.
*
*
* @param string $alias
* @param string $columnAlias
* @param string $columnName
@@ -354,10 +322,10 @@ entity.
$rsm->addEntityResult('User', 'u');
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$query = $this->_em->createNativeQuery('SELECT id, name FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
The result would look like this:
@@ -390,10 +358,10 @@ thus owns the foreign key.
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$rsm->addMetaResult('u', 'address_id', 'address_id');
$query = $this->_em->createNativeQuery('SELECT id, name, address_id FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
Foreign keys are used by Doctrine for lazy-loading purposes when
@@ -419,12 +387,12 @@ associations that are lazy.
$rsm->addFieldResult('a', 'address_id', 'id');
$rsm->addFieldResult('a', 'street', 'street');
$rsm->addFieldResult('a', 'city', 'city');
$sql = 'SELECT u.id, u.name, a.id AS address_id, a.street, a.city FROM users u ' .
'INNER JOIN address a ON u.address_id = a.id WHERE u.name = ?';
$query = $this->_em->createNativeQuery($sql, $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
In this case the nested entity ``Address`` is registered with the
@@ -454,10 +422,10 @@ to map the hierarchy (both use a discriminator column).
$rsm->addFieldResult('u', 'name', 'name');
$rsm->addMetaResult('u', 'discr', 'discr'); // discriminator column
$rsm->setDiscriminatorColumn('u', 'discr');
$query = $this->_em->createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
Note that in the case of Class Table Inheritance, an example as
@@ -465,3 +433,473 @@ 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
-15
View File
@@ -1,15 +0,0 @@
Partial Hydration
=================
Partial hydration of entities is allowed in the array hydrator, when
only a subset of the fields of an entity are loaded from the database
and the nested results are still created based on the entity relationship structure.
.. code-block:: php
<?php
$users = $em->createQuery("SELECT PARTIAL u.{id,name}, partial a.{id,street} FROM MyApp\Domain\User u JOIN u.addresses a")
->getArrayResult();
This is a useful optimization when you are not interested in all fields of an entity
for performance reasons, for example in use-cases for exporting or rendering lots of data.
+3 -1
View File
@@ -5,7 +5,7 @@ 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 Doctrine to this problem is.
of Doctrine2 to this problem is.
.. note::
@@ -86,3 +86,5 @@ 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.
+102 -23
View File
@@ -1,26 +1,96 @@
PHP Mapping
===========
Doctrine ORM 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 inside of a
static function named ``loadMetadata($class)`` on the entity class itself.
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.
Static Function
---------------
PHP Files
---------
In addition to other drivers using configuration languages you can also
programatically 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 attributes. For this you just
need to use the ``StaticPHPDriver``:
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
use Doctrine\Persistence\Mapping\Driver\StaticPHPDriver;
$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',
'options' => array(
'fixed' => true,
'comment' => "User's login name"
)
));
$metadata->mapField(array(
'fieldName' => 'login_count',
'type' => 'integer',
'nullable' => false,
'options' => array(
'unsigned' => true,
'default' => 0
)
));
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);
@@ -87,11 +157,13 @@ The API of the ClassMetadataBuilder has the following methods with a fluent inte
- ``setTable($name)``
- ``addIndex(array $columns, $indexName)``
- ``addUniqueConstraint(array $columns, $constraintName)``
- ``addNamedQuery($name, $dqlQuery)``
- ``setJoinedTableInheritance()``
- ``setSingleTableInheritance()``
- ``setDiscriminatorColumn($name, $type = 'string', $length = 255, $columnDefinition = null, $enumType = null, $options = [])``
- ``setDiscriminatorColumn($name, $type = 'string', $length = 255)``
- ``addDiscriminatorMapClass($name, $class)``
- ``setChangeTrackingPolicyDeferredExplicit()``
- ``setChangeTrackingPolicyNotify()``
- ``addLifecycleEvent($methodName, $event)``
- ``addManyToOne($name, $targetEntity, $inversedBy = null)``
- ``addInverseOneToOne($name, $targetEntity, $mappedBy)``
@@ -108,12 +180,13 @@ It also has several methods that create builders (which are necessary for advanc
- ``createManyToMany($name, $targetEntity)`` returns an ``ManyToManyAssociationBuilder`` instance
- ``createOneToMany($name, $targetEntity)`` returns an ``OneToManyAssociationBuilder`` instance
ClassMetadata API
-----------------
ClassMetadataInfo API
---------------------
The ``ClassMetadata`` class is the 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.
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
~~~~~~~~~~~~~~~
@@ -193,6 +266,7 @@ Inheritance Getters
- ``isInheritanceTypeNone()``
- ``isInheritanceTypeJoined()``
- ``isInheritanceTypeSingleTable()``
- ``isInheritanceTypeTablePerClass()``
- ``isInheritedField($fieldName)``
- ``isInheritedAssociation($fieldName)``
@@ -202,6 +276,7 @@ Change Tracking Getters
- ``isChangeTrackingDeferredExplicit()``
- ``isChangeTrackingDeferredImplicit()``
- ``isChangeTrackingNotify()``
Field & Association Getters
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -229,11 +304,13 @@ Lifecycle Callback Getters
- ``hasLifecycleCallbacks($lifecycleEvent)``
- ``getLifecycleCallbacks($event)``
Runtime reflection methods
~~~~~~~~~~~~~~~~~~~~~~~~~~
ClassMetadata API
-----------------
These are methods related to runtime reflection for working with the
entities themselves.
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()``
@@ -244,3 +321,5 @@ entities themselves.
- ``setIdentifierValues($entity, $id)``
- ``setFieldValue($entity, $field, $value)``
- ``getFieldValue($entity, $field)``
+30 -96
View File
@@ -7,20 +7,14 @@ conditionally constructing a DQL query in several steps.
It provides a set of classes and methods that is able to
programmatically build queries, and also provides a fluent API.
This means that you can change between one methodology to the other
as you want, or just pick a preferred one.
.. note::
The ``QueryBuilder`` is not an abstraction of DQL, but merely a tool to dynamically build it.
You should still use plain DQL when you can, as it is simpler and more readable.
More about this in the :doc:`FAQ <faq>`.
as you want, and also pick one if you prefer.
Constructing a new QueryBuilder object
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The same way you build a normal Query, you build a ``QueryBuilder``
object. Here is an example of how to build a ``QueryBuilder``
object:
object, just providing the correct method name. Here is an example
how to build a ``QueryBuilder`` object:
.. code-block:: php
@@ -30,9 +24,9 @@ object:
// example1: creating a QueryBuilder instance
$qb = $em->createQueryBuilder();
An instance of QueryBuilder has several informative methods. One
good example is to inspect what type of object the
``QueryBuilder`` is.
Once you have created an instance of QueryBuilder, it provides a
set of useful informative functions that you can use. One good
example is to inspect what type of object the ``QueryBuilder`` is.
.. code-block:: php
@@ -86,11 +80,11 @@ Working with QueryBuilder
High level API methods
^^^^^^^^^^^^^^^^^^^^^^
The most straightforward way to build a dynamic query with the ``QueryBuilder`` is by taking
advantage of Helper methods. For all base code, there is a set of
useful methods to simplify a programmer's life. To illustrate how
to work with them, here is the same example 6 re-written using
``QueryBuilder`` helper methods:
To simplify even more the way you build a query in Doctrine, we can take
advantage of what we call Helper methods. For all base code, there
is a set of useful methods to simplify a programmer's life. To
illustrate how to work with them, here is the same example 6
re-written using ``QueryBuilder`` helper methods:
.. code-block:: php
@@ -103,9 +97,10 @@ to work with them, here is the same example 6 re-written using
->orderBy('u.name', 'ASC');
``QueryBuilder`` helper methods are considered the standard way to
use the ``QueryBuilder``. The ``$qb->expr()->*`` methods can help you
build conditional expressions dynamically. Here is a converted example 8 to
suggested way to build queries with dynamic conditions:
build DQL queries. Although it is supported, it should be avoided
to use string based queries and greatly encouraged to use
``$qb->expr()->*`` methods. Here is a converted example 8 to
suggested standard way to build queries:
.. code-block:: php
@@ -118,7 +113,7 @@ suggested way to build queries with dynamic conditions:
$qb->expr()->eq('u.id', '?1'),
$qb->expr()->like('u.nickname', '?2')
))
->orderBy('u.surname', 'ASC');
->orderBy('u.surname', 'ASC'));
Here is a complete list of helper methods available in ``QueryBuilder``:
@@ -131,7 +126,7 @@ Here is a complete list of helper methods available in ``QueryBuilder``:
// Example - $qb->select(array('u', 'p'))
// Example - $qb->select($qb->expr()->select('u', 'p'))
public function select($select = null);
// addSelect does not override previous calls to select
//
// Example - $qb->select('u');
@@ -252,23 +247,8 @@ while the named placeholders start with a : followed by a string.
Calling ``setParameter()`` automatically infers which type you are setting as
value. This works for integers, arrays of strings/integers, DateTime instances
and for managed entities. If you want to set a type explicitly you can call
the third argument to ``setParameter()`` explicitly. It accepts either a DBAL
``Doctrine\DBAL\ParameterType::*`` or a DBAL Type name for conversion.
.. note::
Even though passing DateTime instance is allowed, it impacts performance
as by default there is an attempt to load metadata for object, and if it's not found,
type is inferred from the original value.
.. code-block:: php
<?php
use Doctrine\DBAL\Types\Types;
// prevents attempt to load metadata for date time class, improving performance
$qb->setParameter('date', new \DateTimeImmutable(), Types::DATETIME_IMMUTABLE)
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
@@ -277,17 +257,10 @@ following syntax:
.. code-block:: php
<?php
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Query\Parameter;
// $qb instanceof QueryBuilder
// Query here...
$qb->setParameters(new ArrayCollection([
new Parameter('1', 'value for ?1'),
new Parameter('2', 'value for ?2')
]));
$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()":
@@ -344,7 +317,7 @@ the Query object which can be retrieved from ``EntityManager#createQuery()``.
Executing a Query
^^^^^^^^^^^^^^^^^
The QueryBuilder is a builder object only - it has no means of actually
The QueryBuilder is a builder object only, it has no means of actually
executing the Query. Additionally a set of parameters such as query hints
cannot be set on the QueryBuilder itself. This is why you always have to convert
a querybuilder instance into a Query object:
@@ -361,7 +334,6 @@ a querybuilder instance into a Query object:
// Execute Query
$result = $query->getResult();
$iterableResult = $query->toIterable();
$single = $query->getSingleResult();
$array = $query->getArrayResult();
$scalar = $query->getScalarResult();
@@ -434,12 +406,6 @@ complete list of supported helper methods available:
// Example - $qb->expr()->isNotNull('u.id') => u.id IS NOT NULL
public function isNotNull($x); // Returns string
// Example - $qb->expr()->isMemberOf('?1', 'u.groups') => ?1 MEMBER OF u.groups
public function isMemberOf($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->isInstanceOf('u', Employee::class) => u INSTANCE OF Employee
public function isInstanceOf($x, $y); // Returns Expr\Comparison instance
/** Arithmetic objects **/
@@ -526,9 +492,6 @@ complete list of supported helper methods available:
// Example - $qb->expr()->sqrt('u.currentBalance')
public function sqrt($x); // Returns Expr\Func
// Example - $qb->expr()->mod('u.currentBalance', '10')
public function mod($x); // Returns Expr\Func
// Example - $qb->expr()->count('u.firstname')
public function count($x); // Returns Expr\Func
@@ -536,32 +499,14 @@ complete list of supported helper methods available:
public function countDistinct($x); // Returns Expr\Func
}
Adding a Criteria to a Query
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can also add a :ref:`filtering-collections` to a QueryBuilder by
using ``addCriteria``:
.. code-block:: php
<?php
use Doctrine\Common\Collections\Criteria;
// ...
$criteria = Criteria::create()
->orderBy(['firstName' => Criteria::ASC]);
// $qb instanceof QueryBuilder
$qb->addCriteria($criteria);
// then execute your query like normal
Low Level API
^^^^^^^^^^^^^
Now we will describe the low level method of creating queries.
It may be useful to work at this level for optimization purposes,
but most of the time it is preferred to work at a higher level of
abstraction.
Now we have describe the low level (thought of as the
hardcore method) of creating queries. It may be useful to work at
this level for optimization purposes, but most of the time it is
preferred to work at a higher level of abstraction.
All helper methods in ``QueryBuilder`` actually rely on a single
one: ``add()``. This method is responsible of building every piece
@@ -578,6 +523,8 @@ of DQL. It takes 3 parameters: ``$dqlPartName``, ``$dqlPart`` and
not (no effect on the ``where`` and ``having`` DQL query parts,
which always override all previously defined items)
-
.. code-block:: php
<?php
@@ -612,20 +559,7 @@ same query of example 6 written using
->add('where', new Expr\Comparison('u.id', '=', '?1'))
->add('orderBy', new Expr\OrderBy('u.name', 'ASC'));
Binding Parameters to Placeholders
----------------------------------
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.
It is often not necessary to know about the exact placeholder names when
building a query. You can use a helper method to bind a value to a placeholder
and directly use that placeholder in your query as a return value:
.. code-block:: php
<?php
// $qb instanceof QueryBuilder
$qb->select('u')
->from('User', 'u')
->where('u.email = ' . $qb->createNamedParameter($userInputEmail))
;
// SELECT u FROM User u WHERE email = :dcValue1
+199 -132
View File
@@ -11,7 +11,7 @@ The Second Level Cache is designed to reduce the amount of necessary database ac
It sits between your application and the database to avoid the number of database hits as much as possible.
When turned on, entities will be first searched in cache and if they are not found,
a database query will be fired and then the entity result will be stored in a cache provider.
a database query will be fired an then the entity result will be stored in a cache provider.
There are some flavors of caching available, but is better to cache read-only data.
@@ -31,31 +31,31 @@ Each cache region resides in a specific cache namespace and has its own lifetime
Notice that when caching collection and queries only identifiers are stored.
The entity values will be stored in its own region
Something like below for an entity region:
Something like below for an entity region :
.. code-block:: php
<?php
[
'region_name:entity_1_hash' => ['id' => 1, 'name' => 'FooBar', 'associationName' => null],
'region_name:entity_2_hash' => ['id' => 2, 'name' => 'Foo', 'associationName' => ['id' => 11]],
'region_name:entity_3_hash' => ['id' => 3, 'name' => 'Bar', 'associationName' => ['id' => 22]]
'region_name:entity_1_hash' => ['id'=> 1, 'name' => 'FooBar', 'associationName'=>null],
'region_name:entity_2_hash' => ['id'=> 2, 'name' => 'Foo', 'associationName'=>['id'=>11]],
'region_name:entity_3_hash' => ['id'=> 3, 'name' => 'Bar', 'associationName'=>['id'=>22]]
];
If the entity holds a collection that also needs to be cached.
An collection region could look something like:
An collection region could look something like :
.. code-block:: php
<?php
[
'region_name:entity_1_coll_assoc_name_hash' => ['ownerId' => 1, 'list' => [1, 2, 3]],
'region_name:entity_2_coll_assoc_name_hash' => ['ownerId' => 2, 'list' => [2, 3]],
'region_name:entity_3_coll_assoc_name_hash' => ['ownerId' => 3, 'list' => [2, 4]]
'region_name:entity_1_coll_assoc_name_hash' => ['ownerId'=> 1, 'list' => [1, 2, 3]],
'region_name:entity_2_coll_assoc_name_hash' => ['ownerId'=> 2, 'list' => [2, 3]],
'region_name:entity_3_coll_assoc_name_hash' => ['ownerId'=> 3, 'list' => [2, 4]]
];
A query region might be something like:
A query region might be something like :
.. code-block:: php
@@ -77,10 +77,11 @@ A query region might be something like:
Cache Regions
-------------
``Doctrine\ORM\Cache\Region\DefaultRegion`` is the default implementation.
``Doctrine\ORM\Cache\Region\DefaultRegion`` It's the default implementation.
A simplest cache region compatible with all doctrine-cache drivers but does not support locking.
``Doctrine\ORM\Cache\Region`` and ``Doctrine\ORM\Cache\ConcurrentRegion``
define contracts that should be implemented by a cache provider.
Defines contracts that should be implemented by a cache provider.
It allows you to provide your own cache implementation that might take advantage of specific cache driver.
@@ -90,8 +91,13 @@ If you want to support locking for ``READ_WRITE`` strategies you should implemen
Cache region
~~~~~~~~~~~~
``Doctrine\ORM\Cache\Region`` defines a contract for accessing a particular
cache region.
Defines a contract for accessing a particular region.
``Doctrine\ORM\Cache\Region``
Defines a contract for accessing a particular cache region.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.Region.html/>`_.
Concurrent cache region
~~~~~~~~~~~~~~~~~~~~~~~
@@ -101,7 +107,11 @@ By default, Doctrine provides a very simple implementation based on file locks `
If you want to use an ``READ_WRITE`` cache, you should consider providing your own cache region.
``Doctrine\ORM\Cache\ConcurrentRegion`` defines a contract for concurrently managed data region.
``Doctrine\ORM\Cache\ConcurrentRegion``
Defines contract for concurrently managed data region.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.ConcurrentRegion.html/>`_.
Timestamp region
~~~~~~~~~~~~~~~~
@@ -110,6 +120,8 @@ Timestamp region
Tracks the timestamps of the most recent updates to particular entity.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.TimestampRegion.html/>`_.
.. _reference-second-level-cache-mode:
Caching mode
@@ -126,7 +138,7 @@ Caching mode
* Read Write Cache doesnt employ any locks but can do reads, inserts, updates and deletes.
* Good if the application needs to update data rarely.
* ``READ_WRITE``
@@ -137,25 +149,25 @@ Caching mode
Built-in cached persisters
~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Cached persisters are responsible to access cache regions.
+-----------------------+------------------------------------------------------------------------------------------+
| Cache Usage | Persister |
+=======================+==========================================================================================+
| READ_ONLY | ``Doctrine\ORM\Cache\Persister\Entity\ReadOnlyCachedEntityPersister`` |
+-----------------------+------------------------------------------------------------------------------------------+
| READ_WRITE | ``Doctrine\ORM\Cache\Persister\Entity\ReadWriteCachedEntityPersister`` |
+-----------------------+------------------------------------------------------------------------------------------+
| NONSTRICT_READ_WRITE | ``Doctrine\ORM\Cache\Persister\Entity\NonStrictReadWriteCachedEntityPersister`` |
+-----------------------+------------------------------------------------------------------------------------------+
| READ_ONLY | ``Doctrine\ORM\Cache\Persister\Collection\ReadOnlyCachedCollectionPersister`` |
+-----------------------+------------------------------------------------------------------------------------------+
| READ_WRITE | ``Doctrine\ORM\Cache\Persister\Collection\ReadWriteCachedCollectionPersister`` |
+-----------------------+------------------------------------------------------------------------------------------+
| NONSTRICT_READ_WRITE | ``Doctrine\ORM\Cache\Persister\Collection\NonStrictReadWriteCachedCollectionPersister`` |
+-----------------------+------------------------------------------------------------------------------------------+
+-----------------------+-------------------------------------------------------------------------------+
| Cache Usage | Persister |
+=======================+===============================================================================+
| READ_ONLY | Doctrine\\ORM\\Cache\\Persister\\ReadOnlyCachedEntityPersister |
+-----------------------+-------------------------------------------------------------------------------+
| READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\ReadWriteCachedEntityPersister |
+-----------------------+-------------------------------------------------------------------------------+
| NONSTRICT_READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\NonStrictReadWriteCachedEntityPersister |
+-----------------------+-------------------------------------------------------------------------------+
| READ_ONLY | Doctrine\\ORM\\Cache\\Persister\\ReadOnlyCachedCollectionPersister |
+-----------------------+-------------------------------------------------------------------------------+
| READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\ReadWriteCachedCollectionPersister |
+-----------------------+-------------------------------------------------------------------------------+
| NONSTRICT_READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\NonStrictReadWriteCacheCollectionPersister |
+-----------------------+-------------------------------------------------------------------------------+
Configuration
-------------
@@ -165,17 +177,16 @@ Doctrine allows you to specify configurations and some points of extension for t
Enable Second Level Cache
~~~~~~~~~~~~~~~~~~~~~~~~~
To enable the second-level-cache, you should provide a cache factory.
``Doctrine\ORM\Cache\DefaultCacheFactory`` is the default implementation.
To enable the second-level-cache, you should provide a cache factory
``\Doctrine\ORM\Cache\DefaultCacheFactory`` is the default implementation.
.. code-block:: php
<?php
/** @var \Doctrine\ORM\Cache\RegionsConfiguration $cacheConfig */
/** @var \Psr\Cache\CacheItemPoolInterface $cache */
/** @var \Doctrine\ORM\Configuration $config */
/* @var $config \Doctrine\ORM\Cache\RegionsConfiguration */
/* @var $cache \Doctrine\Common\Cache\Cache */
$factory = new \Doctrine\ORM\Cache\DefaultCacheFactory($cacheConfig, $cache);
$factory = new \Doctrine\ORM\Cache\DefaultCacheFactory($config, $cache);
// Enable second-level-cache
$config->setSecondLevelCacheEnabled();
@@ -190,18 +201,15 @@ Cache Factory
Cache Factory is the main point of extension.
It allows you to provide a specific implementation of the following components:
It allows you to provide a specific implementation of the following components :
``QueryCache``
stores and retrieves query cache results.
``CachedEntityPersister``
stores and retrieves entity results.
``CachedCollectionPersister``
stores and retrieves query results.
``EntityHydrator``
transforms entities into a cache entries and cache entries into entities
``CollectionHydrator``
transforms collections into cache entries and cache entries into collections
* ``QueryCache`` Store and retrieve query cache results.
* ``CachedEntityPersister`` Store and retrieve entity results.
* ``CachedCollectionPersister`` Store and retrieve query results.
* ``EntityHydrator`` Transform an entity into a cache entry and cache entry into entities
* ``CollectionHydrator`` Transform a collection into a cache entry and cache entry into collection
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.DefaultCacheFactory.html/>`_.
Region Lifetime
~~~~~~~~~~~~~~~
@@ -211,28 +219,27 @@ To specify a default lifetime for all regions or specify a different lifetime fo
.. code-block:: php
<?php
/** @var \Doctrine\ORM\Configuration $config */
/** @var \Doctrine\ORM\Cache\CacheConfiguration $cacheConfig */
/** @var \Doctrine\ORM\Cache\RegionsConfiguration $regionConfig */
/* @var $config \Doctrine\ORM\Configuration */
/* @var $cacheConfig \Doctrine\ORM\Configuration */
$cacheConfig = $config->getSecondLevelCacheConfiguration();
$regionConfig = $cacheConfig->getRegionsConfiguration();
// Cache Region lifetime
$regionConfig->setLifetime('my_entity_region', 3600); // Time to live for a specific region (in seconds)
$regionConfig->setDefaultLifetime(7200); // Default time to live (in seconds)
$regionConfig->setLifetime('my_entity_region', 3600); // Time to live for a specific region; In seconds
$regionConfig->setDefaultLifetime(7200); // Default time to live; In seconds
Cache Log
~~~~~~~~~
By providing a cache logger you should be able to get information about all cache operations such as hits, misses and puts.
``Doctrine\ORM\Cache\Logging\StatisticsCacheLogger`` is a built-in implementation that provides basic statistics.
``\Doctrine\ORM\Cache\Logging\StatisticsCacheLogger`` is a built-in implementation that provides basic statistics.
.. code-block:: php
<?php
/** @var \Doctrine\ORM\Configuration $config */
$logger = new \Doctrine\ORM\Cache\Logging\StatisticsCacheLogger();
/* @var $config \Doctrine\ORM\Configuration */
$logger = \Doctrine\ORM\Cache\Logging\StatisticsCacheLogger();
// Cache logger
$config->setSecondLevelCacheEnabled(true);
@@ -260,37 +267,42 @@ By providing a cache logger you should be able to get information about all cach
// Get the total number of cached entries *not* found in all regions.
$logger->getMissCount();
If you want to get more information you should implement
``Doctrine\ORM\Cache\Logging\CacheLogger`` and collect
all the information you want.
If you want to get more information you should implement ``\Doctrine\ORM\Cache\Logging\CacheLogger``.
and collect all information you want.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.CacheLogger.html/>`_.
Entity cache definition
-----------------------
* Entity cache configuration allows you to define the caching strategy and region for an entity.
* ``usage`` specifies the caching strategy: ``READ_ONLY``,
``NONSTRICT_READ_WRITE``, ``READ_WRITE``.
See :ref:`reference-second-level-cache-mode`.
* ``region`` is an optional value that specifies the name of the second
level cache region.
* ``usage`` Specifies the caching strategy: ``READ_ONLY``, ``NONSTRICT_READ_WRITE``, ``READ_WRITE``. see :ref:`reference-second-level-cache-mode`
* ``region`` Optional value that specifies the name of the second level cache region.
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
#[Entity]
#[Cache(usage: 'READ_ONLY', region: 'my_entity_region')]
/**
* @Entity
* @Cache(usage="READ_ONLY", region="my_entity_region")
*/
class Country
{
#[Id]
#[GeneratedValue]
#[Column]
protected int|null $id = null;
/**
* @Id
* @GeneratedValue
* @Column(type="integer")
*/
protected $id;
#[Column(unique: true)]
protected string $name;
/**
* @Column(unique=true)
*/
protected $name;
// other properties and methods
}
@@ -298,10 +310,7 @@ level cache region.
.. code-block:: xml
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Country">
<cache usage="READ_ONLY" region="my_entity_region" />
<id name="id" type="integer" column="id">
@@ -311,6 +320,24 @@ level cache region.
</entity>
</doctrine-mapping>
.. code-block:: yaml
Country:
type: entity
cache:
usage : READ_ONLY
region : my_entity_region
id:
id:
type: integer
id: true
generator:
strategy: IDENTITY
fields:
name:
type: string
Association cache definition
----------------------------
The most common use case is to cache entities. But we can also cache relationships.
@@ -319,30 +346,39 @@ It caches the primary keys of association and cache each element will be cached
.. configuration-block::
.. code-block:: attribute
.. code-block:: php
<?php
#[Entity]
#[Cache(usage: 'NONSTRICT_READ_WRITE')]
/**
* @Entity
* @Cache("NONSTRICT_READ_WRITE")
*/
class State
{
#[Id]
#[GeneratedValue]
#[Column]
protected int|null $id = null;
/**
* @Id
* @GeneratedValue
* @Column(type="integer")
*/
protected $id;
#[Column(unique: true)]
protected string $name;
/**
* @Column(unique=true)
*/
protected $name;
#[Cache(usage: 'NONSTRICT_READ_WRITE')]
#[ManyToOne(targetEntity: Country::class)]
#[JoinColumn(name: 'country_id', referencedColumnName: 'id')]
protected Country|null $country = null;
/**
* @Cache("NONSTRICT_READ_WRITE")
* @ManyToOne(targetEntity="Country")
* @JoinColumn(name="country_id", referencedColumnName="id")
*/
protected $country;
/** @var Collection<int, City> */
#[Cache(usage: 'NONSTRICT_READ_WRITE')]
#[OneToMany(targetEntity: City::class, mappedBy: 'state')]
protected Collection $cities;
/**
* @Cache("NONSTRICT_READ_WRITE")
* @OneToMany(targetEntity="City", mappedBy="state")
*/
protected $cities;
// other properties and methods
}
@@ -350,10 +386,7 @@ It caches the primary keys of association and cache each element will be cached
.. code-block:: xml
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="State">
<cache usage="NONSTRICT_READ_WRITE" />
@@ -363,7 +396,7 @@ It caches the primary keys of association and cache each element will be cached
</id>
<field name="name" type="string" column="name"/>
<many-to-one field="country" target-entity="Country">
<cache usage="NONSTRICT_READ_WRITE" />
@@ -378,9 +411,40 @@ It caches the primary keys of association and cache each element will be cached
</entity>
</doctrine-mapping>
.. note::
.. code-block:: yaml
for this to work, the target entity must also be marked as cacheable.
State:
type: entity
cache:
usage : NONSTRICT_READ_WRITE
id:
id:
type: integer
id: true
generator:
strategy: IDENTITY
fields:
name:
type: string
manyToOne:
state:
targetEntity: Country
joinColumns:
country_id:
referencedColumnName: id
cache:
usage : NONSTRICT_READ_WRITE
oneToMany:
cities:
targetEntity:City
mappedBy: state
cache:
usage : NONSTRICT_READ_WRITE
> Note: for this to work, the target entity must also be marked as cacheable.
Cache usage
~~~~~~~~~~~
@@ -397,8 +461,8 @@ Basic entity cache
$country1 = $em->find('Country', 1); // Retrieve item from cache
$country1->setName('New Name');
$country->setName("New Name");
$em->persist($country);
$em->flush(); // Hit database to update the row and update cache
$em->clear(); // Clear entity manager
@@ -423,7 +487,7 @@ Association cache
$state = $em->find('State', 1);
// Hit database to update the row and update cache entry
$state->setName('New Name');
$state->setName("New Name");
$em->persist($state);
$em->flush();
@@ -474,14 +538,14 @@ The query cache stores the results of the query but as identifiers, entity value
.. code-block:: php
<?php
/** @var \Doctrine\ORM\EntityManager $em */
/* @var $em \Doctrine\ORM\EntityManager */
// Execute database query, store query cache and entity cache
$result1 = $em->createQuery('SELECT c FROM Country c ORDER BY c.name')
->setCacheable(true)
->getResult();
$em->clear();
$em->clear()
// Check if query result is valid and load entities from cache
$result2 = $em->createQuery('SELECT c FROM Country c ORDER BY c.name')
@@ -501,22 +565,21 @@ The Cache Mode controls how a particular query interacts with the second-level c
.. code-block:: php
<?php
/** @var \Doctrine\ORM\EntityManager $em */
/* @var $em \Doctrine\ORM\EntityManager */
// Will refresh the query cache and all entities the cache as it reads from the database.
$result1 = $em->createQuery('SELECT c FROM Country c ORDER BY c.name')
->setCacheMode(\Doctrine\ORM\Cache::MODE_GET)
->setCacheMode(Cache::MODE_GET)
->setCacheable(true)
->getResult();
.. note::
The default query cache mode is ```Cache::MODE_NORMAL```
The the default query cache mode is ```Cache::MODE_NORMAL```
DELETE / UPDATE queries
~~~~~~~~~~~~~~~~~~~~~~~
DQL UPDATE / DELETE statements are ported directly into a database and bypass
the second-level cache.
DQL UPDATE / DELETE statements are ported directly into a database and bypass the second-level cache,
Entities that are already cached will NOT be invalidated.
However the cached data could be evicted using the cache API or an special query hint.
@@ -528,7 +591,7 @@ Execute the ``UPDATE`` and invalidate ``all cache entries`` using ``Query::HINT_
<?php
// Execute and invalidate
$this->_em->createQuery("UPDATE Entity\Country u SET u.name = 'unknown' WHERE u.id = 1")
->setHint(\Doctrine\ORM\Query::HINT_CACHE_EVICT, true)
->setHint(Query::HINT_CACHE_EVICT, true)
->execute();
@@ -556,10 +619,10 @@ Execute the ``UPDATE`` and invalidate ``a specific cache entry`` using the cache
$em->getCache()->evictEntity('Entity\Country', 1);
Using the repository query cache
--------------------------------
---------------------
As well as ``Query Cache`` all persister queries store only identifier values for an individual query.
All persisters use a single timestamp cache region to keep track of the last update for each persister,
All persister use a single timestamps cache region keeps track of the last update for each persister,
When a query is loaded from cache, the timestamp region is checked for the last update for that persister.
Using the last update timestamps as part of the query key invalidate the cache key when an update occurs.
@@ -578,7 +641,7 @@ Using the last update timestamps as part of the query key invalidate the cache k
$em->clear();
// Reload from database.
// At this point the query cache key is no longer valid, the select goes straight to the database
// At this point the query cache key if not logger valid, the select goes straight
$entities = $em->getRepository('Entity\Country')->findAll();
Cache API
@@ -590,7 +653,7 @@ However, you can use the cache API to check / invalidate cache entries.
.. code-block:: php
<?php
/** @var \Doctrine\ORM\Cache $cache */
/* @var $cache \Doctrine\ORM\Cache */
$cache = $em->getCache();
$cache->containsEntity('Entity\State', 1) // Check if the cache exists
@@ -614,35 +677,40 @@ For performance reasons the cache API does not extract from composite primary ke
.. code-block:: php
<?php
#[Entity]
/**
* @Entity
*/
class Reference
{
#[Id]
#[ManyToOne(targetEntity: Article::class, inversedBy: 'references')]
#[JoinColumn(name: 'source_id', referencedColumnName: 'article_id')]
private Article|null $source = null;
/**
* @Id
* @ManyToOne(targetEntity="Article", inversedBy="references")
* @JoinColumn(name="source_id", referencedColumnName="article_id")
*/
private $source;
#[Id]
#[ManyToOne(targetEntity: Article::class, inversedBy: 'references')]
#[JoinColumn(name: 'target_id', referencedColumnName: 'article_id')]
/**
* @Id
* @ManyToOne(targetEntity="Article")
* @JoinColumn(name="target_id", referencedColumnName="article_id")
*/
private $target;
}
// Supported
/** @var Article $article */
/* @var $article Article */
$article = $em->find('Article', 1);
// Supported
/** @var Article $article */
/* @var $article Article */
$article = $em->find('Article', $article);
// Supported
$id = ['source' => 1, 'target' => 2];
$id = array('source' => 1, 'target' => 2);
$reference = $em->find('Reference', $id);
// NOT Supported
$id = ['source' => new Article(1), 'target' => new Article(2)];
$id = array('source' => new Article(1), 'target' => new Article(2));
$reference = $em->find('Reference', $id);
Distributed environments
@@ -660,5 +728,4 @@ Paginator
~~~~~~~~~
Count queries generated by ``Doctrine\ORM\Tools\Pagination\Paginator`` are not cached by second-level cache.
Although entities and query result are cached, count queries will hit the
database every time.
Although entities and query result are cached count queries will hit the database every time.
+17 -17
View File
@@ -10,10 +10,11 @@ we cannot protect you from SQL injection.
Please also read the documentation chapter on Security in Doctrine DBAL. This
page only handles Security issues in the ORM.
- `DBAL Security Page <https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/security.html>`
- [DBAL Security Page](https://github.com/doctrine/dbal/blob/master/docs/en/reference/security.rst)
If you find a Security bug in Doctrine, please follow our
`Security reporting guidelines <https://www.doctrine-project.org/policies/security.html#reporting>`_.
If you find a Security bug in Doctrine, please report it on Jira and change the
Security Level to "Security Issues". It will be visible to Doctrine Core
developers and you only.
User input and Doctrine ORM
---------------------------
@@ -31,7 +32,7 @@ You can consider the following APIs to be safe from SQL injection:
- Queries through the Criteria API on ``Doctrine\ORM\PersistentCollection`` and
``Doctrine\ORM\EntityRepository``.
You are **NOT** safe from SQL injection when using user input with:
You are **NOT** save from SQL injection when using user input with:
- Expression API of ``Doctrine\ORM\QueryBuilder``
- Concatenating user input into DQL SELECT, UPDATE or DELETE statements or
@@ -97,20 +98,19 @@ entity might look like this:
<?php
#[Entity]
/**
* @Entity
*/
class InsecureEntity
{
#[Id, Column, GeneratedValue]
private int|null $id = null;
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @Column */
private $email;
/** @Column(type="boolean") */
private $isAdmin;
#[Column]
private string $email;
#[Column]
private bool $isAdmin;
/** @param array<string, mixed> $userInput */
public function fromArray(array $userInput): void
public function fromArray(array $userInput)
{
foreach ($userInput as $key => $value) {
$this->$key = $value;
@@ -118,8 +118,8 @@ entity might look like this:
}
}
Now the possiblity of mass-assignment exists on this entity and can
be exploited by attackers to set the "isAdmin" flag to true on any
Now the possiblity of mass-asignment exists on this entity and can
be exploitet by attackers to set the "isAdmin" flag to true on any
object when you pass the whole request data to this method like:
.. code-block:: php
+255 -53
View File
@@ -5,67 +5,91 @@ Doctrine Console
----------------
The Doctrine Console is a Command Line Interface tool for simplifying common
administration tasks during the development of a project that uses ORM.
administration tasks during the development of a project that uses Doctrine 2.
For the following examples, we will set up the CLI as ``bin/doctrine``.
Take a look at the :doc:`Installation and Configuration <configuration>`
chapter for more information how to setup the console command.
Setting Up the Console
~~~~~~~~~~~~~~~~~~~~~~
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 a developer. There is no
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``. You have to inject it into the console application.
All the commands of the Doctrine Console require access to the ``EntityManager``
or ``DBAL`` Connection. You have to inject them into the console application
using so called Helper-Sets. This requires either the ``db``
or the ``em`` helpers to be defined in order to work correctly.
Here is an example of a the project-specific ``bin/doctrine`` binary.
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
#!/usr/bin/env php
<?php
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($conn)
));
$cli->setHelperSet($helperSet);
use Doctrine\ORM\Tools\Console\ConsoleRunner;
use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider;
When dealing with the ORM package, the EntityManagerHelper is
required:
// replace with path to your own project bootstrap file
require_once 'bootstrap.php';
.. code-block:: php
// replace with mechanism to retrieve EntityManager in your app
$entityManager = GetEntityManager();
<?php
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em)
));
$cli->setHelperSet($helperSet);
$commands = [
// If you want to add your own custom console commands,
// you can do so here.
];
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:
ConsoleRunner::run(
new SingleManagerProvider($entityManager),
$commands
);
.. code-block:: php
.. note::
<?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.
Display Help Information
~~~~~~~~~~~~~~~~~~~~~~~~
Type ``php 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:
::
$> php bin/doctrine orm:generate-entities --help
Command Overview
~~~~~~~~~~~~~~~~
@@ -83,8 +107,18 @@ The following Commands are currently available:
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
@@ -97,10 +131,14 @@ The following Commands are currently available:
update the database schema of EntityManager Storage Connection or
generate the SQL output.
The following alias is defined:
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::
@@ -135,7 +173,7 @@ When using the SchemaTool class directly, create your schema using
the ``createSchema()`` method. First create an instance of the
``SchemaTool`` and pass it an instance of the ``EntityManager``
that you want to use to create the schema. This method receives an
array of ``ClassMetadata`` instances.
array of ``ClassMetadataInfo`` instances.
.. code-block:: php
@@ -166,8 +204,8 @@ tables of the current model to clean up with orphaned tables.
You can also use database introspection to update your schema
easily with the ``updateSchema()`` method. It will compare your
existing database schema to the passed array of ``ClassMetadata``
instances.
existing database schema to the passed array of
``ClassMetdataInfo`` instances.
.. code-block:: php
@@ -181,40 +219,208 @@ To create the schema use the ``create`` command:
.. code-block:: php
$ php bin/doctrine orm:schema-tool:create
$ php doctrine orm:schema-tool:create
To drop the schema use the ``drop`` command:
.. code-block:: php
$ php bin/doctrine orm:schema-tool:drop
$ php doctrine orm:schema-tool:drop
If you want to drop and then recreate the schema then use both
options:
.. code-block:: php
$ php bin/doctrine orm:schema-tool:drop
$ php bin/doctrine orm:schema-tool:create
$ 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 bin/doctrine orm:schema-tool:update
$ 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 bin/doctrine orm:schema-tool:create --dump-sql
$ 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 ORM has to skip some of the
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.
@@ -225,11 +431,6 @@ You can either use the Doctrine Command Line Tool:
doctrine orm:validate-schema
If the validation fails, you can change the verbosity level to
check the detected errors:
doctrine orm:validate-schema -v
Or you can trigger the validation manually:
.. code-block:: php
@@ -275,7 +476,7 @@ To include a new command on Doctrine Console, you need to do modify the
<?php
// doctrine.php
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Helper\Application;
// as before ...
@@ -324,3 +525,4 @@ HelperSet, like it is described in the configuration section.
// Runs console application
$cli->run();
@@ -1,8 +1,6 @@
Transactions and Concurrency
============================
.. _transactions-and-concurrency_transaction-demarcation:
Transaction Demarcation
-----------------------
@@ -16,20 +14,18 @@ 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 ORM already takes care of proper
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 ORM also allows (and encourages) you to take over
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.
.. _transactions-and-concurrency_approach-implicitly:
Approach 1: Implicitly
~~~~~~~~~~~~~~~~~~~~~~
@@ -53,8 +49,6 @@ the DML operations by the Doctrine ORM and is sufficient if all the
data manipulation that is part of a unit of work happens through
the domain model and thus the ORM.
.. _transactions-and-concurrency_approach-explicitly:
Approach 2: Explicitly
~~~~~~~~~~~~~~~~~~~~~~
@@ -68,14 +62,14 @@ looks like this:
// $em instanceof EntityManager
$em->getConnection()->beginTransaction(); // suspend auto-commit
try {
// ... do some work
//... do some work
$user = new User;
$user->setName('George');
$em->persist($user);
$em->flush();
$em->getConnection()->commit();
} catch (Exception $e) {
$em->getConnection()->rollBack();
$em->getConnection()->rollback();
throw $e;
}
@@ -88,7 +82,7 @@ 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#wrapInTransaction($func)``.
``Connection#transactional($func)`` and ``EntityManager#transactional($func)``.
When used, these control abstractions ensure that you never forget to rollback
the transaction, in addition to the obvious code reduction. An example that is
functionally equivalent to the previously shown code looks as follows:
@@ -96,18 +90,9 @@ functionally equivalent to the previously shown code looks as follows:
.. code-block:: php
<?php
// transactional with Connection instance
// $conn instanceof Connection
$conn->transactional(function($conn) {
// ... do some work
$user = new User;
$user->setName('George');
});
// transactional with EntityManager instance
// $em instanceof EntityManager
$em->wrapInTransaction(function($em) {
// ... do some work
$em->transactional(function($em) {
//... do some work
$user = new User;
$user->setName('George');
$em->persist($user);
@@ -116,10 +101,8 @@ functionally equivalent to the previously shown code looks as follows:
The difference between ``Connection#transactional($func)`` and
``EntityManager#transactional($func)`` is that the latter
abstraction flushes the ``EntityManager`` prior to transaction
commit and in case of an exception the ``EntityManager`` gets closed
in addition to the transaction rollback.
.. _transactions-and-concurrency_exception-handling:
commit and rolls back the transaction when an
exception occurs.
Exception Handling
~~~~~~~~~~~~~~~~~~
@@ -151,18 +134,14 @@ knowing that their state is potentially no longer accurate.
If you intend to start another unit of work after an exception has
occurred you should do that with a new ``EntityManager``.
.. _transactions-and-concurrency_locking-support:
Locking Support
---------------
Doctrine ORM offers support for Pessimistic- and Optimistic-locking
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.
.. _transactions-and-concurrency_optimistic-locking:
Optimistic Locking
~~~~~~~~~~~~~~~~~~
@@ -189,50 +168,30 @@ has been modified by someone else already.
You designate a version field in an entity as follows. In this
example we'll use an integer.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
class User
{
// ...
#[Version, Column(type: 'integer')]
private int $version;
// ...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="User">
<field name="version" type="integer" version="true" />
</entity>
</doctrine-mapping>
<?php
class User
{
// ...
/** @Version @Column(type="integer") */
private $version;
// ...
}
Alternatively a datetime type can be used (which maps to a SQL
timestamp or datetime):
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
class User
{
// ...
#[Version, Column(type: 'datetime')]
private DateTime $version;
// ...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="User">
<field name="version" type="datetime" version="true" />
</entity>
</doctrine-mapping>
<?php
class User
{
// ...
/** @Version @Column(type="datetime") */
private $version;
// ...
}
Version numbers (not timestamps) should however be preferred as
they can not potentially conflict in a highly concurrent
@@ -263,15 +222,15 @@ either when calling ``EntityManager#find()``:
<?php
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\OptimisticLockException;
$theEntityId = 1;
$expectedVersion = 184;
try {
$entity = $em->find('User', $theEntityId, LockMode::OPTIMISTIC, $expectedVersion);
// do the work
$em->flush();
} catch(OptimisticLockException $e) {
echo "Sorry, but someone else has already changed this entity. Please apply the changes again!";
@@ -284,16 +243,16 @@ Or you can use ``EntityManager#lock()`` to find out:
<?php
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\OptimisticLockException;
$theEntityId = 1;
$expectedVersion = 184;
$entity = $em->find('User', $theEntityId);
try {
// assert version
$em->lock($entity, LockMode::OPTIMISTIC, $expectedVersion);
} catch(OptimisticLockException $e) {
echo "Sorry, but someone else has already changed this entity. Please apply the changes again!";
}
@@ -332,7 +291,7 @@ See the example code, The form (GET Request):
<?php
$post = $em->find('BlogPost', 123456);
echo '<input type="hidden" name="id" value="' . $post->getId() . '" />';
echo '<input type="hidden" name="version" value="' . $post->getCurrentVersion() . '" />';
@@ -343,15 +302,13 @@ And the change headline action (POST Request):
<?php
$postId = (int)$_GET['id'];
$postVersion = (int)$_GET['version'];
$post = $em->find('BlogPost', $postId, \Doctrine\DBAL\LockMode::OPTIMISTIC, $postVersion);
.. _transactions-and-concurrency_pessimistic-locking:
Pessimistic Locking
~~~~~~~~~~~~~~~~~~~
Doctrine ORM supports Pessimistic Locking at the database level. No
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
@@ -360,11 +317,11 @@ 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 ORM will throw an
Transaction Demarcation" described above. Doctrine 2 will throw an
Exception if you attempt to acquire an pessimistic lock and no
transaction is running.
Doctrine ORM currently supports two pessimistic lock modes:
Doctrine 2 currently supports two pessimistic lock modes:
- Pessimistic Write
@@ -374,7 +331,7 @@ Doctrine ORM currently supports two pessimistic lock modes:
locks other concurrent requests that attempt to update or lock rows
in write mode.
You can use pessimistic locks in four different scenarios:
You can use pessimistic locks in three different scenarios:
1. Using
@@ -386,10 +343,8 @@ You can use pessimistic locks in four different scenarios:
or
``EntityManager#lock($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``
3. Using
``EntityManager#refresh($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
or
``EntityManager#refresh($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``
4. Using
``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
or
``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``
-164
View File
@@ -1,164 +0,0 @@
Implementing a TypedFieldMapper
===============================
.. versionadded:: 2.14
You can specify custom typed field mapping between PHP type and DBAL type using ``Doctrine\ORM\Configuration``
and a custom ``Doctrine\ORM\Mapping\TypedFieldMapper`` implementation.
.. code-block:: php
<?php
$configuration->setTypedFieldMapper(new CustomTypedFieldMapper());
DefaultTypedFieldMapper
-----------------------
By default the ``Doctrine\ORM\Mapping\DefaultTypedFieldMapper`` is used, and you can pass an array of
PHP type => DBAL type mappings into its constructor to override the default behavior or add new mappings.
.. code-block:: php
<?php
use App\CustomIds\CustomIdObject;
use App\DBAL\Type\CustomIdObjectType;
use Doctrine\ORM\Mapping\DefaultTypedFieldMapper;
$configuration->setTypedFieldMapper(new DefaultTypedFieldMapper([
CustomIdObject::class => CustomIdObjectType::class,
]));
Then, an entity using the ``CustomIdObject`` typed field will be correctly assigned its DBAL type
(``CustomIdObjectType``) without the need of explicit declaration.
.. configuration-block::
.. code-block:: attribute
<?php
#[ORM\Entity]
#[ORM\Table(name: 'cms_users_typed_with_custom_typed_field')]
class UserTypedWithCustomTypedField
{
#[ORM\Column]
public CustomIdObject $customId;
// ...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="UserTypedWithCustomTypedField">
<field name="customId"/>
<!-- -->
</entity>
</doctrine-mapping>
.. code-block:: yaml
UserTypedWithCustomTypedField:
type: entity
fields:
customId: ~
It is perfectly valid to override even the "automatic" mapping rules mentioned above:
.. code-block:: php
<?php
use App\DBAL\Type\CustomIntType;
use Doctrine\ORM\Mapping\DefaultTypedFieldMapper;
$configuration->setTypedFieldMapper(new DefaultTypedFieldMapper([
'int' => CustomIntType::class,
]));
.. note::
If chained, once the first ``TypedFieldMapper`` assigns a type to a field, the ``DefaultTypedFieldMapper`` will
ignore its mapping and not override it anymore (if it is later in the chain). See below for chaining type mappers.
TypedFieldMapper interface
-------------------------
The interface ``Doctrine\ORM\Mapping\TypedFieldMapper`` allows you to implement your own
typed field mapping logic. It consists of just one function
.. code-block:: php
<?php
/**
* Validates & completes the given field mapping based on typed property.
*
* @param array{fieldName: string, enumType?: string, type?: mixed} $mapping The field mapping to validate & complete.
* @param \ReflectionProperty $field
*
* @return array{fieldName: string, enumType?: string, type?: mixed} The updated mapping.
*/
public function validateAndComplete(array $mapping, ReflectionProperty $field): array;
ChainTypedFieldMapper
---------------------
The class ``Doctrine\ORM\Mapping\ChainTypedFieldMapper`` allows you to chain multiple ``TypedFieldMapper`` instances.
When being evaluated, the ``TypedFieldMapper::validateAndComplete`` is called in the order in which
the instances were supplied to the ``ChainTypedFieldMapper`` constructor.
.. code-block:: php
<?php
use App\DBAL\Type\CustomIntType;
use Doctrine\ORM\Mapping\ChainTypedFieldMapper;
use Doctrine\ORM\Mapping\DefaultTypedFieldMapper;
$configuration->setTypedFieldMapper(
new ChainTypedFieldMapper(
new DefaultTypedFieldMapper(['int' => CustomIntType::class,]),
new CustomTypedFieldMapper()
)
);
Implementing a TypedFieldMapper
-------------------------------
If you want to assign all ``BackedEnum`` fields to your custom ``BackedEnumDBALType`` or you want to use different
DBAL types based on whether the entity field is nullable or not, you can achieve this by implementing your own
typed field mapper.
You need to create a class which implements ``Doctrine\ORM\Mapping\TypedFieldMapper``.
.. code-block:: php
<?php
final class CustomEnumTypedFieldMapper implements TypedFieldMapper
{
/**
* {@inheritDoc}
*/
public function validateAndComplete(array $mapping, ReflectionProperty $field): array
{
$type = $field->getType();
if (
! isset($mapping['type'])
&& ($type instanceof ReflectionNamedType)
) {
if (! $type->isBuiltin() && enum_exists($type->getName())) {
$mapping['type'] = BackedEnumDBALType::class;
}
}
return $mapping;
}
}
.. note::
Note that this case checks whether the mapping is already assigned, and if yes, it skips it. This is up to your
implementation. You can make a "greedy" mapper which will always override the mapping with its own type, or one
that behaves like the ``DefaultTypedFieldMapper`` and does not modify the type once its set prior in the chain.
@@ -15,12 +15,12 @@ Bidirectional Associations
The following rules apply to **bidirectional** associations:
- The inverse side has to have the ``mappedBy`` attribute of the OneToOne,
OneToMany, or ManyToMany mapping declaration. The ``mappedBy``
- The inverse side has to use the ``mappedBy`` attribute of the OneToOne,
OneToMany, or ManyToMany mapping declaration. The mappedBy
attribute contains the name of the association-field on the owning side.
- The owning side has to have the ``inversedBy`` attribute of the
OneToOne, ManyToOne, or ManyToMany mapping declaration.
The ``inversedBy`` attribute contains the name of the association-field
- The owning side has to use the ``inversedBy`` attribute of the
OneToOne, ManyToOne, or ManyToMany mapping declaration.
The inversedBy attribute contains the name of the association-field
on the inverse-side.
- ManyToOne is always the owning side of a bidirectional association.
- OneToMany is always the inverse side of a bidirectional association.
@@ -39,7 +39,7 @@ 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 their responsibility). Doctrine needs to know which of these
(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.
+12 -9
View File
@@ -17,7 +17,7 @@ ask for an entity with a specific ID twice, it will return the same instance:
.. code-block:: php
public function testIdentityMap(): void
public function testIdentityMap()
{
$objectA = $this->entityManager->find('EntityName', 1);
$objectB = $this->entityManager->find('EntityName', 1);
@@ -34,11 +34,11 @@ will still end up with the same reference:
.. code-block:: php
public function testIdentityMapReference(): void
public function testIdentityMapReference()
{
$objectA = $this->entityManager->getReference('EntityName', 1);
// check entity is not initialized
$this->assertTrue($this->entityManager->isUninitializedObject($objectA));
// check for proxyinterface
$this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $objectA);
$objectB = $this->entityManager->find('EntityName', 1);
@@ -102,9 +102,9 @@ 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
This means you map php objects into a relational database that don't
necessarily know about the database at all. A natural question would now be,
"how does Doctrine even detect objects have changed?".
"how does Doctrine even detect objects have changed?".
For this Doctrine keeps a second map inside the UnitOfWork. Whenever you fetch
an object from the database Doctrine will keep a copy of all the properties and
@@ -129,10 +129,12 @@ optimize the performance of the Flush Operation:
- Temporarily mark entities as read only. If you have a very large UnitOfWork
but know that a large set of entities has not changed, just mark them as read
only with ``$entityManager->getUnitOfWork()->markReadOnly($entity)``.
- Flush only a single entity with ``$entityManager->flush($entity)``.
- Use :doc:`Change Tracking Policies <change-tracking-policies>` to use more
explicit strategies of notifying the UnitOfWork what objects/properties
changed.
Query Internals
---------------
@@ -146,15 +148,15 @@ 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 they
wish to be hydrated. Default result-types include:
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 the most complex parts of Doctrine
Hydration to entities and arrays is one of most complex parts of Doctrine
algorithm-wise. It can build results with for example:
- Single table selects
@@ -196,3 +198,4 @@ ClassMetadataFactory
~~~~~~~~~~~~~~~~~~~~
tbr

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