Compare commits

..

137 Commits

Author SHA1 Message Date
Marco Pivetta e6c434196c Merge pull request #6178 from doctrine/fix/#6174-#5570-merging-new-entities-should-also-trigger-prepersist-lifecycle-callbacks-2.5
Backport #6177 - fix #6174 #5570: merging new entities should also trigger prepersist lifecycle callbacks with the merged data
2016-12-18 16:42:34 +01:00
Marco Pivetta d52dbe62ac #6174 #5570 switching ::class to string constants for PHP 5.4 compat (still supported in ORM 2.5.x) 2016-12-18 16:24:42 +01:00
Marco Pivetta b0ede40f47 #6174 #5570 removed modifications applied to the CompanyContractListener, since UnitOfWorkTest now completely encapsulates the scenarios being covered 2016-12-18 16:13:11 +01:00
Marco Pivetta 3645a9c44d #6174 #5570 removed unused imports 2016-12-18 16:13:04 +01:00
Marco Pivetta 39ce6f96a0 #6174 #5570 renamed entity for better fitting the use-cases it's in 2016-12-18 16:12:57 +01:00
Marco Pivetta e43f5304ef #6174 #5570 removed unused test class 2016-12-18 16:12:46 +01:00
Marco Pivetta 67724eb7ae #6174 #5570 adding group annotations to newly introduced test 2016-12-18 16:12:36 +01:00
Marco Pivetta 8d4bc0638d #6174 #5570 prePersist listeners should never be called when entities are merged, but are already in the UoW 2016-12-18 16:12:29 +01:00
Marco Pivetta 81186105b6 #6174 #5570 started moving tests around prePersist event subscriber triggering on UnitOfWork into the UnitOfWorkTest 2016-12-18 16:12:03 +01:00
Marco Pivetta beef8acdf5 #6174 #5570 CS fixes around the EntityListenersOnMergeTest 2016-12-18 16:10:17 +01:00
Marco Pivetta 26fc8d60e6 #6174 #5570 adding group annotation to newly introduced tests 2016-12-18 16:10:08 +01:00
Marco Pivetta 12e8ab216a #6174 #5570 CS - spacing/variable naming 2016-12-18 16:09:59 +01:00
Marco Pivetta dac1a16964 #6174 #5570 removed unused/dead code 2016-12-18 16:09:52 +01:00
Marco Pivetta d9821d3fda #6174 #5570 CS - spacing 2016-12-18 16:09:45 +01:00
Marco Pivetta 576a4d7e31 #6174 #5570 CS - spacing 2016-12-18 16:09:38 +01:00
Marco Pivetta eaee924180 #6174 #5570 flattened nested conditionals 2016-12-18 16:09:32 +01:00
Marco Pivetta cf941ce54f #6174 #5570 documenting thrown exception types 2016-12-18 16:09:25 +01:00
Marco Pivetta cfb7461f51 #6174 #5570 CS - alignment 2016-12-18 16:09:18 +01:00
bilouwan 569c08ce55 Rename test 2016-12-18 16:09:11 +01:00
bilouwan 295523cdca Cherry pick unit test from PR #5570 (Fix PrePersist EventListener when using merge instead of persist) 2016-12-18 16:09:00 +01:00
bilouwan 25efabdb74 doMerge will mergeEntityStateIntoManagedCopy BEFORE persistNew to let lifecyle events changes be persisted 2016-12-18 16:08:15 +01:00
bilouwan 1d96178097 Create failing test to reveal the issue 2016-12-18 16:08:05 +01:00
Marco Pivetta 20cb50451d Merge pull request #6159 from nicolas-cajelli/backport-fix-relation-cache-#1551-to-2.5
#5821 Backport #1551 - Fixed support for inverse side second level cache
2016-12-12 08:34:27 +01:00
Guilherme Blanco 0ff512ba8f Fixed support for inverse side second level cache 2016-12-05 14:36:33 -03:00
Marco Pivetta 5e9014fd99 Merge pull request #6156 from Slamdunk/patch-2
Allow doctrine/common 2.7
2016-12-04 06:53:07 +01:00
Filippo Tessarotto a90cd9dfe8 Allow doctrine/common 2.7 2016-12-02 09:00:50 +01:00
Marco Pivetta 4d699789a2 Merge branch 'fix/#6110-collection-clear-should-also-clear-keys-2.5' into 2.5
Close #6110
2016-11-26 06:06:11 +01:00
Marco Pivetta 1486c8f8e2 split test into multiple sub-scenarios involving PersistentCollection key checking #6110 2016-11-26 06:05:31 +01:00
Steevan BARBOYON 3dadfa49d5 Clear $this->collection even when empty, to reset indexes 2016-11-26 06:04:35 +01:00
Marco Pivetta 9b36947a48 Merge branch 'fix/#6028-l2c-inheritance-query-cache-use-parent-entity-name-2.5' into 2.5
Backport #6028 to 2.5.x
2016-11-23 18:06:28 +01:00
Marco Pivetta 2122297fdb #6028 removed specific ::class usage, since 2.5.x still supports PHP 5.4.x 2016-11-23 18:06:14 +01:00
Marco Pivetta af99cba28c #6028 removed specific ::class usage, since 2.5.x still supports PHP 5.4.x 2016-11-23 18:02:15 +01:00
Luís Cobucci 9bcee455ca Make child entity share the timestamp region with parent class 2016-11-23 17:58:05 +01:00
Marco Pivetta 73e4be7c7b Merge branch 'fix/#5768-#5755-clone-proxy-private-properties-in-multi-level-inheritances-2.5' into 2.5
Close #5768
Close #5755
2016-09-10 20:51:13 +02:00
Ed Hartwell Goose d7026c46ec Fixes #5755, uses '->getReflectionProperties()' instead of '->getReflectionClass()->getProperties()' to ensure all fields are copied, and adds test to confirm behaviour 2016-09-10 20:48:12 +02:00
Marco Pivetta b7bfbb6adb Merge branch 'fix/#5689-avoid-object-hash-conflicts-due-to-merge-operations-2.5' into 2.5
Close #5689
2016-09-10 20:22:23 +02:00
Marco Pivetta c9161fcd6f #5689 removed unused reflection access 2016-09-10 20:19:29 +02:00
Marco Pivetta 147f8fffff #5689 removed OidReuseTest, which was moved to UnitOfWork tests 2016-09-10 20:18:10 +02:00
Marco Pivetta e73428a051 #5689 moved OidReuseTest contents into the UnitOfWork tests 2016-09-10 20:17:59 +02:00
Mathieu De Zutter a3d93afc4f Additional assertion to check that unreferenced objects are not in UOW. 2016-09-10 20:16:38 +02:00
Mathieu De Zutter b0e4e3eda4 Remove old code in comments. 2016-09-10 20:16:33 +02:00
Mathieu De Zutter 95dcf51ad5 Avoid conflicts due to spl_object_hash().
When merging an entity with a to-many association, it will store the
original entity data using the object hash of the to-be-merged entity
instead of the managed entity. Since this to-be-merged entity is not
managed by Doctrine, it can disappear from the memory. A new object
can reuse the same memory location and thus have the same object hash.
When one tries to persist this object as new, Doctrine will refuse it
because it thinks that the entity is managed+dirty.

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

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

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

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

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

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

