Compare commits

..

241 Commits

Author SHA1 Message Date
Marco Pivetta
e3aa3f2d1d Preparing v2.5.8 release 2017-08-13 20:46:56 +02:00
Marco Pivetta
c83f479633 Merge pull request #6621 from Powerhamster/patch-1
fixed undefined variable
2017-08-13 20:27:18 +02:00
Thomas Rothe
741f1db198 fixed undefined variable
changed $conditions to $condition so $isConditionalJoin is working
2017-08-12 11:33:11 +02:00
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" ( e501137d1a )
See my comment on a3ece3b419
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" ( 192da14842 ).
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 e501137d1a
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

dd47003641 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
950 changed files with 16937 additions and 28495 deletions

4
.coveralls.yml Normal file
View File

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

1
.gitattributes vendored
View File

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

3
.gitignore vendored
View File

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

View File

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

View File

@@ -1,100 +1,30 @@
dist: trusty
sudo: false
language: php
php:
- 7.1
- 7.2
- nightly
- 5.4
- 5.5
- 5.6
- 7.0
env:
- DB=sqlite
- DB=mysql
- DB=pgsql
- DB=sqlite
before_install:
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{,.disabled} || echo "xdebug not available"
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
install: travis_retry composer update --prefer-dist
- composer install --prefer-source --dev
script:
- if [[ "$DB" == "mysql" || "$DB" == "mariadb" ]]; then mysql -e "CREATE SCHEMA doctrine_tests; GRANT ALL PRIVILEGES ON doctrine_tests.* to travis@'%'"; fi
- ENABLE_SECOND_LEVEL_CACHE=0 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml
- ENABLE_SECOND_LEVEL_CACHE=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
jobs:
include:
- stage: Test
env: DB=mariadb
addons:
mariadb: 10.1
- stage: Test
env: DB=mysql MYSQL_VERSION=5.7
php: 7.1
before_script:
- ./tests/travis/install-mysql-$MYSQL_VERSION.sh
sudo: required
- stage: Test
env: DB=mysql MYSQL_VERSION=5.7
php: 7.2
before_script:
- ./tests/travis/install-mysql-$MYSQL_VERSION.sh
sudo: required
- stage: Test
env: DB=mysql MYSQL_VERSION=5.7
php: nightly
before_script:
- ./tests/travis/install-mysql-$MYSQL_VERSION.sh
sudo: required
- stage: Test
env: DB=sqlite DEPENDENCIES=low
install: travis_retry composer update --prefer-dist --prefer-lowest
- stage: Test
if: type = cron
env: DB=sqlite DEV_DEPENDENCIES
install:
- composer config minimum-stability dev
- travis_retry composer update --prefer-dist
- stage: Test
env: DB=sqlite COVERAGE
before_script:
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{.disabled,}
- if [[ ! $(php -m | grep -si xdebug) ]]; then echo "xdebug required for coverage"; exit 1; fi
script:
- ENABLE_SECOND_LEVEL_CACHE=0 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml --coverage-clover ./build/logs/clover.xml
after_script:
- wget https://scrutinizer-ci.com/ocular.phar
- php ocular.phar code-coverage:upload --format=php-clover build/logs/clover.xml
- stage: Code Quality
env: DB=none STATIC_ANALYSIS
install: travis_retry composer update --prefer-dist --prefer-stable
before_script:
- echo "extension=memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
- echo "extension=redis.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
- travis_retry composer require --dev --prefer-dist --prefer-stable phpstan/phpstan:^0.9
script: vendor/bin/phpstan analyse -l 1 -c phpstan.neon lib
- stage: Code Quality
env: DB=none BENCHMARK
before_script: wget https://phpbench.github.io/phpbench/phpbench.phar https://phpbench.github.io/phpbench/phpbench.phar.pubkey
script: php phpbench.phar run -l dots --report=default
- stage: Code Quality
env: DB=none CODING_STANDARDS
php: nightly
script:
- ./vendor/bin/phpcs
matrix:
fast_finish: true
allow_failures:
- php: nightly
- php: 7.0
cache:
directories:

View File

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

View File

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

30
README.markdown Normal file
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

View File

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

View File

@@ -1,46 +1,5 @@
# Upgrade to 2.6
## Minor BC BREAK: `Doctrine\ORM\Tools\Console\ConsoleRunner` is now final
Since it's just an utilitarian class and should not be inherited.
## Minor BC BREAK: removed `Doctrine\ORM\Query\QueryException::associationPathInverseSideNotSupported()`
Method `Doctrine\ORM\Query\QueryException::associationPathInverseSideNotSupported()`
now has a required parameter `$pathExpr`.
## Minor BC BREAK: removed `Doctrine\ORM\Query\Parser#isInternalFunction()`
Method `Doctrine\ORM\Query\Parser#isInternalFunction()` was removed because
the distinction between internal function and user defined DQL was removed.
[#6500](https://github.com/doctrine/doctrine2/pull/6500)
## Minor BC BREAK: removed `Doctrine\ORM\ORMException#overwriteInternalDQLFunctionNotAllowed()`
Method `Doctrine\ORM\Query\Parser#overwriteInternalDQLFunctionNotAllowed()` was
removed because of the choice to allow users to overwrite internal functions, ie
`AVG`, `SUM`, `COUNT`, `MIN` and `MAX`. [#6500](https://github.com/doctrine/doctrine2/pull/6500)
## PHP 7.1 is now required
Doctrine 2.6 now requires PHP 7.1 or newer.
As a consequence, automatic cache setup in Doctrine\ORM\Tools\Setup::create*Configuration() was changed:
- APCu extension (ext-apcu) will now be used instead of abandoned APC (ext-apc).
- Memcached extension (ext-memcached) will be used instead of obsolete Memcache (ext-memcache).
- XCache support was dropped as it doesn't work with PHP 7.
# Upgrade to 2.5
## Minor BC BREAK: removed `Doctrine\ORM\Query\SqlWalker#walkCaseExpression()`
Method `Doctrine\ORM\Query\SqlWalker#walkCaseExpression()` was unused and part
of the internal API of the ORM, so it was removed. [#5600](https://github.com/doctrine/doctrine2/pull/5600).
## Minor BC BREAK: removed $className parameter on `AbstractEntityInheritancePersister#getSelectJoinColumnSQL()`
As `$className` parameter was not used in the method, it was safely removed.
## Minor BC BREAK: query cache key time is now a float
As of 2.5.5, the `QueryCacheEntry#time` property will contain a float value

View File

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

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

@@ -20,19 +20,16 @@
use Symfony\Component\Console\Helper\HelperSet;
use Doctrine\ORM\Tools\Console\ConsoleRunner;
$autoloadFiles = [
__DIR__ . '/../vendor/autoload.php',
__DIR__ . '/../../../autoload.php'
];
$autoloadFiles = array(__DIR__ . '/../vendor/autoload.php',
__DIR__ . '/../../../autoload.php');
foreach ($autoloadFiles as $autoloadFile) {
if (file_exists($autoloadFile)) {
require_once $autoloadFile;
break;
}
}
$directories = [getcwd(), getcwd() . DIRECTORY_SEPARATOR . 'config'];
$directories = array(getcwd(), getcwd() . DIRECTORY_SEPARATOR . 'config');
$configFile = null;
foreach ($directories as $directory) {
@@ -53,7 +50,7 @@ if ( ! is_readable($configFile)) {
exit(1);
}
$commands = [];
$commands = array();
$helperSet = require $configFile;
@@ -66,4 +63,4 @@ if ( ! ($helperSet instanceof HelperSet)) {
}
}
ConsoleRunner::run($helperSet, $commands);
\Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet, $commands);

View File

@@ -9,48 +9,39 @@
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"},
{"name": "Marco Pivetta", "email": "ocramius@gmail.com"}
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"}
],
"config": {
"sort-packages": true
},
"minimum-stability": "dev",
"require": {
"php": "^7.1",
"php": ">=5.4",
"ext-pdo": "*",
"doctrine/annotations": "~1.5",
"doctrine/cache": "~1.6",
"doctrine/collections": "^1.4",
"doctrine/common": "^2.7.1",
"doctrine/dbal": "^2.6",
"doctrine/instantiator": "~1.1",
"symfony/console": "~3.0|~4.0"
"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": "^1.0",
"phpunit/phpunit": "^6.5",
"squizlabs/php_codesniffer": "^3.2",
"symfony/yaml": "~3.4|~4.0"
"symfony/yaml": "~2.3|~3.0",
"phpunit/phpunit": "~4.0"
},
"suggest": {
"symfony/yaml": "If you want to use YAML Metadata Mapping Driver"
},
"autoload": {
"psr-4": { "Doctrine\\ORM\\": "lib/Doctrine/ORM" }
"psr-0": { "Doctrine\\ORM\\": "lib/" }
},
"autoload-dev": {
"psr-4": {
"Doctrine\\Tests\\": "tests/Doctrine/Tests",
"Doctrine\\Performance\\": "tests/Doctrine/Performance"
}
"psr-0": { "Doctrine\\Tests\\": "tests/" }
},
"bin": ["bin/doctrine"],
"bin": ["bin/doctrine", "bin/doctrine.php"],
"extra": {
"branch-alias": {
"dev-master": "2.6.x-dev"
}
},
"archive": {
"exclude": ["!vendor", "tests", "*phpunit.xml", ".travis.yml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp"]
"exclude": ["!vendor", "tests", "*phpunit.xml", ".travis.yml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp", "*coveralls.yml"]
}
}

View File

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

View File

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

View File

@@ -1,9 +1,9 @@
What is new in Doctrine ORM 2.5?
================================
This document describes changes between Doctrine ORM 2.4 and 2.5.
It contains a description of all the new features and sections about
behavioral changes and potential backwards compatibility breaks.
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
@@ -21,7 +21,7 @@ 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 #a90629 <https://github.com/doctrine/doctrine2/commit/a906295c65f1516737458fbee2f6fa96254f27a5>`_
- `Commit <https://github.com/doctrine/doctrine2/commit/a906295c65f1516737458fbee2f6fa96254f27a5>`_
Events: Add API to programatically add event listeners to Entity
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -58,11 +58,11 @@ metadata generation:
}
}
Embeddable Objects
~~~~~~~~~~~~~~~~~~
Embeddedable Objects
~~~~~~~~~~~~~~~~~~~~
Doctrine now supports creating multiple PHP objects from one database table
implementing a feature called "Embeddable Objects". Next to an ``@Entity``
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
@@ -102,7 +102,7 @@ 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 #835 <https://github.com/doctrine/doctrine2/pull/835>`_
- `Pull Request <https://github.com/doctrine/doctrine2/pull/835>`_
Second-Level-Cache
~~~~~~~~~~~~~~~~~~
@@ -160,7 +160,7 @@ instead of the database.
- `Documentation
<http://docs.doctrine-project.org/en/latest/reference/second-level-cache.html>`_
- `Pull Request #808 <https://github.com/doctrine/doctrine2/pull/808>`_
- `Pull Request <https://github.com/doctrine/doctrine2/pull/808>`_
Criteria API: Support for ManyToMany assocations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -217,8 +217,8 @@ trigger a full load of the collection.
This feature was contributed by `Michaël Gallego <https://github.com/bakura10>`_.
- `Pull Request #882 <https://github.com/doctrine/doctrine2/pull/882>`_
- `Pull Request #1032 <https://github.com/doctrine/doctrine2/pull/1032>`_
- `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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -241,7 +241,7 @@ only with a schema event listener before.
This feature was contributed by `Adrian Olek <https://github.com/adrianolek>`_.
- `Pull Request #973 <https://github.com/doctrine/doctrine2/pull/973>`_
- `Pull Request <https://github.com/doctrine/doctrine2/pull/973>`_
SQLFilter API: Check if a parameter is set
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -272,7 +272,7 @@ Extending on the locale example of the documentation:
This feature was contributed by `Miroslav Demovic <https://github.com/mdemo>`_
- `Pull Request #963 <https://github.com/doctrine/doctrine2/pull/963>`_
- `Pull Request <https://github.com/doctrine/doctrine2/pull/963>`_
EXTRA_LAZY Improvements
@@ -301,7 +301,7 @@ EXTRA_LAZY Improvements
This feature was contributed by `Asmir Mustafic <https://github.com/goetas>`_
- `Pull Request #937 <https://github.com/doctrine/doctrine2/pull/937>`_
- `Pull Request <https://github.com/doctrine/doctrine2/pull/937>`_
2. Add EXTRA_LAZY Support for get() for owning and inverse many-to-many
@@ -415,11 +415,11 @@ object:
This feature was contributed by `Michael Perrin
<https://github.com/michaelperrin>`_.
- `Pull Request #590 <https://github.com/doctrine/doctrine2/pull/590>`_
- `Pull Request <https://github.com/doctrine/doctrine2/pull/590>`_
- `DDC-2319 <http://doctrine-project.org/jira/browse/DDC-2319>`_
Query API: Add support for default Query Hints
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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
@@ -439,7 +439,7 @@ It is now possible to add query hints that are always enabled for every Query:
This feature was contributed by `Artur Eshenbrener
<https://github.com/Strate>`_.
- `Pull Request #863 <https://github.com/doctrine/doctrine2/pull/863>`_
- `Pull Request <https://github.com/doctrine/doctrine2/pull/863>`_
ResultSetMappingBuilder: Add support for Single-Table Inheritance
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -486,8 +486,8 @@ EntityGenerator Command: Avoid backups
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When calling the EntityGenerator for an existing entity, Doctrine would
create a backup file every time to avoid losing changes to the code. You
can now skip generating the backup file by passing the ``--no-backup``
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:
::
@@ -708,4 +708,3 @@ From now on, the resultset will look like this:
),
...
)

View File

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

View File

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

View File

@@ -132,7 +132,7 @@ dql statement.
The ``ArithmeticPrimary`` method call is the most common
denominator of valid EBNF tokens taken from the
`DQL EBNF grammar <http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html#ebnf>`_
`DQL EBNF grammar <http://www.doctrine-project.org/documentation/manual/2_0/en/dql-doctrine-query-language#ebnf>`_
that matches our requirements for valid input into the DateDiff Dql
function. Picking the right tokens for your methods is a tricky
business, but the EBNF grammar is pretty helpful finding it, as is

View File

@@ -4,7 +4,7 @@ Implementing Wakeup or Clone
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
As explained in the
`restrictions for entity classes in the manual <http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/architecture.html#entities>`_,
`restrictions for entity classes in the manual <http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities>`_,
it is usually not allowed for an entity to implement ``__wakeup``
or ``__clone``, because Doctrine makes special use of them.
However, it is quite easy to make use of these methods in a safe

View File

@@ -111,7 +111,7 @@ APC, get rid of EchoSqlLogger, and turn off
autoGenerateProxyClasses.
For more details, consult the
`Doctrine 2 Configuration documentation <http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/configuration.html>`_.
`Doctrine 2 Configuration documentation <http://www.doctrine-project.org/documentation/manual/2_0/en/configuration#configuration-options>`_.
Now to use it
-------------

View File

@@ -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');``.
@@ -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:

View File

@@ -41,9 +41,7 @@ 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) {

View File

@@ -89,7 +89,7 @@ events for one method, this will happen before Beta 1 though.
Now validation is performed whenever you call
``EntityManager#persist($order)`` or when you call
``EntityManager#flush()`` and an order is about to be updated. Any
Exception that happens in the lifecycle callbacks will be caught by
Exception that happens in the lifecycle callbacks will be cached by
the EntityManager and the current transaction is rolled back.
Of course you can do any type of primitive checks, not null,

View File

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

View File

@@ -152,7 +152,6 @@ The recommended implementations for production are:
- ``Doctrine\Common\Cache\ApcCache``
- ``Doctrine\Common\Cache\ApcuCache``
- ``Doctrine\Common\Cache\MemcacheCache``
- ``Doctrine\Common\Cache\XcacheCache``
- ``Doctrine\Common\Cache\RedisCache``
@@ -184,7 +183,6 @@ The recommended implementations for production are:
- ``Doctrine\Common\Cache\ApcCache``
- ``Doctrine\Common\Cache\ApcuCache``
- ``Doctrine\Common\Cache\MemcacheCache``
- ``Doctrine\Common\Cache\XcacheCache``
- ``Doctrine\Common\Cache\RedisCache``
@@ -396,7 +394,7 @@ means that you have to register a special autoloader for these classes:
.. code-block:: php
<?php
use Doctrine\Common\Proxy\Autoloader;
use Doctrine\ORM\Proxy\Autoloader;
$proxyDir = "/path/to/proxies";
$proxyNamespace = "MyProxies";

View File

@@ -37,11 +37,8 @@ Index
- :ref:`@ColumnResult <annref_column_result>`
- :ref:`@Cache <annref_cache>`
- :ref:`@ChangeTrackingPolicy <annref_changetrackingpolicy>`
- :ref:`@CustomIdGenerator <annref_customidgenerator>`
- :ref:`@DiscriminatorColumn <annref_discriminatorcolumn>`
- :ref:`@DiscriminatorMap <annref_discriminatormap>`
- :ref:`@Embeddable <annref_embeddable>`
- :ref:`@Embedded <annref_embedded>`
- :ref:`@Entity <annref_entity>`
- :ref:`@EntityResult <annref_entity_result>`
- :ref:`@FieldResult <annref_field_result>`
@@ -113,7 +110,7 @@ Optional attributes:
- **unique**: Boolean value to determine if the value of the column
should be unique across all rows of the underlying entities table.
- **nullable**: Determines if NULL values allowed for this column. If not specified, default value is false.
- **nullable**: Determines if NULL values allowed for this column.
- **options**: Array of additional options:
@@ -134,9 +131,6 @@ Optional attributes:
- ``collation``: The collation of the column (only supported by Drizzle, Mysql, PostgreSQL>=9.1, Sqlite and SQLServer).
- ``check``: Adds a check constraint type to the column (might not
be supported by all vendors).
- **columnDefinition**: DDL SQL snippet that starts after the column
name and specifies the complete (non-portable!) column definition.
This attribute allows to make use of advanced RMDBS features.
@@ -237,43 +231,16 @@ Example:
*/
class User {}
.. _annref_customidgenerator:
@CustomIdGenerator
~~~~~~~~~~~~~~~~~~~~~
This annotations allows you to specify a user-provided class to generate identifiers. This annotation only works when both :ref:`@Id <annref_id>` and :ref:`@GeneratedValue(strategy="CUSTOM") <annref_generatedvalue>` are specified.
Required attributes:
- **class**: name of the class which should extend Doctrine\ORM\Id\AbstractIdGenerator
Example:
.. code-block:: php
<?php
/**
* @Id
* @Column(type="integer")
* @GeneratedValue(strategy="CUSTOM")
* @CustomIdGenerator(class="My\Namespace\MyIdGenerator")
*/
public $id;
.. _annref_discriminatorcolumn:
@DiscriminatorColumn
~~~~~~~~~~~~~~~~~~~~~
This annotation is an optional annotation for the topmost/super
This annotation is a required annotation for the topmost/super
class of an inheritance hierarchy. It specifies the details of the
column which saves the name of the class, which the entity is
actually instantiated as.
If this annotation is not specified, the discriminator column defaults
to a string column of length 255 called ``dtype``.
Required attributes:
@@ -312,67 +279,6 @@ depending on whether the classes are in the namespace or not.
// ...
}
.. _annref_embeddable:
@Embeddable
~~~~~~~~~~~~~~~~~~~~~
The embeddable annotation is required on a class, in order to make it
embeddable inside an entity. It works together with the :ref:`@Embedded <annref_embedded>`
annotation to establish the relationship between the two classes.
.. code-block:: php
<?php
/**
* @Embeddable
*/
class Address
{
// ...
class User
{
/**
* @Embedded(class = "Address")
*/
private $address;
.. _annref_embedded:
@Embedded
~~~~~~~~~~~~~~~~~~~~~
The embedded annotation is required on an entity's member variable,
in order to specify that it is an embedded class.
Required attributes:
- **class**: The embeddable class
.. code-block:: php
<?php
// ...
class User
{
/**
* @Embedded(class = "Address")
*/
private $address;
/**
* @Embeddable
*/
class Address
{
// ...
.. _annref_entity:
@Entity
@@ -451,12 +357,11 @@ conjunction with @Id.
If this annotation is not specified with @Id the NONE strategy is
used as default.
Optional attributes:
Required attributes:
- **strategy**: Set the name of the identifier generation strategy.
Valid values are AUTO, SEQUENCE, TABLE, IDENTITY, UUID, CUSTOM and NONE.
If not specified, default value is AUTO.
Example:
@@ -1306,13 +1211,12 @@ Example with partial indexes:
.. _annref_version:
@Version
~~~~~~~~
~~~~~~~~~~~~~~
Marker annotation that defines a specified column as version attribute used in
an :ref:`optimistic locking <transactions-and-concurrency_optimistic-locking>`
scenario. It only works on :ref:`@Column <annref_column>` annotations that have
the type ``integer`` or ``datetime``. Combining ``@Version`` with
:ref:`@Id <annref_id>` is not supported.
Marker annotation that defines a specified column as version
attribute used in an optimistic locking scenario. It only works on
:ref:`@Column <annref_column>` annotations that have the type integer or
datetime. Combining @Version with :ref:`@Id <annref_id>` is not supported.
Example:

View File

@@ -16,31 +16,20 @@ This chapter is split into three different sections.
- :ref:`association_mapping_defaults` are explained that simplify the use-case examples.
- :ref:`collections` are introduced that contain entities in associations.
One tip for working with relations is to read the relation from left to right, where the left word refers to the current Entity. For example:
- OneToMany - One instance of the current Entity has Many instances (references) to the refered Entity.
- ManyToOne - Many instances of the current Entity refer to One instance of the refered Entity.
- OneToOne - One instance of the current Entity refers to One instance of the refered Entity.
See below for all the possible relations.
An association is considered to be unidirectional if only one side of the association has
a property referring to the other side.
To gain a full understanding of associations you should also read about :doc:`owning and
inverse sides of associations <unitofwork-associations>`
Many-To-One, Unidirectional
---------------------------
A many-to-one association is the most common association between objects. Example: Many Users have One Address:
A many-to-one association is the most common association between objects.
.. configuration-block::
.. code-block:: php
<?php
/** @Entity */
/** @Entity **/
class User
{
// ...
@@ -48,11 +37,11 @@ A many-to-one association is the most common association between objects. Exampl
/**
* @ManyToOne(targetEntity="Address")
* @JoinColumn(name="address_id", referencedColumnName="id")
*/
**/
private $address;
}
/** @Entity */
/** @Entity **/
class Address
{
// ...
@@ -107,30 +96,31 @@ One-To-One, Unidirectional
--------------------------
Here is an example of a one-to-one association with a ``Product`` entity that
references one ``Shipment`` entity.
references one ``Shipping`` entity. The ``Shipping`` does not reference back to
the ``Product`` so that the reference is said to be unidirectional, in one
direction only.
.. configuration-block::
.. code-block:: php
<?php
/** @Entity */
/** @Entity **/
class Product
{
// ...
/**
* One Product has One Shipment.
* @OneToOne(targetEntity="Shipment")
* @JoinColumn(name="shipment_id", referencedColumnName="id")
*/
private $shipment;
* @OneToOne(targetEntity="Shipping")
* @JoinColumn(name="shipping_id", referencedColumnName="id")
**/
private $shipping;
// ...
}
/** @Entity */
class Shipment
/** @Entity **/
class Shipping
{
// ...
}
@@ -139,8 +129,8 @@ references one ``Shipment`` entity.
<doctrine-mapping>
<entity class="Product">
<one-to-one field="shipment" target-entity="Shipment">
<join-column name="shipment_id" referenced-column-name="id" />
<one-to-one field="shipping" target-entity="Shipping">
<join-column name="shipping_id" referenced-column-name="id" />
</one-to-one>
</entity>
</doctrine-mapping>
@@ -150,10 +140,10 @@ references one ``Shipment`` entity.
Product:
type: entity
oneToOne:
shipment:
targetEntity: Shipment
shipping:
targetEntity: Shipping
joinColumn:
name: shipment_id
name: shipping_id
referencedColumnName: id
Note that the @JoinColumn is not really necessary in this example,
@@ -165,15 +155,15 @@ Generated MySQL Schema:
CREATE TABLE Product (
id INT AUTO_INCREMENT NOT NULL,
shipment_id INT DEFAULT NULL,
UNIQUE INDEX UNIQ_6FBC94267FE4B2B (shipment_id),
shipping_id INT DEFAULT NULL,
UNIQUE INDEX UNIQ_6FBC94267FE4B2B (shipping_id),
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE Shipment (
CREATE TABLE Shipping (
id INT AUTO_INCREMENT NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
ALTER TABLE Product ADD FOREIGN KEY (shipment_id) REFERENCES Shipment(id);
ALTER TABLE Product ADD FOREIGN KEY (shipping_id) REFERENCES Shipping(id);
One-To-One, Bidirectional
-------------------------
@@ -182,39 +172,33 @@ Here is a one-to-one relationship between a ``Customer`` and a
``Cart``. The ``Cart`` has a reference back to the ``Customer`` so
it is bidirectional.
Here we see the ``mappedBy`` and ``inversedBy`` annotations for the first time.
They are used to tell Doctrine which property on the other side refers to the
object.
.. configuration-block::
.. code-block:: php
<?php
/** @Entity */
/** @Entity **/
class Customer
{
// ...
/**
* One Customer has One Cart.
* @OneToOne(targetEntity="Cart", mappedBy="customer")
*/
**/
private $cart;
// ...
}
/** @Entity */
/** @Entity **/
class Cart
{
// ...
/**
* One Cart has One Customer.
* @OneToOne(targetEntity="Customer", inversedBy="cart")
* @JoinColumn(name="customer_id", referencedColumnName="id")
*/
**/
private $customer;
// ...
@@ -267,9 +251,8 @@ Generated MySQL Schema:
) ENGINE = InnoDB;
ALTER TABLE Cart ADD FOREIGN KEY (customer_id) REFERENCES Customer(id);
We had a choice of sides on which to place the ``inversedBy`` attribute. Because it
is on the ``Cart``, that is the owning side of the relation, and thus holds the
foreign key.
See how the foreign key is defined on the owning side of the
relation, the table ``Cart``.
One-To-One, Self-referencing
----------------------------
@@ -280,16 +263,15 @@ below.
.. code-block:: php
<?php
/** @Entity */
/** @Entity **/
class Student
{
// ...
/**
* One Student has One Student.
* @OneToOne(targetEntity="Student")
* @JoinColumn(name="mentor_id", referencedColumnName="id")
*/
**/
private $mentor;
// ...
@@ -312,16 +294,15 @@ With the generated MySQL Schema:
One-To-Many, Bidirectional
--------------------------
A one-to-many association has to be bidirectional, unless you are using a
join table. This is because the many side in a one-to-many association holds
the foreign key, making it the owning side. Doctrine needs the many side
defined in order to understand the association.
A one-to-many association has to be bidirectional, unless you are using an
additional join-table. This is necessary, because of the foreign key
in a one-to-many association being defined on the "many" side. Doctrine
needs a many-to-one association that defines the mapping of this
foreign key.
This bidirectional mapping requires the ``mappedBy`` attribute on the
"one" side and the ``inversedBy`` attribute on the "many" side.
This means there is no difference between a bidirectional one-to-many and a
bidirectional many-to-one.
``OneToMany`` association and the ``inversedBy`` attribute on the ``ManyToOne``
association.
.. configuration-block::
@@ -330,14 +311,13 @@ bidirectional many-to-one.
<?php
use Doctrine\Common\Collections\ArrayCollection;
/** @Entity */
/** @Entity **/
class Product
{
// ...
/**
* One Product has Many Features.
* @OneToMany(targetEntity="Feature", mappedBy="product")
*/
**/
private $features;
// ...
@@ -346,15 +326,14 @@ bidirectional many-to-one.
}
}
/** @Entity */
/** @Entity **/
class Feature
{
// ...
/**
* Many Features have One Product.
* @ManyToOne(targetEntity="Product", inversedBy="features")
* @JoinColumn(name="product_id", referencedColumnName="id")
*/
**/
private $product;
// ...
}
@@ -423,19 +402,18 @@ The following example sets up such a unidirectional one-to-many association:
.. code-block:: php
<?php
/** @Entity */
/** @Entity **/
class User
{
// ...
/**
* Many User have Many Phonenumbers.
* @ManyToMany(targetEntity="Phonenumber")
* @JoinTable(name="users_phonenumbers",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)}
* )
*/
**/
private $phonenumbers;
public function __construct()
@@ -446,7 +424,7 @@ The following example sets up such a unidirectional one-to-many association:
// ...
}
/** @Entity */
/** @Entity **/
class Phonenumber
{
// ...
@@ -525,21 +503,19 @@ database perspective is known as an adjacency list approach.
.. code-block:: php
<?php
/** @Entity */
/** @Entity **/
class Category
{
// ...
/**
* One Category has Many Categories.
* @OneToMany(targetEntity="Category", mappedBy="parent")
*/
**/
private $children;
/**
* Many Categories have One Category.
* @ManyToOne(targetEntity="Category", inversedBy="children")
* @JoinColumn(name="parent_id", referencedColumnName="id")
*/
**/
private $parent;
// ...
@@ -596,19 +572,18 @@ entities:
.. code-block:: php
<?php
/** @Entity */
/** @Entity **/
class User
{
// ...
/**
* Many Users have Many Groups.
* @ManyToMany(targetEntity="Group")
* @JoinTable(name="users_groups",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
* )
*/
**/
private $groups;
// ...
@@ -618,7 +593,7 @@ entities:
}
}
/** @Entity */
/** @Entity **/
class Group
{
// ...
@@ -697,16 +672,15 @@ one is bidirectional.
.. code-block:: php
<?php
/** @Entity */
/** @Entity **/
class User
{
// ...
/**
* Many Users have Many Groups.
* @ManyToMany(targetEntity="Group", inversedBy="users")
* @JoinTable(name="users_groups")
*/
**/
private $groups;
public function __construct() {
@@ -716,14 +690,13 @@ one is bidirectional.
// ...
}
/** @Entity */
/** @Entity **/
class Group
{
// ...
/**
* Many Groups have Many Users.
* @ManyToMany(targetEntity="User", mappedBy="groups")
*/
**/
private $users;
public function __construct() {
@@ -781,22 +754,22 @@ one is bidirectional.
The MySQL schema is exactly the same as for the Many-To-Many
uni-directional case above.
Owning and Inverse Side on a ManyToMany Association
Owning and Inverse Side on a ManyToMany association
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For Many-To-Many associations you can chose which entity is the
owning and which the inverse side. There is a very simple semantic
rule to decide which side is more suitable to be the owning side
from a developers perspective. You only have to ask yourself which
entity is responsible for the connection management, and pick that
from a developers perspective. You only have to ask yourself, which
entity is responsible for the connection management and pick that
as the owning side.
Take an example of two entities ``Article`` and ``Tag``. Whenever
you want to connect an Article to a Tag and vice-versa, it is
mostly the Article that is responsible for this relation. Whenever
you add a new article, you want to connect it with existing or new
tags. Your "Create Article" form will probably support this notion
and allow specifying the tags directly. This is why you should pick
tags. Your create Article form will probably support this notion
and allow to specify the tags directly. This is why you should pick
the Article as owning side, as it makes the code more
understandable:
@@ -846,25 +819,23 @@ field named ``$friendsWithMe`` and ``$myFriends``.
.. code-block:: php
<?php
/** @Entity */
/** @Entity **/
class User
{
// ...
/**
* Many Users have Many Users.
* @ManyToMany(targetEntity="User", mappedBy="myFriends")
*/
**/
private $friendsWithMe;
/**
* Many Users have many Users.
* @ManyToMany(targetEntity="User", inversedBy="friendsWithMe")
* @JoinTable(name="friends",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="friend_user_id", referencedColumnName="id")}
* )
*/
**/
private $myFriends;
public function __construct() {
@@ -912,14 +883,14 @@ As an example, consider this mapping:
.. code-block:: php
<?php
/** @OneToOne(targetEntity="Shipment") */
private $shipment;
/** @OneToOne(targetEntity="Shipping") **/
private $shipping;
.. code-block:: xml
<doctrine-mapping>
<entity class="Product">
<one-to-one field="shipment" target-entity="Shipment" />
<one-to-one field="shipping" target-entity="Shipping" />
</entity>
</doctrine-mapping>
@@ -928,8 +899,8 @@ As an example, consider this mapping:
Product:
type: entity
oneToOne:
shipment:
targetEntity: Shipment
shipping:
targetEntity: Shipping
This is essentially the same as the following, more verbose,
mapping:
@@ -940,18 +911,17 @@ mapping:
<?php
/**
* One Product has One Shipment.
* @OneToOne(targetEntity="Shipment")
* @JoinColumn(name="shipment_id", referencedColumnName="id")
*/
private $shipment;
* @OneToOne(targetEntity="Shipping")
* @JoinColumn(name="shipping_id", referencedColumnName="id")
**/
private $shipping;
.. code-block:: xml
<doctrine-mapping>
<entity class="Product">
<one-to-one field="shipment" target-entity="Shipment">
<join-column name="shipment_id" referenced-column-name="id" />
<one-to-one field="shipping" target-entity="Shipping">
<join-column name="shipping_id" referenced-column-name="id" />
</one-to-one>
</entity>
</doctrine-mapping>
@@ -961,10 +931,10 @@ mapping:
Product:
type: entity
oneToOne:
shipment:
targetEntity: Shipment
shipping:
targetEntity: Shipping
joinColumn:
name: shipment_id
name: shipping_id
referencedColumnName: id
The @JoinTable definition used for many-to-many mappings has
@@ -978,7 +948,7 @@ similar defaults. As an example, consider this mapping:
class User
{
//...
/** @ManyToMany(targetEntity="Group") */
/** @ManyToMany(targetEntity="Group") **/
private $groups;
//...
}
@@ -1010,13 +980,12 @@ This is essentially the same as the following, more verbose, mapping:
{
//...
/**
* Many Users have Many Groups.
* @ManyToMany(targetEntity="Group")
* @JoinTable(name="User_Group",
* joinColumns={@JoinColumn(name="User_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="Group_id", referencedColumnName="id")}
* )
*/
**/
private $groups;
//...
}
@@ -1095,17 +1064,12 @@ and ``@ManyToMany`` associations in the constructor of your entities:
.. code-block:: php
<?php
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
/** @Entity */
/** @Entity **/
class User
{
/**
* Many Users have Many Groups.
* @var Collection
* @ManyToMany(targetEntity="Group")
*/
/** @ManyToMany(targetEntity="Group") **/
private $groups;
public function __construct()

View File

@@ -295,8 +295,7 @@ annotation.
class Message
{
/**
* @Id
* @Column(type="integer")
* @Id @Column(type="integer")
* @GeneratedValue
*/
private $id;
@@ -355,8 +354,6 @@ Here is the list of possible generation strategies:
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).
- ``UUID``: Tells Doctrine to use the built-in Universally Unique Identifier
generator. This strategy provides full portability.
- ``TABLE``: Tells Doctrine to use a separate table for ID
generation. This strategy provides full portability.
***This strategy is not yet implemented!***
@@ -364,8 +361,6 @@ Here is the list of possible generation strategies:
thus generated) by your code. The assignment must take place before
a new entity is passed to ``EntityManager#persist``. NONE is the
same as leaving off the @GeneratedValue entirely.
- ``CUSTOM``: With this option, you can use the ``@CustomIdGenerator`` annotation.
It will allow you to pass a :doc:`class of your own to generate the identifiers.<_annref_customidgenerator>`
Sequence Generator
^^^^^^^^^^^^^^^^^^
@@ -451,7 +446,7 @@ need to access the sequence once to generate the identifiers for
Composite Keys
~~~~~~~~~~~~~~
With Doctrine 2 you can use composite primary keys, using ``@Id`` on more then
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

View File

@@ -100,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

View File

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

View File

@@ -13,7 +13,7 @@ Cache Drivers
The cache drivers follow a simple interface that is defined in
``Doctrine\Common\Cache\Cache``. All the cache drivers extend a
base class ``Doctrine\Common\Cache\CacheProvider`` which implements
base class ``Doctrine\Common\Cache\AbstractCache`` which implements
this interface.
The interface defines the following public methods for you to implement:
@@ -21,10 +21,10 @@ 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 for x seconds. 0 = infinite time
- save($id, $data, $lifeTime = false) - Puts data into the cache
- delete($id) - Deletes a cache entry
Each driver extends the ``CacheProvider`` class which defines a few
Each driver extends the ``AbstractCache`` class which defines a few
abstract protected methods that each of the drivers must
implement:
@@ -38,13 +38,9 @@ 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 ``CacheProvider`` can build custom functionality on top of
the ``AbstractCache`` can build custom functionality on top of
these methods.
This documentation does not cover every single cache driver included
with Doctrine. For an up-to-date-list, see the
`cache directory on GitHub <https://github.com/doctrine/cache/tree/master/lib/Doctrine/Common/Cache>`.
APC
~~~
@@ -63,24 +59,6 @@ by itself.
$cacheDriver = new \Doctrine\Common\Cache\ApcCache();
$cacheDriver->save('cache_id', 'my_data');
APCu
~~~~
In order to use the APCu cache driver you must have it compiled and
enabled in your php.ini. You can read about APCu
`in the PHP Documentation <http://us2.php.net/apcu>`_. It will give
you a little background information about what it is and how you
can use it as well as how to install it.
Below is a simple example of how you could use the APCu cache driver
by itself.
.. code-block:: php
<?php
$cacheDriver = new \Doctrine\Common\Cache\ApcuCache();
$cacheDriver->save('cache_id', 'my_data');
Memcache
~~~~~~~~
@@ -104,7 +82,7 @@ driver by itself.
$cacheDriver->save('cache_id', 'my_data');
Memcached
~~~~~~~~~
~~~~~~~~
Memcached is a more recent and complete alternative extension to
Memcache.
@@ -305,7 +283,7 @@ use on your ORM configuration.
<?php
$config = new \Doctrine\ORM\Configuration();
$config->setQueryCacheImpl(new \Doctrine\Common\Cache\ApcuCache());
$config->setQueryCacheImpl(new \Doctrine\Common\Cache\ApcCache());
Result Cache
~~~~~~~~~~~~
@@ -318,7 +296,7 @@ cache implementation.
.. code-block:: php
<?php
$config->setResultCacheImpl(new \Doctrine\Common\Cache\ApcuCache());
$config->setResultCacheImpl(new \Doctrine\Common\Cache\ApcCache());
Now when you're executing DQL queries you can configure them to use
the result cache.
@@ -335,7 +313,7 @@ result cache driver.
.. code-block:: php
<?php
$query->setResultCacheDriver(new \Doctrine\Common\Cache\ApcuCache());
$query->setResultCacheDriver(new \Doctrine\Common\Cache\ApcCache());
.. note::
@@ -387,7 +365,7 @@ first.
.. code-block:: php
<?php
$config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcuCache());
$config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcCache());
Now the metadata information will only be parsed once and stored in
the cache driver.
@@ -423,39 +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.
Cache Chaining
--------------
A common pattern is to use a static cache to store data that is
requested many times in a single PHP request. Even though this data
may be stored in a fast memory cache, often that cache is over a
network link leading to sizable network traffic.
The ChainCache class allows multiple caches to be registered at once.
For example, a per-request ArrayCache can be used first, followed by
a (relatively) slower MemcacheCache if the ArrayCache misses.
ChainCache automatically handles pushing data up to faster caches in
the chain and clearing data in the entire stack when it is deleted.
A ChainCache takes a simple array of CacheProviders in the order that
they should be used.
.. code-block:: php
$arrayCache = new \Doctrine\Common\Cache\ArrayCache();
$memcache = new Memcache();
$memcache->connect('memcache_host', 11211);
$chainCache = new \Doctrine\Common\Cache\ChainCache([
$arrayCache,
$memcache,
]);
ChainCache itself extends the CacheProvider interface, so it is
possible to create chains of chains. While this may seem like an easy
way to build a simple high-availability cache, ChainCache does not
implement any exception handling so using it as a high-availability
mechanism is not recommended.
Cache Slams
-----------

View File

@@ -79,13 +79,6 @@ Or if you prefer YAML:
$config = Setup::createYAMLMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);
.. note::
If you want to use yml mapping you should add yaml dependency to your `composer.json`:
::
"symfony/yaml": "*"
Inside the ``Setup`` methods several assumptions are made:
- If `$isDevMode` is true caching is done in memory with the ``ArrayCache``. Proxy objects are recreated on every request.

View File

@@ -2,7 +2,7 @@ Doctrine Query Language
===========================
DQL stands for Doctrine Query Language and is an Object
Query Language derivative that is very similar to the Hibernate
Query Language derivate that is very similar to the Hibernate
Query Language (HQL) or the Java Persistence Query Language (JPQL).
In essence, DQL provides powerful querying capabilities over your
@@ -34,9 +34,9 @@ object model.
DQL SELECT statements are a very powerful way of retrieving parts
of your domain model that are not accessible via associations.
Additionally they allow you to retrieve entities and their associations
Additionally they allow to retrieve entities and their associations
in one single SQL select statement which can make a huge difference
in performance compared to using several queries.
in performance in contrast to using several queries.
DQL UPDATE and DELETE statements offer a way to execute bulk
changes on the entities of your domain model. This is often
@@ -49,6 +49,10 @@ SELECT queries
DQL SELECT clause
~~~~~~~~~~~~~~~~~
The select clause of a DQL query specifies what appears in the
query result. The composition of all the expressions in the select
clause also influences the nature of the query result.
Here is an example that selects all users with an age > 20:
.. code-block:: php
@@ -79,58 +83,14 @@ Lets examine the query:
The result of this query would be a list of User objects where all
users are older than 20.
Result format
~~~~~~~~~~~~~
The composition of the expressions in the SELECT clause also
influences the nature of the query result. There are three
cases:
**All objects**
.. code-block:: sql
SELECT u, p, n FROM Users u...
In this case, the result will be an array of User objects because of
the FROM clause, with children ``p`` and ``n`` hydrated because of
their inclusion in the SELECT clause.
**All scalars**
.. code-block:: sql
SELECT u.name, u.address FROM Users u...
In this case, the result will be an array of arrays. In the example
above, each element of the result array would be an array of the
scalar name and address values.
You can select scalars from any entity in the query.
**Mixed**
.. code-block:: sql
``SELECT u, p.quantity FROM Users u...``
Here, the result will again be an array of arrays, with each element
being an array made up of a User object and the scalar value
``p.quantity``.
Multiple FROM clauses are allowed, which would cause the result
array elements to cycle through the classes included in the
multiple FROM clauses.
.. note::
You cannot select other entities unless you also select the
root of the selection (which is the first entity in FROM).
For example, ``SELECT p,n FROM Users u...`` would be wrong because
``u`` is not part of the SELECT
Doctrine throws an exception if you violate this constraint.
The SELECT clause allows to specify both class identification
variables that signal the hydration of a complete entity class or
just fields of the entity using the syntax ``u.name``. Combinations
of both are also allowed and it is possible to wrap both fields and
identification values into aggregation and DQL functions. Numerical
fields can be part of computations using mathematical operations.
See the sub-section on `Functions, Operators, Aggregates`_ for
more information.
Joins
~~~~~
@@ -359,8 +319,7 @@ article-ids:
$query = $em->createQuery('SELECT u.id, a.id as article_id FROM CmsUser u LEFT JOIN u.articles a');
$results = $query->getResult(); // array of user ids and every article_id for each user
Restricting a JOIN clause by additional conditions specified by
WITH:
Restricting a JOIN clause by additional conditions:
.. code-block:: php
@@ -493,18 +452,6 @@ Joins between entities without associations were not possible until version
<?php
$query = $em->createQuery('SELECT u FROM User u JOIN Blacklist b WITH u.email = b.email');
.. note::
The differences between WHERE, WITH and HAVING clauses may be
confusing.
- WHERE is applied to the results of an entire query
- WITH is applied to a join as an additional condition. For
arbitrary joins (SELECT f, b FROM Foo f, Bar b WITH f.id = b.id)
the WITH is required, even if it is 1 = 1
- HAVING is applied to the results of a query after
aggregation (GROUP BY)
Partial Object Syntax
^^^^^^^^^^^^^^^^^^^^^
@@ -539,9 +486,9 @@ You use the partial syntax when joining as well:
Using the ``NEW`` operator you can construct Data Transfer Objects (DTOs) directly from DQL queries.
- When using ``SELECT NEW`` you don't need to specify a mapped entity.
- You can specify any PHP class, it only requires that the constructor of this class matches the ``NEW`` statement.
- You can specify any PHP class, it's only require that the constructor of this class matches the ``NEW`` statement.
- This approach involves determining exactly which columns you really need,
and instantiating a data-transfer object that contains a constructor with those arguments.
and instantiating data-transfer object that containing a constructor with those arguments.
If you want to select data-transfer objects you should create a class:
@@ -654,15 +601,12 @@ The same restrictions apply for the reference of related entities.
DQL DELETE statements are ported directly into a
Database DELETE statement and therefore bypass any events and checks for the
version column if they are not explicitly added to the WHERE clause
of the query. Additionally Deletes of specified entities are *NOT*
of the query. Additionally Deletes of specifies entities are *NOT*
cascaded to related entities even if specified in the metadata.
Functions, Operators, Aggregates
--------------------------------
It is possible to wrap both fields and identification values into
aggregation and DQL functions. Numerical fields can be part of
computations using mathematical operations.
DQL Functions
~~~~~~~~~~~~~
@@ -775,6 +719,8 @@ classes have to implement the base class :
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$lexer = $parser->getLexer();
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
@@ -965,7 +911,7 @@ An instance of the ``Doctrine\ORM\Query`` class represents a DQL
query. You create a Query instance be calling
``EntityManager#createQuery($dql)``, passing the DQL query string.
Alternatively you can create an empty ``Query`` instance and invoke
``Query#setDQL($dql)`` afterwards. Here are some examples:
``Query#setDql($dql)`` afterwards. Here are some examples:
.. code-block:: php
@@ -975,9 +921,9 @@ Alternatively you can create an empty ``Query`` instance and invoke
// example1: passing a DQL string
$q = $em->createQuery('select u from MyProject\Model\User u');
// example2: using setDQL
// example2: using setDql
$q = $em->createQuery();
$q->setDQL('select u from MyProject\Model\User u');
$q->setDql('select u from MyProject\Model\User u');
Query Result Formats
~~~~~~~~~~~~~~~~~~~~
@@ -1060,7 +1006,7 @@ structure:
.. code-block:: php
$dql = "SELECT u, 'some scalar string', count(g.id) AS num FROM User u JOIN u.groups g GROUP BY u.id";
$dql = "SELECT u, 'some scalar string', count(u.groups) AS num FROM User u JOIN u.groups g GROUP BY u.id";
array
[0]
@@ -1160,22 +1106,6 @@ Object hydration hydrates the result set into the object graph:
$query = $em->createQuery('SELECT u FROM CmsUser u');
$users = $query->getResult(Query::HYDRATE_OBJECT);
Sometimes the behavior in the object hydrator can be confusing, which is
why we are listing as many of the assumptions here for reference:
- Objects fetched in a FROM clause are returned as a Set, that means every
object is only ever included in the resulting array once. This is the case
even when using JOIN or GROUP BY in ways that return the same row for an
object multiple times. If the hydrator sees the same object multiple times,
then it makes sure it is only returned once.
- If an object is already in memory from a previous query of any kind, then
then the previous object is used, even if the database may contain more
recent data. Data from the database is discarded. This even happens if the
previous object is still an unloaded proxy.
This list might be incomplete.
Array Hydration
^^^^^^^^^^^^^^^
@@ -1452,13 +1382,7 @@ Given that there are 10 users and corresponding addresses in the database the ex
SELECT * FROM address WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
.. note::
Changing the fetch mode during a query mostly makes sense for one-to-one and many-to-one relations. In that case,
all the necessary IDs are available after the root entity (``user`` in the above example) has been loaded. So, one
query per association can be executed to fetch all the referred-to entities (``address``).
For one-to-many relations, changing the fetch mode to eager will cause to execute one query **for every root entity
loaded**. This gives no improvement over the ``lazy`` fetch mode which will also initialize the associations on
a one-by-one basis once they are accessed.
Changing the fetch mode during a query is only possible for one-to-one and many-to-one relations.
EBNF
@@ -1487,9 +1411,7 @@ Terminals
~~~~~~~~~
- identifier (name, email, ...) must match ``[a-z_][a-z0-9_]*``
- fully_qualified_name (Doctrine\Tests\Models\CMS\CmsUser) matches PHP's fully qualified class names
- aliased_name (CMS:CmsUser) uses two identifiers, one for the namespace alias and one for the class inside it
- identifier (name, email, ...)
- string ('foo', 'bar''s house', '%ninja%', ...)
- char ('/', '\\', ' ', ...)
- integer (-1, 0, 1, 34, ...)
@@ -1523,8 +1445,8 @@ Identifiers
/* Alias Identification declaration (the "u" of "FROM User u") */
AliasIdentificationVariable :: = identifier
/* identifier that must be a class name (the "User" of "FROM User u"), possibly as a fully qualified class name or namespace-aliased */
AbstractSchemaName ::= fully_qualified_name | aliased_name | identifier
/* identifier that must be a class name (the "User" of "FROM User u") */
AbstractSchemaName ::= identifier
/* Alias ResultVariable declaration (the "total" of "COUNT(*) AS total") */
AliasResultVariable = identifier
@@ -1623,7 +1545,7 @@ Select Expressions
SimpleSelectExpression ::= (StateFieldPathExpression | IdentificationVariable | FunctionDeclaration | AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable]
PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")"
NewObjectExpression ::= "NEW" IdentificationVariable "(" NewObjectArg {"," NewObjectArg}* ")"
NewObjectArg ::= ScalarExpression | "(" Subselect ")"
Conditional Expressions

View File

@@ -164,8 +164,7 @@ the life-time of their registered entities.
database insert operations. Generated primary key values are
available in the postPersist event.
- preUpdate - The preUpdate event occurs before the database
update operations to entity data. It is not called for a DQL UPDATE statement
nor when the computed changeset is empty.
update operations to entity data. It is not called for a DQL UPDATE statement.
- postUpdate - The postUpdate event occurs after the database
update operations to entity data. It is not called for a DQL UPDATE statement.
- postLoad - The postLoad event occurs for an entity after the
@@ -179,7 +178,7 @@ the life-time of their registered entities.
allows providing fallback metadata even when no actual metadata exists
or could be found. This event is not a lifecycle callback.
- preFlush - The preFlush event occurs at the very beginning of a flush
operation.
operation. This event is not a lifecycle callback.
- onFlush - The onFlush event occurs after the change-sets of all
managed entities are computed. This event is not a lifecycle
callback.
@@ -406,8 +405,8 @@ behaviors across different entity classes.
Note that they require much more detailed knowledge about the inner
workings of the EntityManager and UnitOfWork. Please read the
:ref:`reference-events-implementing-listeners` section carefully if you
are trying to write your own listener.
*Implementing Event Listeners* section carefully if you are trying
to write your own listener.
For event subscribers, there are no surprises. They declare the
lifecycle events in their ``getSubscribedEvents`` method and provide
@@ -434,7 +433,7 @@ A lifecycle event listener looks like the following:
}
}
A lifecycle event subscriber may look like this:
A lifecycle event subscriber may looks like this:
.. code-block:: php
@@ -653,8 +652,7 @@ preUpdate
PreUpdate is the most restrictive to use event, since it is called
right before an update statement is called for an entity inside the
``EntityManager#flush()`` method. Note that this event is not
triggered when the computed changeset is empty.
``EntityManager#flush()`` method.
Changes to associations of the updated entity are never allowed in
this event, since Doctrine cannot guarantee to correctly handle
@@ -740,7 +738,7 @@ The three post events are called inside ``EntityManager#flush()``.
Changes in here are not relevant to the persistence in the
database, but you can use these events to alter non-persistable items,
like non-mapped fields, logging or even associated classes that are
not directly mapped by Doctrine.
directly mapped by Doctrine.
postLoad
~~~~~~~~
@@ -888,9 +886,6 @@ you need to map the listener method using the event type mapping:
preRemove: [preRemoveHandler]
# ....
.. note::
The order of execution of multiple methods for the same event (e.g. multiple @PrePersist) is not guaranteed.
Entity listeners resolver

View File

@@ -125,8 +125,9 @@ Example:
Things to note:
- The @InheritanceType and @DiscriminatorColumn must be specified
on the topmost class that is part of the mapped entity hierarchy.
- The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap
must be specified on the topmost class that is part of the mapped
entity hierarchy.
- The @DiscriminatorMap specifies which values of the
discriminator column identify a row as being of a certain type. In
the case above a value of "person" identifies a row as being of
@@ -159,7 +160,7 @@ This strategy is very efficient for querying across all types in
the hierarchy or for specific types. No table joins are required,
only a WHERE clause listing the type identifiers. In particular,
relationships involving types that employ this mapping strategy are
very performing.
very performant.
There is a general performance consideration with Single Table
Inheritance: If the target-entity of a many-to-one or one-to-one
@@ -454,8 +455,6 @@ Things to note:
- This feature is available for all kind of associations. (OneToOne, OneToMany, ManyToOne, ManyToMany)
- The association type *CANNOT* be changed.
- The override could redefine the joinTables or joinColumns depending on the association type.
- The override could redefine inversedBy to reference more than one extended entity.
- The override could redefine fetch to modify the fetch strategy of the extended entity.
Attribute Override
~~~~~~~~~~~~~~~~~~~~

View File

@@ -83,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 <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
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -112,10 +114,10 @@ in the core library. We don't think behaviors add more value than
they cost pain and debugging hell. Please see the many different
blog posts we have written on this topics:
- `Doctrine2 "Behaviors" in a Nutshell <http://www.doctrine-project.org/2010/02/17/doctrine2-behaviours-nutshell.html>`_
- `A re-usable Versionable behavior for Doctrine2 <http://www.doctrine-project.org/2010/02/24/doctrine2-versionable.html>`_
- `Write your own ORM on top of Doctrine2 <http://www.doctrine-project.org/2010/07/19/your-own-orm-doctrine2.html>`_
- `Doctrine 2 Behavioral Extensions <http://www.doctrine-project.org/2010/11/18/doctrine2-behavioral-extensions.html>`_
- `Doctrine2 "Behaviors" in a Nutshell <http://www.doctrine-project.org/blog/doctrine2-behaviours-nutshell>`_
- `A re-usable Versionable behavior for Doctrine2 <http://www.doctrine-project.org/blog/doctrine2-versionable>`_
- `Write your own ORM on top of Doctrine2 <http://www.doctrine-project.org/blog/your-own-orm-doctrine2>`_
- `Doctrine 2 Behavioral Extensions <http://www.doctrine-project.org/blog/doctrine2-behavioral-extensions>`_
- `Doctrator <https://github.com/pablodip/doctrator`>_
Doctrine 2 has enough hooks and extension points so that **you** can
@@ -180,27 +182,3 @@ MySQL with MyISAM tables
Doctrine cannot provide atomic operations when calling ``EntityManager#flush()`` if one
of the tables involved uses the storage engine MyISAM. You must use InnoDB or
other storage engines that support transactions if you need integrity.
Entities, Proxies and Reflection
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Using methods for Reflection on entities can be prone to error, when the entity
is actually a proxy the following methods will not work correctly:
- ``new ReflectionClass``
- ``new ReflectionObject``
- ``get_class()``
- ``get_parent_class()``
This is why ``Doctrine\Common\Util\ClassUtils`` class exists that has similar
methods, which resolve the proxy problem beforehand.
.. code-block:: php
<?php
use Doctrine\Common\Util\ClassUtils;
$bookProxy = $entityManager->getReference('Acme\Book');
$reflection = ClassUtils::newReflectionClass($bookProxy);
$class = ClassUtils::getClass($bookProxy)¸

View File

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

View File

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

View File

@@ -63,7 +63,7 @@ This has several benefits:
- The API is much simpler than the usual ``ResultSetMapping`` API.
One downside is that the builder API does not yet support entities
with inheritance hierarchies.
with inheritance hierachies.
.. code-block:: php

View File

@@ -7,14 +7,14 @@ conditionally constructing a DQL query in several steps.
It provides a set of classes and methods that is able to
programmatically build queries, and also provides a fluent API.
This means that you can change between one methodology to the other
as you want, or just pick a preferred one.
as you want, and also pick one if you prefer.
Constructing a new QueryBuilder object
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The same way you build a normal Query, you build a ``QueryBuilder``
object. Here is an example of how to build a ``QueryBuilder``
object:
object, just providing the correct method name. Here is an example
how to build a ``QueryBuilder`` object:
.. code-block:: php
@@ -24,9 +24,9 @@ object:
// example1: creating a QueryBuilder instance
$qb = $em->createQueryBuilder();
An instance of QueryBuilder has several informative methods. One
good example is to inspect what type of object the
``QueryBuilder`` is.
Once you have created an instance of QueryBuilder, it provides a
set of useful informative functions that you can use. One good
example is to inspect what type of object the ``QueryBuilder`` is.
.. code-block:: php
@@ -80,11 +80,11 @@ Working with QueryBuilder
High level API methods
^^^^^^^^^^^^^^^^^^^^^^
To simplify even more the way you build a query in Doctrine, you can take
advantage of Helper methods. For all base code, there is a set of
useful methods to simplify a programmer's life. To illustrate how
to work with them, here is the same example 6 re-written using
``QueryBuilder`` helper methods:
To simplify even more the way you build a query in Doctrine, we can take
advantage of what we call Helper methods. For all base code, there
is a set of useful methods to simplify a programmer's life. To
illustrate how to work with them, here is the same example 6
re-written using ``QueryBuilder`` helper methods:
.. code-block:: php
@@ -97,8 +97,8 @@ to work with them, here is the same example 6 re-written using
->orderBy('u.name', 'ASC');
``QueryBuilder`` helper methods are considered the standard way to
build DQL queries. Although it is supported, using string-based
queries should be avoided. You are greatly encouraged to use
build DQL queries. Although it is supported, it should be avoided
to use string based queries and greatly encouraged to use
``$qb->expr()->*`` methods. Here is a converted example 8 to
suggested standard way to build queries:
@@ -113,7 +113,7 @@ suggested standard way to build queries:
$qb->expr()->eq('u.id', '?1'),
$qb->expr()->like('u.nickname', '?2')
))
->orderBy('u.surname', 'ASC');
->orderBy('u.surname', 'ASC'));
Here is a complete list of helper methods available in ``QueryBuilder``:
@@ -126,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');
@@ -317,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:
@@ -499,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:`Criteria <filtering-collections>` to a QueryBuilder by
using ``addCriteria``:
.. code-block:: php
<?php
use Doctrine\Common\Collections\Criteria;
// ...
$criteria = Criteria::create()
->orderBy(['firstName', 'ASC']);
// $qb instanceof QueryBuilder
$qb->addCriteria($criteria);
// then execute your query like normal
Low Level API
^^^^^^^^^^^^^
Now we will describe the low level method of creating queries.
It may be useful to work at this level for optimization purposes,
but most of the time it is preferred to work at a higher level of
abstraction.
Now we have describe the low level (thought of as the
hardcore method) of creating queries. It may be useful to work at
this level for optimization purposes, but most of the time it is
preferred to work at a higher level of abstraction.
All helper methods in ``QueryBuilder`` actually rely on a single
one: ``add()``. This method is responsible of building every piece
@@ -577,3 +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'));
Of course this is the hardest way to build a DQL query in Doctrine.
To simplify some of these efforts, we introduce what we call as
``Expr`` helper class.

View File

@@ -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.
@@ -97,7 +97,7 @@ Defines a contract for accessing a particular 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>`_.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.Region.html/>`_.
Concurrent cache region
~~~~~~~~~~~~~~~~~~~~~~~
@@ -111,7 +111,7 @@ If you want to use an ``READ_WRITE`` cache, you should consider providing your o
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>`_.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.ConcurrentRegion.html/>`_.
Timestamp region
~~~~~~~~~~~~~~~~
@@ -120,7 +120,7 @@ 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>`_.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.TimestampRegion.html/>`_.
.. _reference-second-level-cache-mode:
@@ -149,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
-------------
@@ -209,7 +209,7 @@ It allows you to provide a specific implementation of the following components :
* ``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>`_.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.DefaultCacheFactory.html/>`_.
Region Lifetime
~~~~~~~~~~~~~~~
@@ -220,7 +220,7 @@ To specify a default lifetime for all regions or specify a different lifetime fo
<?php
/* @var $config \Doctrine\ORM\Configuration */
/* @var $cacheConfig \Doctrine\ORM\Cache\CacheConfiguration */
/* @var $cacheConfig \Doctrine\ORM\Configuration */
$cacheConfig = $config->getSecondLevelCacheConfiguration();
$regionConfig = $cacheConfig->getRegionsConfiguration();
@@ -239,7 +239,7 @@ By providing a cache logger you should be able to get information about all cach
<?php
/* @var $config \Doctrine\ORM\Configuration */
$logger = new \Doctrine\ORM\Cache\Logging\StatisticsCacheLogger();
$logger = \Doctrine\ORM\Cache\Logging\StatisticsCacheLogger();
// Cache logger
$config->setSecondLevelCacheEnabled(true);
@@ -270,7 +270,7 @@ By providing a cache logger you should be able to get information about all cach
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>`_.
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.CacheLogger.html/>`_.
Entity cache definition
@@ -619,7 +619,7 @@ 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 persister use a single timestamps cache region keeps track of the last update for each persister,

View File

@@ -32,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

View File

@@ -205,7 +205,7 @@ tables of the current model to clean up with orphaned tables.
You can also use database introspection to update your schema
easily with the ``updateSchema()`` method. It will compare your
existing database schema to the passed array of
``ClassMetadataInfo`` instances.
``ClassMetdataInfo`` instances.
.. code-block:: php
@@ -385,7 +385,7 @@ First you need to retrieve the metadata instances with the
)
);
$cmf = new \Doctrine\ORM\Tools\DisconnectedClassMetadataFactory();
$cmf = new DisconnectedClassMetadataFactory();
$cmf->setEntityManager($em);
$metadata = $cmf->getAllMetadata();
@@ -395,7 +395,6 @@ to yml:
.. code-block:: php
<?php
$cme = new \Doctrine\ORM\Tools\Export\ClassMetadataExporter();
$exporter = $cme->getExporter('yml', '/path/to/export/yml');
$exporter->setMetadata($metadata);
$exporter->export();
@@ -477,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 ...

View File

@@ -1,8 +1,6 @@
Transactions and Concurrency
============================
.. _transactions-and-concurrency_transaction-demarcation:
Transaction Demarcation
-----------------------
@@ -28,8 +26,6 @@ and control transaction demarcation yourself.
These are two ways to deal with transactions when using the
Doctrine ORM and are now described in more detail.
.. _transactions-and-concurrency_approach-implicitly:
Approach 1: Implicitly
~~~~~~~~~~~~~~~~~~~~~~
@@ -53,8 +49,6 @@ the DML operations by the Doctrine ORM and is sufficient if all the
data manipulation that is part of a unit of work happens through
the domain model and thus the ORM.
.. _transactions-and-concurrency_approach-explicitly:
Approach 2: Explicitly
~~~~~~~~~~~~~~~~~~~~~~
@@ -75,7 +69,7 @@ looks like this:
$em->flush();
$em->getConnection()->commit();
} catch (Exception $e) {
$em->getConnection()->rollBack();
$em->getConnection()->rollback();
throw $e;
}
@@ -104,21 +98,12 @@ functionally equivalent to the previously shown code looks as follows:
$em->persist($user);
});
.. warning::
For historical reasons, ``EntityManager#transactional($func)`` will return
``true`` whenever the return value of ``$func`` is loosely false.
Some examples of this include ``array()``, ``"0"``, ``""``, ``0``, and
``null``.
The difference between ``Connection#transactional($func)`` and
``EntityManager#transactional($func)`` is that the latter
abstraction flushes the ``EntityManager`` prior to transaction
commit and rolls back the transaction when an
exception occurs.
.. _transactions-and-concurrency_exception-handling:
Exception Handling
~~~~~~~~~~~~~~~~~~
@@ -149,8 +134,6 @@ knowing that their state is potentially no longer accurate.
If you intend to start another unit of work after an exception has
occurred you should do that with a new ``EntityManager``.
.. _transactions-and-concurrency_locking-support:
Locking Support
---------------
@@ -159,8 +142,6 @@ strategies natively. This allows to take very fine-grained control
over what kind of locking is required for your Entities in your
application.
.. _transactions-and-concurrency_optimistic-locking:
Optimistic Locking
~~~~~~~~~~~~~~~~~~
@@ -187,68 +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:: php
<?php
class User
{
// ...
/** @Version @Column(type="integer") */
private $version;
// ...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="User">
<field name="version" type="integer" version="true" />
</entity>
</doctrine-mapping>
.. code-block:: yaml
User:
type: entity
fields:
version:
type: integer
version: true
<?php
class User
{
// ...
/** @Version @Column(type="integer") */
private $version;
// ...
}
Alternatively a datetime type can be used (which maps to a SQL
timestamp or datetime):
.. configuration-block::
.. code-block:: php
.. code-block:: php
<?php
class User
{
// ...
/** @Version @Column(type="datetime") */
private $version;
// ...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="User">
<field name="version" type="datetime" version="true" />
</entity>
</doctrine-mapping>
.. code-block:: yaml
User:
type: entity
fields:
version:
type: datetime
version: true
<?php
class User
{
// ...
/** @Version @Column(type="datetime") */
private $version;
// ...
}
Version numbers (not timestamps) should however be preferred as
they can not potentially conflict in a highly concurrent
@@ -362,8 +305,6 @@ And the change headline action (POST Request):
$post = $em->find('BlogPost', $postId, \Doctrine\DBAL\LockMode::OPTIMISTIC, $postVersion);
.. _transactions-and-concurrency_pessimistic-locking:
Pessimistic Locking
~~~~~~~~~~~~~~~~~~~

View File

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

View File

@@ -238,14 +238,14 @@ the database permanently.
Notice how both sides of the bidirectional association are always
updated. Unidirectional associations are consequently simpler to
handle.
Also note that if you use type-hinting in your methods, you will
have to specify a nullable type, i.e. ``setAddress(?Address $address)``,
otherwise ``setAddress(null)`` will fail to remove the association.
Another way to deal with this is to provide a special method, like
``removeAddress()``. This can also provide better encapsulation as
it hides the internal meaning of not having an address.
handle. Also note that if you use type-hinting in your methods, i.e.
``setAddress(Address $address)``, PHP will only allow null
values if ``null`` is set as default value. Otherwise
setAddress(null) will fail for removing the association. If you
insist on type-hinting a typical way to deal with this is to
provide a special method, like ``removeAddress()``. This can also
provide better encapsulation as it hides the internal meaning of
not having an address.
When working with collections, keep in mind that a Collection is
essentially an ordered map (just like a PHP array). That is why the
@@ -396,25 +396,54 @@ There are two approaches to handle this problem in your code:
1. Ignore updating the inverse side of bidirectional collections,
BUT never read from them in requests that changed their state. In
the next request Doctrine hydrates the consistent collection state
the next Request Doctrine hydrates the consistent collection state
again.
2. Always keep the bidirectional collections in sync through
association management methods. Reads of the Collections directly
after changes are consistent then.
.. _transitive-persistence:
Transitive persistence / Cascade Operations
-------------------------------------------
Doctrine 2 provides a mechanism for transitive persistence through cascading of certain operations.
Each association to another entity or a collection of
entities can be configured to automatically cascade the following operations to the associated entities:
``persist``, ``remove``, ``merge``, ``detach``, ``refresh`` or ``all``.
Persisting, removing, detaching, refreshing and merging individual entities can
become pretty cumbersome, especially when a highly interweaved object graph
is involved. Therefore Doctrine 2 provides a
mechanism for transitive persistence through cascading of these
operations. Each association to another entity or a collection of
entities can be configured to automatically cascade certain
operations. By default, no operations are cascaded.
The main use case for ``cascade: persist`` is to avoid "exposing" associated entities to your PHP application.
Continuing with the User-Comment example of this chapter, this is how the creation of a new user and a new
comment might look like in your controller (without ``cascade: persist``):
The following cascade options exist:
- persist : Cascades persist operations to the associated
entities.
- remove : Cascades remove operations to the associated entities.
- merge : Cascades merge operations to the associated entities.
- detach : Cascades detach operations to the associated entities.
- refresh : Cascades refresh operations to the associated entities.
- all : Cascades persist, remove, merge, refresh and detach operations to
associated entities.
.. note::
Cascade operations are performed in memory. That means collections and related entities
are fetched into memory, even if they are still marked as lazy when
the cascade operation is about to be performed. However this approach allows
entity lifecycle events to be performed for each of these operations.
However, pulling objects graph into memory on cascade can cause considerable performance
overhead, especially when cascading collections are large. Makes sure
to weigh the benefits and downsides of each cascade operation that you define.
To rely on the database level cascade operations for the delete operation instead, you can
configure each join column with the **onDelete** option. See the respective
mapping driver chapters for more information.
The following example is an extension to the User-Comment example
of this chapter. Suppose in our application a user is created
whenever he writes his first comment. In this case we would use the
following code:
.. code-block:: php
@@ -424,39 +453,37 @@ comment might look like in your controller (without ``cascade: persist``):
$user->addComment($myFirstComment);
$em->persist($user);
$em->persist($myFirstComment); // required, if `cascade: persist` is not set
$em->persist($myFirstComment);
$em->flush();
Note that the Comment entity is instantiated right here in the controller.
To avoid this, ``cascade: persist`` allows you to "hide" the Comment entity from the controller,
only accessing it through the User entity:
Even if you *persist* a new User that contains our new Comment this
code would fail if you removed the call to
``EntityManager#persist($myFirstComment)``. Doctrine 2 does not
cascade the persist operation to all nested entities that are new
as well.
More complicated is the deletion of all of a user's comments when he is
removed from the system:
.. code-block:: php
<?php
// User entity
class User
{
private $id;
private $comments;
public function __construct()
{
$this->id = User::new();
$this->comments = new ArrayCollection();
}
public function comment(string $text, DateTimeInterface $time) : void
{
$newComment = Comment::create($text, $time);
$newComment->setUser($this);
$this->comments->add($newComment);
}
// ...
$user = $em->find('User', $deleteUserId);
foreach ($user->getAuthoredComments() as $comment) {
$em->remove($comment);
}
$em->remove($user);
$em->flush();
If you then set up the cascading to the ``User#commentsAuthored`` property...
Without the loop over all the authored comments Doctrine would use
an UPDATE statement only to set the foreign key to NULL and only
the User would be deleted from the database during the
flush()-Operation.
To have Doctrine handle both cases automatically we can change the
``User#commentsAuthored`` property to cascade both the "persist"
and the "remove" operation.
.. code-block:: php
@@ -473,51 +500,10 @@ If you then set up the cascading to the ``User#commentsAuthored`` property...
//...
}
...you can now create a user and an associated comment like this:
.. code-block:: php
<?php
$user = new User();
$user->comment('Lorem ipsum', new DateTime());
$em->persist($user);
$em->flush();
.. note::
The idea of ``cascade: persist`` is not to save you any lines of code in the controller.
If you instantiate the comment object in the controller (i.e. don't set up the user entity as shown above),
even with ``cascade: persist`` you still have to call ``$myFirstComment->setUser($user);``.
Thanks to ``cascade: remove``, you can easily delete a user and all linked comments without having to loop through them:
.. code-block:: php
<?php
$user = $em->find('User', $deleteUserId);
$em->remove($user);
$em->flush();
.. note::
Cascade operations are performed in memory. That means collections and related entities
are fetched into memory (even if they are marked as lazy) when
the cascade operation is about to be performed. This approach allows
entity lifecycle events to be performed for each of these operations.
However, pulling object graphs into memory on cascade can cause considerable performance
overhead, especially when the cascaded collections are large. Make sure
to weigh the benefits and downsides of each cascade operation that you define.
To rely on the database level cascade operations for the delete operation instead, you can
configure each join column with :doc:`the onDelete option <working-with-objects>`.
Even though automatic cascading is convenient, it should be used
with care. Do not blindly apply ``cascade=all`` to all associations as
Even though automatic cascading is convenient it should be used
with care. Do not blindly apply cascade=all to all associations as
it will unnecessarily degrade the performance of your application.
For each cascade operation that gets activated, Doctrine also
For each cascade operation that gets activated Doctrine also
applies that operation to the association, be it single or
collection valued.
@@ -525,20 +511,21 @@ Persistence by Reachability: Cascade Persist
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are additional semantics that apply to the Cascade Persist
operation. During each ``flush()`` operation Doctrine detects if there
operation. During each flush() operation Doctrine detects if there
are new entities in any collection and three possible cases can
happen:
1. New entities in a collection marked as ``cascade: persist`` will be
1. New entities in a collection marked as cascade persist will be
directly persisted by Doctrine.
2. New entities in a collection not marked as ``cascade: persist`` will
produce an Exception and rollback the ``flush()`` operation.
2. New entities in a collection not marked as cascade persist will
produce an Exception and rollback the flush() operation.
3. Collections without new entities are skipped.
This concept is called Persistence by Reachability: New entities
that are found on already managed entities are automatically
persisted as long as the association is defined as ``cascade: persist``.
persisted as long as the association is defined as cascade
persist.
Orphan Removal
--------------
@@ -616,11 +603,11 @@ address reference. When flush is called not only are the references removed
but both the old standing data and the one address entity are also deleted
from the database.
.. _filtering-collections:
Filtering Collections
---------------------
.. filtering-collections:
Collections have a filtering API that allows to slice parts of data from
a collection. If the collection has not been loaded from the database yet,
the filtering API can work on the SQL level to make optimized access to
@@ -716,8 +703,6 @@ methods:
* ``in($field, array $values)``
* ``notIn($field, array $values)``
* ``contains($field, $value)``
* ``startsWith($field, $value)``
* ``endsWith($field, $value)``
.. note::

View File

@@ -238,7 +238,7 @@ as follows:
persist operation. However, the persist operation is cascaded to
entities referenced by X, if the relationships from X to these
other entities are mapped with cascade=PERSIST or cascade=ALL (see
":ref:`Transitive Persistence <transitive-persistence>`").
"Transitive Persistence").
- If X is a removed entity, it becomes managed.
- If X is a detached entity, an exception will be thrown on
flush.
@@ -279,12 +279,12 @@ as follows:
- If X is a new entity, it is ignored by the remove operation.
However, the remove operation is cascaded to entities referenced by
X, if the relationship from X to these other entities is mapped
with cascade=REMOVE or cascade=ALL (see ":ref:`Transitive Persistence <transitive-persistence>`").
with cascade=REMOVE or cascade=ALL (see "Transitive Persistence").
- If X is a managed entity, the remove operation causes it to
become removed. The remove operation is cascaded to entities
referenced by X, if the relationships from X to these other
entities is mapped with cascade=REMOVE or cascade=ALL (see
":ref:`Transitive Persistence <transitive-persistence>`").
"Transitive Persistence").
- If X is a detached entity, an InvalidArgumentException will be
thrown.
- If X is a removed entity, it is ignored by the remove operation.
@@ -350,14 +350,14 @@ as follows:
become detached. The detach operation is cascaded to entities
referenced by X, if the relationships from X to these other
entities is mapped with cascade=DETACH or cascade=ALL (see
":ref:`Transitive Persistence <transitive-persistence>`"). Entities which previously referenced X
"Transitive Persistence"). Entities which previously referenced X
will continue to reference X.
- If X is a new or detached entity, it is ignored by the detach
operation.
- If X is a removed entity, the detach operation is cascaded to
entities referenced by X, if the relationships from X to these
other entities is mapped with cascade=DETACH or cascade=ALL (see
":ref:`Transitive Persistence <transitive-persistence>`"). Entities which previously referenced X
"Transitive Persistence"). Entities which previously referenced X
will continue to reference X.
There are several situations in which an entity is detached
@@ -416,7 +416,8 @@ as follows:
- If X is a managed entity, it is ignored by the merge operation,
however, the merge operation is cascaded to entities referenced by
relationships from X if these relationships have been mapped with
the cascade element value MERGE or ALL (see ":ref:`Transitive Persistence <transitive-persistence>`").
the cascade element value MERGE or ALL (see "Transitive
Persistence").
- For all entities Y referenced by relationships from X having the
cascade element value MERGE or ALL, Y is merged recursively as Y'.
For all such Y referenced by X, X' is set to reference Y'. (Note
@@ -698,6 +699,8 @@ You can also load by owning side associations through the repository:
$number = $em->find('MyProject\Domain\Phonenumber', 1234);
$user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('phone' => $number->getId()));
Be careful that this only works by passing the ID of the associated entity, not yet by passing the associated entity itself.
The ``EntityRepository#findBy()`` method additionally accepts orderings, limit and offset as second to fourth parameters:
.. code-block:: php
@@ -726,14 +729,6 @@ examples are equivalent:
// A single user by its nickname (__call magic)
$user = $em->getRepository('MyProject\Domain\User')->findOneByNickname('romanb');
Additionally, you can just count the result of the provided conditions when you don't really need the data:
.. code-block:: php
<?php
// Check there is no user with nickname
$availableNickname = 0 === $em->getRepository('MyProject\Domain\User')->count(['nickname' => 'nonexistent']);
By Criteria
~~~~~~~~~~~
@@ -743,7 +738,8 @@ The Repository implement the ``Doctrine\Common\Collections\Selectable``
interface. That means you can build ``Doctrine\Common\Collections\Criteria``
and pass them to the ``matching($criteria)`` method.
See section `Filtering collections` of chapter :doc:`Working with Associations <working-with-associations>`
See the :ref:`Working with Associations: Filtering collections
<filtering-collections>`.
By Eager Loading
~~~~~~~~~~~~~~~~

View File

@@ -321,12 +321,12 @@ Using the simplified definition above Doctrine will use no
identifier strategy for this entity. That means you have to
manually set the identifier before calling
``EntityManager#persist($entity)``. This is the so called
``NONE`` strategy.
``ASSIGNED`` strategy.
If you want to switch the identifier generation strategy you have
to nest a ``<generator />`` element inside the id-element. This of
course only works for surrogate keys. For composite keys you always
have to use the ``NONE`` strategy.
have to use the ``ASSIGNED`` strategy.
.. code-block:: xml

View File

@@ -13,7 +13,7 @@ This tutorial shows how the semantics of composite primary keys work and how the
General Considerations
~~~~~~~~~~~~~~~~~~~~~~
Every entity with a composite key cannot use an id generator other than "NONE". That means
Every entity with a composite key cannot use an id generator other than "ASSIGNED". That means
the ID fields have to have their values set before you call ``EntityManager#persist($entity)``.
Primitive Types only
@@ -38,7 +38,7 @@ and year of production as primary keys:
/** @Id @Column(type="string") */
private $name;
/** @Id @Column(type="integer") */
private $year;
private $year
public function __construct($name, $year)
{

View File

@@ -1,14 +1,11 @@
Separating Concerns using Embeddables
-------------------------------------
Embeddables are classes which are not entities themselves, but are embedded
Embeddables are classes which are not entities themself, but are embedded
in entities and can also be queried in DQL. You'll mostly want to use them
to reduce duplication or separating concerns. Value objects such as date range
or address are the primary use case for this feature.
.. note::
Embeddables can only contain properties with basic ``@Column`` mapping.
or address are the primary use case for this feature. Embeddables can only
contain properties with basic ``@Column`` mapping.
For the purposes of this tutorial, we will assume that you have a ``User``
class in your application and you would like to store an address in
@@ -79,20 +76,6 @@ In terms of your database schema, Doctrine will automatically inline all
columns from the ``Address`` class into the table of the ``User`` class,
just as if you had declared them directly there.
Initializing embeddables
------------------------
In case all fields in the embeddable are ``nullable``, you might want
to initialize the embeddable, to avoid getting a null value instead of
the embedded object.
.. code-block:: php
public function __construct()
{
$this->address = new Address();
}
Column Prefixing
----------------

View File

@@ -14,10 +14,10 @@ Guide Assumptions
-----------------
This guide is designed for beginners that haven't worked with Doctrine ORM
before. There are some prerequisites for the tutorial that have to be
before. There are some prerequesites for the tutorial that have to be
installed:
- PHP (latest stable version)
- PHP 5.4 or above
- Composer Package Manager (`Install Composer
<http://getcomposer.org/doc/00-intro.md>`_)
@@ -51,7 +51,7 @@ Entities are PHP Objects that can be identified over many requests
by a unique identifier or primary key. These classes don't need to extend any
abstract base class or interface. An entity class must not be final
or contain final methods. Additionally it must not implement
**clone** nor **wakeup**, unless it :doc:`does so safely <../cookbook/implementing-wakeup-or-clone>`.
**clone** nor **wakeup** or :doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
An entity contains persistable properties. A persistable property
is an instance variable of the entity that is saved into and retrieved from the database
@@ -62,7 +62,7 @@ An Example Model: Bug Tracker
For this Getting Started Guide for Doctrine we will implement the
Bug Tracker domain model from the
`Zend\_Db\_Table <http://framework.zend.com/manual/1.12/en/zend.db.adapter.html>`_
`Zend\_Db\_Table <http://framework.zend.com/manual/en/zend.db.table.html>`_
documentation. Reading their documentation we can extract the
requirements:
@@ -118,8 +118,8 @@ Add the following directories:
Obtaining the EntityManager
---------------------------
Doctrine's public interface is through the ``EntityManager``. This class
provides access points to the complete lifecycle management for your entities,
Doctrine's public interface is the EntityManager, it provides the
access point to the complete lifecycle management of your entities
and transforms entities from and back to persistence. You have to
configure and create it to use your entities with Doctrine 2. I
will show the configuration steps and then discuss them step by
@@ -150,8 +150,8 @@ step:
// obtaining the entity manager
$entityManager = EntityManager::create($conn, $config);
The require_once statement sets up the class autoloading for Doctrine and
its dependencies using Composer's autoloader.
The first require statement sets up the autoloading capabilities of Doctrine
using the Composer autoload.
The second block consists of the instantiation of the ORM
``Configuration`` object using the Setup helper. It assumes a bunch
@@ -159,10 +159,10 @@ of defaults that you don't have to bother about for now. You can
read up on the configuration details in the
:doc:`reference chapter on configuration <../reference/configuration>`.
The third block shows the configuration options required to connect to
a database. In this case, we'll use a file-based SQLite database. All the
The third block shows the configuration options required to connect
to a database, in my case a file-based sqlite database. All the
configuration options for all the shipped drivers are given in the
`DBAL Configuration section of the manual <http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/>`_.
`DBAL Configuration section of the manual <http://www.doctrine-project.org/documentation/manual/2_0/en/dbal>`_.
The last block shows how the ``EntityManager`` is obtained from a
factory method.
@@ -170,10 +170,15 @@ factory method.
Generating the Database Schema
------------------------------
Doctrine has a command-line interface that allows you to access the SchemaTool,
a component that can generate a relational database schema based entirely on the
defined entity classes and their metadata. For this tool to work, a
cli-config.php file must exist in the project root directory:
Now that we have defined the Metadata mappings and bootstrapped the
EntityManager we want to generate the relational database schema
from it. Doctrine has a Command-Line Interface that allows you to
access the SchemaTool, a component that generates the required
tables to work with the metadata.
For the command-line tool to work a cli-config.php file has to be
present in the project root directory, where you will execute the
doctrine command. Its a fairly simple file:
.. code-block:: php
@@ -183,38 +188,40 @@ cli-config.php file must exist in the project root directory:
return \Doctrine\ORM\Tools\Console\ConsoleRunner::createHelperSet($entityManager);
Change into your project directory and call the Doctrine command-line tool:
You can then change into your project directory and call the
Doctrine command-line tool:
::
$ cd project/
$ vendor/bin/doctrine orm:schema-tool:create
Since we haven't added any entity metadata in `src` yet, you'll see a message
stating "No Metadata Classes to process." In the next section, we'll create a
Product entity along with the corresponding metadata, and run this command again.
At this point no entity metadata exists in `src` so you will see a message like
"No Metadata Classes to process." Don't worry, we'll create a Product entity and
corresponding metadata in the next section.
Note that as you modify your entities' metadata during the development process,
you'll need to update your database schema to stay in sync with the metadata.
You can rasily recreate the database using the following commands:
You should be aware that during the development process you'll periodically need
to update your database schema to be in sync with your Entities metadata.
You can easily recreate the database:
::
$ vendor/bin/doctrine orm:schema-tool:drop --force
$ vendor/bin/doctrine orm:schema-tool:create
Or you can use the update functionality:
Or use the update functionality:
::
$ vendor/bin/doctrine orm:schema-tool:update --force
The updating of databases uses a Diff Algorithm for a given
Database Schema. This is a cornerstone of the ``Doctrine\DBAL`` package,
Database Schema, a cornerstone of the ``Doctrine\DBAL`` package,
which can even be used without the Doctrine ORM package.
Starting with the Product Entity
--------------------------------
Starting with the Product
-------------------------
We start with the simplest entity, the Product. Create a ``src/Product.php`` file to contain the ``Product``
entity definition:
@@ -250,9 +257,9 @@ entity definition:
}
}
When creating entity classes, all of the fields should be protected or private
(not public), with getter and setter methods for each one (except $id).
The use of mutators allows Doctrine to hook into calls which
Note that all fields are set to protected (not public) with a
mutator (getter and setter) defined for every field except $id.
The use of mutators allows Doctrine to hook into calls which
manipulate the entities in ways that it could not if you just
directly set the values with ``entity#field = foo;``
@@ -267,10 +274,9 @@ language. The metadata language describes how entities, their
properties and references should be persisted and what constraints
should be applied to them.
Metadata for an Entity can be configured using DocBlock annotations directly
in the Entity class itself, or in an external XML or YAML file. This Getting
Started guide will demonstrate metadata mappings using all three methods,
but you only need to choose one.
Metadata for entities are configured using a XML, YAML or Docblock Annotations.
This Getting Started Guide will show the mappings for all Mapping Drivers.
References in the text will be made to the XML mapping.
.. configuration-block::
@@ -326,28 +332,27 @@ but you only need to choose one.
The top-level ``entity`` definition tag specifies information about
the class and table-name. The primitive type ``Product#name`` is
defined as a ``field`` attribute. The ``id`` property is defined with
the ``id`` tag. It has a ``generator`` tag nested inside, which
specifies that the primary key generation mechanism should automatically
use the database platform's native id generation strategy (for
example, AUTO INCREMENT in the case of MySql, or Sequences in the
the ``id`` tag, this has a ``generator`` tag nested inside which
defines that the primary key generation mechanism automatically
uses the database platforms native id generation strategy (for
example AUTO INCREMENT in the case of MySql or Sequences in the
case of PostgreSql and Oracle).
Now that we have defined our first entity and its metadata,
let's update the database schema:
Now that we have defined our first entity, let's update the database:
::
$ vendor/bin/doctrine orm:schema-tool:update --force --dump-sql
Specifying both flags ``--force`` and ``--dump-sql`` will cause the DDL
statements to be executed and then printed to the screen.
Specifying both flags ``--force`` and ``-dump-sql`` prints and executes the DDL
statements.
Now, we'll create a new script to insert products into the database:
Now create a new script that will insert products into the database:
.. code-block:: php
<?php
// create_product.php <name>
// create_product.php
require_once "bootstrap.php";
$newProductName = $argv[1];
@@ -367,19 +372,22 @@ Call this script from the command-line to see how new products are created:
$ php create_product.php ORM
$ php create_product.php DBAL
What is happening here? Using the ``Product`` class is pretty standard OOP.
What is happening here? Using the ``Product`` is pretty standard OOP.
The interesting bits are the use of the ``EntityManager`` service. To
notify the EntityManager that a new entity should be inserted into the database,
you have to call ``persist()``. To initiate a transaction to actually *perform*
the insertion, you have to explicitly call ``flush()`` on the ``EntityManager``.
notify the EntityManager that a new entity should be inserted into the database
you have to call ``persist()``. To initiate a transaction to actually perform
the insertion, You have to explicitly call ``flush()`` on the ``EntityManager``.
This distinction between persist and flush is what allows the aggregation of
all database writes (INSERT, UPDATE, DELETE) into one single transaction, which
is executed when ``flush()`` is called. Using this approach, the write-performance
is significantly better than in a scenario in which writes are performed on
each entity in isolation.
This distinction between persist and flush is allows to aggregate all writes
(INSERT, UPDATE, DELETE) into one single transaction, which is executed when
flush is called. Using this approach the write-performance is significantly
better than in a scenario where updates are done for each entity in isolation.
Next, we'll fetch a list of all the Products in the database. Let's create a
Doctrine follows the UnitOfWork pattern which additionally detects all entities
that were fetched and have changed during the request. You don't have to keep track of
entities yourself, when Doctrine already knows about them.
As a next step we want to fetch a list of all the Products. Let's create a
new script for this:
.. code-block:: php
@@ -396,10 +404,10 @@ new script for this:
}
The ``EntityManager#getRepository()`` method can create a finder object (called
a repository) for every type of entity. It is provided by Doctrine and contains
some finder methods like ``findAll()``.
a repository) for every entity. It is provided by Doctrine and contains some
finder methods such as ``findAll()``.
Let's continue by creating a script to display the name of a product based on its ID:
Let's continue with displaying the name of a product based on its ID:
.. code-block:: php
@@ -417,13 +425,9 @@ Let's continue by creating a script to display the name of a product based on it
echo sprintf("-%s\n", $product->getName());
Next we'll update a product's name, given its id. This simple example will
help demonstrate Doctrine's implementation of the UnitOfWork pattern. Doctrine
keeps track of all the entities that were retrieved from the Entity Manager,
and can detect when any of those entities' properties have been modified.
As a result, rather than needing to call ``persist($entity)`` for each individual
entity whose properties were changed, a single call to ``flush()`` at the end of a
request is sufficient to update the database for all of the modified entities.
Updating a product name demonstrates the functionality UnitOfWork of pattern
discussed before. We only need to find a product entity and all changes to its
properties are written to the database:
.. code-block:: php
@@ -451,8 +455,9 @@ product name changed by calling the ``show_product.php`` script.
Adding Bug and User Entities
----------------------------
We continue with the bug tracker example by creating the ``Bug`` and ``User``
classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
We continue with the bug tracker domain, by creating the missing classes
``Bug`` and ``User`` and putting them into ``src/Bug.php`` and
``src/User.php`` respectively.
.. code-block:: php
@@ -556,15 +561,14 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
}
}
All of the properties we've seen so far are of simple types (integer, string,
and datetime). But now, we'll add properties that will store objects of
specific *entity types* in order to model the relationships between different
entities.
All of the properties discussed so far are simple string and integer values,
for example the id fields of the entities, their names, description, status and
change dates. Next we will model the dynamic relationships between the entities
by defining the references between entities.
At the database level, relationships between entities are represented by foreign
keys. But with Doctrine, you'll never have to (and never should) work with
the foreign keys directly. You should only work with objects that represent
foreign keys through their own identities.
References between objects are foreign keys in the database. You never have to
(and never should) work with the foreign keys directly, only with the objects
that represent the foreign key through their own identity.
For every foreign key you either have a Doctrine ManyToOne or OneToOne
association. On the inverse sides of these foreign keys you can have
@@ -598,7 +602,6 @@ domain model to match the requirements:
<?php
// src/User.php
use Doctrine\Common\Collections\ArrayCollection;
class User
{
// ... (previous code)
@@ -613,13 +616,12 @@ domain model to match the requirements:
}
}
.. note::
Whenever an entity is created from the database, a ``Collection``
implementation of the type ``PersistentCollection`` will be injected into
your entity instead of an ``ArrayCollection``. This helps Doctrine ORM
understand the changes that have happened to the collection that are
noteworthy for persistence.
Whenever an entity is recreated from the database, an Collection
implementation of the type Doctrine is injected into your entity
instead of an array. Compared to the ArrayCollection this
implementation helps the Doctrine ORM understand the changes that
have happened to the collection which are noteworthy for
persistence.
.. warning::
@@ -642,22 +644,24 @@ able to work with Doctrine 2. These assumptions are not unique to
Doctrine 2 but are best practices in handling database relations
and Object-Relational Mapping.
- In a one-to-one relation, the entity holding the foreign key of
the related entity on its own database table is *always* the owning
side of the relation.
- In a many-to-one relation, the Many-side is the owning side by
default because it holds the foreign key. Accordingly, the One-side
is the inverse side by default.
- In a many-to-one relation, the One-side can only be the owning side if
the relation is implemented as a ManyToMany with a join table, and the
One-side is restricted to allow only UNIQUE values per database constraint.
- In a many-to-many relation, both sides can be the owning side of
the relation. However, in a bi-directional many-to-many relation,
only one side is allowed to be the owning side.
- Changes to Collections are saved or updated, when the entity on
the *owning* side of the collection is saved or updated.
- Saving an Entity at the inverse side of a relation never
triggers a persist operation to changes to the collection.
- In a one-to-one relation the entity holding the foreign key of
the related entity on its own database table is *always* the owning
side of the relation.
- In a many-to-many relation, both sides can be the owning side of
the relation. However in a bi-directional many-to-many relation
only one is allowed to be.
- In a many-to-one relation the Many-side is the owning side by
default, because it holds the foreign key.
- The OneToMany side of a relation is inverse by default, since
the foreign key is saved on the Many side. A OneToMany relation can
only be the owning side, if its implemented using a ManyToMany
relation with join table and restricting the one side to allow only
UNIQUE values per database constraint.
.. note::
@@ -682,13 +686,13 @@ the bi-directional reference:
protected $engineer;
protected $reporter;
public function setEngineer(User $engineer)
public function setEngineer($engineer)
{
$engineer->assignedToBug($this);
$this->engineer = $engineer;
}
public function setReporter(User $reporter)
public function setReporter($reporter)
{
$reporter->addReportedBug($this);
$this->reporter = $reporter;
@@ -713,15 +717,15 @@ the bi-directional reference:
{
// ... (previous code)
protected $reportedBugs;
protected $assignedBugs;
private $reportedBugs = null;
private $assignedBugs = null;
public function addReportedBug(Bug $bug)
public function addReportedBug($bug)
{
$this->reportedBugs[] = $bug;
}
public function assignedToBug(Bug $bug)
public function assignedToBug($bug)
{
$this->assignedBugs[] = $bug;
}
@@ -737,7 +741,7 @@ You can see from ``User#addReportedBug()`` and
``User#assignedToBug()`` that using this method in userland alone
would not add the Bug to the collection of the owning side in
``Bug#reporter`` or ``Bug#engineer``. Using these methods and
calling Doctrine for persistence would not update the Collections'
calling Doctrine for persistence would not update the collections
representation in the database.
Only using ``Bug#setEngineer()`` or ``Bug#setReporter()``
@@ -745,7 +749,7 @@ correctly saves the relation information.
The ``Bug#reporter`` and ``Bug#engineer`` properties are
Many-To-One relations, which point to a User. In a normalized
relational model, the foreign key is saved on the Bug's table, hence
relational model the foreign key is saved on the Bug's table, hence
in our object-relation model the Bug is at the owning side of the
relation. You should always make sure that the use-cases of your
domain model should drive which side is an inverse or owning one in
@@ -754,7 +758,7 @@ or an engineer is assigned to the bug, we don't want to update the
User to persist the reference, but the Bug. This is the case with
the Bug being at the owning side of the relation.
Bugs reference Products by a uni-directional ManyToMany relation in
Bugs reference Products by an uni-directional ManyToMany relation in
the database that points from Bugs to Products.
.. code-block:: php
@@ -767,7 +771,7 @@ the database that points from Bugs to Products.
protected $products = null;
public function assignToProduct(Product $product)
public function assignToProduct($product)
{
$this->products[] = $product;
}
@@ -779,7 +783,7 @@ the database that points from Bugs to Products.
}
We are now finished with the domain model given the requirements.
Lets add metadata mappings for the ``Bug`` entity, as we did for
Lets add metadata mappings for the ``User`` and ``Bug`` as we did for
the ``Product`` before:
.. configuration-block::
@@ -886,13 +890,13 @@ For the "created" field we have used the ``datetime`` type,
which translates the YYYY-mm-dd HH:mm:ss database format
into a PHP DateTime instance and back.
After the field definitions, the two qualified references to the
After the field definitions the two qualified references to the
user entity are defined. They are created by the ``many-to-one``
tag. The class name of the related entity has to be specified with
the ``target-entity`` attribute, which is enough information for
the database mapper to access the foreign-table. Since
``reporter`` and ``engineer`` are on the owning side of a
bi-directional relation, we also have to specify the ``inversed-by``
bi-directional relation we also have to specify the ``inversed-by``
attribute. They have to point to the field names on the inverse
side of the relationship. We will see in the next example that the ``inversed-by``
attribute has a counterpart ``mapped-by`` which makes that
@@ -903,7 +907,7 @@ holds all products where the specific bug occurs. Again
you have to define the ``target-entity`` and ``field`` attributes
on the ``many-to-many`` tag.
Finally, we'll add metadata mappings for the ``User`` entity.
The last missing definition is that of the User entity:
.. configuration-block::
@@ -930,13 +934,13 @@ Finally, we'll add metadata mappings for the ``User`` entity.
/**
* @OneToMany(targetEntity="Bug", mappedBy="reporter")
* @var Bug[] An ArrayCollection of Bug objects.
* @var Bug[]
**/
protected $reportedBugs = null;
/**
* @OneToMany(targetEntity="Bug", mappedBy="engineer")
* @var Bug[] An ArrayCollection of Bug objects.
* @var Bug[]
**/
protected $assignedBugs = null;
@@ -965,7 +969,7 @@ Finally, we'll add metadata mappings for the ``User`` entity.
.. code-block:: yaml
# config/yaml/User.dcm.yml
# config/xml/User.dcm.yml
User:
type: entity
table: users
@@ -992,7 +996,10 @@ means the join details have already been defined on the owning
side. Therefore we only have to specify the property on the Bug
class that holds the owning sides.
Update your database schema by running:
This example has a fair overview of the most basic features of the
metadata definition language.
Update your database running:
::
$ vendor/bin/doctrine orm:schema-tool:update --force
@@ -1001,8 +1008,7 @@ Update your database schema by running:
Implementing more Requirements
------------------------------
So far, we've seen the most basic features of the metadata definition language.
To explore additional functionality, let's first create new ``User`` entities:
For starters we need a create user entities:
.. code-block:: php
@@ -1026,22 +1032,23 @@ Now call:
$ php create_user.php beberlei
We now have the necessary data to create a new Bug entity:
We now have the data to create a bug and the code for this scenario may look
like this:
.. code-block:: php
<?php
// create_bug.php <reporter-id> <engineer-id> <product-ids>
// create_bug.php
require_once "bootstrap.php";
$reporterId = $argv[1];
$engineerId = $argv[2];
$theReporterId = $argv[1];
$theDefaultEngineerId = $argv[2];
$productIds = explode(",", $argv[3]);
$reporter = $entityManager->find("User", $reporterId);
$engineer = $entityManager->find("User", $engineerId);
$reporter = $entityManager->find("User", $theReporterId);
$engineer = $entityManager->find("User", $theDefaultEngineerId);
if (!$reporter || !$engineer) {
echo "No reporter and/or engineer found for the given id(s).\n";
echo "No reporter and/or engineer found for the input.\n";
exit(1);
}
@@ -1063,17 +1070,22 @@ We now have the necessary data to create a new Bug entity:
echo "Your new Bug Id: ".$bug->getId()."\n";
Since we only have one user and product, probably with the ID of 1, we can
call this script as follows:
Since we only have one user and product, probably with the ID of 1, we can call this script with:
::
php create_bug.php 1 1 1
See how simple it is to relate a Bug, Reporter, Engineer and Products?
Also recall that thanks to the UnitOfWork pattern, Doctrine will detect
these relations and update all of the modified entities in the database
automatically when ``flush()`` is called.
This is the first contact with the read API of the EntityManager,
showing that a call to ``EntityManager#find($name, $id)`` returns a
single instance of an entity queried by primary key. Besides this
we see the persist + flush pattern again to save the Bug into the
database.
See how simple relating Bug, Reporter, Engineer and Products is
done by using the discussed methods in the "A first prototype"
section. The UnitOfWork will detect this relations when flush is
called and relate them in the database appropriately.
Queries for Application Use-Cases
---------------------------------
@@ -1082,7 +1094,7 @@ List of Bugs
~~~~~~~~~~~~
Using the previous examples we can fill up the database quite a
bit. However, we now need to discuss how to query the underlying
bit, however we now need to discuss how to query the underlying
mapper for the required view representations. When opening the
application, bugs can be paginated through a list-view, which is
the first read-only use-case:
@@ -1138,7 +1150,7 @@ The console output of this script is then:
An important reason why DQL is favourable to the Query API of most
ORMs is its similarity to SQL. The DQL language allows query
constructs that most ORMs don't: GROUP BY even with HAVING,
constructs that most ORMs don't, GROUP BY even with HAVING,
Sub-selects, Fetch-Joins of nested classes, mixed results with
entities and scalar data such as COUNT() results and much more.
Using DQL you should seldom come to the point where you want to
@@ -1213,7 +1225,7 @@ write scenarios:
.. code-block:: php
<?php
// show_bug.php <id>
// show_bug.php
require_once "bootstrap.php";
$theBugId = $argv[1];
@@ -1288,7 +1300,7 @@ and usage of bound parameters:
.. code-block:: php
<?php
// dashboard.php <user-id>
// dashboard.php
require_once "bootstrap.php";
$theUserId = $argv[1];
@@ -1354,7 +1366,7 @@ should be able to close a bug. This looks like:
.. code-block:: php
<?php
// close_bug.php <bug-id>
// close_bug.php
require_once "bootstrap.php";
$theBugId = $argv[1];
@@ -1523,16 +1535,6 @@ As an example here is the code of the first use case "List of Bugs":
Using EntityRepositories you can avoid coupling your model with specific query logic.
You can also re-use query logic easily throughout your application.
The method ``count()`` takes an array of fields or association keys and the values to match against.
This provides you with a convenient and lightweight way to count a resultset when you don't need to
deal with it:
.. code-block:: php
<?php
$productCount = $entityManager->getRepository(Product::class)
->count(['name' => $productName]);
Conclusion
----------

View File

@@ -58,7 +58,6 @@ which has mapping metadata that is overridden by the annotation above:
.. code-block:: php
<?php
/**
* Trait class
*/
@@ -83,7 +82,6 @@ The case for just extending a class would be just the same but:
.. code-block:: php
<?php
class ExampleEntityWithOverride extends BaseEntityWithSomeMapping
{
// ...

View File

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

View File

@@ -101,7 +101,7 @@
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="result-class" type="orm:fqcn" />
<xs:attribute name="result-class" type="xs:string" />
<xs:attribute name="result-set-mapping" type="xs:string" />
</xs:complexType>
@@ -117,7 +117,7 @@
<xs:element name="lifecycle-callback" type="orm:lifecycle-callback" minOccurs="0" maxOccurs="unbounded"/>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="class" type="orm:fqcn"/>
<xs:attribute name="class" type="xs:string"/>
</xs:complexType>
<xs:complexType name="entity-listeners">
@@ -139,7 +139,7 @@
<xs:sequence>
<xs:element name="field-result" type="orm:field-result" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="entity-class" type="orm:fqcn" use="required" />
<xs:attribute name="entity-class" type="xs:string" use="required" />
<xs:attribute name="discriminator-column" type="xs:string" use="optional" />
</xs:complexType>
@@ -167,7 +167,7 @@
</xs:complexType>
<xs:complexType name="entity">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:sequence>
<xs:element name="cache" type="orm:cache" minOccurs="0" maxOccurs="1"/>
<xs:element name="options" type="orm:options" minOccurs="0" />
<xs:element name="indexes" type="orm:indexes" minOccurs="0"/>
@@ -189,24 +189,17 @@
<xs:element name="association-overrides" type="orm:association-overrides" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="attribute-overrides" type="orm:attribute-overrides" minOccurs="0" maxOccurs="unbounded" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:choice>
</xs:sequence>
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="table" type="orm:tablename" />
<xs:attribute name="table" type="xs:NMTOKEN" />
<xs:attribute name="schema" type="xs:NMTOKEN" />
<xs:attribute name="repository-class" type="orm:fqcn"/>
<xs:attribute name="repository-class" type="xs:string"/>
<xs:attribute name="inheritance-type" type="orm:inheritance-type"/>
<xs:attribute name="change-tracking-policy" type="orm:change-tracking-policy" />
<xs:attribute name="read-only" type="xs:boolean" default="false" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:simpleType name="tablename" id="tablename">
<xs:restriction base="xs:token">
<xs:pattern value="[a-zA-Z_u01-uff.]+" id="tablename.pattern">
</xs:pattern>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="option" mixed="true">
<xs:sequence minOccurs="0" maxOccurs="unbounded">
<xs:element name="option" type="orm:option"/>
@@ -309,7 +302,7 @@
<xs:complexType name="embedded">
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="class" type="orm:fqcn" use="required" />
<xs:attribute name="class" type="xs:string" use="required" />
<xs:attribute name="column-prefix" type="xs:string" use="optional" />
<xs:attribute name="use-column-prefix" type="xs:boolean" default="true" use="optional" />
</xs:complexType>
@@ -368,7 +361,7 @@
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="value" type="xs:NMTOKEN" use="required"/>
<xs:attribute name="class" type="orm:fqcn" use="required"/>
<xs:attribute name="class" type="xs:string" use="required"/>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
@@ -419,16 +412,9 @@
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="class" type="orm:fqcn" use="required" />
<xs:attribute name="class" type="xs:NMTOKEN" use="required" />
</xs:complexType>
<xs:simpleType name="fqcn" id="fqcn">
<xs:restriction base="xs:token">
<xs:pattern value="[a-zA-Z_u01-uff][a-zA-Z0-9_u01-uff]+" id="fqcn.pattern">
</xs:pattern>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="inverse-join-columns">
<xs:sequence>
<xs:element name="join-column" type="orm:join-column" minOccurs="1" maxOccurs="unbounded" />
@@ -501,11 +487,11 @@
<xs:element name="order-by" type="orm:order-by" minOccurs="0" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="target-entity" type="xs:string" use="required" />
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="mapped-by" type="xs:NMTOKEN" />
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
<xs:attribute name="index-by" type="xs:NMTOKEN" />
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
<xs:anyAttribute namespace="##other"/>
@@ -518,12 +504,12 @@
<xs:element name="order-by" type="orm:order-by" minOccurs="0" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="target-entity" type="xs:string" use="required" />
<xs:attribute name="mapped-by" type="xs:NMTOKEN" use="required" />
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="index-by" type="xs:NMTOKEN" />
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
@@ -538,10 +524,11 @@
</xs:choice>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="target-entity" type="xs:string" use="required" />
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
@@ -560,8 +547,8 @@
<xs:attribute name="target-entity" type="xs:string" use="required" />
<xs:attribute name="mapped-by" type="xs:NMTOKEN" />
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
@@ -576,15 +563,9 @@
<xs:sequence>
<xs:element name="join-table" type="orm:join-table" minOccurs="0" />
<xs:element name="join-columns" type="orm:join-columns" minOccurs="0" />
<xs:element name="inversed-by" type="orm:inversed-by-override" minOccurs="0" maxOccurs="1" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
<xs:attribute name="fetch" type="orm:fetch-type" use="optional" />
</xs:complexType>
<xs:complexType name="inversed-by-override">
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
</xs:complexType>
<xs:complexType name="attribute-overrides">

View File

@@ -27,6 +27,9 @@ use Doctrine\ORM\Query\Parameter;
use Doctrine\ORM\Cache\QueryCacheKey;
use Doctrine\DBAL\Cache\QueryCacheProfile;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Query\ResultSetMapping;
/**
* Base contract for ORM queries. Base class for Query and NativeQuery.
*
@@ -93,7 +96,7 @@ abstract class AbstractQuery
*
* @var array
*/
protected $_hints = [];
protected $_hints = array();
/**
* The hydration mode.
@@ -103,7 +106,7 @@ abstract class AbstractQuery
protected $_hydrationMode = self::HYDRATE_OBJECT;
/**
* @var \Doctrine\DBAL\Cache\QueryCacheProfile
* @param \Doctrine\DBAL\Cache\QueryCacheProfile
*/
protected $_queryCacheProfile;
@@ -115,7 +118,7 @@ abstract class AbstractQuery
protected $_expireResultCache = false;
/**
* @var \Doctrine\DBAL\Cache\QueryCacheProfile
* @param \Doctrine\DBAL\Cache\QueryCacheProfile
*/
protected $_hydrationCacheProfile;
@@ -175,6 +178,7 @@ abstract class AbstractQuery
}
/**
*
* Enable/disable second level query (result) caching for this query.
*
* @param boolean $cacheable
@@ -211,7 +215,7 @@ abstract class AbstractQuery
/**
* Obtain the name of the second level query cache region in which query results will be stored
*
* @return string|null The cache region name; NULL indicates the default region.
* @return The cache region name; NULL indicates the default region.
*/
public function getCacheRegion()
{
@@ -239,7 +243,7 @@ abstract class AbstractQuery
*
* @param integer $lifetime
*
* @return \Doctrine\ORM\AbstractQuery This query instance.
* @return static This query instance.
*/
public function setLifetime($lifetime)
{
@@ -259,7 +263,7 @@ abstract class AbstractQuery
/**
* @param integer $cacheMode
*
* @return \Doctrine\ORM\AbstractQuery This query instance.
* @return static This query instance.
*/
public function setCacheMode($cacheMode)
{
@@ -321,14 +325,14 @@ abstract class AbstractQuery
public function getParameter($key)
{
$filteredParameters = $this->parameters->filter(
function (Query\Parameter $parameter) use ($key) : bool {
$parameterName = $parameter->getName();
return $key === $parameterName || (string) $key === (string) $parameterName;
function ($parameter) use ($key)
{
// Must not be identical because of string to integer conversion
return ($key == $parameter->getName());
}
);
return ! $filteredParameters->isEmpty() ? $filteredParameters->first() : null;
return count($filteredParameters) ? $filteredParameters->first() : null;
}
/**
@@ -369,10 +373,17 @@ abstract class AbstractQuery
*/
public function setParameter($key, $value, $type = null)
{
$existingParameter = $this->getParameter($key);
$filteredParameters = $this->parameters->filter(
function ($parameter) use ($key)
{
// Must not be identical because of string to integer conversion
return ($key == $parameter->getName());
}
);
if ($existingParameter !== null) {
$existingParameter->setValue($value, $type);
if (count($filteredParameters)) {
$parameter = $filteredParameters->first();
$parameter->setValue($value, $type);
return $this;
}
@@ -387,7 +398,7 @@ abstract class AbstractQuery
*
* @param mixed $value
*
* @return array|string
* @return array
*
* @throws \Doctrine\ORM\ORMInvalidArgumentException
*/
@@ -491,7 +502,7 @@ abstract class AbstractQuery
*/
public function setHydrationCacheProfile(QueryCacheProfile $profile = null)
{
if ($profile !== null && ! $profile->getResultCacheDriver()) {
if ( ! $profile->getResultCacheDriver()) {
$resultCacheDriver = $this->_em->getConfiguration()->getHydrationCacheImpl();
$profile = $profile->setResultCacheDriver($resultCacheDriver);
}
@@ -521,7 +532,7 @@ abstract class AbstractQuery
*/
public function setResultCacheProfile(QueryCacheProfile $profile = null)
{
if ($profile !== null && ! $profile->getResultCacheDriver()) {
if ( ! $profile->getResultCacheDriver()) {
$resultCacheDriver = $this->_em->getConfiguration()->getResultCacheImpl();
$profile = $profile->setResultCacheDriver($resultCacheDriver);
}
@@ -709,7 +720,7 @@ abstract class AbstractQuery
*
* @param int $hydrationMode
*
* @return mixed
* @return array
*/
public function getResult($hydrationMode = self::HYDRATE_OBJECT)
{
@@ -786,7 +797,7 @@ abstract class AbstractQuery
* @return mixed
*
* @throws NonUniqueResultException If the query result is not unique.
* @throws NoResultException If the query returned no result and hydration mode is not HYDRATE_SINGLE_SCALAR.
* @throws NoResultException If the query returned no result.
*/
public function getSingleResult($hydrationMode = null)
{
@@ -812,9 +823,10 @@ abstract class AbstractQuery
*
* Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
*
* @return mixed The scalar result, or NULL if the query returned no result.
* @return mixed
*
* @throws NonUniqueResultException If the query result is not unique.
* @throws NoResultException If the query returned no result.
*/
public function getSingleScalarResult()
{
@@ -851,7 +863,7 @@ abstract class AbstractQuery
/**
* Check if the query has a hint
*
* @param string $name The name of the hint
* @param string $name The name of the hint
*
* @return bool False if the query does not have any hint
*/
@@ -944,7 +956,7 @@ abstract class AbstractQuery
}
if ( ! $result) {
$result = [];
$result = array();
}
$setCacheEntry = function($data) use ($cache, $result, $cacheKey, $realCacheKey, $queryCacheProfile) {
@@ -1038,7 +1050,7 @@ abstract class AbstractQuery
*/
protected function getHydrationCacheId()
{
$parameters = [];
$parameters = array();
foreach ($this->getParameters() as $parameter) {
$parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
@@ -1100,7 +1112,7 @@ abstract class AbstractQuery
{
$this->parameters = new ArrayCollection();
$this->_hints = [];
$this->_hints = array();
$this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints();
}

View File

@@ -43,8 +43,8 @@ class AssociationCacheEntry implements CacheEntry
public $class;
/**
* @param string $class The entity class.
* @param array $identifier The entity identifier.
* @param string $class The entity class.
* @param array $identifier The entity identifier.
*/
public function __construct($class, array $identifier)
{
@@ -58,8 +58,6 @@ class AssociationCacheEntry implements CacheEntry
* This method allow Doctrine\Common\Cache\PhpFileCache compatibility
*
* @param array $values array containing property values
*
* @return AssociationCacheEntry
*/
public static function __set_state(array $values)
{

View File

@@ -62,9 +62,8 @@ class CacheException extends ORMException
/**
* @param string $entityName
* @param string $field
*
* @return CacheException
* @return \Doctrine\ORM\Cache\CacheException
*/
public static function nonCacheableEntityAssociation($entityName, $field)
{

View File

@@ -50,7 +50,7 @@ class CollectionCacheEntry implements CacheEntry
*
* @param array $values array containing property values
*
* @return CollectionCacheEntry
* @return self
*/
public static function __set_state(array $values)
{

View File

@@ -53,7 +53,7 @@ class DefaultCache implements Cache
/**
* @var \Doctrine\ORM\Cache\QueryCache[]
*/
private $queryCaches = [];
private $queryCaches = array();
/**
* @var \Doctrine\ORM\Cache\QueryCache
@@ -314,7 +314,7 @@ class DefaultCache implements Cache
private function buildCollectionCacheKey(ClassMetadata $metadata, $association, $ownerIdentifier)
{
if ( ! is_array($ownerIdentifier)) {
$ownerIdentifier = $this->toIdentifierArray($metadata, $ownerIdentifier);
$ownerIdentifier = $this->toIdentifierArray($metadata, $ownerIdentifier);;
}
return new CollectionCacheKey($metadata->rootEntityName, $association, $ownerIdentifier);
@@ -336,7 +336,7 @@ class DefaultCache implements Cache
}
}
return [$metadata->identifier[0] => $identifier];
return array($metadata->identifier[0] => $identifier);
}
}

View File

@@ -30,6 +30,7 @@ use Doctrine\ORM\Cache\Persister\Collection\ReadWriteCachedCollectionPersister;
use Doctrine\ORM\Cache\Persister\Entity\NonStrictReadWriteCachedEntityPersister;
use Doctrine\ORM\Cache\Persister\Entity\ReadOnlyCachedEntityPersister;
use Doctrine\ORM\Cache\Persister\Entity\ReadWriteCachedEntityPersister;
use Doctrine\ORM\Cache\Region;
use Doctrine\ORM\Cache\Region\DefaultMultiGetRegion;
use Doctrine\ORM\Cache\Region\DefaultRegion;
use Doctrine\ORM\Cache\Region\FileLockRegion;
@@ -63,7 +64,7 @@ class DefaultCacheFactory implements CacheFactory
/**
* @var \Doctrine\ORM\Cache\Region[]
*/
private $regions = [];
private $regions = array();
/**
* @var string|null
@@ -166,10 +167,10 @@ class DefaultCacheFactory implements CacheFactory
return new DefaultQueryCache(
$em,
$this->getRegion(
[
array(
'region' => $regionName ?: Cache::DEFAULT_QUERY_REGION_NAME,
'usage' => ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE
]
)
)
);
}
@@ -199,9 +200,14 @@ class DefaultCacheFactory implements CacheFactory
return $this->regions[$cache['region']];
}
$name = $cache['region'];
$cacheAdapter = $this->createRegionCache($name);
$lifetime = $this->regionsConfig->getLifetime($cache['region']);
$cacheAdapter = clone $this->cache;
if ($cacheAdapter instanceof CacheProvider) {
$cacheAdapter->setNamespace($cache['region']);
}
$name = $cache['region'];
$lifetime = $this->regionsConfig->getLifetime($cache['region']);
$region = ($cacheAdapter instanceof MultiGetCache)
? new DefaultMultiGetRegion($name, $cacheAdapter, $lifetime)
@@ -209,13 +215,10 @@ class DefaultCacheFactory implements CacheFactory
if ($cache['usage'] === ClassMetadata::CACHE_USAGE_READ_WRITE) {
if (
'' === $this->fileLockRegionDirectory ||
null === $this->fileLockRegionDirectory
) {
if ( ! $this->fileLockRegionDirectory) {
throw new \LogicException(
'If you want to use a "READ_WRITE" cache an implementation of "Doctrine\ORM\Cache\ConcurrentRegion" is required, ' .
'The default implementation provided by doctrine is "Doctrine\ORM\Cache\Region\FileLockRegion" if you want to use it please provide a valid directory, DefaultCacheFactory#setFileLockRegionDirectory(). '
'If you what to use a "READ_WRITE" cache an implementation of "Doctrine\ORM\Cache\ConcurrentRegion" is required, ' .
'The default implementation provided by doctrine is "Doctrine\ORM\Cache\Region\FileLockRegion" if you what to use it please provide a valid directory, DefaultCacheFactory#setFileLockRegionDirectory(). '
);
}
@@ -226,30 +229,6 @@ class DefaultCacheFactory implements CacheFactory
return $this->regions[$cache['region']] = $region;
}
/**
* @param string $name
*
* @return CacheAdapter
*/
private function createRegionCache($name)
{
$cacheAdapter = clone $this->cache;
if (!$cacheAdapter instanceof CacheProvider) {
return $cacheAdapter;
}
$namespace = $cacheAdapter->getNamespace();
if ('' !== $namespace) {
$namespace .= ':';
}
$cacheAdapter->setNamespace($namespace . $name);
return $cacheAdapter;
}
/**
* {@inheritdoc}
*/

View File

@@ -46,7 +46,7 @@ class DefaultCollectionHydrator implements CollectionHydrator
/**
* @var array
*/
private static $hints = [Query::HINT_CACHE_ENABLED => true];
private static $hints = array(Query::HINT_CACHE_ENABLED => true);
/**
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
@@ -62,12 +62,11 @@ class DefaultCollectionHydrator implements CollectionHydrator
*/
public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, $collection)
{
$data = [];
$data = array();
foreach ($collection as $index => $entity) {
$data[$index] = new EntityCacheKey($metadata->rootEntityName, $this->uow->getEntityIdentifier($entity));
$data[$index] = new EntityCacheKey($metadata->name, $this->uow->getEntityIdentifier($entity));
}
return new CollectionCacheEntry($data);
}
@@ -80,7 +79,7 @@ class DefaultCollectionHydrator implements CollectionHydrator
/* @var $targetPersister \Doctrine\ORM\Cache\Persister\CachedPersister */
$targetPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
$targetRegion = $targetPersister->getCacheRegion();
$list = [];
$list = array();
$entityEntries = $targetRegion->getMultiple($entry);

View File

@@ -36,7 +36,7 @@ use Doctrine\ORM\Utility\IdentifierFlattener;
class DefaultEntityHydrator implements EntityHydrator
{
/**
* @var \Doctrine\ORM\EntityManagerInterface
* @var \Doctrine\ORM\EntityManager
*/
private $em;
@@ -55,7 +55,7 @@ class DefaultEntityHydrator implements EntityHydrator
/**
* @var array
*/
private static $hints = [Query::HINT_CACHE_ENABLED => true];
private static $hints = array(Query::HINT_CACHE_ENABLED => true);
/**
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
@@ -75,48 +75,35 @@ class DefaultEntityHydrator implements EntityHydrator
$data = $this->uow->getOriginalEntityData($entity);
$data = array_merge($data, $metadata->getIdentifierValues($entity)); // why update has no identifier values ?
if ($metadata->isVersioned) {
$data[$metadata->versionField] = $metadata->getFieldValue($entity, $metadata->versionField);
}
foreach ($metadata->associationMappings as $name => $assoc) {
if ( ! isset($data[$name])) {
continue;
}
if ( ! ($assoc['type'] & ClassMetadata::TO_ONE)) {
if (! ($assoc['type'] & ClassMetadata::TO_ONE)) {
unset($data[$name]);
continue;
}
if ( ! isset($assoc['cache'])) {
$targetClassMetadata = $this->em->getClassMetadata($assoc['targetEntity']);
$owningAssociation = ( ! $assoc['isOwningSide'])
? $targetClassMetadata->associationMappings[$assoc['mappedBy']]
: $assoc;
$associationIds = $this->identifierFlattener->flattenIdentifier(
$targetClassMetadata,
$targetClassMetadata->getIdentifierValues($data[$name])
);
$associationIds = $this->identifierFlattener->flattenIdentifier($targetClassMetadata, $targetClassMetadata->getIdentifierValues($data[$name]));
unset($data[$name]);
foreach ($associationIds as $fieldName => $fieldValue) {
if (isset($targetClassMetadata->fieldMappings[$fieldName])) {
$fieldMapping = $targetClassMetadata->fieldMappings[$fieldName];
$data[$owningAssociation['targetToSourceKeyColumns'][$fieldMapping['columnName']]] = $fieldValue;
if (isset($targetClassMetadata->associationMappings[$fieldName])){
$targetAssoc = $targetClassMetadata->associationMappings[$fieldName];
continue;
}
foreach($assoc['targetToSourceKeyColumns'] as $referencedColumn => $localColumn) {
$targetAssoc = $targetClassMetadata->associationMappings[$fieldName];
foreach($assoc['targetToSourceKeyColumns'] as $referencedColumn => $localColumn) {
if (isset($targetAssoc['sourceToTargetKeyColumns'][$referencedColumn])) {
$data[$localColumn] = $fieldValue;
if (isset($targetAssoc['sourceToTargetKeyColumns'][$referencedColumn])) {
$data[$localColumn] = $fieldValue;
}
}
}else{
$data[$assoc['targetToSourceKeyColumns'][$targetClassMetadata->columnNames[$fieldName]]] = $fieldValue;
}
}
@@ -139,10 +126,11 @@ class DefaultEntityHydrator implements EntityHydrator
// @TODO - fix it !
// handle UnitOfWork#createEntity hash generation
if ( ! is_array($targetId)) {
$data[reset($assoc['joinColumnFieldNames'])] = $targetId;
$targetEntity = $this->em->getClassMetadata($assoc['targetEntity']);
$targetId = [$targetEntity->identifier[0] => $targetId];
$targetId = array($targetEntity->identifier[0] => $targetId);
}
$data[$name] = new AssociationCacheEntry($assoc['targetEntity'], $targetId);
@@ -179,8 +167,7 @@ class DefaultEntityHydrator implements EntityHydrator
continue;
}
$assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']);
$assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocId);
$assocKey = new EntityCacheKey($assoc['targetEntity'], $assocId);
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
$assocRegion = $assocPersister->getCacheRegion();
$assocEntry = $assocRegion->get($assocKey);

View File

@@ -66,7 +66,7 @@ class DefaultQueryCache implements QueryCache
/**
* @var array
*/
private static $hints = [Query::HINT_CACHE_ENABLED => true];
private static $hints = array(Query::HINT_CACHE_ENABLED => true);
/**
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
@@ -86,57 +86,49 @@ class DefaultQueryCache implements QueryCache
/**
* {@inheritdoc}
*/
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = [])
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = array())
{
if ( ! ($key->cacheMode & Cache::MODE_GET)) {
return null;
}
$cacheEntry = $this->region->get($key);
$entry = $this->region->get($key);
if ( ! $cacheEntry instanceof QueryCacheEntry) {
if ( ! $entry instanceof QueryCacheEntry) {
return null;
}
if ( ! $this->validator->isValid($key, $cacheEntry)) {
if ( ! $this->validator->isValid($key, $entry)) {
$this->region->evict($key);
return null;
}
$result = [];
$result = array();
$entityName = reset($rsm->aliasMap);
$hasRelation = ( ! empty($rsm->relationMap));
$persister = $this->uow->getEntityPersister($entityName);
$region = $persister->getCacheRegion();
$regionName = $region->getName();
$cm = $this->em->getClassMetadata($entityName);
$generateKeys = function (array $entry) use ($cm): EntityCacheKey {
return new EntityCacheKey($cm->rootEntityName, $entry['identifier']);
};
$cacheKeys = new CollectionCacheEntry(array_map($generateKeys, $cacheEntry->result));
$entries = $region->getMultiple($cacheKeys);
// @TODO - move to cache hydration component
foreach ($cacheEntry->result as $index => $entry) {
$entityEntry = is_array($entries) && array_key_exists($index, $entries) ? $entries[$index] : null;
foreach ($entry->result as $index => $entry) {
if (($entityEntry = $region->get($entityKey = new EntityCacheKey($entityName, $entry['identifier']))) === null) {
if ($entityEntry === null) {
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheMiss($regionName, $cacheKeys->identifiers[$index]);
$this->cacheLogger->entityCacheMiss($regionName, $entityKey);
}
return null;
}
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheHit($regionName, $cacheKeys->identifiers[$index]);
$this->cacheLogger->entityCacheHit($regionName, $entityKey);
}
if ( ! $hasRelation) {
$result[$index] = $this->uow->createEntity($entityEntry->class, $entityEntry->resolveAssociationEntries($this->em), self::$hints);
continue;
@@ -145,13 +137,13 @@ class DefaultQueryCache implements QueryCache
$data = $entityEntry->data;
foreach ($entry['associations'] as $name => $assoc) {
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
$assocRegion = $assocPersister->getCacheRegion();
$assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']);
if ($assoc['type'] & ClassMetadata::TO_ONE) {
if (($assocEntry = $assocRegion->get($assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assoc['identifier']))) === null) {
if (($assocEntry = $assocRegion->get($assocKey = new EntityCacheKey($assoc['targetEntity'], $assoc['identifier']))) === null) {
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey);
@@ -175,20 +167,15 @@ class DefaultQueryCache implements QueryCache
continue;
}
$generateKeys = function ($id) use ($assocMetadata): EntityCacheKey {
return new EntityCacheKey($assocMetadata->rootEntityName, $id);
};
$collection = new PersistentCollection($this->em, $assocMetadata, new ArrayCollection());
$assocKeys = new CollectionCacheEntry(array_map($generateKeys, $assoc['list']));
$assocEntries = $assocRegion->getMultiple($assocKeys);
$targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
$collection = new PersistentCollection($this->em, $targetClass, new ArrayCollection());
foreach ($assoc['list'] as $assocIndex => $assocId) {
$assocEntry = is_array($assocEntries) && array_key_exists($assocIndex, $assocEntries) ? $assocEntries[$assocIndex] : null;
if ($assocEntry === null) {
if (($assocEntry = $assocRegion->get($assocKey = new EntityCacheKey($assoc['targetEntity'], $assocId))) === null) {
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKeys->identifiers[$assocIndex]);
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey);
}
$this->uow->hydrationComplete();
@@ -201,7 +188,7 @@ class DefaultQueryCache implements QueryCache
$collection->hydrateSet($assocIndex, $element);
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKeys->identifiers[$assocIndex]);
$this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKey);
}
}
@@ -210,25 +197,6 @@ class DefaultQueryCache implements QueryCache
$collection->setInitialized(true);
}
foreach ($data as $fieldName => $unCachedAssociationData) {
// In some scenarios, such as EAGER+ASSOCIATION+ID+CACHE, the
// cache key information in `$cacheEntry` will not contain details
// for fields that are associations.
//
// This means that `$data` keys for some associations that may
// actually not be cached will not be converted to actual association
// data, yet they contain L2 cache AssociationCacheEntry objects.
//
// We need to unwrap those associations into proxy references,
// since we don't have actual data for them except for identifiers.
if ($unCachedAssociationData instanceof AssociationCacheEntry) {
$data[$fieldName] = $this->em->getReference(
$unCachedAssociationData->class,
$unCachedAssociationData->identifier
);
}
}
$result[$index] = $this->uow->createEntity($entityEntry->class, $data, self::$hints);
}
@@ -240,7 +208,7 @@ class DefaultQueryCache implements QueryCache
/**
* {@inheritdoc}
*/
public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = [])
public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = array())
{
if ($rsm->scalarMappings) {
throw new CacheException("Second level cache does not support scalar results.");
@@ -262,9 +230,10 @@ class DefaultQueryCache implements QueryCache
return false;
}
$data = [];
$data = array();
$entityName = reset($rsm->aliasMap);
$rootAlias = key($rsm->aliasMap);
$hasRelation = ( ! empty($rsm->relationMap));
$metadata = $this->em->getClassMetadata($entityName);
$persister = $this->uow->getEntityPersister($entityName);
if ( ! ($persister instanceof CachedPersister)) {
@@ -275,185 +244,86 @@ class DefaultQueryCache implements QueryCache
foreach ($result as $index => $entity) {
$identifier = $this->uow->getEntityIdentifier($entity);
$entityKey = new EntityCacheKey($entityName, $identifier);
$data[$index]['identifier'] = $identifier;
$data[$index]['associations'] = array();
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $region->contains($entityKey)) {
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $region->contains($entityKey = new EntityCacheKey($entityName, $identifier))) {
// Cancel put result if entity put fail
if ( ! $persister->storeEntityCache($entity, $entityKey)) {
return false;
}
}
$data[$index]['identifier'] = $identifier;
$data[$index]['associations'] = [];
if ( ! $hasRelation) {
continue;
}
// @TODO - move to cache hydration components
foreach ($rsm->relationMap as $alias => $name) {
$parentAlias = $rsm->parentAliasMap[$alias];
$parentClass = $rsm->aliasMap[$parentAlias];
$metadata = $this->em->getClassMetadata($parentClass);
$assoc = $metadata->associationMappings[$name];
$assocValue = $this->getAssociationValue($rsm, $alias, $entity);
foreach ($rsm->relationMap as $name) {
$assoc = $metadata->associationMappings[$name];
if ($assocValue === null) {
if (($assocValue = $metadata->getFieldValue($entity, $name)) === null || $assocValue instanceof Proxy) {
continue;
}
// root entity association
if ($rootAlias === $parentAlias) {
// Cancel put result if association put fail
if ( ($assocInfo = $this->storeAssociationCache($key, $assoc, $assocValue)) === null) {
return false;
if ( ! isset($assoc['cache'])) {
throw CacheException::nonCacheableEntityAssociation($entityName, $name);
}
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
$assocRegion = $assocPersister->getCacheRegion();
$assocMetadata = $assocPersister->getClassMetadata();
// Handle *-to-one associations
if ($assoc['type'] & ClassMetadata::TO_ONE) {
$assocIdentifier = $this->uow->getEntityIdentifier($assocValue);
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier))) {
// Cancel put result if association entity put fail
if ( ! $assocPersister->storeEntityCache($assocValue, $entityKey)) {
return false;
}
}
$data[$index]['associations'][$name] = $assocInfo;
$data[$index]['associations'][$name] = array(
'targetEntity' => $assocMetadata->rootEntityName,
'identifier' => $assocIdentifier,
'type' => $assoc['type']
);
continue;
}
// store single nested association
if ( ! is_array($assocValue)) {
// Cancel put result if association put fail
if ($this->storeAssociationCache($key, $assoc, $assocValue) === null) {
return false;
// Handle *-to-many associations
$list = array();
foreach ($assocValue as $assocItemIndex => $assocItem) {
$assocIdentifier = $this->uow->getEntityIdentifier($assocItem);
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier))) {
// Cancel put result if entity put fail
if ( ! $assocPersister->storeEntityCache($assocItem, $entityKey)) {
return false;
}
}
continue;
$list[$assocItemIndex] = $assocIdentifier;
}
// store array of nested association
foreach ($assocValue as $aVal) {
// Cancel put result if association put fail
if ($this->storeAssociationCache($key, $assoc, $aVal) === null) {
return false;
}
}
$data[$index]['associations'][$name] = array(
'targetEntity' => $assocMetadata->rootEntityName,
'type' => $assoc['type'],
'list' => $list,
);
}
}
return $this->region->put($key, new QueryCacheEntry($data));
}
/**
* @param \Doctrine\ORM\Cache\QueryCacheKey $key
* @param array $assoc
* @param mixed $assocValue
*
* @return array|null
*/
private function storeAssociationCache(QueryCacheKey $key, array $assoc, $assocValue)
{
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
$assocMetadata = $assocPersister->getClassMetadata();
$assocRegion = $assocPersister->getCacheRegion();
// Handle *-to-one associations
if ($assoc['type'] & ClassMetadata::TO_ONE) {
$assocIdentifier = $this->uow->getEntityIdentifier($assocValue);
$entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier);
if ( ! $assocValue instanceof Proxy && ($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) {
// Entity put fail
if ( ! $assocPersister->storeEntityCache($assocValue, $entityKey)) {
return null;
}
}
return [
'targetEntity' => $assocMetadata->rootEntityName,
'identifier' => $assocIdentifier,
'type' => $assoc['type']
];
}
// Handle *-to-many associations
$list = [];
foreach ($assocValue as $assocItemIndex => $assocItem) {
$assocIdentifier = $this->uow->getEntityIdentifier($assocItem);
$entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier);
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) {
// Entity put fail
if ( ! $assocPersister->storeEntityCache($assocItem, $entityKey)) {
return null;
}
}
$list[$assocItemIndex] = $assocIdentifier;
}
return [
'targetEntity' => $assocMetadata->rootEntityName,
'type' => $assoc['type'],
'list' => $list,
];
}
/**
* @param \Doctrine\ORM\Query\ResultSetMapping $rsm
* @param string $assocAlias
* @param object $entity
*
* @return array|object
*/
private function getAssociationValue(ResultSetMapping $rsm, $assocAlias, $entity)
{
$path = [];
$alias = $assocAlias;
while (isset($rsm->parentAliasMap[$alias])) {
$parent = $rsm->parentAliasMap[$alias];
$field = $rsm->relationMap[$alias];
$class = $rsm->aliasMap[$parent];
array_unshift($path, [
'field' => $field,
'class' => $class
]
);
$alias = $parent;
}
return $this->getAssociationPathValue($entity, $path);
}
/**
* @param mixed $value
* @param array $path
*
* @return array|object|null
*/
private function getAssociationPathValue($value, array $path)
{
$mapping = array_shift($path);
$metadata = $this->em->getClassMetadata($mapping['class']);
$assoc = $metadata->associationMappings[$mapping['field']];
$value = $metadata->getFieldValue($value, $mapping['field']);
if ($value === null) {
return null;
}
if (empty($path)) {
return $value;
}
// Handle *-to-one associations
if ($assoc['type'] & ClassMetadata::TO_ONE) {
return $this->getAssociationPathValue($value, $path);
}
$values = [];
foreach ($value as $item) {
$values[] = $this->getAssociationPathValue($item, $path);
}
return $values;
}
/**
* {@inheritdoc}
*/

View File

@@ -60,8 +60,6 @@ class EntityCacheEntry implements CacheEntry
* This method allow Doctrine\Common\Cache\PhpFileCache compatibility
*
* @param array $values array containing property values
*
* @return EntityCacheEntry
*/
public static function __set_state(array $values)
{
@@ -71,7 +69,7 @@ class EntityCacheEntry implements CacheEntry
/**
* Retrieves the entity data resolving cache entries
*
* @param \Doctrine\ORM\EntityManagerInterface $em
* @param \Doctrine\ORM\EntityManagerInterfac $em
*
* @return array
*/

View File

@@ -53,6 +53,6 @@ class Lock
*/
public static function createLockRead()
{
return new self(uniqid(time(), true));
return new self(uniqid(time()));
}
}

View File

@@ -35,7 +35,7 @@ class CacheLoggerChain implements CacheLogger
/**
* @var array<\Doctrine\ORM\Cache\Logging\CacheLogger>
*/
private $loggers = [];
private $loggers = array();
/**
* @param string $name

View File

@@ -35,17 +35,17 @@ class StatisticsCacheLogger implements CacheLogger
/**
* @var array
*/
private $cacheMissCountMap = [];
private $cacheMissCountMap = array();
/**
* @var array
*/
private $cacheHitCountMap = [];
private $cacheHitCountMap = array();
/**
* @var array
*/
private $cachePutCountMap = [];
private $cachePutCountMap = array();
/**
* {@inheritdoc}
@@ -214,9 +214,9 @@ class StatisticsCacheLogger implements CacheLogger
*/
public function clearStats()
{
$this->cachePutCountMap = [];
$this->cacheHitCountMap = [];
$this->cacheMissCountMap = [];
$this->cachePutCountMap = array();
$this->cacheHitCountMap = array();
$this->cacheMissCountMap = array();
}
/**

View File

@@ -70,7 +70,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
/**
* @var array
*/
protected $queuedCache = [];
protected $queuedCache = array();
/**
* @var \Doctrine\ORM\Cache\Region

View File

@@ -46,7 +46,7 @@ class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPers
}
}
$this->queuedCache = [];
$this->queuedCache = array();
}
/**
@@ -54,7 +54,7 @@ class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPers
*/
public function afterTransactionRolledBack()
{
$this->queuedCache = [];
$this->queuedCache = array();
}
/**
@@ -96,9 +96,9 @@ class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPers
$this->persister->update($collection);
$this->queuedCache['update'][spl_object_hash($collection)] = [
$this->queuedCache['update'][spl_object_hash($collection)] = array(
'key' => $key,
'list' => $collection
];
);
}
}

View File

@@ -35,7 +35,7 @@ class ReadOnlyCachedCollectionPersister extends NonStrictReadWriteCachedCollecti
*/
public function update(PersistentCollection $collection)
{
if ($collection->isDirty() && $collection->getSnapshot()) {
if ($collection->isDirty() && count($collection->getSnapshot()) > 0) {
throw CacheException::updateReadOnlyCollection(ClassUtils::getClass($collection->getOwner()), $this->association['fieldName']);
}

View File

@@ -60,7 +60,7 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
}
}
$this->queuedCache = [];
$this->queuedCache = array();
}
/**
@@ -80,7 +80,7 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
}
}
$this->queuedCache = [];
$this->queuedCache = array();
}
/**
@@ -98,10 +98,10 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
return;
}
$this->queuedCache['delete'][spl_object_hash($collection)] = [
$this->queuedCache['delete'][spl_object_hash($collection)] = array(
'key' => $key,
'lock' => $lock
];
);
}
/**
@@ -126,9 +126,9 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
return;
}
$this->queuedCache['update'][spl_object_hash($collection)] = [
$this->queuedCache['update'][spl_object_hash($collection)] = array(
'key' => $key,
'lock' => $lock
];
);
}
}

View File

@@ -27,6 +27,7 @@ use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\TimestampCacheKey;
use Doctrine\ORM\Cache\QueryCacheKey;
use Doctrine\ORM\Cache\Persister\CachedPersister;
use Doctrine\ORM\Cache\CacheException;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\EntityManagerInterface;
@@ -64,7 +65,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* @var array
*/
protected $queuedCache = [];
protected $queuedCache = array();
/**
* @var \Doctrine\ORM\Cache\Region
@@ -104,7 +105,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* Associations configured as FETCH_EAGER, as well as all inverse one-to-one associations.
*
* @var array|null
* @var array
*/
protected $joinedAssociations;
@@ -160,7 +161,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
public function getCountSQL($criteria = [])
public function getCountSQL($criteria = array())
{
return $this->persister->getCountSQL($criteria);
}
@@ -233,6 +234,14 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$class = $this->metadataFactory->getMetadataFor($className);
}
if ($class->containsForeignIdentifier) {
foreach ($class->associationMappings as $name => $assoc) {
if (!empty($assoc['id']) && !isset($assoc['cache'])) {
throw CacheException::nonCacheableEntityAssociation($class->name, $name);
}
}
}
$entry = $this->hydrator->buildCacheEntry($class, $key, $entity);
$cached = $this->region->put($key, $entry);
@@ -249,7 +258,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
private function storeJoinedAssociations($entity)
{
if ($this->joinedAssociations === null) {
$associations = [];
$associations = array();
foreach ($this->class->associationMappings as $name => $assoc) {
if (isset($assoc['cache']) &&
@@ -272,8 +281,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
$assocId = $this->uow->getEntityIdentifier($assocEntity);
$assocMetadata = $this->metadataFactory->getMetadataFor($assoc['targetEntity']);
$assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocId);
$assocKey = new EntityCacheKey($assoc['targetEntity'], $assocId);
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
$assocPersister->storeEntityCache($assocEntity, $assocKey);
@@ -283,9 +291,9 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* Generates a string of currently query
*
* @param array $query
* @param string $criteria
* @param array $orderBy
* @param array $query
* @param string $criteria
* @param array $orderBy
* @param integer $limit
* @param integer $offset
*
@@ -361,7 +369,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritdoc}
*/
public function load(array $criteria, $entity = null, $assoc = null, array $hints = [], $lockMode = null, $limit = null, array $orderBy = null)
public function load(array $criteria, $entity = null, $assoc = null, array $hints = array(), $lockMode = null, $limit = null, array $orderBy = null)
{
if ($entity !== null || $assoc !== null || ! empty($hints) || $lockMode !== null) {
return $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy);
@@ -387,7 +395,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
return null;
}
$cached = $queryCache->put($queryKey, $rsm, [$result]);
$cached = $queryCache->put($queryKey, $rsm, array($result));
if ($this->cacheLogger) {
if ($result) {
@@ -405,7 +413,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritdoc}
*/
public function loadAll(array $criteria = [], array $orderBy = null, $limit = null, $offset = null)
public function loadAll(array $criteria = array(), array $orderBy = null, $limit = null, $offset = null)
{
$query = $this->persister->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy);
$hash = $this->getHash($query, $criteria, null, null, null);
@@ -477,7 +485,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$cacheEntry = $this->hydrator->buildCacheEntry($class, $cacheKey, $entity);
$cached = $this->region->put($cacheKey, $cacheEntry);
if ($cached && (null === $this->joinedAssociations || $this->joinedAssociations)) {
if ($cached && ($this->joinedAssociations === null || count($this->joinedAssociations) > 0)) {
$this->storeJoinedAssociations($entity);
}
@@ -495,7 +503,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
public function count($criteria = [])
public function count($criteria = array())
{
return $this->persister->count($criteria);
}
@@ -548,28 +556,28 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$hasCache = ($persister instanceof CachedPersister);
$key = null;
if ( ! $hasCache) {
return $this->persister->loadManyToManyCollection($assoc, $sourceEntity, $coll);
}
if ($hasCache) {
$ownerId = $this->uow->getEntityIdentifier($coll->getOwner());
$key = new CollectionCacheKey($assoc['sourceEntity'], $assoc['fieldName'], $ownerId);
$list = $persister->loadCollectionCache($coll, $key);
$ownerId = $this->uow->getEntityIdentifier($coll->getOwner());
$key = $this->buildCollectionCacheKey($assoc, $ownerId);
$list = $persister->loadCollectionCache($coll, $key);
if ($list !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key);
}
if ($list !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key);
return $list;
}
return $list;
}
$list = $this->persister->loadManyToManyCollection($assoc, $sourceEntity, $coll);
$persister->storeCollectionCache($key, $list);
if ($hasCache) {
$persister->storeCollectionCache($key, $list);
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
}
}
return $list;
@@ -583,28 +591,28 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$persister = $this->uow->getCollectionPersister($assoc);
$hasCache = ($persister instanceof CachedPersister);
if ( ! $hasCache) {
return $this->persister->loadOneToManyCollection($assoc, $sourceEntity, $coll);
}
if ($hasCache) {
$ownerId = $this->uow->getEntityIdentifier($coll->getOwner());
$key = new CollectionCacheKey($assoc['sourceEntity'], $assoc['fieldName'], $ownerId);
$list = $persister->loadCollectionCache($coll, $key);
$ownerId = $this->uow->getEntityIdentifier($coll->getOwner());
$key = $this->buildCollectionCacheKey($assoc, $ownerId);
$list = $persister->loadCollectionCache($coll, $key);
if ($list !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key);
}
if ($list !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key);
return $list;
}
return $list;
}
$list = $this->persister->loadOneToManyCollection($assoc, $sourceEntity, $coll);
$persister->storeCollectionCache($key, $list);
if ($hasCache) {
$persister->storeCollectionCache($key, $list);
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
if ($this->cacheLogger) {
$this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
}
}
return $list;
@@ -613,7 +621,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritdoc}
*/
public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = [])
public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = array())
{
return $this->persister->loadOneToOneEntity($assoc, $sourceEntity, $identifier);
}
@@ -634,17 +642,4 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$this->persister->refresh($id, $entity, $lockMode);
}
/**
* @param array $association
* @param array $ownerId
*
* @return CollectionCacheKey
*/
protected function buildCollectionCacheKey(array $association, $ownerId)
{
/** @var ClassMetadata $metadata */
$metadata = $this->metadataFactory->getMetadataFor($association['sourceEntity']);
return new CollectionCacheKey($metadata->rootEntityName, $association['fieldName'], $ownerId);
}
}

View File

@@ -38,9 +38,8 @@ interface CachedEntityPersister extends CachedPersister, EntityPersister
public function getEntityHydrator();
/**
* @param object $entity
* @param \Doctrine\ORM\Cache\EntityCacheKey $key
*
* @param object $entity
* @param \Doctrine\ORM\Cache\EntityCacheKey $key
* @return boolean
*/
public function storeEntityCache($entity, EntityCacheKey $key);

View File

@@ -20,6 +20,7 @@
namespace Doctrine\ORM\Cache\Persister\Entity;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\ORM\Cache\EntityCacheKey;
/**
@@ -62,7 +63,7 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
$this->timestampRegion->update($this->timestampKey);
}
$this->queuedCache = [];
$this->queuedCache = array();
}
/**
@@ -70,7 +71,7 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
*/
public function afterTransactionRolledBack()
{
$this->queuedCache = [];
$this->queuedCache = array();
}
/**

View File

@@ -73,7 +73,7 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
$this->timestampRegion->update($this->timestampKey);
}
$this->queuedCache = [];
$this->queuedCache = array();
}
/**
@@ -93,7 +93,7 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
}
}
$this->queuedCache = [];
$this->queuedCache = array();
}
/**
@@ -113,10 +113,10 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
return $deleted;
}
$this->queuedCache['delete'][] = [
$this->queuedCache['delete'][] = array(
'lock' => $lock,
'key' => $key
];
);
return $deleted;
}
@@ -135,9 +135,9 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
return;
}
$this->queuedCache['update'][] = [
$this->queuedCache['update'][] = array(
'lock' => $lock,
'key' => $key
];
);
}
}

View File

@@ -44,7 +44,7 @@ interface QueryCache
*
* @return boolean
*/
public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = []);
public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = array());
/**
* @param \Doctrine\ORM\Cache\QueryCacheKey $key
@@ -53,7 +53,7 @@ interface QueryCache
*
* @return array|null
*/
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = []);
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = array());
/**
* @return \Doctrine\ORM\Cache\Region

View File

@@ -54,8 +54,6 @@ class QueryCacheEntry implements CacheEntry
/**
* @param array $values
*
* @return QueryCacheEntry
*/
public static function __set_state(array $values)
{

View File

@@ -21,6 +21,7 @@
namespace Doctrine\ORM\Cache\Region;
use Doctrine\Common\Cache\MultiGetCache;
use Doctrine\ORM\Cache\Region;
use Doctrine\ORM\Cache\CollectionCacheEntry;
/**
@@ -55,7 +56,7 @@ class DefaultMultiGetRegion extends DefaultRegion
*/
public function getMultiple(CollectionCacheEntry $collection)
{
$keysToRetrieve = [];
$keysToRetrieve = array();
foreach ($collection->identifiers as $index => $key) {
$keysToRetrieve[$index] = $this->getCacheEntryKey($key);
@@ -66,7 +67,7 @@ class DefaultMultiGetRegion extends DefaultRegion
return null;
}
$returnableItems = [];
$returnableItems = array();
foreach ($keysToRetrieve as $index => $key) {
$returnableItems[$index] = $items[$key];
}

View File

@@ -102,7 +102,7 @@ class DefaultRegion implements Region
*/
public function getMultiple(CollectionCacheEntry $collection)
{
$result = [];
$result = array();
foreach ($collection->identifiers as $key) {
$entryKey = $this->getCacheEntryKey($key);

View File

@@ -114,7 +114,7 @@ class FileLockRegion implements ConcurrentRegion
/**
* @param \Doctrine\ORM\Cache\CacheKey $key
*
* @return string
* return string
*/
private function getLockFileName(CacheKey $key)
{
@@ -124,7 +124,7 @@ class FileLockRegion implements ConcurrentRegion
/**
* @param string $filename
*
* @return string
* return string
*/
private function getLockContent($filename)
{
@@ -134,7 +134,7 @@ class FileLockRegion implements ConcurrentRegion
/**
* @param string $filename
*
* @return integer
* return integer
*/
private function getLockTime($filename)
{
@@ -142,7 +142,7 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* {@inheritdoc}
* {inheritdoc}
*/
public function getName()
{
@@ -150,7 +150,7 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* {@inheritdoc}
* {inheritdoc}
*/
public function contains(CacheKey $key)
{
@@ -162,7 +162,7 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* {@inheritdoc}
* {inheritdoc}
*/
public function get(CacheKey $key)
{
@@ -186,7 +186,7 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* {@inheritdoc}
* {inheritdoc}
*/
public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null)
{
@@ -198,7 +198,7 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* {@inheritdoc}
* {inheritdoc}
*/
public function evict(CacheKey $key)
{
@@ -210,7 +210,7 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* {@inheritdoc}
* {inheritdoc}
*/
public function evictAll()
{
@@ -228,7 +228,7 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* {@inheritdoc}
* {inheritdoc}
*/
public function lock(CacheKey $key)
{
@@ -248,7 +248,7 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* {@inheritdoc}
* {inheritdoc}
*/
public function unlock(CacheKey $key, Lock $lock)
{

View File

@@ -31,12 +31,12 @@ class RegionsConfiguration
/**
* @var array
*/
private $lifetimes = [];
private $lifetimes = array();
/**
* @var array
*/
private $lockLifetimes = [];
private $lockLifetimes = array();
/**
* @var integer

View File

@@ -40,7 +40,7 @@ class TimestampCacheEntry implements CacheEntry
*/
public function __construct($time = null)
{
$this->time = $time ? (float) $time : microtime(true);
$this->time = $time ? (float)$time : microtime(true);
}
/**
@@ -49,8 +49,6 @@ class TimestampCacheEntry implements CacheEntry
* This method allow Doctrine\Common\Cache\PhpFileCache compatibility
*
* @param array $values array containing property values
*
* @return TimestampCacheEntry
*/
public static function __set_state(array $values)
{

View File

@@ -25,11 +25,9 @@ use Doctrine\Common\Annotations\CachedReader;
use Doctrine\Common\Annotations\SimpleAnnotationReader;
use Doctrine\Common\Cache\ArrayCache;
use Doctrine\Common\Cache\Cache as CacheDriver;
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;
use Doctrine\Common\Persistence\ObjectRepository;
use Doctrine\Common\Proxy\AbstractProxyFactory;
use Doctrine\ORM\Cache\CacheConfiguration;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;
use Doctrine\ORM\Mapping\DefaultEntityListenerResolver;
use Doctrine\ORM\Mapping\DefaultNamingStrategy;
use Doctrine\ORM\Mapping\DefaultQuoteStrategy;
@@ -100,7 +98,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function setAutoGenerateProxyClasses($autoGenerate)
{
$this->_attributes['autoGenerateProxyClasses'] = (int) $autoGenerate;
$this->_attributes['autoGenerateProxyClasses'] = (int)$autoGenerate;
}
/**
@@ -151,7 +149,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* @return AnnotationDriver
*/
public function newDefaultAnnotationDriver($paths = [], $useSimpleAnnotationReader = true)
public function newDefaultAnnotationDriver($paths = array(), $useSimpleAnnotationReader = true)
{
AnnotationRegistry::registerFile(__DIR__ . '/Mapping/Driver/DoctrineAnnotations.php');
@@ -351,7 +349,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function addNamedNativeQuery($name, $sql, Query\ResultSetMapping $rsm)
{
$this->_attributes['namedNativeQueries'][$name] = [$sql, $rsm];
$this->_attributes['namedNativeQueries'][$name] = array($sql, $rsm);
}
/**
@@ -420,9 +418,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
* @param string|callable $className Class name or a callable that returns the function.
*
* @return void
*
* @throws ORMException
*/
public function addCustomStringFunction($name, $className)
{
if (Query\Parser::isInternalFunction($name)) {
throw ORMException::overwriteInternalDQLFunctionNotAllowed($name);
}
$this->_attributes['customStringFunctions'][strtolower($name)] = $className;
}
@@ -472,9 +476,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
* @param string|callable $className Class name or a callable that returns the function.
*
* @return void
*
* @throws ORMException
*/
public function addCustomNumericFunction($name, $className)
{
if (Query\Parser::isInternalFunction($name)) {
throw ORMException::overwriteInternalDQLFunctionNotAllowed($name);
}
$this->_attributes['customNumericFunctions'][strtolower($name)] = $className;
}
@@ -524,9 +534,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
* @param string|callable $className Class name or a callable that returns the function.
*
* @return void
*
* @throws ORMException
*/
public function addCustomDatetimeFunction($name, $className)
{
if (Query\Parser::isInternalFunction($name)) {
throw ORMException::overwriteInternalDQLFunctionNotAllowed($name);
}
$this->_attributes['customDatetimeFunctions'][strtolower($name)] = $className;
}
@@ -574,7 +590,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function setCustomHydrationModes($modes)
{
$this->_attributes['customHydrationModes'] = [];
$this->_attributes['customHydrationModes'] = array();
foreach ($modes as $modeName => $hydrator) {
$this->addCustomHydrationMode($modeName, $hydrator);
@@ -626,7 +642,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
public function getClassMetadataFactoryName()
{
if ( ! isset($this->_attributes['classMetadataFactoryName'])) {
$this->_attributes['classMetadataFactoryName'] = ClassMetadataFactory::class;
$this->_attributes['classMetadataFactoryName'] = 'Doctrine\ORM\Mapping\ClassMetadataFactory';
}
return $this->_attributes['classMetadataFactoryName'];
@@ -648,7 +664,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* @param string $name The name of the filter.
*
* @return string The class name of the filter, or null if it is not
* @return string The class name of the filter, or null of it is not
* defined.
*/
public function getFilterClassName($name)
@@ -673,7 +689,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
{
$reflectionClass = new \ReflectionClass($className);
if ( ! $reflectionClass->implementsInterface(ObjectRepository::class)) {
if ( ! $reflectionClass->implementsInterface('Doctrine\Common\Persistence\ObjectRepository')) {
throw ORMException::invalidEntityRepository($className);
}
@@ -691,7 +707,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
{
return isset($this->_attributes['defaultRepositoryClassName'])
? $this->_attributes['defaultRepositoryClassName']
: EntityRepository::class;
: 'Doctrine\ORM\EntityRepository';
}
/**
@@ -865,7 +881,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function getDefaultQueryHints()
{
return isset($this->_attributes['defaultQueryHints']) ? $this->_attributes['defaultQueryHints'] : [];
return isset($this->_attributes['defaultQueryHints']) ? $this->_attributes['defaultQueryHints'] : array();
}
/**

View File

@@ -19,15 +19,14 @@
namespace Doctrine\ORM;
use Exception;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\Query\FilterCollection;
use Doctrine\Common\Util\ClassUtils;
use Throwable;
/**
* The EntityManager is the central access point to ORM functionality.
@@ -237,9 +236,9 @@ use Throwable;
$this->conn->commit();
return $return ?: true;
} catch (Throwable $e) {
} catch (Exception $e) {
$this->close();
$this->conn->rollBack();
$this->conn->rollback();
throw $e;
}
@@ -258,7 +257,7 @@ use Throwable;
*/
public function rollback()
{
$this->conn->rollBack();
$this->conn->rollback();
}
/**
@@ -290,7 +289,7 @@ use Throwable;
$query = new Query($this);
if ( ! empty($dql)) {
$query->setDQL($dql);
$query->setDql($dql);
}
return $query;
@@ -311,7 +310,7 @@ use Throwable;
{
$query = new NativeQuery($this);
$query->setSQL($sql);
$query->setSql($sql);
$query->setResultSetMapping($rsm);
return $query;
@@ -349,7 +348,6 @@ use Throwable;
*
* @throws \Doctrine\ORM\OptimisticLockException If a version check on an entity that
* makes use of optimistic locking fails.
* @throws ORMException
*/
public function flush($entity = null)
{
@@ -385,7 +383,7 @@ use Throwable;
throw ORMInvalidArgumentException::invalidCompositeIdentifier();
}
$id = [$class->identifier[0] => $id];
$id = array($class->identifier[0] => $id);
}
foreach ($id as $i => $value) {
@@ -398,7 +396,7 @@ use Throwable;
}
}
$sortedId = [];
$sortedId = array();
foreach ($class->identifier as $identifier) {
if ( ! isset($id[$identifier])) {
@@ -451,13 +449,14 @@ use Throwable;
return $entity;
case LockMode::NONE === $lockMode:
case LockMode::PESSIMISTIC_READ === $lockMode:
case LockMode::PESSIMISTIC_WRITE === $lockMode:
if ( ! $this->getConnection()->isTransactionActive()) {
throw TransactionRequiredException::transactionRequired();
}
return $persister->load($sortedId, null, null, [], $lockMode);
return $persister->load($sortedId, null, null, array(), $lockMode);
default:
return $persister->loadById($sortedId);
@@ -472,10 +471,10 @@ use Throwable;
$class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
if ( ! is_array($id)) {
$id = [$class->identifier[0] => $id];
$id = array($class->identifier[0] => $id);
}
$sortedId = [];
$sortedId = array();
foreach ($class->identifier as $identifier) {
if ( ! isset($id[$identifier])) {
@@ -483,11 +482,6 @@ use Throwable;
}
$sortedId[$identifier] = $id[$identifier];
unset($id[$identifier]);
}
if ($id) {
throw ORMException::unrecognizedIdentifierFields($class->name, array_keys($id));
}
// Check identity map first, if its already in there just return it.
@@ -499,9 +493,13 @@ use Throwable;
return $this->find($entityName, $sortedId);
}
if ( ! is_array($sortedId)) {
$sortedId = array($class->identifier[0] => $sortedId);
}
$entity = $this->proxyFactory->getProxy($class->name, $sortedId);
$this->unitOfWork->registerManaged($entity, $sortedId, []);
$this->unitOfWork->registerManaged($entity, $sortedId, array());
return $entity;
}
@@ -519,14 +517,14 @@ use Throwable;
}
if ( ! is_array($identifier)) {
$identifier = [$class->identifier[0] => $identifier];
$identifier = array($class->identifier[0] => $identifier);
}
$entity = $class->newInstance();
$class->setIdentifierValues($entity, $identifier);
$this->unitOfWork->registerManaged($entity, $identifier, []);
$this->unitOfWork->registerManaged($entity, $identifier, array());
$this->unitOfWork->markReadOnly($entity);
return $entity;
@@ -539,22 +537,10 @@ use Throwable;
* @param string|null $entityName if given, only entities of this type will get detached
*
* @return void
*
* @throws ORMInvalidArgumentException if a non-null non-string value is given
* @throws \Doctrine\Common\Persistence\Mapping\MappingException if a $entityName is given, but that entity is not
* found in the mappings
*/
public function clear($entityName = null)
{
if (null !== $entityName && ! is_string($entityName)) {
throw ORMInvalidArgumentException::invalidEntityName($entityName);
}
$this->unitOfWork->clear(
null === $entityName
? null
: $this->metadataFactory->getMetadataFor($entityName)->getName()
);
$this->unitOfWork->clear($entityName);
}
/**
@@ -581,12 +567,11 @@ use Throwable;
* @return void
*
* @throws ORMInvalidArgumentException
* @throws ORMException
*/
public function persist($entity)
{
if ( ! is_object($entity)) {
throw ORMInvalidArgumentException::invalidObject('EntityManager#persist()', $entity);
throw ORMInvalidArgumentException::invalidObject('EntityManager#persist()' , $entity);
}
$this->errorIfClosed();
@@ -605,12 +590,11 @@ use Throwable;
* @return void
*
* @throws ORMInvalidArgumentException
* @throws ORMException
*/
public function remove($entity)
{
if ( ! is_object($entity)) {
throw ORMInvalidArgumentException::invalidObject('EntityManager#remove()', $entity);
throw ORMInvalidArgumentException::invalidObject('EntityManager#remove()' , $entity);
}
$this->errorIfClosed();
@@ -627,12 +611,11 @@ use Throwable;
* @return void
*
* @throws ORMInvalidArgumentException
* @throws ORMException
*/
public function refresh($entity)
{
if ( ! is_object($entity)) {
throw ORMInvalidArgumentException::invalidObject('EntityManager#refresh()', $entity);
throw ORMInvalidArgumentException::invalidObject('EntityManager#refresh()' , $entity);
}
$this->errorIfClosed();
@@ -656,7 +639,7 @@ use Throwable;
public function detach($entity)
{
if ( ! is_object($entity)) {
throw ORMInvalidArgumentException::invalidObject('EntityManager#detach()', $entity);
throw ORMInvalidArgumentException::invalidObject('EntityManager#detach()' , $entity);
}
$this->unitOfWork->detach($entity);
@@ -672,12 +655,11 @@ use Throwable;
* @return object The managed copy of the entity.
*
* @throws ORMInvalidArgumentException
* @throws ORMException
*/
public function merge($entity)
{
if ( ! is_object($entity)) {
throw ORMInvalidArgumentException::invalidObject('EntityManager#merge()', $entity);
throw ORMInvalidArgumentException::invalidObject('EntityManager#merge()' , $entity);
}
$this->errorIfClosed();
@@ -709,7 +691,7 @@ use Throwable;
*
* @param string $entityName The name of the entity.
*
* @return \Doctrine\Common\Persistence\ObjectRepository|\Doctrine\ORM\EntityRepository The repository class.
* @return \Doctrine\ORM\EntityRepository The repository class.
*/
public function getRepository($entityName)
{
@@ -833,59 +815,39 @@ use Throwable;
/**
* Factory method to create EntityManager instances.
*
* @param array|Connection $connection An array with the connection parameters or an existing Connection instance.
* @param Configuration $config The Configuration instance to use.
* @param EventManager $eventManager The EventManager instance to use.
* @param mixed $conn An array with the connection parameters or an existing Connection instance.
* @param Configuration $config The Configuration instance to use.
* @param EventManager $eventManager The EventManager instance to use.
*
* @return EntityManager The created EntityManager.
*
* @throws \InvalidArgumentException
* @throws ORMException
*/
public static function create($connection, Configuration $config, EventManager $eventManager = null)
public static function create($conn, Configuration $config, EventManager $eventManager = null)
{
if ( ! $config->getMetadataDriverImpl()) {
throw ORMException::missingMappingDriverImpl();
}
$connection = static::createConnection($connection, $config, $eventManager);
switch (true) {
case (is_array($conn)):
$conn = \Doctrine\DBAL\DriverManager::getConnection(
$conn, $config, ($eventManager ?: new EventManager())
);
break;
return new EntityManager($connection, $config, $connection->getEventManager());
}
case ($conn instanceof Connection):
if ($eventManager !== null && $conn->getEventManager() !== $eventManager) {
throw ORMException::mismatchedEventManager();
}
break;
/**
* Factory method to create Connection instances.
*
* @param array|Connection $connection An array with the connection parameters or an existing Connection instance.
* @param Configuration $config The Configuration instance to use.
* @param EventManager $eventManager The EventManager instance to use.
*
* @return Connection
*
* @throws \InvalidArgumentException
* @throws ORMException
*/
protected static function createConnection($connection, Configuration $config, EventManager $eventManager = null)
{
if (is_array($connection)) {
return DriverManager::getConnection($connection, $config, $eventManager ?: new EventManager());
default:
throw new \InvalidArgumentException("Invalid argument: " . $conn);
}
if ( ! $connection instanceof Connection) {
throw new \InvalidArgumentException(
sprintf(
'Invalid $connection argument of type %s given%s.',
is_object($connection) ? get_class($connection) : gettype($connection),
is_object($connection) ? '' : ': "' . $connection . '"'
)
);
}
if ($eventManager !== null && $connection->getEventManager() !== $eventManager) {
throw ORMException::mismatchedEventManager();
}
return $connection;
return new EntityManager($conn, $config, $conn->getEventManager());
}
/**

View File

@@ -7,9 +7,9 @@
* 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
* 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
* (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
@@ -150,7 +150,7 @@ interface EntityManagerInterface extends ObjectManager
* @param string $entityName The name of the entity type.
* @param mixed $id The entity identifier.
*
* @return object|null The entity reference.
* @return object The entity reference.
*
* @throws ORMException
*/

View File

@@ -37,7 +37,7 @@ class EntityNotFoundException extends ORMException
*/
public static function fromClassNameAndIdentifier($className, array $id)
{
$ids = [];
$ids = array();
foreach ($id as $key => $value) {
$ids[] = $key . '(' . $value . ')';

View File

@@ -19,11 +19,11 @@
namespace Doctrine\ORM;
use Doctrine\Common\Util\Inflector;
use Doctrine\ORM\Query\ResultSetMappingBuilder;
use Doctrine\Common\Persistence\ObjectRepository;
use Doctrine\Common\Collections\Selectable;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\ArrayCollection;
/**
* An EntityRepository serves as a repository for entities with generic as well as
@@ -61,7 +61,7 @@ class EntityRepository implements ObjectRepository, Selectable
* @param EntityManager $em The EntityManager to use.
* @param Mapping\ClassMetadata $class The class descriptor.
*/
public function __construct(EntityManagerInterface $em, Mapping\ClassMetadata $class)
public function __construct($em, Mapping\ClassMetadata $class)
{
$this->_entityName = $class->name;
$this->_em = $em;
@@ -161,7 +161,7 @@ class EntityRepository implements ObjectRepository, Selectable
*/
public function findAll()
{
return $this->findBy([]);
return $this->findBy(array());
}
/**
@@ -184,7 +184,7 @@ class EntityRepository implements ObjectRepository, Selectable
/**
* Finds a single entity by a set of criteria.
*
* @param array $criteria
* @param array $criteria
* @param array|null $orderBy
*
* @return object|null The entity instance or NULL if the entity can not be found.
@@ -193,52 +193,68 @@ class EntityRepository implements ObjectRepository, Selectable
{
$persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
return $persister->load($criteria, null, null, [], null, 1, $orderBy);
return $persister->load($criteria, null, null, array(), null, 1, $orderBy);
}
/**
* Counts entities by a set of criteria.
*
* @todo Add this method to `ObjectRepository` interface in the next major release
*
* @param array $criteria
*
* @return int The cardinality of the objects that match the given criteria.
*/
public function count(array $criteria)
{
return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->count($criteria);
}
/**
* Adds support for magic method calls.
* Adds support for magic finders.
*
* @param string $method
* @param array $arguments
*
* @return mixed The returned value from the resolved method.
* @return array|object The found entity/entities.
*
* @throws ORMException
* @throws \BadMethodCallException If the method called is invalid
* @throws \BadMethodCallException If the method called is an invalid find* method
* or no find* method at all and therefore an invalid
* method call.
*/
public function __call($method, $arguments)
{
if (0 === strpos($method, 'findBy')) {
return $this->resolveMagicCall('findBy', substr($method, 6), $arguments);
switch (true) {
case (0 === strpos($method, 'findBy')):
$by = substr($method, 6);
$method = 'findBy';
break;
case (0 === strpos($method, 'findOneBy')):
$by = substr($method, 9);
$method = 'findOneBy';
break;
default:
throw new \BadMethodCallException(
"Undefined method '$method'. The method name must start with ".
"either findBy or findOneBy!"
);
}
if (0 === strpos($method, 'findOneBy')) {
return $this->resolveMagicCall('findOneBy', substr($method, 9), $arguments);
if (empty($arguments)) {
throw ORMException::findByRequiresParameter($method . $by);
}
if (0 === strpos($method, 'countBy')) {
return $this->resolveMagicCall('count', substr($method, 7), $arguments);
$fieldName = lcfirst(\Doctrine\Common\Util\Inflector::classify($by));
if ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName)) {
switch (count($arguments)) {
case 1:
return $this->$method(array($fieldName => $arguments[0]));
case 2:
return $this->$method(array($fieldName => $arguments[0]), $arguments[1]);
case 3:
return $this->$method(array($fieldName => $arguments[0]), $arguments[1], $arguments[2]);
case 4:
return $this->$method(array($fieldName => $arguments[0]), $arguments[1], $arguments[2], $arguments[3]);
default:
// Do nothing
}
}
throw new \BadMethodCallException(
"Undefined method '$method'. The method name must start with ".
"either findBy, findOneBy or countBy!"
);
throw ORMException::invalidFindByCall($this->_entityName, $fieldName, $method.$by);
}
/**
@@ -287,30 +303,4 @@ class EntityRepository implements ObjectRepository, Selectable
return new LazyCriteriaCollection($persister, $criteria);
}
/**
* Resolves a magic method call to the proper existent method at `EntityRepository`.
*
* @param string $method The method to call
* @param string $by The property name used as condition
* @param array $arguments The arguments to pass at method call
*
* @throws ORMException If the method called is invalid or the requested field/association does not exist
*
* @return mixed
*/
private function resolveMagicCall($method, $by, array $arguments)
{
if (! $arguments) {
throw ORMException::findByRequiresParameter($method . $by);
}
$fieldName = lcfirst(Inflector::classify($by));
if (! ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName))) {
throw ORMException::invalidMagicCall($this->_entityName, $fieldName, $method . $by);
}
return $this->$method([$fieldName => $arguments[0]], ...array_slice($arguments, 1));
}
}

View File

@@ -15,7 +15,7 @@
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
*/
namespace Doctrine\ORM\Event;

View File

@@ -62,8 +62,8 @@ class ListenersInvoker
/**
* Get the subscribed event systems
*
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param string $eventName The entity lifecycle event.
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param string $eventName The entity lifecycle event.
*
* @return integer Bitmask of subscribed event systems.
*/
@@ -89,21 +89,21 @@ class ListenersInvoker
/**
* Dispatches the lifecycle event of the given entity.
*
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param string $eventName The entity lifecycle event.
* @param object $entity The Entity on which the event occurred.
* @param \Doctrine\Common\EventArgs $event The Event args.
* @param integer $invoke Bitmask to invoke listeners.
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
* @param string $eventName The entity lifecycle event.
* @param object $entity The Entity on which the event occurred.
* @param \Doctrine\Common\EventArgs $event The Event args.
* @param integer $invoke Bitmask to invoke listeners.
*/
public function invoke(ClassMetadata $metadata, $eventName, $entity, EventArgs $event, $invoke)
{
if ($invoke & self::INVOKE_CALLBACKS) {
if($invoke & self::INVOKE_CALLBACKS) {
foreach ($metadata->lifecycleCallbacks[$eventName] as $callback) {
$entity->$callback($event);
}
}
if ($invoke & self::INVOKE_LISTENERS) {
if($invoke & self::INVOKE_LISTENERS) {
foreach ($metadata->entityListeners[$eventName] as $listener) {
$class = $listener['class'];
$method = $listener['method'];
@@ -113,8 +113,8 @@ class ListenersInvoker
}
}
if ($invoke & self::INVOKE_MANAGER) {
if($invoke & self::INVOKE_MANAGER) {
$this->eventManager->dispatchEvent($eventName, $event);
}
}
}
}

View File

@@ -26,7 +26,7 @@ abstract class AbstractIdGenerator
/**
* Generates an identifier for an entity.
*
* @param EntityManager $em
* @param EntityManager|EntityManager $em
* @param \Doctrine\ORM\Mapping\Entity $entity
* @return mixed
*/

View File

@@ -44,7 +44,7 @@ class AssignedGenerator extends AbstractIdGenerator
{
$class = $em->getClassMetadata(get_class($entity));
$idFields = $class->getIdentifierFieldNames();
$identifier = [];
$identifier = array();
foreach ($idFields as $idField) {
$value = $class->getFieldValue($entity, $idField);

View File

@@ -50,9 +50,10 @@ class IdentityGenerator extends AbstractIdGenerator
/**
* {@inheritDoc}
*/
public function generate(EntityManager $em, $entity)
public function generate(
EntityManager $em, $entity)
{
return (int) $em->getConnection()->lastInsertId($this->sequenceName);
return (int)$em->getConnection()->lastInsertId($this->sequenceName);
}
/**

View File

@@ -76,8 +76,7 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
$conn = $em->getConnection();
$sql = $conn->getDatabasePlatform()->getSequenceNextValSQL($this->_sequenceName);
// Using `query` to force usage of the master server in MasterSlaveConnection
$this->_nextValue = (int) $conn->query($sql)->fetchColumn();
$this->_nextValue = (int)$conn->fetchColumn($sql);
$this->_maxValue = $this->_nextValue + $this->_allocationSize;
}
@@ -109,12 +108,10 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
*/
public function serialize()
{
return serialize(
[
return serialize(array(
'allocationSize' => $this->_allocationSize,
'sequenceName' => $this->_sequenceName
]
);
));
}
/**

View File

@@ -92,7 +92,7 @@ class TableGenerator extends AbstractIdGenerator
$this->_tableName, $this->_sequenceName, $this->_allocationSize
);
if ($conn->executeUpdate($updateSql, [1 => $currentLevel, 2 => $currentLevel+1]) !== 1) {
if ($conn->executeUpdate($updateSql, array(1 => $currentLevel, 2 => $currentLevel+1)) !== 1) {
// no affected rows, concurrency issue, throw exception
}
} else {

View File

@@ -36,7 +36,6 @@ class UuidGenerator extends AbstractIdGenerator
{
$conn = $em->getConnection();
$sql = 'SELECT ' . $conn->getDatabasePlatform()->getGuidExpression();
return $conn->query($sql)->fetchColumn(0);
}
}

View File

@@ -1,5 +1,4 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -21,164 +20,137 @@
namespace Doctrine\ORM\Internal;
/**
* CommitOrderCalculator implements topological sorting, which is an ordering
* algorithm for directed graphs (DG) and/or directed acyclic graphs (DAG) by
* using a depth-first searching (DFS) to traverse the graph built in memory.
* This algorithm have a linear running time based on nodes (V) and dependency
* between the nodes (E), resulting in a computational complexity of O(V + E).
* The CommitOrderCalculator is used by the UnitOfWork to sort out the
* correct order in which changes to entities need to be persisted.
*
* @since 2.0
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @since 2.0
* @author Roman Borschel <roman@code-factory.org>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
*/
class CommitOrderCalculator
{
const NOT_VISITED = 0;
const IN_PROGRESS = 1;
const VISITED = 2;
const NOT_VISITED = 1;
const IN_PROGRESS = 2;
const VISITED = 3;
/**
* Matrix of nodes (aka. vertex).
* Keys are provided hashes and values are the node definition objects.
*
* The node state definition contains the following properties:
*
* - <b>state</b> (integer)
* Whether the node is NOT_VISITED or IN_PROGRESS
*
* - <b>value</b> (object)
* Actual node value
*
* - <b>dependencyList</b> (array<string>)
* Map of node dependencies defined as hashes.
*
* @var array<\stdClass>
* @var array
*/
private $nodeList = [];
private $_nodeStates = array();
/**
* Volatile variable holding calculated nodes during sorting process.
* The nodes to sort.
*
* @var array
*/
private $sortedNodeList = [];
private $_classes = array();
/**
* Checks for node (vertex) existence in graph.
*
* @param string $hash
*
* @return boolean
* @var array
*/
public function hasNode($hash)
{
return isset($this->nodeList[$hash]);
}
private $_relatedClasses = array();
/**
* Adds a new node (vertex) to the graph, assigning its hash and value.
*
* @param string $hash
* @param object $node
* @var array
*/
private $_sorted = array();
/**
* Clears the current graph.
*
* @return void
*/
public function addNode($hash, $node)
public function clear()
{
$vertex = new \stdClass();
$vertex->hash = $hash;
$vertex->state = self::NOT_VISITED;
$vertex->value = $node;
$vertex->dependencyList = [];
$this->nodeList[$hash] = $vertex;
$this->_classes = array();
$this->_relatedClasses = array();
}
/**
* Adds a new dependency (edge) to the graph using their hashes.
* Gets a valid commit order for all current nodes.
*
* @param string $fromHash
* @param string $toHash
* @param integer $weight
* Uses a depth-first search (DFS) to traverse the graph.
* The desired topological sorting is the reverse postorder of these searches.
*
* @return array The list of ordered classes.
*/
public function getCommitOrder()
{
// Check whether we need to do anything. 0 or 1 node is easy.
$nodeCount = count($this->_classes);
if ($nodeCount <= 1) {
return ($nodeCount == 1) ? array_values($this->_classes) : array();
}
// Init
foreach ($this->_classes as $node) {
$this->_nodeStates[$node->name] = self::NOT_VISITED;
}
// Go
foreach ($this->_classes as $node) {
if ($this->_nodeStates[$node->name] == self::NOT_VISITED) {
$this->_visitNode($node);
}
}
$sorted = array_reverse($this->_sorted);
$this->_sorted = $this->_nodeStates = array();
return $sorted;
}
/**
* @param \Doctrine\ORM\Mapping\ClassMetadata $node
*
* @return void
*/
public function addDependency($fromHash, $toHash, $weight)
private function _visitNode($node)
{
$vertex = $this->nodeList[$fromHash];
$edge = new \stdClass();
$this->_nodeStates[$node->name] = self::IN_PROGRESS;
$edge->from = $fromHash;
$edge->to = $toHash;
$edge->weight = $weight;
$vertex->dependencyList[$toHash] = $edge;
}
/**
* Return a valid order list of all current nodes.
* The desired topological sorting is the reverse post order of these searches.
*
* {@internal Highly performance-sensitive method.}
*
* @return array
*/
public function sort()
{
foreach ($this->nodeList as $vertex) {
if ($vertex->state !== self::NOT_VISITED) {
continue;
}
$this->visit($vertex);
}
$sortedList = $this->sortedNodeList;
$this->nodeList = [];
$this->sortedNodeList = [];
return array_reverse($sortedList);
}
/**
* Visit a given node definition for reordering.
*
* {@internal Highly performance-sensitive method.}
*
* @param \stdClass $vertex
*/
private function visit($vertex)
{
$vertex->state = self::IN_PROGRESS;
foreach ($vertex->dependencyList as $edge) {
$adjacentVertex = $this->nodeList[$edge->to];
switch ($adjacentVertex->state) {
case self::VISITED:
// Do nothing, since node was already visited
break;
case self::IN_PROGRESS:
if (isset($adjacentVertex->dependencyList[$vertex->hash]) &&
$adjacentVertex->dependencyList[$vertex->hash]->weight < $edge->weight) {
$adjacentVertex->state = self::VISITED;
$this->sortedNodeList[] = $adjacentVertex->value;
}
break;
case self::NOT_VISITED:
$this->visit($adjacentVertex);
if (isset($this->_relatedClasses[$node->name])) {
foreach ($this->_relatedClasses[$node->name] as $relatedNode) {
if ($this->_nodeStates[$relatedNode->name] == self::NOT_VISITED) {
$this->_visitNode($relatedNode);
}
}
}
if ($vertex->state !== self::VISITED) {
$vertex->state = self::VISITED;
$this->_nodeStates[$node->name] = self::VISITED;
$this->_sorted[] = $node;
}
$this->sortedNodeList[] = $vertex->value;
}
/**
* @param \Doctrine\ORM\Mapping\ClassMetadata $fromClass
* @param \Doctrine\ORM\Mapping\ClassMetadata $toClass
*
* @return void
*/
public function addDependency($fromClass, $toClass)
{
$this->_relatedClasses[$fromClass->name][] = $toClass;
}
/**
* @param string $className
*
* @return bool
*/
public function hasClass($className)
{
return isset($this->_classes[$className]);
}
/**
* @param \Doctrine\ORM\Mapping\ClassMetadata $class
*
* @return void
*/
public function addClass($class)
{
$this->_classes[$class->name] = $class;
}
}

View File

@@ -24,8 +24,6 @@ use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Events;
use Doctrine\ORM\Mapping\ClassMetadata;
use PDO;
use function array_map;
use function in_array;
/**
* Base class for all hydrators. A hydrator is a class that provides some form
@@ -71,14 +69,14 @@ abstract class AbstractHydrator
*
* @var array
*/
protected $_metadataCache = [];
protected $_metadataCache = array();
/**
* The cache used during row-by-row hydration.
*
* @var array
*/
protected $_cache = [];
protected $_cache = array();
/**
* The statement that provides the data to hydrate.
@@ -115,15 +113,14 @@ abstract class AbstractHydrator
*
* @return IterableResult
*/
public function iterate($stmt, $resultSetMapping, array $hints = [])
public function iterate($stmt, $resultSetMapping, array $hints = array())
{
$this->_stmt = $stmt;
$this->_rsm = $resultSetMapping;
$this->_hints = $hints;
$evm = $this->_em->getEventManager();
$evm->addEventListener([Events::onClear], $this);
$evm->addEventListener(array(Events::onClear), $this);
$this->prepare();
@@ -139,14 +136,12 @@ abstract class AbstractHydrator
*
* @return array
*/
public function hydrateAll($stmt, $resultSetMapping, array $hints = [])
public function hydrateAll($stmt, $resultSetMapping, array $hints = array())
{
$this->_stmt = $stmt;
$this->_rsm = $resultSetMapping;
$this->_hints = $hints;
$this->_em->getEventManager()->addEventListener([Events::onClear], $this);
$this->prepare();
$result = $this->hydrateAllData();
@@ -172,7 +167,7 @@ abstract class AbstractHydrator
return false;
}
$result = [];
$result = array();
$this->hydrateRowData($row, $result);
@@ -213,13 +208,11 @@ abstract class AbstractHydrator
$this->_stmt = null;
$this->_rsm = null;
$this->_cache = [];
$this->_metadataCache = [];
$this->_cache = array();
$this->_metadataCache = array();
$this
->_em
->getEventManager()
->removeEventListener([Events::onClear], $this);
$evm = $this->_em->getEventManager();
$evm->removeEventListener(array(Events::onClear), $this);
}
/**
@@ -264,7 +257,7 @@ abstract class AbstractHydrator
*/
protected function gatherRowData(array $data, array &$id, array &$nonemptyComponents)
{
$rowData = ['data' => []];
$rowData = array('data' => array());
foreach ($data as $key => $value) {
if (($cacheKeyInfo = $this->hydrateColumnInfo($key)) === null) {
@@ -296,14 +289,6 @@ abstract class AbstractHydrator
$dqlAlias = $cacheKeyInfo['dqlAlias'];
$type = $cacheKeyInfo['type'];
// If there are field name collisions in the child class, then we need
// to only hydrate if we are looking at the correct discriminator value
if (isset($cacheKeyInfo['discriminatorColumn'], $data[$cacheKeyInfo['discriminatorColumn']])
&& ! in_array((string) $data[$cacheKeyInfo['discriminatorColumn']], $cacheKeyInfo['discriminatorValues'], true)
) {
break;
}
// in an inheritance hierarchy the same field could be defined several times.
// We overwrite this value so long we don't have a non-null value, that value we keep.
// Per definition it cannot be that a field is defined several times and has several values.
@@ -340,7 +325,7 @@ abstract class AbstractHydrator
*/
protected function gatherScalarRowData(&$data)
{
$rowData = [];
$rowData = array();
foreach ($data as $key => $value) {
if (($cacheKeyInfo = $this->hydrateColumnInfo($key)) === null) {
@@ -385,34 +370,19 @@ abstract class AbstractHydrator
$classMetadata = $this->getClassMetadata($this->_rsm->declaringClasses[$key]);
$fieldName = $this->_rsm->fieldMappings[$key];
$fieldMapping = $classMetadata->fieldMappings[$fieldName];
$ownerMap = $this->_rsm->columnOwnerMap[$key];
$columnInfo = [
'isIdentifier' => \in_array($fieldName, $classMetadata->identifier, true),
return $this->_cache[$key] = array(
'isIdentifier' => in_array($fieldName, $classMetadata->identifier),
'fieldName' => $fieldName,
'type' => Type::getType($fieldMapping['type']),
'dqlAlias' => $ownerMap,
];
// the current discriminator value must be saved in order to disambiguate fields hydration,
// should there be field name collisions
if ($classMetadata->parentClasses && isset($this->_rsm->discriminatorColumns[$ownerMap])) {
return $this->_cache[$key] = \array_merge(
$columnInfo,
[
'discriminatorColumn' => $this->_rsm->discriminatorColumns[$ownerMap],
'discriminatorValue' => $classMetadata->discriminatorValue,
'discriminatorValues' => $this->getDiscriminatorValues($classMetadata),
]
);
}
return $this->_cache[$key] = $columnInfo;
'dqlAlias' => $this->_rsm->columnOwnerMap[$key],
);
case (isset($this->_rsm->newObjectMappings[$key])):
// WARNING: A NEW object is also a scalar, so it must be declared before!
$mapping = $this->_rsm->newObjectMappings[$key];
return $this->_cache[$key] = [
return $this->_cache[$key] = array(
'isScalar' => true,
'isNewObjectParameter' => true,
'fieldName' => $this->_rsm->scalarMappings[$key],
@@ -420,33 +390,31 @@ abstract class AbstractHydrator
'argIndex' => $mapping['argIndex'],
'objIndex' => $mapping['objIndex'],
'class' => new \ReflectionClass($mapping['className']),
];
);
case (isset($this->_rsm->scalarMappings[$key])):
return $this->_cache[$key] = [
return $this->_cache[$key] = array(
'isScalar' => true,
'fieldName' => $this->_rsm->scalarMappings[$key],
'type' => Type::getType($this->_rsm->typeMappings[$key]),
];
);
case (isset($this->_rsm->metaMappings[$key])):
// Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns).
$fieldName = $this->_rsm->metaMappings[$key];
$dqlAlias = $this->_rsm->columnOwnerMap[$key];
$type = isset($this->_rsm->typeMappings[$key])
$fieldName = $this->_rsm->metaMappings[$key];
$dqlAlias = $this->_rsm->columnOwnerMap[$key];
$classMetadata = $this->getClassMetadata($this->_rsm->aliasMap[$dqlAlias]);
$type = isset($this->_rsm->typeMappings[$key])
? Type::getType($this->_rsm->typeMappings[$key])
: null;
// Cache metadata fetch
$this->getClassMetadata($this->_rsm->aliasMap[$dqlAlias]);
return $this->_cache[$key] = [
return $this->_cache[$key] = array(
'isIdentifier' => isset($this->_rsm->isIdentifierColumn[$dqlAlias][$key]),
'isMetaColumn' => true,
'fieldName' => $fieldName,
'type' => $type,
'dqlAlias' => $dqlAlias,
];
);
}
// this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2
@@ -454,23 +422,6 @@ abstract class AbstractHydrator
return null;
}
/**
* @return string[]
*/
private function getDiscriminatorValues(ClassMetadata $classMetadata) : array
{
$values = array_map(
function (string $subClass) : string {
return (string) $this->getClassMetadata($subClass)->discriminatorValue;
},
$classMetadata->subClasses
);
$values[] = (string) $classMetadata->discriminatorValue;
return $values;
}
/**
* Retrieve ClassMetadata associated to entity class name.
*
@@ -501,7 +452,7 @@ abstract class AbstractHydrator
protected function registerManaged(ClassMetadata $class, $entity, array $data)
{
if ($class->isIdentifierComposite) {
$id = [];
$id = array();
foreach ($class->identifier as $fieldName) {
$id[$fieldName] = isset($class->associationMappings[$fieldName])
@@ -510,11 +461,11 @@ abstract class AbstractHydrator
}
} else {
$fieldName = $class->identifier[0];
$id = [
$id = array(
$fieldName => isset($class->associationMappings[$fieldName])
? $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']]
: $data[$fieldName]
];
);
}
$this->_em->getUnitOfWork()->registerManaged($entity, $id, $data);

View File

@@ -35,7 +35,7 @@ class ArrayHydrator extends AbstractHydrator
/**
* @var array
*/
private $_rootAliases = [];
private $_rootAliases = array();
/**
* @var bool
@@ -45,17 +45,17 @@ class ArrayHydrator extends AbstractHydrator
/**
* @var array
*/
private $_identifierMap = [];
private $_identifierMap = array();
/**
* @var array
*/
private $_resultPointers = [];
private $_resultPointers = array();
/**
* @var array
*/
private $_idTemplate = [];
private $_idTemplate = array();
/**
* @var int
@@ -70,8 +70,8 @@ class ArrayHydrator extends AbstractHydrator
$this->_isSimpleQuery = count($this->_rsm->aliasMap) <= 1;
foreach ($this->_rsm->aliasMap as $dqlAlias => $className) {
$this->_identifierMap[$dqlAlias] = [];
$this->_resultPointers[$dqlAlias] = [];
$this->_identifierMap[$dqlAlias] = array();
$this->_resultPointers[$dqlAlias] = array();
$this->_idTemplate[$dqlAlias] = '';
}
}
@@ -81,7 +81,7 @@ class ArrayHydrator extends AbstractHydrator
*/
protected function hydrateAllData()
{
$result = [];
$result = array();
while ($data = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
$this->hydrateRowData($data, $result);
@@ -97,7 +97,7 @@ class ArrayHydrator extends AbstractHydrator
{
// 1) Initialize
$id = $this->_idTemplate; // initialize the id-memory
$nonemptyComponents = [];
$nonemptyComponents = array();
$rowData = $this->gatherRowData($row, $id, $nonemptyComponents);
// 2) Now hydrate the data found in the current row.
@@ -138,7 +138,7 @@ class ArrayHydrator extends AbstractHydrator
$oneToOne = false;
if ( ! isset($baseElement[$relationAlias])) {
$baseElement[$relationAlias] = [];
$baseElement[$relationAlias] = array();
}
if (isset($nonemptyComponents[$dqlAlias])) {
@@ -187,7 +187,7 @@ class ArrayHydrator extends AbstractHydrator
// if this row has a NULL value for the root result id then make it a null result.
if ( ! isset($nonemptyComponents[$dqlAlias]) ) {
$result[] = $this->_rsm->isMixed
? [$entityKey => null]
? array($entityKey => null)
: null;
$resultKey = $this->_resultCounter;
@@ -199,7 +199,7 @@ class ArrayHydrator extends AbstractHydrator
// Check for an existing element
if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
$element = $this->_rsm->isMixed
? [$entityKey => $data]
? array($entityKey => $data)
: $data;
if (isset($this->_rsm->indexByMap[$dqlAlias])) {

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