https://github.com/doctrine/cache/commit/dd47003641aa5425820c0ec8a6f4a85e7412ffcd removes the 'DoctrineNamespaceCacheKey[]' entry from the cache. Thus, all tests counting cache entries were off by one.
2015-09-19 10:44:47 +02:00
Benjamin Eberlei 8070b50150 Bump version to 2.5.2 2015-08-31 14:59:39 +02:00
80 changed files with 1855 additions and 189 deletions
-3
View File
@@ -23,9 +23,6 @@ script:
- ENABLE_SECOND_LEVEL_CACHE=0 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml $PHPUNIT_FLAGS
- ENABLE_SECOND_LEVEL_CACHE=1 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml --exclude-group performance,non-cacheable,locking_functional
after_script:
- php vendor/bin/coveralls -v
matrix:
exclude:
- php: hhvm
+10
View File
@@ -1,5 +1,11 @@
# Upgrade to 2.5
## Minor BC BREAK: query cache key time is now a float
As of 2.5.5, the `QueryCacheEntry#time` property will contain a float value
instead of an integer in order to have more precision and also to be consistent
with the `TimestampCacheEntry#time`.
## Minor BC BREAK: discriminator map must now include all non-transient classes
It is now required that you declare the root of an inheritance in the
@@ -138,6 +144,10 @@ From now on, the resultset will look like this:
...
)
## Minor BC BREAK: added second parameter $indexBy in EntityRepository#createQueryBuilder method signature
Added way to access the underlying QueryBuilder#from() method's 'indexBy' parameter when using EntityRepository#createQueryBuilder()
# Upgrade to 2.4
## BC BREAK: Compatibility Bugfix in PersistentCollection#matching()
+4 -5
View File
@@ -18,14 +18,13 @@
"doctrine/collections": "~1.2",
"doctrine/dbal": ">=2.5-dev,<2.6-dev",
"doctrine/instantiator": "~1.0.1",
"doctrine/common": ">=2.5-dev,<2.6-dev",
"doctrine/common": ">=2.5-dev,<2.8-dev",
"doctrine/cache": "~1.4",
"symfony/console": "~2.5"
"symfony/console": "~2.5|~3.0"
},
"require-dev": {
"symfony/yaml": "~2.1",
"phpunit/phpunit": "~4.0",
"satooshi/php-coveralls": "dev-master"
"symfony/yaml": "~2.3|~3.0",
"phpunit/phpunit": "~4.0"
},
"suggest": {
"symfony/yaml": "If you want to use YAML Metadata Mapping Driver"
+29 -7
View File
@@ -28,7 +28,7 @@ use Doctrine\ORM\Cache\QueryCacheKey;
use Doctrine\DBAL\Cache\QueryCacheProfile;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\Query\ResultSetMapping;
/**
* Base contract for ORM queries. Base class for Query and NativeQuery.
@@ -993,32 +993,54 @@ abstract class AbstractQuery
private function executeUsingQueryCache($parameters = null, $hydrationMode = null)
{
$rsm = $this->getResultSetMapping();
$querykey = new QueryCacheKey($this->getHash(), $this->lifetime, $this->cacheMode ?: Cache::MODE_NORMAL);
$queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion);
$result = $queryCache->get($querykey, $rsm, $this->_hints);
$queryKey = new QueryCacheKey(
$this->getHash(),
$this->lifetime,
$this->cacheMode ?: Cache::MODE_NORMAL,
$this->getTimestampKey()
);
$result = $queryCache->get($queryKey, $rsm, $this->_hints);
if ($result !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $querykey);
$this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $queryKey);
}
return $result;
}
$result = $this->executeIgnoreQueryCache($parameters, $hydrationMode);
$cached = $queryCache->put($querykey, $rsm, $result, $this->_hints);
$cached = $queryCache->put($queryKey, $rsm, $result, $this->_hints);
if ($this->cacheLogger) {
$this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $querykey);
$this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $queryKey);
if ($cached) {
$this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $querykey);
$this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $queryKey);
}
}
return $result;
}
/**
* @return \Doctrine\ORM\Cache\TimestampCacheKey|null
*/
private function getTimestampKey()
{
$entityName = reset($this->_resultSetMapping->aliasMap);
if (empty($entityName)) {
return null;
}
$metadata = $this->_em->getClassMetadata($entityName);
return new Cache\TimestampCacheKey($metadata->rootEntityName);
}
/**
* Get the result cache id to use to store the result set cache entry.
* Will return the configured id if it exists otherwise a hash will be
@@ -110,7 +110,9 @@ class CacheConfiguration
public function getQueryValidator()
{
if ($this->queryValidator === null) {
$this->queryValidator = new TimestampQueryCacheValidator();
$this->queryValidator = new TimestampQueryCacheValidator(
$this->cacheFactory->getTimestampRegion()
);
}
return $this->queryValidator;
@@ -73,7 +73,7 @@ class DefaultEntityHydrator implements EntityHydrator
public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $entity)
{
$data = $this->uow->getOriginalEntityData($entity);
$data = array_merge($data, $key->identifier); // why update has no identifier values ?
$data = array_merge($data, $metadata->getIdentifierValues($entity)); // why update has no identifier values ?
foreach ($metadata->associationMappings as $name => $assoc) {
@@ -32,6 +32,7 @@ use Doctrine\Common\Util\ClassUtils;
/**
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @since 2.5
*/
abstract class AbstractCollectionPersister implements CachedCollectionPersister
@@ -164,10 +165,18 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
public function storeCollectionCache(CollectionCacheKey $key, $elements)
{
/* @var $targetPersister CachedEntityPersister */
$associationMapping = $this->sourceEntity->associationMappings[$key->association];
$targetPersister = $this->uow->getEntityPersister($this->targetEntity->rootEntityName);
$targetRegion = $targetPersister->getCacheRegion();
$targetHydrator = $targetPersister->getEntityHydrator();
$entry = $this->hydrator->buildCacheEntry($this->targetEntity, $key, $elements);
// Only preserve ordering if association configured it
if ( ! (isset($associationMapping['indexBy']) && $associationMapping['indexBy'])) {
// Elements may be an array or a Collection
$elements = array_values(is_array($elements) ? $elements : $elements->getValues());
}
$entry = $this->hydrator->buildCacheEntry($this->targetEntity, $key, $elements);
foreach ($entry->identifiers as $index => $entityKey) {
if ($targetRegion->contains($entityKey)) {
@@ -131,7 +131,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$this->cacheLogger = $cacheConfig->getCacheLogger();
$this->timestampRegion = $cacheFactory->getTimestampRegion();
$this->hydrator = $cacheFactory->buildEntityHydrator($em, $class);
$this->timestampKey = new TimestampCacheKey($this->class->getTableName());
$this->timestampKey = new TimestampCacheKey($this->class->rootEntityName);
}
/**
@@ -296,17 +296,16 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
* @param array $orderBy
* @param integer $limit
* @param integer $offset
* @param integer $timestamp
*
* @return string
*/
protected function getHash($query, $criteria, array $orderBy = null, $limit = null, $offset = null, $timestamp = null)
protected function getHash($query, $criteria, array $orderBy = null, $limit = null, $offset = null)
{
list($params) = ($criteria instanceof Criteria)
? $this->persister->expandCriteriaParameters($criteria)
: $this->persister->expandParameters($criteria);
return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset . $timestamp);
return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset);
}
/**
@@ -377,17 +376,16 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
//handle only EntityRepository#findOneBy
$timestamp = $this->timestampRegion->get($this->timestampKey);
$query = $this->persister->getSelectSQL($criteria, null, null, $limit, null, $orderBy);
$hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null);
$hash = $this->getHash($query, $criteria, null, null, null);
$rsm = $this->getResultSetMapping();
$querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL);
$queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey);
$queryCache = $this->cache->getQueryCache($this->regionName);
$result = $queryCache->get($querykey, $rsm);
$result = $queryCache->get($queryKey, $rsm);
if ($result !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->queryCacheHit($this->regionName, $querykey);
$this->cacheLogger->queryCacheHit($this->regionName, $queryKey);
}
return $result[0];
@@ -397,15 +395,15 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
return null;
}
$cached = $queryCache->put($querykey, $rsm, array($result));
$cached = $queryCache->put($queryKey, $rsm, array($result));
if ($this->cacheLogger) {
if ($result) {
$this->cacheLogger->queryCacheMiss($this->regionName, $querykey);
$this->cacheLogger->queryCacheMiss($this->regionName, $queryKey);
}
if ($cached) {
$this->cacheLogger->queryCachePut($this->regionName, $querykey);
$this->cacheLogger->queryCachePut($this->regionName, $queryKey);
}
}
@@ -417,32 +415,31 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
*/
public function loadAll(array $criteria = array(), array $orderBy = null, $limit = null, $offset = null)
{
$timestamp = $this->timestampRegion->get($this->timestampKey);
$query = $this->persister->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy);
$hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null);
$hash = $this->getHash($query, $criteria, null, null, null);
$rsm = $this->getResultSetMapping();
$querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL);
$queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey);
$queryCache = $this->cache->getQueryCache($this->regionName);
$result = $queryCache->get($querykey, $rsm);
$result = $queryCache->get($queryKey, $rsm);
if ($result !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->queryCacheHit($this->regionName, $querykey);
$this->cacheLogger->queryCacheHit($this->regionName, $queryKey);
}
return $result;
}
$result = $this->persister->loadAll($criteria, $orderBy, $limit, $offset);
$cached = $queryCache->put($querykey, $rsm, $result);
$cached = $queryCache->put($queryKey, $rsm, $result);
if ($this->cacheLogger) {
if ($result) {
$this->cacheLogger->queryCacheMiss($this->regionName, $querykey);
$this->cacheLogger->queryCacheMiss($this->regionName, $queryKey);
}
if ($cached) {
$this->cacheLogger->queryCachePut($this->regionName, $querykey);
$this->cacheLogger->queryCachePut($this->regionName, $queryKey);
}
}
@@ -516,32 +513,34 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
*/
public function loadCriteria(Criteria $criteria)
{
$orderBy = $criteria->getOrderings();
$limit = $criteria->getMaxResults();
$offset = $criteria->getFirstResult();
$query = $this->persister->getSelectSQL($criteria);
$timestamp = $this->timestampRegion->get($this->timestampKey);
$hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null);
$hash = $this->getHash($query, $criteria, $orderBy, $limit, $offset);
$rsm = $this->getResultSetMapping();
$querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL);
$queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey);
$queryCache = $this->cache->getQueryCache($this->regionName);
$cacheResult = $queryCache->get($querykey, $rsm);
$cacheResult = $queryCache->get($queryKey, $rsm);
if ($cacheResult !== null) {
if ($this->cacheLogger) {
$this->cacheLogger->queryCacheHit($this->regionName, $querykey);
$this->cacheLogger->queryCacheHit($this->regionName, $queryKey);
}
return $cacheResult;
}
$result = $this->persister->loadCriteria($criteria);
$cached = $queryCache->put($querykey, $rsm, $result);
$cached = $queryCache->put($queryKey, $rsm, $result);
if ($this->cacheLogger) {
if ($result) {
$this->cacheLogger->queryCacheMiss($this->regionName, $querykey);
$this->cacheLogger->queryCacheMiss($this->regionName, $queryKey);
}
if ($cached) {
$this->cacheLogger->queryCachePut($this->regionName, $querykey);
$this->cacheLogger->queryCachePut($this->regionName, $queryKey);
}
}
@@ -27,6 +27,7 @@ use Doctrine\ORM\Cache\EntityCacheKey;
* Specific non-strict read/write cached entity persister
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @since 2.5
*/
class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
@@ -78,13 +79,16 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
*/
public function delete($entity)
{
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
$deleted = $this->persister->delete($entity);
if ($this->persister->delete($entity)) {
if ($deleted) {
$this->region->evict($key);
}
$this->queuedCache['delete'][] = $key;
return $deleted;
}
/**
@@ -30,6 +30,7 @@ use Doctrine\ORM\Cache\EntityCacheKey;
* Specific read-write entity persister
*
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @since 2.5
*/
class ReadWriteCachedEntityPersister extends AbstractEntityPersister
@@ -100,21 +101,24 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
*/
public function delete($entity)
{
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
$lock = $this->region->lock($key);
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
$lock = $this->region->lock($key);
$deleted = $this->persister->delete($entity);
if ($this->persister->delete($entity)) {
if ($deleted) {
$this->region->evict($key);
}
if ($lock === null) {
return;
return $deleted;
}
$this->queuedCache['delete'][] = array(
'lock' => $lock,
'key' => $key
);
return $deleted;
}
/**
+3 -3
View File
@@ -38,18 +38,18 @@ class QueryCacheEntry implements CacheEntry
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var integer Time creation of this cache entry
* @var float Time creation of this cache entry
*/
public $time;
/**
* @param array $result
* @param integer $time
* @param float $time
*/
public function __construct($result, $time = null)
{
$this->result = $result;
$this->time = $time ?: time();
$this->time = $time ?: microtime(true);
}
/**
+21 -8
View File
@@ -45,14 +45,27 @@ class QueryCacheKey extends CacheKey
public $cacheMode;
/**
* @param string $hash Result cache id
* @param integer $lifetime Query lifetime
* @param integer $cacheMode Query cache mode
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var TimestampCacheKey|null
*/
public function __construct($hash, $lifetime = 0, $cacheMode = Cache::MODE_NORMAL)
{
$this->hash = $hash;
$this->lifetime = $lifetime;
$this->cacheMode = $cacheMode;
public $timestampKey;
/**
* @param string $hash Result cache id
* @param integer $lifetime Query lifetime
* @param int $cacheMode Query cache mode
* @param TimestampCacheKey|null $timestampKey
*/
public function __construct(
$hash,
$lifetime = 0,
$cacheMode = Cache::MODE_NORMAL,
TimestampCacheKey $timestampKey = null
) {
$this->hash = $hash;
$this->lifetime = $lifetime;
$this->cacheMode = $cacheMode;
$this->timestampKey = $timestampKey;
}
}
@@ -26,15 +26,49 @@ namespace Doctrine\ORM\Cache;
*/
class TimestampQueryCacheValidator implements QueryCacheValidator
{
/**
* @var TimestampRegion
*/
private $timestampRegion;
/**
* @param TimestampRegion $timestampRegion
*/
public function __construct(TimestampRegion $timestampRegion)
{
$this->timestampRegion = $timestampRegion;
}
/**
* {@inheritdoc}
*/
public function isValid(QueryCacheKey $key, QueryCacheEntry $entry)
{
if ($this->regionUpdated($key, $entry)) {
return false;
}
if ($key->lifetime == 0) {
return true;
}
return ($entry->time + $key->lifetime) > time();
return ($entry->time + $key->lifetime) > microtime(true);
}
/**
* @param QueryCacheKey $key
* @param QueryCacheEntry $entry
*
* @return bool
*/
private function regionUpdated(QueryCacheKey $key, QueryCacheEntry $entry)
{
if ($key->timestampKey === null) {
return false;
}
$timestamp = $this->timestampRegion->get($key->timestampKey);
return $timestamp && $timestamp->time > $entry->time;
}
}
@@ -332,6 +332,9 @@ class ObjectHydrator extends AbstractHydrator
// Split the row data into chunks of class data.
$rowData = $this->gatherRowData($row, $id, $nonemptyComponents);
// reset result pointers for each data row
$this->resultPointers = [];
// Hydrate the data chunks
foreach ($rowData['data'] as $dqlAlias => $data) {
$entityName = $this->_rsm->aliasMap[$dqlAlias];
@@ -122,6 +122,9 @@ class SimpleObjectHydrator extends AbstractHydrator
continue;
}
// Check if value is null before conversion (because some types convert null to something else)
$valueIsNull = null === $value;
// Convert field to a valid PHP value
if (isset($cacheKeyInfo['type'])) {
$type = $cacheKeyInfo['type'];
@@ -131,7 +134,7 @@ class SimpleObjectHydrator extends AbstractHydrator
$fieldName = $cacheKeyInfo['fieldName'];
// Prevent overwrite in case of inherit classes using same property name (See AbstractHydrator)
if ( ! isset($data[$fieldName]) || $value !== null) {
if ( ! isset($data[$fieldName]) || ! $valueIsNull) {
$data[$fieldName] = $value;
}
}
@@ -940,8 +940,13 @@ class ClassMetadataInfo implements ClassMetadata
continue;
}
$parentReflFields[$property] = $reflService->getAccessibleProperty($this->name, $property);
$this->reflFields[$property] = $reflService->getAccessibleProperty($this->name, $property);
$fieldRefl = $reflService->getAccessibleProperty(
isset($embeddedClass['declared']) ? $embeddedClass['declared'] : $this->name,
$property
);
$parentReflFields[$property] = $fieldRefl;
$this->reflFields[$property] = $fieldRefl;
}
foreach ($this->fieldMappings as $field => $mapping) {
@@ -514,9 +514,8 @@ class YamlDriver extends FileDriver
if ( ! isset($joinColumnElement['name'])) {
$joinColumnElement['name'] = $joinColumnName;
}
$joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement);
}
$joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement);
}
if (isset($joinTableElement['inverseJoinColumns'])) {
@@ -524,9 +523,8 @@ class YamlDriver extends FileDriver
if ( ! isset($joinColumnElement['name'])) {
$joinColumnElement['name'] = $joinColumnName;
}
$joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement);
}
$joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement);
}
$mapping['joinTable'] = $joinTable;
@@ -536,6 +536,8 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
public function clear()
{
if ($this->initialized && $this->isEmpty()) {
$this->collection->clear();
return;
}
@@ -90,10 +90,6 @@ abstract class AbstractCollectionPersister implements CollectionPersister
// If Entity is scheduled for inclusion, it is not in this collection.
// We can assure that because it would have return true before on array check
if ($entityState === UnitOfWork::STATE_MANAGED && $this->uow->isScheduledForInsert($entity)) {
return false;
}
return true;
return ! ($entityState === UnitOfWork::STATE_MANAGED && $this->uow->isScheduledForInsert($entity));
}
}
+1 -1
View File
@@ -228,7 +228,7 @@ class ProxyFactory extends AbstractProxyFactory
);
}
foreach ($class->getReflectionClass()->getProperties() as $property) {
foreach ($class->getReflectionProperties() as $property) {
if ( ! $class->hasField($property->name) && ! $class->hasAssociation($property->name)) {
continue;
}
+1 -1
View File
@@ -965,7 +965,7 @@ class Parser
$schemaName = $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
}
$exists = class_exists($schemaName, true);
$exists = class_exists($schemaName, true) || interface_exists($schemaName, true);
if ( ! $exists) {
$this->semanticalError("Class '$schemaName' is not defined.", $this->lexer->token);
@@ -438,8 +438,8 @@ class ResultSetMappingBuilder extends ResultSetMapping
$sql .= $class->fieldMappings[$this->fieldMappings[$columnName]]['columnName'];
} else if (isset($this->metaMappings[$columnName])) {
$sql .= $this->metaMappings[$columnName];
} else if (isset($this->discriminatorColumn[$columnName])) {
$sql .= $this->discriminatorColumn[$columnName];
} else if (isset($this->discriminatorColumns[$dqlAlias])) {
$sql .= $this->discriminatorColumns[$dqlAlias];
}
$sql .= " AS " . $columnName;
+3 -1
View File
@@ -2264,7 +2264,9 @@ class SqlWalker implements TreeWalker
public function walkArithmeticFactor($factor)
{
if (is_string($factor)) {
return $factor;
return (isset($this->queryComponents[$factor]))
? $this->walkResultVariable($this->queryComponents[$factor]['token']['value'])
: $factor;
}
// Phase 2 AST optimization: Skip processing of ArithmeticFactor
@@ -117,7 +117,7 @@ class PhpExporter extends AbstractExporter
$oneToOneMappingArray = array(
'mappedBy' => $associationMapping['mappedBy'],
'inversedBy' => $associationMapping['inversedBy'],
'joinColumns' => $associationMapping['joinColumns'],
'joinColumns' => $associationMapping['isOwningSide'] ? $associationMapping['joinColumns'] : [],
'orphanRemoval' => $associationMapping['orphanRemoval'],
);
@@ -163,7 +163,7 @@ class YamlExporter extends AbstractExporter
}
if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
$joinColumns = $associationMapping['joinColumns'];
$joinColumns = $associationMapping['isOwningSide'] ? $associationMapping['joinColumns'] : [];
$newJoinColumns = array();
foreach ($joinColumns as $joinColumn) {
@@ -438,7 +438,10 @@ class LimitSubqueryOutputWalker extends SqlWalker
// Field was declared in a parent class, so we need to get the proper SQL table alias
// for the joined parent table.
$otherClassMetadata = $this->em->getClassMetadata($fieldMapping['declared']);
$sqlTableAliasForFieldAlias = $this->getSQLTableAlias($otherClassMetadata->getTableName(), $dqlAliasForFieldAlias);
if (!$otherClassMetadata->isMappedSuperclass) {
$sqlTableAliasForFieldAlias = $this->getSQLTableAlias($otherClassMetadata->getTableName(), $dqlAliasForFieldAlias);
}
}
// Compose search/replace patterns
+10
View File
@@ -21,6 +21,7 @@ namespace Doctrine\ORM\Tools;
use Doctrine\ORM\ORMException;
use Doctrine\DBAL\Schema\Comparator;
use Doctrine\DBAL\Schema\Index;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector;
@@ -274,6 +275,15 @@ class SchemaTool
if (isset($class->table['uniqueConstraints'])) {
foreach ($class->table['uniqueConstraints'] as $indexName => $indexData) {
$uniqIndex = new Index($indexName, $indexData['columns'], true, false, [], isset($indexData['options']) ? $indexData['options'] : []);
foreach ($table->getIndexes() as $tableIndexName => $tableIndex) {
if ($tableIndex->isFullfilledBy($uniqIndex)) {
$table->dropIndex($tableIndexName);
break;
}
}
$table->addUniqueIndex($indexData['columns'], is_numeric($indexName) ? null : $indexName, isset($indexData['options']) ? $indexData['options'] : array());
}
}
+85 -39
View File
@@ -733,7 +733,6 @@ class UnitOfWork implements PropertyChangedListener
// Look for changes in associations of the entity
foreach ($class->associationMappings as $field => $assoc) {
if (($val = $class->reflFields[$field]->getValue($entity)) === null) {
continue;
}
@@ -799,7 +798,7 @@ class UnitOfWork implements PropertyChangedListener
// Only MANAGED entities that are NOT SCHEDULED FOR INSERTION OR DELETION are processed here.
$oid = spl_object_hash($entity);
if ( ! isset($this->entityInsertions[$oid]) && ! isset($this->entityDeletions[$oid]) && isset($this->entityStates[$oid])) {
if ( ! isset($this->entityInsertions[$oid]) && ! isset($this->entityDeletions[$oid]) && isset($this->entityStates[$oid])) {
$this->computeChangeSet($class, $entity);
}
}
@@ -826,10 +825,7 @@ class UnitOfWork implements PropertyChangedListener
if ($value instanceof PersistentCollection && $value->isDirty()) {
$coid = spl_object_hash($value);
if ($assoc['isOwningSide']) {
$this->collectionUpdates[$coid] = $value;
}
$this->collectionUpdates[$coid] = $value;
$this->visitedCollections[$coid] = $value;
}
@@ -1803,7 +1799,7 @@ class UnitOfWork implements PropertyChangedListener
* @throws OptimisticLockException If the entity uses optimistic locking through a version
* attribute and the version check against the managed copy fails.
* @throws ORMInvalidArgumentException If the entity instance is NEW.
* @throws EntityNotFoundException
* @throws EntityNotFoundException if an assigned identifier is used in the entity, but none is provided
*/
private function doMerge($entity, array &$visited, $prevManagedCopy = null, $assoc = null)
{
@@ -1835,6 +1831,7 @@ class UnitOfWork implements PropertyChangedListener
if ( ! $id) {
$managedCopy = $this->newInstance($class);
$this->mergeEntityStateIntoManagedCopy($entity, $managedCopy);
$this->persistNew($class, $managedCopy);
} else {
$flatId = ($class->containsForeignIdentifier)
@@ -1866,31 +1863,16 @@ class UnitOfWork implements PropertyChangedListener
$managedCopy = $this->newInstance($class);
$class->setIdentifierValues($managedCopy, $id);
$this->mergeEntityStateIntoManagedCopy($entity, $managedCopy);
$this->persistNew($class, $managedCopy);
}
}
if ($class->isVersioned) {
$reflField = $class->reflFields[$class->versionField];
$managedCopyVersion = $reflField->getValue($managedCopy);
$entityVersion = $reflField->getValue($entity);
// Throw exception if versions don't match.
if ($managedCopyVersion != $entityVersion) {
throw OptimisticLockException::lockFailedVersionMismatch($entity, $entityVersion, $managedCopyVersion);
} else {
$this->ensureVersionMatch($class, $entity, $managedCopy);
$this->mergeEntityStateIntoManagedCopy($entity, $managedCopy);
}
}
$visited[$oid] = $managedCopy; // mark visited
if (!($entity instanceof Proxy && ! $entity->__isInitialized())) {
if ($managedCopy instanceof Proxy && ! $managedCopy->__isInitialized()) {
$managedCopy->__load();
}
$this->mergeEntityStateIntoManagedCopy($entity, $managedCopy);
}
if ($class->isChangeTrackingDeferredExplicit()) {
$this->scheduleForDirtyCheck($entity);
}
@@ -1908,6 +1890,45 @@ class UnitOfWork implements PropertyChangedListener
return $managedCopy;
}
/**
* @param ClassMetadata $class
* @param object $entity
* @param object $managedCopy
*
* @return void
*
* @throws OptimisticLockException
*/
private function ensureVersionMatch(ClassMetadata $class, $entity, $managedCopy)
{
if (! ($class->isVersioned && $this->isLoaded($managedCopy) && $this->isLoaded($entity))) {
return;
}
$reflField = $class->reflFields[$class->versionField];
$managedCopyVersion = $reflField->getValue($managedCopy);
$entityVersion = $reflField->getValue($entity);
// Throw exception if versions don't match.
if ($managedCopyVersion == $entityVersion) {
return;
}
throw OptimisticLockException::lockFailedVersionMismatch($entity, $entityVersion, $managedCopyVersion);
}
/**
* Tests if an entity is loaded - must either be a loaded proxy or not a proxy
*
* @param object $entity
*
* @return bool
*/
private function isLoaded($entity)
{
return !($entity instanceof Proxy) || $entity->__isInitialized();
}
/**
* Sets/adds associated managed copies into the previous entity's association field
*
@@ -2395,17 +2416,8 @@ class UnitOfWork implements PropertyChangedListener
$this->commitOrderCalculator->clear();
}
} else {
$visited = array();
foreach ($this->identityMap as $className => $entities) {
if ($className !== $entityName) {
continue;
}
foreach ($entities as $entity) {
$this->doDetach($entity, $visited, false);
}
}
$this->clearIdentityMapForEntityName($entityName);
$this->clearEntityInsertionsForEntityName($entityName);
}
if ($this->evm->hasListeners(Events::onClear)) {
@@ -3357,6 +3369,14 @@ class UnitOfWork implements PropertyChangedListener
*/
private function mergeEntityStateIntoManagedCopy($entity, $managedCopy)
{
if (! $this->isLoaded($entity)) {
return;
}
if (! $this->isLoaded($managedCopy)) {
$managedCopy->__load();
}
$class = $this->em->getClassMetadata(get_class($entity));
foreach ($this->reflectionPropertiesGetter->getProperties($class->name) as $prop) {
@@ -3419,8 +3439,6 @@ class UnitOfWork implements PropertyChangedListener
);
$managedCol->setOwner($managedCopy, $assoc2);
$prop->setValue($managedCopy, $managedCol);
$this->originalEntityData[spl_object_hash($entity)][$name] = $managedCol;
}
if ($assoc2['isCascadeMerge']) {
@@ -3459,4 +3477,32 @@ class UnitOfWork implements PropertyChangedListener
{
$this->hydrationCompleteHandler->hydrationComplete();
}
/**
* @param string $entityName
*/
private function clearIdentityMapForEntityName($entityName)
{
if (! isset($this->identityMap[$entityName])) {
return;
}
$visited = [];
foreach ($this->identityMap[$entityName] as $entity) {
$this->doDetach($entity, $visited, false);
}
}
/**
* @param string $entityName
*/
private function clearEntityInsertionsForEntityName($entityName)
{
foreach ($this->entityInsertions as $hash => $entity) {
if (get_class($entity) === $entityName) {
unset($this->entityInsertions[$hash]);
}
}
}
}
+1 -1
View File
@@ -36,7 +36,7 @@ class Version
/**
* Current Doctrine Version
*/
const VERSION = '2.5.1';
const VERSION = '2.5.5-DEV';
/**
* Compares a Doctrine version with the current one.
+1 -1
View File
@@ -33,7 +33,7 @@ class State
protected $country;
/**
* @Cache
* @Cache("NONSTRICT_READ_WRITE")
* @OneToMany(targetEntity="City", mappedBy="state")
*/
protected $cities;
@@ -26,7 +26,7 @@ class Traveler
protected $name;
/**
* @Cache()
* @Cache("NONSTRICT_READ_WRITE")
* @OneToMany(targetEntity="Travel", mappedBy="traveler", cascade={"persist", "remove"}, orphanRemoval=true)
*
* @var \Doctrine\Common\Collections\Collection
@@ -6,17 +6,17 @@ class CompanyContractListener
{
public $postPersistCalls;
public $prePersistCalls;
public $postUpdateCalls;
public $preUpdateCalls;
public $postRemoveCalls;
public $preRemoveCalls;
public $preFlushCalls;
public $postLoadCalls;
/**
* @PostPersist
*/
@@ -80,5 +80,4 @@ class CompanyContractListener
{
$this->postLoadCalls[] = func_get_args();
}
}
@@ -0,0 +1,78 @@
<?php
/**
* @author Marc Pantel <pantel.m@gmail.com>
*/
namespace Doctrine\Tests\Models\DDC3711;
use Doctrine\Common\Collections\ArrayCollection;
class DDC3711EntityA
{
/**
* @var int
*/
private $id1;
/**
* @var int
*/
private $id2;
/**
* @var ArrayCollection
*/
private $entityB;
/**
* @return mixed
*/
public function getId1()
{
return $this->id1;
}
/**
* @param mixed $id1
*/
public function setId1($id1)
{
$this->id1 = $id1;
}
/**
* @return mixed
*/
public function getId2()
{
return $this->id2;
}
/**
* @param mixed $id2
*/
public function setId2($id2)
{
$this->id2 = $id2;
}
/**
* @return ArrayCollection
*/
public function getEntityB()
{
return $this->entityB;
}
/**
* @param ArrayCollection $entityB
*
* @return DDC3711EntityA
*/
public function addEntityB($entityB)
{
$this->entityB[] = $entityB;
return $this;
}
}
@@ -0,0 +1,75 @@
<?php
/**
* @author Marc Pantel <pantel.m@gmail.com>
*/
namespace Doctrine\Tests\Models\DDC3711;
use Doctrine\Common\Collections\ArrayCollection;
class DDC3711EntityB
{
/**
* @var int
*/
private $id1;
/**
* @var int
*/
private $id2;
/**
* @var ArrayCollection
*/
private $entityA;
/**
* @return int
*/
public function getId1()
{
return $this->id1;
}
/**
* @param int $id1
*/
public function setId1($id1)
{
$this->id1 = $id1;
}
/**
* @return int
*/
public function getId2()
{
return $this->id2;
}
/**
* @param int $id2
*/
public function setId2($id2)
{
$this->id2 = $id2;
}
/**
* @return ArrayCollection
*/
public function getEntityA()
{
return $this->entityA;
}
/**
* @param ArrayCollection $entityA
*/
public function addEntityA($entityA)
{
$this->entityA[] = $entityA;
}
}
@@ -0,0 +1,25 @@
<?php
namespace Doctrine\Tests\Models\DDC3899;
/**
* @Entity
* @Table(name="dc3899_contracts")
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({
* "fix" = "DDC3899FixContract",
* "flexible" = "DDC3899FlexContract"
* })
*/
abstract class DDC3899Contract
{
/** @Id @Column(type="integer") */
public $id;
/** @Column(type="boolean") */
public $completed = false;
/** @ManyToOne(targetEntity="DDC3899User", inversedBy="contract") */
public $user;
}
@@ -0,0 +1,12 @@
<?php
namespace Doctrine\Tests\Models\DDC3899;
/**
* @Entity
*/
class DDC3899FixContract extends DDC3899Contract
{
/** @column(type="integer") */
public $fixPrice = 0;
}
@@ -0,0 +1,15 @@
<?php
namespace Doctrine\Tests\Models\DDC3899;
/**
* @Entity
*/
class DDC3899FlexContract extends DDC3899Contract
{
/** @column(type="integer") */
public $hoursWorked = 0;
/** @column(type="integer") */
public $pricePerHour = 0;
}
@@ -0,0 +1,16 @@
<?php
namespace Doctrine\Tests\Models\DDC3899;
/**
* @Entity
* @Table(name="dc3899_users")
*/
class DDC3899User
{
/** @Id @Column(type="integer") */
public $id;
/** @OneToMany(targetEntity="DDC3899Contract", mappedBy="user") */
public $contracts;
}
@@ -9,6 +9,8 @@ namespace Doctrine\Tests\Models\GeoNames;
*/
class Country
{
const CLASSNAME = __CLASS__;
/**
* @Id
* @Column(type="string", length=2)
@@ -0,0 +1,17 @@
<?php
namespace Doctrine\Tests\Models\Issue5989;
/**
* @Entity
* @Table(name="issue5989_employees")
*/
class Issue5989Employee extends Issue5989Person
{
/**
* @Column(type="simple_array", nullable=true)
*
* @var array
*/
public $tags;
}
@@ -0,0 +1,17 @@
<?php
namespace Doctrine\Tests\Models\Issue5989;
/**
* @Entity
* @Table(name="issue5989_managers")
*/
class Issue5989Manager extends Issue5989Person
{
/**
* @Column(type="simple_array", nullable=true)
*
* @var array
*/
public $tags;
}
@@ -0,0 +1,26 @@
<?php
namespace Doctrine\Tests\Models\Issue5989;
/**
* @Entity
* @Table(name="issue5989_persons")
* @InheritanceType("JOINED")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({
* "person" = "Issue5989Person",
* "manager" = "Issue5989Manager",
* "employee" = "Issue5989Employee"
* })
*/
class Issue5989Person
{
const CLASSNAME = __CLASS__;
/**
* @Id
* @Column(type="integer")
* @GeneratedValue
*/
public $id;
}
@@ -0,0 +1,37 @@
<?php
namespace Doctrine\Tests\Models\VersionedManyToOne;
/**
* @Entity
* @Table(name="versioned_many_to_one_article")
*/
class Article
{
const CLASSNAME = __CLASS__;
/**
* @Id
* @Column(name="id", type="integer")
* @GeneratedValue(strategy="AUTO")
*/
public $id;
/**
* @Column(name="name")
*/
public $name;
/**
* @ManyToOne(targetEntity="Category", cascade={"merge", "persist"})
*/
public $category;
/**
* Version column
*
* @Column(type="integer", name="version")
* @Version
*/
public $version;
}
@@ -0,0 +1,27 @@
<?php
namespace Doctrine\Tests\Models\VersionedManyToOne;
/**
* @Entity
* @Table(name="versioned_many_to_one_category")
*/
class Category
{
const CLASSNAME = __CLASS__;
/**
* @Id
* @Column(name="id", type="integer")
* @GeneratedValue(strategy="AUTO")
*/
public $id;
/**
* Version column
*
* @Column(type="integer", name="version")
* @Version
*/
public $version;
}
@@ -2,8 +2,8 @@
namespace Doctrine\Tests\ORM\Cache;
use Doctrine\Tests\DoctrineTestCase;
use Doctrine\ORM\Cache\CacheConfiguration;
use Doctrine\Tests\DoctrineTestCase;
/**
* @group DDC-2183
@@ -64,6 +64,11 @@ class CacheConfigTest extends DoctrineTestCase
public function testSetGetQueryValidator()
{
$factory = $this->getMock('Doctrine\ORM\Cache\CacheFactory');
$factory->method('getTimestampRegion')->willReturn($this->getMock('Doctrine\ORM\Cache\TimestampRegion'));
$this->config->setCacheFactory($factory);
$validator = $this->getMock('Doctrine\ORM\Cache\QueryCacheValidator');
$this->assertInstanceOf('Doctrine\ORM\Cache\TimestampQueryCacheValidator', $this->config->getQueryValidator());
@@ -72,4 +77,4 @@ class CacheConfigTest extends DoctrineTestCase
$this->assertEquals($validator, $this->config->getQueryValidator());
}
}
}
@@ -119,7 +119,7 @@ class DefaultEntityHydratorTest extends OrmTestCase
$this->assertArrayHasKey('name', $cache->data);
$this->assertArrayHasKey('country', $cache->data);
$this->assertEquals(array(
'id' => 11,
'id' => 12,
'name' => 'Bar',
'country' => new AssociationCacheEntry(Country::CLASSNAME, array('id' => 11)),
), $cache->data);
@@ -147,9 +147,39 @@ class DefaultEntityHydratorTest extends OrmTestCase
$this->assertArrayHasKey('name', $cache->data);
$this->assertArrayHasKey('country', $cache->data);
$this->assertEquals(array(
'id' => 11,
'id' => 12,
'name' => 'Bar',
'country' => new AssociationCacheEntry(Country::CLASSNAME, array('id' => 11)),
), $cache->data);
}
}
public function testCacheEntryWithWrongIdentifierType()
{
$proxy = $this->em->getReference(Country::CLASSNAME, 11);
$entity = new State('Bat', $proxy);
$uow = $this->em->getUnitOfWork();
$entityData = array('id'=> 12, 'name'=>'Bar', 'country' => $proxy);
$metadata = $this->em->getClassMetadata(State::CLASSNAME);
$key = new EntityCacheKey($metadata->name, array('id'=>'12'));
$entity->setId(12);
$uow->registerManaged($entity, array('id'=>12), $entityData);
$cache = $this->structure->buildCacheEntry($metadata, $key, $entity);
$this->assertInstanceOf('Doctrine\ORM\Cache\CacheEntry', $cache);
$this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheEntry', $cache);
$this->assertArrayHasKey('id', $cache->data);
$this->assertArrayHasKey('name', $cache->data);
$this->assertArrayHasKey('country', $cache->data);
$this->assertSame($entity->getId(), $cache->data['id']);
$this->assertEquals(array(
'id' => 12,
'name' => 'Bar',
'country' => new AssociationCacheEntry(Country::CLASSNAME, array('id' => 11)),
), $cache->data);
}
}
@@ -386,7 +386,7 @@ class DefaultQueryCacheTest extends OrmTestCase
array('id'=>2, 'name' => 'Bar')
);
$entry->time = time() - 100;
$entry->time = microtime(true) - 100;
$this->region->addReturn('get', $entry);
$this->region->addReturn('get', new EntityCacheEntry(Country::CLASSNAME, $entities[0]));
@@ -0,0 +1,45 @@
<?php
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\ORM\OptimisticLockException;
use Doctrine\ORM\ORMException;
use Doctrine\Tests\Models\VersionedManyToOne\Article;
use Doctrine\Tests\Models\VersionedManyToOne\Category;
/**
* @group MergeVersionedOneToMany
*/
class MergeVersionedManyToOneTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
protected function setUp()
{
$this->useModelSet('versioned_many_to_one');
parent::setUp();
}
/**
* This test case asserts that a detached and unmodified entity could be merge without firing
* OptimisticLockException.
*/
public function testSetVersionOnCreate()
{
$category = new Category();
$article = new Article();
$article->name = 'Article';
$article->category = $category;
$this->_em->persist($article);
$this->_em->flush();
$this->_em->clear();
$articleMerged = $this->_em->merge($article);
$articleMerged->name = 'Article Merged';
$this->_em->flush();
$this->assertEquals(2, $articleMerged->version);
}
}
@@ -791,4 +791,20 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertSQLEquals('u.id AS id0, u.status AS status1, u.username AS username2, u.name AS name3, u.email_id AS email_id4', (string)$rsm);
}
/**
* @group DDC-3899
*/
public function testGenerateSelectClauseWithDiscriminatorColumn()
{
$rsm = new ResultSetMappingBuilder($this->_em, ResultSetMappingBuilder::COLUMN_RENAMING_INCREMENT);
$rsm->addEntityResult('Doctrine\Tests\Models\DDC3899\DDC3899User', 'u');
$rsm->addJoinedEntityResult('Doctrine\Tests\Models\DDC3899\DDC3899FixContract', 'c', 'u', 'contracts');
$rsm->addFieldResult('u', $this->platform->getSQLResultCasing('id'), 'id');
$rsm->setDiscriminatorColumn('c', $this->platform->getSQLResultCasing('discr'));
$selectClause = $rsm->generateSelectClause(array('u' => 'u1', 'c' => 'c1'));
$this->assertSQLEquals('u1.id as id, c1.discr as discr', $selectClause);
}
}
@@ -45,12 +45,12 @@ class QueryCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
$query->setQueryCacheDriver($cache);
$query->getResult();
$this->assertEquals(2, $this->getCacheSize($cache));
$this->assertEquals(1, $this->getCacheSize($cache));
$query->setHint('foo', 'bar');
$query->getResult();
$this->assertEquals(3, $this->getCacheSize($cache));
$this->assertEquals(2, $this->getCacheSize($cache));
return $query;
}
@@ -105,16 +105,16 @@ class QueryCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
$query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux');
$cache = new \Doctrine\Common\Cache\ArrayCache();
$cache = $this->getMock('Doctrine\Common\Cache\Cache');
$query->setQueryCacheDriver($cache);
$users = $query->getResult();
$cache
->expects(self::once())
->method('save')
->with(self::isType('string'), self::isInstanceOf('Doctrine\ORM\Query\ParserResult'));
$data = $this->cacheDataReflection->getValue($cache);
$this->assertEquals(2, count($data));
$this->assertInstanceOf('Doctrine\ORM\Query\ParserResult', array_pop($data));
$query->getResult();
}
public function testQueryCache_HitDoesNotSaveParserResult()
@@ -141,7 +141,7 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals(0, $this->getCacheSize($cache));
$query->getResult();
$this->assertEquals(2, $this->getCacheSize($cache));
$this->assertEquals(1, $this->getCacheSize($cache));
return $query;
}
@@ -343,18 +343,18 @@ class SQLFilterTest extends \Doctrine\Tests\OrmFunctionalTestCase
$query->setQueryCacheDriver($cache);
$query->getResult();
$this->assertEquals(2, sizeof($cacheDataReflection->getValue($cache)));
$this->assertEquals(1, sizeof($cacheDataReflection->getValue($cache)));
$conf = $this->_em->getConfiguration();
$conf->addFilter("locale", "\Doctrine\Tests\ORM\Functional\MyLocaleFilter");
$this->_em->getFilters()->enable("locale");
$query->getResult();
$this->assertEquals(3, sizeof($cacheDataReflection->getValue($cache)));
$this->assertEquals(2, sizeof($cacheDataReflection->getValue($cache)));
// Another time doesn't add another cache entry
$query->getResult();
$this->assertEquals(3, sizeof($cacheDataReflection->getValue($cache)));
$this->assertEquals(2, sizeof($cacheDataReflection->getValue($cache)));
}
public function testQueryGeneration_DependsOnFilters()
@@ -3,8 +3,8 @@
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\Cache\Attraction;
use Doctrine\Tests\Models\Cache\AttractionInfo;
use Doctrine\Tests\Models\Cache\AttractionContactInfo;
use Doctrine\Tests\Models\Cache\AttractionInfo;
use Doctrine\Tests\Models\Cache\AttractionLocationInfo;
/**
@@ -188,4 +188,47 @@ class SecondLevelCacheJoinTableInheritanceTest extends SecondLevelCacheAbstractT
$this->assertInstanceOf(AttractionContactInfo::CLASSNAME, $entity->getInfos()->get(0));
$this->assertEquals($this->attractionsInfo[0]->getFone(), $entity->getInfos()->get(0)->getFone());
}
}
public function testQueryCacheShouldBeEvictedOnTimestampUpdate()
{
$this->loadFixturesCountries();
$this->loadFixturesStates();
$this->loadFixturesCities();
$this->loadFixturesAttractions();
$this->loadFixturesAttractionsInfo();
$this->evictRegions();
$this->_em->clear();
$queryCount = $this->getCurrentQueryCount();
$dql = 'SELECT attractionInfo FROM Doctrine\Tests\Models\Cache\AttractionInfo attractionInfo';
$result1 = $this->_em->createQuery($dql)
->setCacheable(true)
->getResult();
$this->assertCount(count($this->attractionsInfo), $result1);
$this->assertEquals($queryCount + 5, $this->getCurrentQueryCount());
$contact = new AttractionContactInfo(
'1234-1234',
$this->_em->find(Attraction::CLASSNAME, $this->attractions[5]->getId())
);
$this->_em->persist($contact);
$this->_em->flush();
$this->_em->clear();
$queryCount = $this->getCurrentQueryCount();
$result2 = $this->_em->createQuery($dql)
->setCacheable(true)
->getResult();
$this->assertCount(count($this->attractionsInfo) + 1, $result2);
$this->assertEquals($queryCount + 6, $this->getCurrentQueryCount());
foreach ($result2 as $entity) {
$this->assertInstanceOf(AttractionInfo::CLASSNAME, $entity);
}
}
}
@@ -2,10 +2,10 @@
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\Cache\City;
use Doctrine\Tests\Models\Cache\ComplexAction;
use Doctrine\Tests\Models\Cache\Country;
use Doctrine\Tests\Models\Cache\State;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\Tests\Models\Cache\Token;
use Doctrine\Tests\Models\Cache\Action;
@@ -98,6 +98,40 @@ class SecondLevelCacheManyToOneTest extends SecondLevelCacheAbstractTest
$this->assertEquals($this->states[1]->getCountry()->getName(), $c4->getCountry()->getName());
}
public function testInverseSidePutShouldEvictCollection()
{
$this->loadFixturesCountries();
$this->loadFixturesStates();
$this->_em->clear();
$this->cache->evictEntityRegion(State::CLASSNAME);
$this->cache->evictEntityRegion(Country::CLASSNAME);
//evict collection on add
$c3 = $this->_em->find(State::CLASSNAME, $this->states[0]->getId());
$prev = $c3->getCities();
$count = $prev->count();
$city = new City("Buenos Aires", $c3);
$c3->addCity($city);
$this->_em->persist($city);
$this->_em->persist($c3);
$this->_em->flush();
$this->_em->clear();
$state = $this->_em->find(State::CLASSNAME, $c3->getId());
$queryCount = $this->getCurrentQueryCount();
// Association was cleared from EM
$this->assertNotEquals($prev, $state->getCities());
// New association has one more item (cache was evicted)
$this->assertEquals($count + 1, $state->getCities()->count());
$this->assertEquals($queryCount, $this->getCurrentQueryCount());
}
public function testShouldNotReloadWhenAssociationIsMissing()
{
$this->loadFixturesCountries();
@@ -14,18 +14,18 @@ use Doctrine\Tests\Models\Cache\Traveler;
*/
class SecondLevelCacheOneToManyTest extends SecondLevelCacheAbstractTest
{
public function testShouldNotPutCollectionInverseSideOnPersist()
public function testShouldPutCollectionInverseSideOnPersist()
{
$this->loadFixturesCountries();
$this->loadFixturesStates();
$this->loadFixturesCities();
$this->_em->clear();
$this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[0]->getId()));
$this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[1]->getId()));
$this->assertFalse($this->cache->containsCollection(State::CLASSNAME, 'cities', $this->states[0]->getId()));
$this->assertFalse($this->cache->containsCollection(State::CLASSNAME, 'cities', $this->states[1]->getId()));
$this->assertTrue($this->cache->containsCollection(State::CLASSNAME, 'cities', $this->states[0]->getId()));
$this->assertTrue($this->cache->containsCollection(State::CLASSNAME, 'cities', $this->states[1]->getId()));
}
public function testPutAndLoadOneToManyRelation()
@@ -187,6 +187,7 @@ class SecondLevelCacheOneToManyTest extends SecondLevelCacheAbstractTest
$this->loadFixturesCountries();
$this->loadFixturesStates();
$this->loadFixturesCities();
$this->_em->clear();
$this->secondLevelCacheLogger->clearStats();
@@ -247,8 +248,8 @@ class SecondLevelCacheOneToManyTest extends SecondLevelCacheAbstractTest
$this->_em->remove($city0);
$this->_em->persist($state);
$this->_em->flush();
$this->_em->clear();
$this->secondLevelCacheLogger->clearStats();
$queryCount = $this->getCurrentQueryCount();
@@ -261,19 +262,19 @@ class SecondLevelCacheOneToManyTest extends SecondLevelCacheAbstractTest
$this->assertInstanceOf(City::CLASSNAME, $city1);
$this->assertEquals($entity->getCities()->get(1)->getName(), $city1->getName());
$this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(2, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(State::CLASSNAME)));
$this->assertEquals(0, $this->secondLevelCacheLogger->getRegionHitCount($this->getCollectionRegion(State::CLASSNAME, 'cities')));
$this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getCollectionRegion(State::CLASSNAME, 'cities')));
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
$this->assertEquals($queryCount, $this->getCurrentQueryCount());
$state->getCities()->remove(0);
$this->_em->remove($city1);
$this->_em->persist($state);
$this->_em->flush();
$this->_em->clear();
$this->secondLevelCacheLogger->clearStats();
$queryCount = $this->getCurrentQueryCount();
@@ -281,9 +282,9 @@ class SecondLevelCacheOneToManyTest extends SecondLevelCacheAbstractTest
$this->assertCount(0, $state->getCities());
$this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(2, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(State::CLASSNAME)));
$this->assertEquals(0, $this->secondLevelCacheLogger->getRegionHitCount($this->getCollectionRegion(State::CLASSNAME, 'cities')));
$this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getCollectionRegion(State::CLASSNAME, 'cities')));
}
public function testOneToManyWithEmptyRelation()
@@ -346,11 +347,12 @@ class SecondLevelCacheOneToManyTest extends SecondLevelCacheAbstractTest
public function testCacheInitializeCollectionWithNewObjects()
{
$this->_em->clear();
$this->evictRegions();
$traveler = new Traveler("Doctrine Bot");
for ($i=0; $i<3; ++$i) {
for ($i = 0; $i < 3; ++$i) {
$traveler->getTravels()->add(new Travel($traveler));
}
@@ -373,7 +375,7 @@ class SecondLevelCacheOneToManyTest extends SecondLevelCacheAbstractTest
$this->assertFalse($entity->getTravels()->isInitialized());
$this->assertCount(4, $entity->getTravels());
$this->assertTrue($entity->getTravels()->isInitialized());
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
$this->assertEquals($queryCount, $this->getCurrentQueryCount());
$this->_em->flush();
$this->_em->clear();
@@ -1035,4 +1035,37 @@ class SecondLevelCacheQueryCacheTest extends SecondLevelCacheAbstractTest
->setCacheable(true)
->getResult();
}
}
public function testQueryCacheShouldBeEvictedOnTimestampUpdate()
{
$this->loadFixturesCountries();
$this->_em->clear();
$queryCount = $this->getCurrentQueryCount();
$dql = 'SELECT country FROM Doctrine\Tests\Models\Cache\Country country';
$result1 = $this->_em->createQuery($dql)
->setCacheable(true)
->getResult();
$this->assertCount(2, $result1);
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
$this->_em->persist(new Country('France'));
$this->_em->flush();
$this->_em->clear();
$queryCount = $this->getCurrentQueryCount();
$result2 = $this->_em->createQuery($dql)
->setCacheable(true)
->getResult();
$this->assertCount(3, $result2);
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
foreach ($result2 as $entity) {
$this->assertInstanceOf(Country::CLASSNAME, $entity);
}
}
}
@@ -210,4 +210,45 @@ class SecondLevelCacheSingleTableInheritanceTest extends SecondLevelCacheAbstrac
$this->assertEquals($this->attractions[0]->getName(), $entity->getAttractions()->get(0)->getName());
$this->assertEquals($this->attractions[1]->getName(), $entity->getAttractions()->get(1)->getName());
}
}
public function testQueryCacheShouldBeEvictedOnTimestampUpdate()
{
$this->loadFixturesCountries();
$this->loadFixturesStates();
$this->loadFixturesCities();
$this->loadFixturesAttractions();
$this->_em->clear();
$queryCount = $this->getCurrentQueryCount();
$dql = 'SELECT attraction FROM Doctrine\Tests\Models\Cache\Attraction attraction';
$result1 = $this->_em->createQuery($dql)
->setCacheable(true)
->getResult();
$this->assertCount(count($this->attractions), $result1);
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
$contact = new Beach(
'Botafogo',
$this->_em->find(City::CLASSNAME, $this->cities[1]->getId())
);
$this->_em->persist($contact);
$this->_em->flush();
$this->_em->clear();
$queryCount = $this->getCurrentQueryCount();
$result2 = $this->_em->createQuery($dql)
->setCacheable(true)
->getResult();
$this->assertCount(count($this->attractions) + 1, $result2);
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
foreach ($result2 as $entity) {
$this->assertInstanceOf(Attraction::CLASSNAME, $entity);
}
}
}
@@ -0,0 +1,93 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Tests\OrmFunctionalTestCase;
class DDC3303Test extends OrmFunctionalTestCase
{
protected function setUp()
{
parent::setUp();
$this->_schemaTool->createSchema([$this->_em->getClassMetadata(DDC3303Employee::CLASSNAME)]);
}
/**
* @group 4097
* @group 4277
* @group 5867
*
* When using an embedded field in an inheritance, private properties should also be inherited.
*/
public function testEmbeddedObjectsAreAlsoInherited()
{
$employee = new DDC3303Employee(
'John Doe',
new DDC3303Address('Somewhere', 123, 'Over the rainbow'),
'Doctrine Inc'
);
$this->_em->persist($employee);
$this->_em->flush();
$this->_em->clear();
self::assertEquals($employee, $this->_em->find(DDC3303Employee::CLASSNAME, 'John Doe'));
}
}
/** @MappedSuperclass */
abstract class DDC3303Person
{
/** @Id @GeneratedValue(strategy="NONE") @Column(type="string") @var string */
private $name;
/** @Embedded(class="DDC3303Address") @var DDC3303Address */
private $address;
public function __construct($name, DDC3303Address $address)
{
$this->name = $name;
$this->address = $address;
}
}
/**
* @Embeddable
*/
class DDC3303Address
{
/** @Column(type="string") @var string */
private $street;
/** @Column(type="integer") @var int */
private $number;
/** @Column(type="string") @var string */
private $city;
public function __construct($street, $number, $city)
{
$this->street = $street;
$this->number = $number;
$this->city = $city;
}
}
/**
* @Entity
* @Table(name="ddc3303_employee")
*/
class DDC3303Employee extends DDC3303Person
{
const CLASSNAME = __CLASS__;
/** @Column(type="string") @var string */
private $company;
public function __construct($name, DDC3303Address $address, $company)
{
parent::__construct($name, $address);
$this->company = $company;
}
}
@@ -0,0 +1,30 @@
<?php
/**
* @author Marc Pantel <pantel.m@gmail.com>
*/
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\Tests\ORM\Mapping\YamlMappingDriverTest;
class DDC3711Test extends YamlMappingDriverTest
{
public function testCompositeKeyForJoinTableInManyToManyCreation()
{
$yamlDriver = $this->_loadDriver();
$em = $this->_getTestEntityManager();
$em->getConfiguration()->setMetadataDriverImpl($yamlDriver);
$factory = new \Doctrine\ORM\Mapping\ClassMetadataFactory();
$factory->setEntityManager($em);
$entityA = new ClassMetadata('Doctrine\Tests\Models\DDC3711\DDC3711EntityA');
$entityA = $factory->getMetadataFor('Doctrine\Tests\Models\DDC3711\DDC3711EntityA');
$this->assertEquals(array('link_a_id1' => "id1", 'link_a_id2' => "id2"), $entityA->associationMappings['entityB']['relationToSourceKeyColumns']);
$this->assertEquals(array('link_b_id1' => "id1", 'link_b_id2' => "id2"), $entityA->associationMappings['entityB']['relationToTargetKeyColumns']);
}
}
@@ -0,0 +1,35 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Tests\Models\Cache\Country;
use Doctrine\Tests\ORM\Functional\SecondLevelCacheAbstractTest;
class DDC3967Test extends SecondLevelCacheAbstractTest
{
protected function setUp()
{
parent::setUp();
$this->loadFixturesCountries();
$this->_em->getCache()->evictEntityRegion(Country::CLASSNAME);
$this->_em->clear();
}
public function testIdentifierCachedWithProperType()
{
$country = array_pop($this->countries);
$id = $country->getId();
// First time, loaded from database
$this->_em->find(Country::CLASSNAME, "$id");
$this->_em->clear();
// Second time, loaded from cache
/** @var Country $country */
$country = $this->_em->find(Country::CLASSNAME, "$id");
// Identifier type should be integer
$this->assertSame($country->getId(), $id);
}
}
@@ -0,0 +1,193 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\PersistentCollection;
use Doctrine\Tests\OrmFunctionalTestCase;
/**
* @group GH-5762
*/
class GH5762Test extends OrmFunctionalTestCase
{
protected function setUp()
{
parent::setUp();
$this->_schemaTool->createSchema(array(
$this->_em->getClassMetadata(GH5762Driver::CLASSNAME),
$this->_em->getClassMetadata(GH5762DriverRide::CLASSNAME),
$this->_em->getClassMetadata(GH5762Car::CLASSNAME),
));
}
public function testIssue()
{
$result = $this->fetchData();
self::assertInstanceOf(GH5762Driver::CLASSNAME, $result);
self::assertInstanceOf('Doctrine\ORM\PersistentCollection', $result->driverRides);
self::assertInstanceOf(GH5762DriverRide::CLASSNAME, $result->driverRides->get(0));
self::assertInstanceOf(GH5762Car::CLASSNAME, $result->driverRides->get(0)->car);
$cars = array();
foreach ($result->driverRides as $ride) {
$cars[] = $ride->car->brand;
}
self::assertEquals(count($cars), count(array_unique($cars)));
self::assertContains('BMW', $cars);
self::assertContains('Crysler', $cars);
self::assertContains('Dodge', $cars);
self::assertContains('Mercedes', $cars);
self::assertContains('Volvo', $cars);
}
private function fetchData()
{
$this->createData();
$qb = $this->_em->createQueryBuilder();
$qb->select('d, dr, c')
->from(GH5762Driver::CLASSNAME, 'd')
->leftJoin('d.driverRides', 'dr')
->leftJoin('dr.car', 'c')
->where('d.id = 1');
return $qb->getQuery()->getSingleResult();
}
private function createData()
{
$car1 = new GH5762Car('BMW', '7 Series');
$car2 = new GH5762Car('Crysler', '300');
$car3 = new GH5762Car('Dodge', 'Dart');
$car4 = new GH5762Car('Mercedes', 'C-Class');
$car5 = new GH5762Car('Volvo', 'XC90');
$driver = new GH5762Driver(1, 'John Doe');
$ride1 = new GH5762DriverRide($driver, $car1);
$ride2 = new GH5762DriverRide($driver, $car2);
$ride3 = new GH5762DriverRide($driver, $car3);
$ride4 = new GH5762DriverRide($driver, $car4);
$ride5 = new GH5762DriverRide($driver, $car5);
$this->_em->persist($car1);
$this->_em->persist($car2);
$this->_em->persist($car3);
$this->_em->persist($car4);
$this->_em->persist($car5);
$this->_em->persist($driver);
$this->_em->persist($ride1);
$this->_em->persist($ride2);
$this->_em->persist($ride3);
$this->_em->persist($ride4);
$this->_em->persist($ride5);
$this->_em->flush();
$this->_em->clear();
}
}
/**
* @Entity
* @Table(name="driver")
*/
class GH5762Driver
{
const CLASSNAME = __CLASS__;
/**
* @Id
* @Column(type="integer")
* @GeneratedValue(strategy="NONE")
*/
public $id;
/**
* @Column(type="string", length=255);
*/
public $name;
/**
* @OneToMany(targetEntity="GH5762DriverRide", mappedBy="driver")
*/
public $driverRides;
public function __construct($id, $name)
{
$this->driverRides = new ArrayCollection();
$this->id = $id;
$this->name = $name;
}
}
/**
* @Entity
* @Table(name="driver_ride")
*/
class GH5762DriverRide
{
const CLASSNAME = __CLASS__;
/**
* @Id
* @ManyToOne(targetEntity="GH5762Driver", inversedBy="driverRides")
* @JoinColumn(name="driver_id", referencedColumnName="id")
*/
public $driver;
/**
* @Id
* @ManyToOne(targetEntity="GH5762Car", inversedBy="carRides")
* @JoinColumn(name="car", referencedColumnName="brand")
*/
public $car;
function __construct(GH5762Driver $driver, GH5762Car $car)
{
$this->driver = $driver;
$this->car = $car;
$this->driver->driverRides->add($this);
$this->car->carRides->add($this);
}
}
/**
* @Entity
* @Table(name="car")
*/
class GH5762Car
{
const CLASSNAME = __CLASS__;
/**
* @Id
* @Column(type="string", length=25)
* @GeneratedValue(strategy="NONE")
*/
public $brand;
/**
* @Column(type="string", length=255);
*/
public $model;
/**
* @OneToMany(targetEntity="GH5762DriverRide", mappedBy="car")
*/
public $carRides;
public function __construct($brand, $model)
{
$this->carRides = new ArrayCollection();
$this->brand = $brand;
$this->model = $model;
}
}
@@ -0,0 +1,51 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Tests\Models\Issue5989\Issue5989Employee;
use Doctrine\Tests\Models\Issue5989\Issue5989Manager;
use Doctrine\Tests\Models\Issue5989\Issue5989Person;
/**
* @group issue-5989
*/
class Issue5989Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
public function setUp()
{
$this->useModelSet('issue5989');
parent::setUp();
}
public function testSimpleArrayTypeHydratedCorrectlyInJoinedInheritance()
{
$manager = new Issue5989Manager();
$managerTags = ['tag1', 'tag2'];
$manager->tags = $managerTags;
$this->_em->persist($manager);
$employee = new Issue5989Employee();
$employeeTags =['tag2', 'tag3'];
$employee->tags = $employeeTags;
$this->_em->persist($employee);
$this->_em->flush();
$managerId = $manager->id;
$employeeId = $employee->id;
// clear entity manager so that $repository->find actually fetches them and uses the hydrator
// instead of just returning the existing managed entities
$this->_em->clear();
$repository = $this->_em->getRepository(Issue5989Person::CLASSNAME);
$manager = $repository->find($managerId);
$employee = $repository->find($employeeId);
static::assertEquals($managerTags, $manager->tags);
static::assertEquals($employeeTags, $employee->tags);
}
}
@@ -86,4 +86,34 @@ class SimpleObjectHydratorTest extends HydrationTestCase
$hydrator = new \Doctrine\ORM\Internal\Hydration\SimpleObjectHydrator($this->_em);
$hydrator->hydrateAll($stmt, $rsm);
}
/**
* @group issue-5989
*/
public function testNullValueShouldNotOverwriteFieldWithSameNameInJoinedInheritance()
{
$rsm = new ResultSetMapping;
$rsm->addEntityResult('Doctrine\Tests\Models\Issue5989\Issue5989Person', 'p');
$rsm->addFieldResult('p', 'p__id', 'id');
$rsm->addFieldResult('p', 'm__tags', 'tags', 'Doctrine\Tests\Models\Issue5989\Issue5989Manager');
$rsm->addFieldResult('p', 'e__tags', 'tags', 'Doctrine\Tests\Models\Issue5989\Issue5989Employee');
$rsm->addMetaResult('p', 'discr', 'discr', false, 'string');
$resultSet = array(
array(
'p__id' => '1',
'm__tags' => 'tag1,tag2',
'e__tags' => null,
'discr' => 'manager'
),
);
$expectedEntity = new \Doctrine\Tests\Models\Issue5989\Issue5989Manager();
$expectedEntity->id = 1;
$expectedEntity->tags = ['tag1', 'tag2'];
$stmt = new HydratorMockStatement($resultSet);
$hydrator = new \Doctrine\ORM\Internal\Hydration\SimpleObjectHydrator($this->_em);
$result = $hydrator->hydrateAll($stmt, $rsm);
$this->assertEquals($result[0], $expectedEntity);
}
}
@@ -16,7 +16,7 @@ class AnnotationDriverTest extends AbstractMappingDriverTest
{
$cm = new ClassMetadata('stdClass');
$cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService);
$reader = new \Doctrine\Common\Annotations\AnnotationReader(new \Doctrine\Common\Cache\ArrayCache());
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
$annotationDriver = new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($reader);
$this->setExpectedException('Doctrine\ORM\Mapping\MappingException');
@@ -49,7 +49,7 @@ abstract class AbstractDriverTest extends \PHPUnit_Framework_TestCase
{
$this->setExpectedException(
'Doctrine\Common\Persistence\Mapping\MappingException',
"No mapping file found named '".$this->dir."/Foo".$this->getFileExtension()."' for class 'MyNamespace\MySubnamespace\Entity\Foo'."
"No mapping file found named"
);
$driver = $this->getDriver(array(
@@ -0,0 +1,23 @@
Doctrine\Tests\Models\DDC3711\DDC3711EntityA:
type: entity
table: ddc3711.entityA
id:
id1:
type: int
id2:
type: int
manyToMany:
entityB:
targetEntity: Doctrine\Tests\Models\DDC3711\DDC3711EntityB
joinTable:
name: link
joinColumns:
link_a_id1:
referencedColumnName: id1
link_a_id2:
referencedColumnName: id2
inverseJoinColumns:
link_b_id1:
referencedColumnName: id1
link_b_id2:
referencedColumnName: id2
@@ -0,0 +1,8 @@
Doctrine\Tests\Models\DDC3711\DDC3711EntityB:
type: entity
table: ddc3711.entityB
id:
id1:
type: int
id2:
type: int
@@ -3,7 +3,6 @@
namespace Doctrine\Tests\ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\PersistentCollection;
use Doctrine\Tests\Mocks\ConnectionMock;
use Doctrine\Tests\Mocks\DriverMock;
@@ -83,4 +82,44 @@ class PersistentCollectionTest extends OrmTestCase
$this->collection->next();
$this->assertTrue($this->collection->isInitialized());
}
/**
* @group 6110
*/
public function testRemovingElementsAlsoRemovesKeys()
{
$this->setUpPersistentCollection();
$this->collection->add('dummy');
$this->assertEquals([0], array_keys($this->collection->toArray()));
$this->collection->removeElement('dummy');
$this->assertEquals([], array_keys($this->collection->toArray()));
}
/**
* @group 6110
*/
public function testClearWillAlsoClearKeys()
{
$this->setUpPersistentCollection();
$this->collection->add('dummy');
$this->collection->clear();
$this->assertEquals([], array_keys($this->collection->toArray()));
}
/**
* @group 6110
*/
public function testClearWillAlsoResetKeyPositions()
{
$this->setUpPersistentCollection();
$this->collection->add('dummy');
$this->collection->removeElement('dummy');
$this->collection->clear();
$this->collection->add('dummy');
$this->assertEquals([0], array_keys($this->collection->toArray()));
}
}
@@ -2,15 +2,17 @@
namespace Doctrine\Tests\ORM\Proxy;
use Doctrine\Common\Persistence\Mapping\RuntimeReflectionService;
use Doctrine\Common\Proxy\AbstractProxyFactory;
use Doctrine\ORM\EntityNotFoundException;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\Common\Proxy\ProxyGenerator;
use Doctrine\Tests\Mocks\ConnectionMock;
use Doctrine\Tests\Mocks\DriverMock;
use Doctrine\Tests\Mocks\EntityManagerMock;
use Doctrine\Tests\Mocks\UnitOfWorkMock;
use Doctrine\Tests\Mocks\DriverMock;
use Doctrine\Common\Proxy\AbstractProxyFactory;
use Doctrine\Tests\OrmTestCase;
use Doctrine\Tests\Models\Company\CompanyEmployee;
/**
* Test the proxy generator. Its work is generating on-the-fly subclasses of a given model, which implement the Proxy pattern.
@@ -62,9 +64,9 @@ class ProxyFactoryTest extends \Doctrine\Tests\OrmTestCase
$persister
->expects($this->atLeastOnce())
->method('load')
->with($this->equalTo($identifier), $this->isInstanceOf($proxyClass))
->will($this->returnValue(new \stdClass()));
->method('load')
->with($this->equalTo($identifier), $this->isInstanceOf($proxyClass))
->will($this->returnValue(new \stdClass()));
$proxy->getDescription();
}
@@ -75,7 +77,7 @@ class ProxyFactoryTest extends \Doctrine\Tests\OrmTestCase
public function testSkipAbstractClassesOnGeneration()
{
$cm = new ClassMetadata(__NAMESPACE__ . '\\AbstractClass');
$cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService);
$cm->initializeReflection(new RuntimeReflectionService);
$this->assertNotNull($cm->reflClass);
$num = $this->proxyFactory->generateProxyClasses(array($cm));
@@ -136,6 +138,45 @@ class ProxyFactoryTest extends \Doctrine\Tests\OrmTestCase
$this->assertInstanceOf('Closure', $proxy->__getInitializer(), 'The initializer wasn\'t removed');
$this->assertInstanceOf('Closure', $proxy->__getCloner(), 'The cloner wasn\'t removed');
}
public function testProxyClonesParentFields()
{
$companyEmployee = new CompanyEmployee();
$companyEmployee->setSalary(1000); // A property on the CompanyEmployee
$companyEmployee->setName("Bob"); // A property on the parent class, CompanyPerson
// Set the id of the CompanyEmployee (which is in the parent CompanyPerson)
$class = new \ReflectionClass('Doctrine\Tests\Models\Company\CompanyPerson');
$property = $class->getProperty('id');
$property->setAccessible(true);
$property->setValue($companyEmployee, 42);
$classMetaData = $this->emMock->getClassMetadata('Doctrine\Tests\Models\Company\CompanyEmployee');
$persister = $this->getMock('Doctrine\ORM\Persisters\Entity\BasicEntityPersister', array('load', 'getClassMetadata'), array(), '', false);
$this->uowMock->setEntityPersister('Doctrine\Tests\Models\Company\CompanyEmployee', $persister);
/* @var $proxy \Doctrine\Common\Proxy\Proxy */
$proxy = $this->proxyFactory->getProxy('Doctrine\Tests\Models\Company\CompanyEmployee', array('id' => 42));
$persister
->expects($this->atLeastOnce())
->method('load')
->will($this->returnValue($companyEmployee));
$persister
->expects($this->atLeastOnce())
->method('getClassMetadata')
->will($this->returnValue($classMetaData));
$cloned = clone $proxy;
$this->assertEquals(42, $cloned->getId(), "Expected the Id to be cloned");
$this->assertEquals(1000, $cloned->getSalary(), "Expect properties on the CompanyEmployee class to be cloned");
$this->assertEquals("Bob", $cloned->getName(), "Expect properties on the CompanyPerson class to be cloned");
}
}
abstract class AbstractClass
@@ -2242,6 +2242,27 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
'SELECT COUNT(c0_.name) AS sclr_0 FROM cms_users c0_ HAVING sclr_0 IS NULL'
);
}
/**
* GitHub issue #4764: https://github.com/doctrine/doctrine2/issues/4764
* @group DDC-3907
* @dataProvider mathematicOperatorsProvider
*/
public function testHavingRegressionUsingVariableWithMathOperatorsExpression($operator)
{
$this->assertSqlGeneration(
'SELECT COUNT(u.name) AS countName FROM Doctrine\Tests\Models\CMS\CmsUser u HAVING 1 ' . $operator . ' countName > 0',
'SELECT COUNT(c0_.name) AS sclr_0 FROM cms_users c0_ HAVING 1 ' . $operator . ' sclr_0 > 0'
);
}
/**
* @return array
*/
public function mathematicOperatorsProvider()
{
return [['+'], ['-'], ['*'], ['/']];
}
}
class MyAbsFunction extends \Doctrine\ORM\Query\AST\Functions\FunctionNode
@@ -85,6 +85,6 @@ class RunDqlCommandTest extends OrmFunctionalTestCase
))
);
$this->assertStringMatchesFormat('string%sSELECT %a', $this->tester->getDisplay());
$this->assertStringMatchesFormat('%Astring%sSELECT %a', $this->tester->getDisplay());
}
}
@@ -57,6 +57,18 @@ $metadata->mapOneToOne(array(
'orphanRemoval' => true,
'fetch' => ClassMetadataInfo::FETCH_EAGER,
));
$metadata->mapOneToOne(array(
'fieldName' => 'cart',
'targetEntity' => 'Doctrine\\Tests\\ORM\\Tools\\Export\\Cart',
'mappedBy' => 'user',
'cascade' =>
array(
0 => 'persist',
),
'inversedBy' => NULL,
'orphanRemoval' => false,
'fetch' => ClassMetadataInfo::FETCH_EAGER,
));
$metadata->mapOneToMany(array(
'fieldName' => 'phonenumbers',
'targetEntity' => 'Doctrine\\Tests\\ORM\\Tools\\Export\\Phonenumber',
@@ -30,6 +30,12 @@
<join-column name="address_id" referenced-column-name="id" on-delete="CASCADE" on-update="CASCADE"/>
</one-to-one>
<one-to-one field="cart" target-entity="Doctrine\Tests\ORM\Tools\Export\Cart" mapped-by="user">
<cascade>
<cascade-remove/>
</cascade>
</one-to-one>
<many-to-one field="mainGroup" target-entity="Doctrine\Tests\ORM\Tools\Export\Group" />
<one-to-many field="phonenumbers" target-entity="Doctrine\Tests\ORM\Tools\Export\Phonenumber" mapped-by="user" orphan-removal="true" fetch="LAZY">
@@ -30,6 +30,10 @@ Doctrine\Tests\ORM\Tools\Export\User:
inversedBy: user
orphanRemoval: true
fetch: EAGER
cart:
targetEntity: Doctrine\Tests\ORM\Tools\Export\Cart
mappedBy: user
cascade: [ remove ]
manyToOne:
mainGroup:
targetEntity: Doctrine\Tests\ORM\Tools\Export\Group
@@ -350,7 +350,26 @@ ORDER BY b.id DESC'
$query->getSQL()
);
}
/**
* This tests ordering by property that has the 'declared' field.
*/
public function testLimitSubqueryOrderByFieldFromMappedSuperclass()
{
$this->entityManager->getConnection()->setDatabasePlatform(new MySqlPlatform());
// now use the third one in query
$query = $this->entityManager->createQuery(
'SELECT b FROM Doctrine\Tests\ORM\Tools\Pagination\Banner b ORDER BY b.id DESC'
);
$query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'Doctrine\ORM\Tools\Pagination\LimitSubqueryOutputWalker');
$this->assertEquals(
'SELECT DISTINCT id_0 FROM (SELECT b0_.id AS id_0, b0_.name AS name_1 FROM Banner b0_) dctrn_result ORDER BY id_0 DESC',
$query->getSQL()
);
}
/**
* Tests order by on a subselect expression (mysql).
*/
@@ -168,4 +168,22 @@ class Avatar
public $image_width;
/** @Column(type="string", length=255) */
public $image_alt_desc;
}
}
/** @MappedSuperclass */
abstract class Identified
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
public function getId()
{
return $this->id;
}
}
/** @Entity */
class Banner extends Identified
{
/** @Column(type="string") */
public $name;
}
@@ -9,19 +9,19 @@ use Doctrine\ORM\Events;
class ResolveTargetEntityListenerTest extends \Doctrine\Tests\OrmTestCase
{
/**
* @var EntityManager
* @var \Doctrine\ORM\EntityManager
*/
private $em = null;
private $em;
/**
* @var ResolveTargetEntityListener
*/
private $listener = null;
private $listener;
/**
* @var ClassMetadataFactory
*/
private $factory = null;
private $factory;
public function setUp()
{
@@ -106,6 +106,32 @@ class ResolveTargetEntityListenerTest extends \Doctrine\Tests\OrmTestCase
$this->assertSame('Doctrine\Tests\ORM\Tools\TargetEntity', $meta['targetEntity']);
$this->assertEquals(array('resolvetargetentity_id', 'targetinterface_id'), $meta['joinTableColumns']);
}
/**
* @group 1572
* @group functional
*
* @coversNothing
*/
public function testDoesResolveTargetEntitiesInDQLAlsoWithInterfaces()
{
$evm = $this->em->getEventManager();
$this->listener->addResolveTargetEntity(
'Doctrine\Tests\ORM\Tools\ResolveTargetInterface',
'Doctrine\Tests\ORM\Tools\ResolveTargetEntity',
array()
);
$evm->addEventSubscriber($this->listener);
$this->assertStringMatchesFormat(
'SELECT%AFROM ResolveTargetEntity%A',
$this
->em
->createQuery('SELECT f FROM Doctrine\Tests\ORM\Tools\ResolveTargetInterface f')
->getSQL()
);
}
}
interface ResolveTargetInterface
@@ -116,6 +116,28 @@ class SchemaToolTest extends \Doctrine\Tests\OrmTestCase
$this->assertSame(array(), $customSchemaOptions);
}
/**
* @group DDC-3671
*/
public function testSchemaHasProperIndexesFromUniqueConstraintAnnotation()
{
$em = $this->_getTestEntityManager();
$schemaTool = new SchemaTool($em);
$classes = [
$em->getClassMetadata(__NAMESPACE__ . '\\UniqueConstraintAnnotationModel'),
];
$schema = $schemaTool->getSchemaFromMetadata($classes);
$this->assertTrue($schema->hasTable('unique_constraint_annotation_table'));
$table = $schema->getTable('unique_constraint_annotation_table');
$this->assertEquals(2, count($table->getIndexes()));
$this->assertTrue($table->hasIndex('primary'));
$this->assertTrue($table->hasIndex('uniq_hash'));
}
}
/**
@@ -148,3 +170,20 @@ class GenerateSchemaEventListener
$this->schemaCalled = true;
}
}
/**
* @Entity
* @Table(name="unique_constraint_annotation_table", uniqueConstraints={
* @UniqueConstraint(name="uniq_hash", columns={"hash"})
* })
*/
class UniqueConstraintAnnotationModel
{
/** @Id @Column */
private $id;
/**
* @Column(name="hash", type="string", length=8, nullable=false, unique=true)
*/
private $hash;
}
+208 -6
View File
@@ -3,8 +3,11 @@
namespace Doctrine\Tests\ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\EventManager;
use Doctrine\Common\NotifyPropertyChanged;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use Doctrine\Common\PropertyChangedListener;
use Doctrine\ORM\Events;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\UnitOfWork;
use Doctrine\Tests\Mocks\ConnectionMock;
@@ -12,8 +15,13 @@ use Doctrine\Tests\Mocks\DriverMock;
use Doctrine\Tests\Mocks\EntityManagerMock;
use Doctrine\Tests\Mocks\EntityPersisterMock;
use Doctrine\Tests\Mocks\UnitOfWorkMock;
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\Models\Forum\ForumAvatar;
use Doctrine\Tests\Models\Forum\ForumUser;
use Doctrine\Tests\Models\GeoNames\City;
use Doctrine\Tests\Models\GeoNames\Country;
use Doctrine\Tests\OrmTestCase;
use stdClass;
/**
@@ -42,18 +50,22 @@ class UnitOfWorkTest extends \Doctrine\Tests\OrmTestCase
*/
private $_emMock;
protected function setUp() {
/**
* @var EventManager|\PHPUnit_Framework_MockObject_MockObject
*/
private $eventManager;
protected function setUp()
{
parent::setUp();
$this->_connectionMock = new ConnectionMock(array(), new DriverMock());
$this->_emMock = EntityManagerMock::create($this->_connectionMock);
$this->_connectionMock = new ConnectionMock([], new DriverMock());
$this->eventManager = $this->getMockBuilder('Doctrine\Common\EventManager')->getMock();
$this->_emMock = EntityManagerMock::create($this->_connectionMock, null, $this->eventManager);
// SUT
$this->_unitOfWork = new UnitOfWorkMock($this->_emMock);
$this->_emMock->setUnitOfWork($this->_unitOfWork);
}
protected function tearDown() {
}
public function testRegisterRemovedOnNewEntityIsIgnored()
{
$user = new ForumUser();
@@ -321,6 +333,28 @@ class UnitOfWorkTest extends \Doctrine\Tests\OrmTestCase
$this->assertTrue($this->_unitOfWork->isInIdentityMap($entity));
}
/**
* @group 5849
* @group 5850
*/
public function testPersistedEntityAndClearManager()
{
$entity1 = new City(123, 'London');
$entity2 = new Country(456, 'United Kingdom');
$this->_unitOfWork->persist($entity1);
$this->assertTrue($this->_unitOfWork->isInIdentityMap($entity1));
$this->_unitOfWork->persist($entity2);
$this->assertTrue($this->_unitOfWork->isInIdentityMap($entity2));
$this->_unitOfWork->clear(Country::CLASSNAME);
$this->assertTrue($this->_unitOfWork->isInIdentityMap($entity1));
$this->assertFalse($this->_unitOfWork->isInIdentityMap($entity2));
$this->assertTrue($this->_unitOfWork->isScheduledForInsert($entity1));
$this->assertFalse($this->_unitOfWork->isScheduledForInsert($entity2));
}
/**
* Data Provider
*
@@ -337,6 +371,116 @@ class UnitOfWorkTest extends \Doctrine\Tests\OrmTestCase
[new ArrayCollection()],
];
}
/**
* @group 5689
* @group 1465
*/
public function testObjectHashesOfMergedEntitiesAreNotUsedInOriginalEntityDataMap()
{
$user = new CmsUser();
$user->name = 'ocramius';
$mergedUser = $this->_unitOfWork->merge($user);
self::assertSame([], $this->_unitOfWork->getOriginalEntityData($user), 'No original data was stored');
self::assertSame([], $this->_unitOfWork->getOriginalEntityData($mergedUser), 'No original data was stored');
$user = null;
$mergedUser = null;
// force garbage collection of $user (frees the used object hashes, which may be recycled)
gc_collect_cycles();
$newUser = new CmsUser();
$newUser->name = 'ocramius';
$this->_unitOfWork->persist($newUser);
self::assertSame([], $this->_unitOfWork->getOriginalEntityData($newUser), 'No original data was stored');
}
/**
* @group DDC-1955
* @group 5570
* @group 6174
*/
public function testMergeWithNewEntityWillPersistItAndTriggerPrePersistListenersWithMergedEntityData()
{
$entity = new EntityWithRandomlyGeneratedField();
$generatedFieldValue = $entity->generatedField;
$this
->eventManager
->expects(self::any())
->method('hasListeners')
->willReturnCallback(function ($eventName) {
return $eventName === Events::prePersist;
});
$this
->eventManager
->expects(self::once())
->method('dispatchEvent')
->with(
self::anything(),
self::callback(function (LifecycleEventArgs $args) use ($entity, $generatedFieldValue) {
/* @var $object EntityWithRandomlyGeneratedField */
$object = $args->getObject();
self::assertInstanceOf('Doctrine\Tests\ORM\EntityWithRandomlyGeneratedField', $object);
self::assertNotSame($entity, $object);
self::assertSame($generatedFieldValue, $object->generatedField);
return true;
})
);
/* @var $object EntityWithRandomlyGeneratedField */
$object = $this->_unitOfWork->merge($entity);
self::assertNotSame($object, $entity);
self::assertInstanceOf('Doctrine\Tests\ORM\EntityWithRandomlyGeneratedField', $object);
self::assertSame($object->generatedField, $entity->generatedField);
}
/**
* @group DDC-1955
* @group 5570
* @group 6174
*/
public function testMergeWithExistingEntityWillNotPersistItNorTriggerPrePersistListeners()
{
$persistedEntity = new EntityWithRandomlyGeneratedField();
$mergedEntity = new EntityWithRandomlyGeneratedField();
$mergedEntity->id = $persistedEntity->id;
$mergedEntity->generatedField = mt_rand(
$persistedEntity->generatedField + 1,
$persistedEntity->generatedField + 1000
);
$this
->eventManager
->expects(self::any())
->method('hasListeners')
->willReturnCallback(function ($eventName) {
return $eventName === Events::prePersist;
});
$this->eventManager->expects(self::never())->method('dispatchEvent');
$this->_unitOfWork->registerManaged(
$persistedEntity,
['id' => $persistedEntity->id],
['generatedField' => $persistedEntity->generatedField]
);
/* @var $merged EntityWithRandomlyGeneratedField */
$merged = $this->_unitOfWork->merge($mergedEntity);
self::assertSame($merged, $persistedEntity);
self::assertSame($persistedEntity->generatedField, $mergedEntity->generatedField);
}
}
/**
@@ -443,3 +587,61 @@ class VersionedAssignedIdentifierEntity
*/
public $version;
}
/** @Entity */
class EntityWithStringIdentifier
{
/**
* @Id @Column(type="string")
*
* @var string|null
*/
public $id;
}
/** @Entity */
class EntityWithBooleanIdentifier
{
/**
* @Id @Column(type="boolean")
*
* @var bool|null
*/
public $id;
}
/** @Entity */
class EntityWithCompositeStringIdentifier
{
/**
* @Id @Column(type="string")
*
* @var string|null
*/
public $id1;
/**
* @Id @Column(type="string")
*
* @var string|null
*/
public $id2;
}
/** @Entity */
class EntityWithRandomlyGeneratedField
{
/** @Id @Column(type="string") */
public $id;
/**
* @Column(type="integer")
*/
public $generatedField;
public function __construct()
{
$this->id = uniqid('id', true);
$this->generatedField = mt_rand(0, 100000);
}
}
@@ -280,6 +280,15 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
'Doctrine\Tests\Models\Pagination\User',
'Doctrine\Tests\Models\Pagination\User1',
),
'versioned_many_to_one' => array(
'Doctrine\Tests\Models\VersionedManyToOne\Category',
'Doctrine\Tests\Models\VersionedManyToOne\Article',
),
'issue5989' => array(
'Doctrine\Tests\Models\Issue5989\Issue5989Person',
'Doctrine\Tests\Models\Issue5989\Issue5989Employee',
'Doctrine\Tests\Models\Issue5989\Issue5989Manager',
),
);
/**
@@ -535,6 +544,17 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
$conn->executeUpdate('DELETE FROM pagination_user');
}
if (isset($this->_usedModelSets['versioned_many_to_one'])) {
$conn->executeUpdate('DELETE FROM versioned_many_to_one_article');
$conn->executeUpdate('DELETE FROM versioned_many_to_one_category');
}
if (isset($this->_usedModelSets['issue5989'])) {
$conn->executeUpdate('DELETE FROM issue5989_persons');
$conn->executeUpdate('DELETE FROM issue5989_employees');
$conn->executeUpdate('DELETE FROM issue5989_managers');
}
$this->_em->clear();
}