Compare commits

..

96 Commits

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

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

View File

@@ -5,8 +5,6 @@ php:
- 5.5
- 5.6
- 7.0
- hhvm
- hhvm-nightly
env:
- DB=mysql
@@ -15,7 +13,7 @@ env:
before_script:
- if [[ $TRAVIS_PHP_VERSION = '5.6' && $DB = 'sqlite' ]]; then PHPUNIT_FLAGS="--coverage-clover ./build/logs/clover.xml"; else PHPUNIT_FLAGS=""; fi
- if [[ $TRAVIS_PHP_VERSION != '5.6' && $TRAVIS_PHP_VERSION != 'hhvm' && $TRAVIS_PHP_VERSION != 'hhvm-nightly' && $TRAVIS_PHP_VERSION != '7.0' ]]; then phpenv config-rm xdebug.ini; fi
- if [[ $PHPUNIT_FLAGS == "" ]]; then phpenv config-rm xdebug.ini; fi
- composer self-update
- composer install --prefer-source --dev
@@ -24,15 +22,10 @@ script:
- ENABLE_SECOND_LEVEL_CACHE=1 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml --exclude-group performance,non-cacheable,locking_functional
matrix:
exclude:
- php: hhvm
env: DB=pgsql # driver currently unsupported by HHVM
- php: hhvm
env: DB=mysqli # driver currently unsupported by HHVM
- php: hhvm-nightly
env: DB=pgsql # driver currently unsupported by HHVM
- php: hhvm-nightly
env: DB=mysqli # driver currently unsupported by HHVM
fast_finish: true
allow_failures:
- php: 7.0
- php: hhvm-nightly # hhvm-nightly currently chokes on composer installation
cache:
directories:
- $HOME/.composer/cache

View File

@@ -16,9 +16,9 @@
"php": ">=5.4",
"ext-pdo": "*",
"doctrine/collections": "~1.2",
"doctrine/dbal": ">=2.5-dev,<2.6-dev",
"doctrine/dbal": ">=2.5-dev,<2.7-dev",
"doctrine/instantiator": "~1.0.1",
"doctrine/common": ">=2.5-dev,<2.7-dev",
"doctrine/common": ">=2.5-dev,<2.9-dev",
"doctrine/cache": "~1.4",
"symfony/console": "~2.5|~3.0"
},

View File

@@ -1038,7 +1038,7 @@ abstract class AbstractQuery
$metadata = $this->_em->getClassMetadata($entityName);
return new Cache\TimestampCacheKey($metadata->getTableName());
return new Cache\TimestampCacheKey($metadata->rootEntityName);
}
/**

View File

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

View File

@@ -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);
}
/**

View File

@@ -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;
}
/**

View File

@@ -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;
}
/**

View File

@@ -30,7 +30,7 @@ use Doctrine\Common\Persistence\Event\LoadClassMetadataEventArgs as BaseLoadClas
* Note: method annotations are used instead of method overrides (due to BC policy)
*
* @method __construct(\Doctrine\ORM\Mapping\ClassMetadata $classMetadata, \Doctrine\ORM\EntityManager $objectManager)
* @method \Doctrine\ORM\EntityManager getClassMetadata()
* @method \Doctrine\ORM\Mapping\ClassMetadata getClassMetadata()
*/
class LoadClassMetadataEventArgs extends BaseLoadClassMetadataEventArgs
{

View File

@@ -210,6 +210,9 @@ abstract class AbstractHydrator
$this->_rsm = null;
$this->_cache = array();
$this->_metadataCache = array();
$evm = $this->_em->getEventManager();
$evm->removeEventListener(array(Events::onClear), $this);
}
/**

View File

@@ -360,8 +360,8 @@ class ObjectHydrator extends AbstractHydrator
// Get a reference to the parent object to which the joined element belongs.
if ($this->_rsm->isMixed && isset($this->rootAliases[$parentAlias])) {
$first = reset($this->resultPointers);
$parentObject = $first[key($first)];
$objectClass = $this->resultPointers[$parentAlias];
$parentObject = $objectClass[key($objectClass)];
} else if (isset($this->resultPointers[$parentAlias])) {
$parentObject = $this->resultPointers[$parentAlias];
} else {

View File

@@ -19,7 +19,6 @@
namespace Doctrine\ORM;
use Closure;
use Doctrine\Common\Collections\AbstractLazyCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
@@ -375,11 +374,7 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
$persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
if ($persister->removeElement($this, $element)) {
return $element;
}
return null;
return $persister->removeElement($this, $element);
}
$removed = parent::removeElement($element);
@@ -536,6 +531,8 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
public function clear()
{
if ($this->initialized && $this->isEmpty()) {
$this->collection->clear();
return;
}
@@ -687,21 +684,41 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
protected function doInitialize()
{
// Has NEW objects added through add(). Remember them.
$newObjects = array();
$newlyAddedDirtyObjects = array();
if ($this->isDirty) {
$newObjects = $this->collection->toArray();
$newlyAddedDirtyObjects = $this->collection->toArray();
}
$this->collection->clear();
$this->em->getUnitOfWork()->loadCollection($this);
$this->takeSnapshot();
// Reattach NEW objects added through add(), if any.
if ($newObjects) {
foreach ($newObjects as $obj) {
$this->collection->add($obj);
}
if ($newlyAddedDirtyObjects) {
$this->restoreNewObjectsInDirtyCollection($newlyAddedDirtyObjects);
}
}
/**
* @param object[] $newObjects
*
* @return void
*
* Note: the only reason why this entire looping/complexity is performed via `spl_object_hash`
* is because we want to prevent using `array_udiff()`, which is likely to cause very
* high overhead (complexity of O(n^2)). `array_diff_key()` performs the operation in
* core, which is faster than using a callback for comparisons
*/
private function restoreNewObjectsInDirtyCollection(array $newObjects)
{
$loadedObjects = $this->collection->toArray();
$newObjectsByOid = \array_combine(\array_map('spl_object_hash', $newObjects), $newObjects);
$loadedObjectsByOid = \array_combine(\array_map('spl_object_hash', $loadedObjects), $loadedObjects);
$newObjectsThatWereNotLoaded = \array_diff_key($newObjectsByOid, $loadedObjectsByOid);
if ($newObjectsThatWereNotLoaded) {
// Reattach NEW objects added through add(), if any.
\array_walk($newObjectsThatWereNotLoaded, [$this->collection, 'add']);
$this->isDirty = true;
}

View File

@@ -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));
}
}

View File

@@ -110,7 +110,7 @@ class ResultSetMappingBuilder extends ResultSetMapping
* @param string $class The class name of the joined entity.
* @param string $alias The unique alias to use for the joined entity.
* @param string $parentAlias The alias of the entity result that is the parent of this joined result.
* @param object $relation The association field that connects the parent entity result
* @param string $relation The association field that connects the parent entity result
* with the joined entity result.
* @param array $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName).
* @param int|null $renameMode One of the COLUMN_RENAMING_* constants or array for BC reasons (CUSTOM).

View File

@@ -870,6 +870,19 @@ class SqlWalker implements TreeWalker
* @return string
*/
public function walkRangeVariableDeclaration($rangeVariableDeclaration)
{
return $this->generateRangeVariableDeclarationSQL($rangeVariableDeclaration, false);
}
/**
* Generate appropriate SQL for RangeVariableDeclaration AST node
*
* @param AST\RangeVariableDeclaration $rangeVariableDeclaration
* @param bool $buildNestedJoins
*
* @return string
*/
private function generateRangeVariableDeclarationSQL($rangeVariableDeclaration, $buildNestedJoins)
{
$class = $this->em->getClassMetadata($rangeVariableDeclaration->abstractSchemaName);
$dqlAlias = $rangeVariableDeclaration->aliasIdentificationVariable;
@@ -885,7 +898,11 @@ class SqlWalker implements TreeWalker
);
if ($class->isInheritanceTypeJoined()) {
$sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
if ($buildNestedJoins) {
$sql = '(' . $sql . $this->_generateClassTableInheritanceJoins($class, $dqlAlias) . ')';
} else {
$sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
}
}
return $sql;
@@ -1121,16 +1138,17 @@ class SqlWalker implements TreeWalker
: ' INNER JOIN ';
switch (true) {
case ($joinDeclaration instanceof \Doctrine\ORM\Query\AST\RangeVariableDeclaration):
case ($joinDeclaration instanceof AST\RangeVariableDeclaration):
$class = $this->em->getClassMetadata($joinDeclaration->abstractSchemaName);
$dqlAlias = $joinDeclaration->aliasIdentificationVariable;
$tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
$condition = '(' . $this->walkConditionalExpression($join->conditionalExpression) . ')';
$condExprConjunction = ($class->isInheritanceTypeJoined() && $joinType != AST\Join::JOIN_TYPE_LEFT && $joinType != AST\Join::JOIN_TYPE_LEFTOUTER)
$isUnconditionalJoin = empty($condition);
$condExprConjunction = ($class->isInheritanceTypeJoined() && $joinType != AST\Join::JOIN_TYPE_LEFT && $joinType != AST\Join::JOIN_TYPE_LEFTOUTER && $isUnconditionalJoin)
? ' AND '
: ' ON ';
$sql .= $this->walkRangeVariableDeclaration($joinDeclaration);
$sql .= $this->generateRangeVariableDeclarationSQL($joinDeclaration, !$isUnconditionalJoin);
$conditions = array($condition);
@@ -1151,7 +1169,7 @@ class SqlWalker implements TreeWalker
$sql .= $condExprConjunction . implode(' AND ', $conditions);
break;
case ($joinDeclaration instanceof \Doctrine\ORM\Query\AST\JoinAssociationDeclaration):
case ($joinDeclaration instanceof AST\JoinAssociationDeclaration):
$sql .= $this->walkJoinAssociationDeclaration($joinDeclaration, $joinType, $join->conditionalExpression);
break;
}

View File

@@ -84,10 +84,6 @@ class MetadataFilter extends \FilterIterator implements \Countable
);
}
if ($pregResult === 0) {
return false;
}
if ($pregResult) {
return true;
}

View File

@@ -85,6 +85,14 @@ class CountOutputWalker extends SqlWalker
$sql = parent::walkSelectStatement($AST);
if ($AST->groupByClause) {
return sprintf(
'SELECT %s AS dctrn_count FROM (%s) dctrn_table',
$this->platform->getCountExpression('*'),
$sql
);
}
// Find out the SQL alias of the identifier column of the root entity
// It may be possible to make this work with multiple root entities but that
// would probably require issuing multiple queries or doing a UNION SELECT

View File

@@ -345,7 +345,7 @@ class SchemaTool
$discrColumn = $class->discriminatorColumn;
if ( ! isset($discrColumn['type']) ||
(strtolower($discrColumn['type']) == 'string' && $discrColumn['length'] === null)
(strtolower($discrColumn['type']) == 'string' && ! isset($discrColumn['length']))
) {
$discrColumn['type'] = 'string';
$discrColumn['length'] = 255;

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 && $this->isLoaded($managedCopy) && $this->isLoaded($entity)) {
$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 ($this->isLoaded($entity)) {
if ($managedCopy instanceof Proxy && ! $managedCopy->__isInitialized()) {
$managedCopy->__load();
}
$this->mergeEntityStateIntoManagedCopy($entity, $managedCopy);
}
if ($class->isChangeTrackingDeferredExplicit()) {
$this->scheduleForDirtyCheck($entity);
}
@@ -1908,6 +1890,33 @@ 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
*
@@ -3360,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) {

View File

@@ -36,7 +36,7 @@ class Version
/**
* Current Doctrine Version
*/
const VERSION = '2.5.5-DEV';
const VERSION = '2.5.8';
/**
* Compares a Doctrine version with the current one.

View File

@@ -33,7 +33,7 @@ class State
protected $country;
/**
* @Cache
* @Cache("NONSTRICT_READ_WRITE")
* @OneToMany(targetEntity="City", mappedBy="state")
*/
protected $cities;

View File

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

View File

@@ -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();
}
}

View File

@@ -636,11 +636,13 @@ class ExtraLazyCollectionTest extends OrmFunctionalTestCase
$group = $this->_em->find('Doctrine\Tests\Models\CMS\CmsGroup', $this->groupId);
$queryCount = $this->getCurrentQueryCount();
$user->groups->removeElement($group);
$this->assertTrue($user->groups->removeElement($group));
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount(), "Removing a persisted entity should cause one query to be executed.");
$this->assertFalse($user->groups->isInitialized(), "Post-Condition: Collection is not initialized.");
$this->assertFalse($user->groups->removeElement($group), "Removing an already removed element returns false");
// Test Many to Many removal with Entity state as new
$group = new \Doctrine\Tests\Models\CMS\CmsGroup();
$group->name = "A New group!";

View File

@@ -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);
}
}
}

View File

@@ -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();

View File

@@ -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();

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
/**
* @group DDC-3146
* @author Emiel Nijpels <emiel@silverstreet.com>
*/
class DDC3146Test extends \Doctrine\Tests\OrmFunctionalTestCase
{
/**
* Verify that the number of added events to the event listener from the abstract hydrator class is equal to the number of removed events
*/
public function testEventListeners()
{
// Create mock connection to be returned from the entity manager interface
$mockConnection = $this->getMockBuilder('Doctrine\DBAL\Connection')->disableOriginalConstructor()->getMock();
$mockEntityManagerInterface = $this->getMockBuilder('Doctrine\ORM\EntityManagerInterface')->disableOriginalConstructor()->getMock();
$mockEntityManagerInterface->expects($this->any())->method('getConnection')->will($this->returnValue($mockConnection));
// Create mock event manager to be returned from the entity manager interface
$mockEventManager = $this->getMockBuilder('Doctrine\Common\EventManager')->disableOriginalConstructor()->getMock();
$mockEntityManagerInterface->expects($this->any())->method('getEventManager')->will($this->returnValue($mockEventManager));
// Create mock statement and result mapping
$mockStatement = $this->getMockBuilder('Doctrine\DBAL\Driver\Statement')->disableOriginalConstructor()->getMock();
$mockStatement->expects($this->once())->method('fetch')->will($this->returnValue(false));
$mockResultMapping = $this->getMockBuilder('Doctrine\ORM\Query\ResultSetMapping')->disableOriginalConstructor()->getMock();
// Create mock abstract hydrator
$mockAbstractHydrator = $this->getMockBuilder('Doctrine\ORM\Internal\Hydration\AbstractHydrator')
->setConstructorArgs(array($mockEntityManagerInterface))
->setMethods(array('hydrateAllData'))
->getMock();
// Increase counter every time the event listener is added and decrease the counter every time the event listener is removed
$eventCounter = 0;
$mockEventManager->expects($this->any())
->method('addEventListener')
->will(
$this->returnCallback(
function () use (&$eventCounter) {
$eventCounter++;
}
)
);
$mockEventManager->expects($this->any())
->method('removeEventListener')
->will(
$this->returnCallback(
function () use (&$eventCounter) {
$eventCounter--;
}
)
);
// Create iterable result
$iterableResult = $mockAbstractHydrator->iterate($mockStatement, $mockResultMapping, array());
$iterableResult->next();
// Number of added events listeners should be equal or less than the number of removed events
$this->assertLessThanOrEqual(0, $eventCounter, 'More events added to the event listener than removed; this can create a memory leak when references are not cleaned up');
}
}

View File

@@ -0,0 +1,152 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Tests\OrmFunctionalTestCase;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Tests\Mocks\HydratorMockStatement;
final class GH6362Test extends OrmFunctionalTestCase
{
protected function setUp()
{
parent::setUp();
$this->_schemaTool->createSchema(
[
$this->_em->getClassMetadata(GH6362Start::CLASSNAME),
$this->_em->getClassMetadata(GH6362Base::CLASSNAME),
$this->_em->getClassMetadata(GH6362Child::CLASSNAME),
$this->_em->getClassMetadata(GH6362Join::CLASSNAME),
]
);
}
/**
* @group 6362
*
* SELECT a as base, b, c, d
* FROM Start a
* LEFT JOIN a.bases b
* LEFT JOIN Child c WITH b.id = c.id
* LEFT JOIN c.joins d
*/
public function testInheritanceJoinAlias()
{
$rsm = new ResultSetMapping;
$rsm->addEntityResult(GH6362Start::CLASSNAME, 'a', 'base');
$rsm->addJoinedEntityResult(GH6362Base::CLASSNAME, 'b', 'a', 'bases');
$rsm->addEntityResult(GH6362Child::CLASSNAME, 'c');
$rsm->addJoinedEntityResult(GH6362Join::CLASSNAME, 'd', 'c', 'joins');
$rsm->addFieldResult('a', 'id_0', 'id');
$rsm->addFieldResult('b', 'id_1', 'id');
$rsm->addFieldResult('c', 'id_2', 'id');
$rsm->addFieldResult('d', 'id_3', 'id');
$rsm->addMetaResult('a', 'bases_id_4', 'bases_id', false, 'integer');
$rsm->addMetaResult('b', 'type_5', 'type');
$rsm->addMetaResult('c', 'type_6', 'type');
$rsm->addMetaResult('d', 'child_id_7', 'child_id', false, 'integer');
$rsm->setDiscriminatorColumn('b', 'type_5');
$rsm->setDiscriminatorColumn('c', 'type_6');
$resultSet = [
[
'id_0' => '1',
'id_1' => '1',
'id_2' => '1',
'id_3' => '1',
'bases_id_4' => '1',
'type_5' => 'child',
'type_6' => 'child',
'child_id_7' => '1',
],
];
$stmt = new HydratorMockStatement($resultSet);
$hydrator = new \Doctrine\ORM\Internal\Hydration\ObjectHydrator($this->_em);
$result = $hydrator->hydrateAll($stmt, $rsm, [Query::HINT_FORCE_PARTIAL_LOAD => true]);
$this->assertInstanceOf(GH6362Start::CLASSNAME, $result[0]['base']);
$this->assertInstanceOf(GH6362Child::CLASSNAME, $result[1][0]);
}
}
/**
* @Entity
*/
class GH6362Start
{
const CLASSNAME = __CLASS__;
/**
* @Column(type="integer")
* @Id
* @GeneratedValue
*/
protected $id;
/**
* @ManyToOne(targetEntity="GH6362Base", inversedBy="starts")
*/
private $bases;
}
/**
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="type", type="string")
* @DiscriminatorMap({"child" = "GH6362Child"})
* @Entity
*/
abstract class GH6362Base
{
const CLASSNAME = __CLASS__;
/**
* @Column(type="integer")
* @Id
* @GeneratedValue
*/
protected $id;
/**
* @OneToMany(targetEntity="GH6362Start", mappedBy="bases")
*/
private $starts;
}
/**
* @Entity
*/
class GH6362Child extends GH6362Base
{
const CLASSNAME = __CLASS__;
/**
* @OneToMany(targetEntity="GH6362Join", mappedBy="child")
*/
private $joins;
}
/**
* @Entity
*/
class GH6362Join
{
const CLASSNAME = __CLASS__;
/**
* @Column(type="integer")
* @Id
* @GeneratedValue
*/
private $id;
/**
* @ManyToOne(targetEntity="GH6362Child", inversedBy="joins")
*/
private $child;
}

View File

@@ -0,0 +1,81 @@
<?php
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Tests\OrmFunctionalTestCase;
/**
* @group GH-6464
*/
class GH6464Test extends OrmFunctionalTestCase
{
/**
* {@inheritDoc}
*/
protected function setUp()
{
parent::setUp();
$this->_schemaTool->createSchema([
$this->_em->getClassMetadata(GH6464Post::CLASS_NAME),
$this->_em->getClassMetadata(GH6464User::CLASS_NAME),
$this->_em->getClassMetadata(GH6464Author::CLASS_NAME),
]);
}
/**
* Verifies that SqlWalker generates valid SQL for an INNER JOIN to CTI table
*
* SqlWalker needs to generate nested INNER JOIN statements, otherwise there would be INNER JOIN
* statements without an ON clause, which are valid on e.g. MySQL but rejected by PostgreSQL.
*/
public function testIssue()
{
$query = $this->_em->createQueryBuilder()
->select('p')
->from(GH6464Post::CLASS_NAME, 'p')
->innerJoin(GH6464Author::CLASS_NAME, 'a', 'WITH', 'p.authorId = a.id')
->getQuery();
$this->assertNotRegExp(
'/INNER JOIN \w+ \w+ INNER JOIN/',
$query->getSQL(),
'As of GH-6464, every INNER JOIN should have an ON clause, which is missing here'
);
// Query shouldn't yield a result, yet it shouldn't crash (anymore)
$this->assertEquals([], $query->getResult());
}
}
/** @Entity */
class GH6464Post
{
const CLASS_NAME = __CLASS__;
/** @Id @Column(type="integer") @GeneratedValue */
public $id;
/** @Column(type="integer") */
public $authorId;
}
/**
* @Entity
* @InheritanceType("JOINED")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"author" = "GH6464Author"})
*/
abstract class GH6464User
{
const CLASS_NAME = __CLASS__;
/** @Id @Column(type="integer") @GeneratedValue */
public $id;
}
/** @Entity */
class GH6464Author extends GH6464User
{
const CLASS_NAME = __CLASS__;
}

View File

@@ -3,8 +3,8 @@
namespace Doctrine\Tests\ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\UnitOfWork;
use Doctrine\Tests\Mocks\ConnectionMock;
use Doctrine\Tests\Mocks\DriverMock;
use Doctrine\Tests\Mocks\EntityManagerMock;
@@ -24,7 +24,7 @@ class PersistentCollectionTest extends OrmTestCase
protected $collection;
/**
* @var \Doctrine\ORM\EntityManagerInterface
* @var EntityManagerMock
*/
private $_emMock;
@@ -33,6 +33,8 @@ class PersistentCollectionTest extends OrmTestCase
parent::setUp();
$this->_emMock = EntityManagerMock::create(new ConnectionMock([], new DriverMock()));
$this->setUpPersistentCollection();
}
/**
@@ -59,7 +61,6 @@ class PersistentCollectionTest extends OrmTestCase
*/
public function testCurrentInitializesCollection()
{
$this->setUpPersistentCollection();
$this->collection->current();
$this->assertTrue($this->collection->isInitialized());
}
@@ -69,7 +70,6 @@ class PersistentCollectionTest extends OrmTestCase
*/
public function testKeyInitializesCollection()
{
$this->setUpPersistentCollection();
$this->collection->key();
$this->assertTrue($this->collection->isInitialized());
}
@@ -79,8 +79,175 @@ class PersistentCollectionTest extends OrmTestCase
*/
public function testNextInitializesCollection()
{
$this->setUpPersistentCollection();
$this->collection->next();
$this->assertTrue($this->collection->isInitialized());
}
/**
* @group 6110
*/
public function testRemovingElementsAlsoRemovesKeys()
{
$dummy = new \stdClass();
$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->collection->add(new \stdClass());
$this->collection->clear();
$this->assertEquals([], array_keys($this->collection->toArray()));
}
/**
* @group 6110
*/
public function testClearWillAlsoResetKeyPositions()
{
$dummy = new \stdClass();
$this->collection->add($dummy);
$this->collection->removeElement($dummy);
$this->collection->clear();
$this->collection->add($dummy);
$this->assertEquals([0], array_keys($this->collection->toArray()));
}
/**
* @group 6613
* @group 6614
* @group 6616
*/
public function testWillKeepNewItemsInDirtyCollectionAfterInitialization()
{
/* @var $unitOfWork UnitOfWork|\PHPUnit_Framework_MockObject_MockObject */
$unitOfWork = $this
->getMockBuilder('Doctrine\ORM\UnitOfWork')
->disableOriginalConstructor()
->getMock();
$this->_emMock->setUnitOfWork($unitOfWork);
$newElement = new \stdClass();
$persistedElement = new \stdClass();
$this->collection->add($newElement);
self::assertFalse($this->collection->isInitialized());
self::assertTrue($this->collection->isDirty());
$unitOfWork
->expects(self::once())
->method('loadCollection')
->with($this->collection)
->willReturnCallback(function (PersistentCollection $persistentCollection) use ($persistedElement) {
$persistentCollection->unwrap()->add($persistedElement);
});
$this->collection->initialize();
self::assertSame([$persistedElement, $newElement], $this->collection->toArray());
self::assertTrue($this->collection->isInitialized());
self::assertTrue($this->collection->isDirty());
}
/**
* @group 6613
* @group 6614
* @group 6616
*/
public function testWillDeDuplicateNewItemsThatWerePreviouslyPersistedInDirtyCollectionAfterInitialization()
{
/* @var $unitOfWork UnitOfWork|\PHPUnit_Framework_MockObject_MockObject */
$unitOfWork = $this
->getMockBuilder('Doctrine\ORM\UnitOfWork')
->disableOriginalConstructor()
->getMock();
$this->_emMock->setUnitOfWork($unitOfWork);
$newElement = new \stdClass();
$newElementThatIsAlsoPersisted = new \stdClass();
$persistedElement = new \stdClass();
$this->collection->add($newElementThatIsAlsoPersisted);
$this->collection->add($newElement);
self::assertFalse($this->collection->isInitialized());
self::assertTrue($this->collection->isDirty());
$unitOfWork
->expects(self::once())
->method('loadCollection')
->with($this->collection)
->willReturnCallback(function (PersistentCollection $persistentCollection) use (
$persistedElement,
$newElementThatIsAlsoPersisted
) {
$persistentCollection->unwrap()->add($newElementThatIsAlsoPersisted);
$persistentCollection->unwrap()->add($persistedElement);
});
$this->collection->initialize();
self::assertSame(
[$newElementThatIsAlsoPersisted, $persistedElement, $newElement],
$this->collection->toArray()
);
self::assertTrue($this->collection->isInitialized());
self::assertTrue($this->collection->isDirty());
}
/**
* @group 6613
* @group 6614
* @group 6616
*/
public function testWillNotMarkCollectionAsDirtyAfterInitializationIfNoElementsWereAdded()
{
/* @var $unitOfWork UnitOfWork|\PHPUnit_Framework_MockObject_MockObject */
$unitOfWork = $this
->getMockBuilder('Doctrine\ORM\UnitOfWork')
->disableOriginalConstructor()
->getMock();
$this->_emMock->setUnitOfWork($unitOfWork);
$newElementThatIsAlsoPersisted = new \stdClass();
$persistedElement = new \stdClass();
$this->collection->add($newElementThatIsAlsoPersisted);
self::assertFalse($this->collection->isInitialized());
self::assertTrue($this->collection->isDirty());
$unitOfWork
->expects(self::once())
->method('loadCollection')
->with($this->collection)
->willReturnCallback(function (PersistentCollection $persistentCollection) use (
$persistedElement,
$newElementThatIsAlsoPersisted
) {
$persistentCollection->unwrap()->add($newElementThatIsAlsoPersisted);
$persistentCollection->unwrap()->add($persistedElement);
});
$this->collection->initialize();
self::assertSame(
[$newElementThatIsAlsoPersisted, $persistedElement],
$this->collection->toArray()
);
self::assertTrue($this->collection->isInitialized());
self::assertFalse($this->collection->isDirty());
}
}

View File

@@ -153,12 +153,12 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
{
$this->assertSqlGeneration(
'SELECT e FROM Doctrine\Tests\Models\Company\CompanyEmployee e JOIN Doctrine\Tests\Models\Company\CompanyManager m WITH e.id = m.id',
'SELECT c0_.id AS id_0, c0_.name AS name_1, c1_.salary AS salary_2, c1_.department AS department_3, c1_.startDate AS startDate_4, c0_.discr AS discr_5 FROM company_employees c1_ INNER JOIN company_persons c0_ ON c1_.id = c0_.id INNER JOIN company_managers c2_ INNER JOIN company_employees c4_ ON c2_.id = c4_.id INNER JOIN company_persons c3_ ON c2_.id = c3_.id AND (c0_.id = c3_.id)'
'SELECT c0_.id AS id_0, c0_.name AS name_1, c1_.salary AS salary_2, c1_.department AS department_3, c1_.startDate AS startDate_4, c0_.discr AS discr_5 FROM company_employees c1_ INNER JOIN company_persons c0_ ON c1_.id = c0_.id INNER JOIN (company_managers c2_ INNER JOIN company_employees c4_ ON c2_.id = c4_.id INNER JOIN company_persons c3_ ON c2_.id = c3_.id) ON (c0_.id = c3_.id)'
);
$this->assertSqlGeneration(
'SELECT e FROM Doctrine\Tests\Models\Company\CompanyEmployee e LEFT JOIN Doctrine\Tests\Models\Company\CompanyManager m WITH e.id = m.id',
'SELECT c0_.id AS id_0, c0_.name AS name_1, c1_.salary AS salary_2, c1_.department AS department_3, c1_.startDate AS startDate_4, c0_.discr AS discr_5 FROM company_employees c1_ INNER JOIN company_persons c0_ ON c1_.id = c0_.id LEFT JOIN company_managers c2_ INNER JOIN company_employees c4_ ON c2_.id = c4_.id INNER JOIN company_persons c3_ ON c2_.id = c3_.id ON (c0_.id = c3_.id)'
'SELECT c0_.id AS id_0, c0_.name AS name_1, c1_.salary AS salary_2, c1_.department AS department_3, c1_.startDate AS startDate_4, c0_.discr AS discr_5 FROM company_employees c1_ INNER JOIN company_persons c0_ ON c1_.id = c0_.id LEFT JOIN (company_managers c2_ INNER JOIN company_employees c4_ ON c2_.id = c4_.id INNER JOIN company_persons c3_ ON c2_.id = c3_.id) ON (c0_.id = c3_.id)'
);
}
@@ -2165,7 +2165,7 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
// the where clause when not joining onto that table
$this->assertSqlGeneration(
'SELECT c FROM Doctrine\Tests\Models\Company\CompanyContract c LEFT JOIN Doctrine\Tests\Models\Company\CompanyEmployee e WITH e.id = c.salesPerson WHERE c.completed = true',
"SELECT c0_.id AS id_0, c0_.completed AS completed_1, c0_.fixPrice AS fixPrice_2, c0_.hoursWorked AS hoursWorked_3, c0_.pricePerHour AS pricePerHour_4, c0_.maxPrice AS maxPrice_5, c0_.discr AS discr_6 FROM company_contracts c0_ LEFT JOIN company_employees c1_ INNER JOIN company_persons c2_ ON c1_.id = c2_.id ON (c2_.id = c0_.salesPerson_id) WHERE (c0_.completed = 1) AND c0_.discr IN ('fix', 'flexible', 'flexultra')"
"SELECT c0_.id AS id_0, c0_.completed AS completed_1, c0_.fixPrice AS fixPrice_2, c0_.hoursWorked AS hoursWorked_3, c0_.pricePerHour AS pricePerHour_4, c0_.maxPrice AS maxPrice_5, c0_.discr AS discr_6 FROM company_contracts c0_ LEFT JOIN (company_employees c1_ INNER JOIN company_persons c2_ ON c1_.id = c2_.id) ON (c2_.id = c0_.salesPerson_id) WHERE (c0_.completed = 1) AND c0_.discr IN ('fix', 'flexible', 'flexultra')"
);
}

View File

@@ -0,0 +1,195 @@
<?php
namespace Doctrine\Tests\ORM\Tools\Console;
use Doctrine\ORM\Tools\Console\MetadataFilter;
use Doctrine\ORM\Tools\DisconnectedClassMetadataFactory;
/**
* Tests for {@see \Doctrine\ORM\Tools\Console\MetadataFilter}
*
* @covers \Doctrine\ORM\Tools\Console\MetadataFilter
*/
class MetadataFilterTest extends \Doctrine\Tests\OrmTestCase
{
/**
* @var DisconnectedClassMetadataFactory
*/
private $cmf;
protected function setUp()
{
parent::setUp();
$driver = $this->createAnnotationDriver();
$em = $this->_getTestEntityManager();
$em->getConfiguration()->setMetadataDriverImpl($driver);
$this->cmf = new DisconnectedClassMetadataFactory();
$this->cmf->setEntityManager($em);
}
public function testFilterWithEmptyArray()
{
$originalMetadatas = array(
$metadataAaa = $this->cmf->getMetadataFor(MetadataFilterTestEntityAaa::CLASSNAME),
$metadataBbb = $this->cmf->getMetadataFor(MetadataFilterTestEntityBbb::CLASSNAME),
);
$metadatas = $originalMetadatas;
$metadatas = MetadataFilter::filter($metadatas, array());
$this->assertContains($metadataAaa, $metadatas);
$this->assertContains($metadataBbb, $metadatas);
$this->assertCount(count($originalMetadatas), $metadatas);
}
public function testFilterWithString()
{
$originalMetadatas = array(
$metadataAaa = $this->cmf->getMetadataFor(MetadataFilterTestEntityAaa::CLASSNAME),
$metadataBbb = $this->cmf->getMetadataFor(MetadataFilterTestEntityBbb::CLASSNAME),
$metadataCcc = $this->cmf->getMetadataFor(MetadataFilterTestEntityCcc::CLASSNAME),
);
$metadatas = $originalMetadatas;
$metadatas = MetadataFilter::filter($metadatas, 'MetadataFilterTestEntityAaa');
$this->assertContains($metadataAaa, $metadatas);
$this->assertNotContains($metadataBbb, $metadatas);
$this->assertNotContains($metadataCcc, $metadatas);
$this->assertCount(1, $metadatas);
$metadatas = $originalMetadatas;
$metadatas = MetadataFilter::filter($metadatas, 'MetadataFilterTestEntityBbb');
$this->assertNotContains($metadataAaa, $metadatas);
$this->assertContains($metadataBbb, $metadatas);
$this->assertNotContains($metadataCcc, $metadatas);
$this->assertCount(1, $metadatas);
$metadatas = $originalMetadatas;
$metadatas = MetadataFilter::filter($metadatas, 'MetadataFilterTestEntityCcc');
$this->assertNotContains($metadataAaa, $metadatas);
$this->assertNotContains($metadataBbb, $metadatas);
$this->assertContains($metadataCcc, $metadatas);
$this->assertCount(1, $metadatas);
}
public function testFilterWithString2()
{
$originalMetadatas = array(
$metadataFoo = $this->cmf->getMetadataFor(MetadataFilterTestEntityFoo::CLASSNAME),
$metadataFooBar = $this->cmf->getMetadataFor(MetadataFilterTestEntityFooBar::CLASSNAME),
$metadataBar = $this->cmf->getMetadataFor(MetadataFilterTestEntityBar::CLASSNAME),
);
$metadatas = $originalMetadatas;
$metadatas = MetadataFilter::filter($metadatas, 'MetadataFilterTestEntityFoo');
$this->assertContains($metadataFoo, $metadatas);
$this->assertContains($metadataFooBar, $metadatas);
$this->assertNotContains($metadataBar, $metadatas);
$this->assertCount(2, $metadatas);
}
public function testFilterWithArray()
{
$originalMetadatas = array(
$metadataAaa = $this->cmf->getMetadataFor(MetadataFilterTestEntityAaa::CLASSNAME),
$metadataBbb = $this->cmf->getMetadataFor(MetadataFilterTestEntityBbb::CLASSNAME),
$metadataCcc = $this->cmf->getMetadataFor(MetadataFilterTestEntityCcc::CLASSNAME),
);
$metadatas = $originalMetadatas;
$metadatas = MetadataFilter::filter($metadatas, array(
'MetadataFilterTestEntityAaa',
'MetadataFilterTestEntityCcc',
));
$this->assertContains($metadataAaa, $metadatas);
$this->assertNotContains($metadataBbb, $metadatas);
$this->assertContains($metadataCcc, $metadatas);
$this->assertCount(2, $metadatas);
}
public function testFilterWithRegex()
{
$originalMetadatas = array(
$metadataFoo = $this->cmf->getMetadataFor(MetadataFilterTestEntityFoo::CLASSNAME),
$metadataFooBar = $this->cmf->getMetadataFor(MetadataFilterTestEntityFooBar::CLASSNAME),
$metadataBar = $this->cmf->getMetadataFor(MetadataFilterTestEntityBar::CLASSNAME),
);
$metadatas = $originalMetadatas;
$metadatas = MetadataFilter::filter($metadatas, 'Foo$');
$this->assertContains($metadataFoo, $metadatas);
$this->assertNotContains($metadataFooBar, $metadatas);
$this->assertNotContains($metadataBar, $metadatas);
$this->assertCount(1, $metadatas);
$metadatas = $originalMetadatas;
$metadatas = MetadataFilter::filter($metadatas, 'Bar$');
$this->assertNotContains($metadataFoo, $metadatas);
$this->assertContains($metadataFooBar, $metadatas);
$this->assertContains($metadataBar, $metadatas);
$this->assertCount(2, $metadatas);
}
}
/** @Entity */
class MetadataFilterTestEntityAaa
{
const CLASSNAME = __CLASS__;
/** @Id @Column(type="integer") */
protected $id;
}
/** @Entity */
class MetadataFilterTestEntityBbb
{
const CLASSNAME = __CLASS__;
/** @Id @Column(type="integer") */
protected $id;
}
/** @Entity */
class MetadataFilterTestEntityCcc
{
const CLASSNAME = __CLASS__;
/** @Id @Column(type="integer") */
protected $id;
}
/** @Entity */
class MetadataFilterTestEntityFoo
{
const CLASSNAME = __CLASS__;
/** @Id @Column(type="integer") */
protected $id;
}
/** @Entity */
class MetadataFilterTestEntityBar
{
const CLASSNAME = __CLASS__;
/** @Id @Column(type="integer") */
protected $id;
}
/** @Entity */
class MetadataFilterTestEntityFooBar
{
const CLASSNAME = __CLASS__;
/** @Id @Column(type="integer") */
protected $id;
}

View File

@@ -30,6 +30,18 @@ class CountOutputWalkerTest extends PaginationTestCase
);
}
public function testCountQuery_GroupBy()
{
$query = $this->entityManager->createQuery(
'SELECT p.name FROM Doctrine\Tests\ORM\Tools\Pagination\Person p GROUP BY p.name');
$query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'Doctrine\ORM\Tools\Pagination\CountOutputWalker');
$query->setFirstResult(null)->setMaxResults(null);
$this->assertSame(
"SELECT COUNT(*) AS dctrn_count FROM (SELECT p0_.name AS name_0 FROM Person p0_ GROUP BY p0_.name) dctrn_table", $query->getSQL()
);
}
public function testCountQuery_Having()
{
$query = $this->entityManager->createQuery(
@@ -37,8 +49,8 @@ class CountOutputWalkerTest extends PaginationTestCase
$query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'Doctrine\ORM\Tools\Pagination\CountOutputWalker');
$query->setFirstResult(null)->setMaxResults(null);
$this->assertEquals(
"SELECT COUNT(*) AS dctrn_count FROM (SELECT DISTINCT id_1 FROM (SELECT count(u0_.id) AS sclr_0, g1_.id AS id_1, u0_.id AS id_2 FROM groups g1_ LEFT JOIN user_group u2_ ON g1_.id = u2_.group_id LEFT JOIN User u0_ ON u0_.id = u2_.user_id GROUP BY g1_.id HAVING sclr_0 > 0) dctrn_result) dctrn_table", $query->getSql()
$this->assertSame(
"SELECT COUNT(*) AS dctrn_count FROM (SELECT count(u0_.id) AS sclr_0, g1_.id AS id_1, u0_.id AS id_2 FROM groups g1_ LEFT JOIN user_group u2_ ON g1_.id = u2_.group_id LEFT JOIN User u0_ ON u0_.id = u2_.user_id GROUP BY g1_.id HAVING sclr_0 > 0) dctrn_table", $query->getSQL()
);
}

View File

@@ -2,10 +2,11 @@
namespace Doctrine\Tests\ORM\Tools;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
use Doctrine\ORM\Tools\Event\GenerateSchemaTableEventArgs;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\ORM\Tools\ToolEvents;
use Doctrine\ORM\Tools\Event\GenerateSchemaTableEventArgs;
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
class SchemaToolTest extends \Doctrine\Tests\OrmTestCase
{
@@ -138,6 +139,26 @@ class SchemaToolTest extends \Doctrine\Tests\OrmTestCase
$this->assertTrue($table->hasIndex('primary'));
$this->assertTrue($table->hasIndex('uniq_hash'));
}
public function testSetDiscriminatorColumnWithoutLength()
{
$em = $this->_getTestEntityManager();
$schemaTool = new SchemaTool($em);
$metadata = $em->getClassMetadata(__NAMESPACE__ . '\\FirstEntity');
$metadata->setInheritanceType(ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE);
$metadata->setDiscriminatorColumn(['name' => 'discriminator', 'type' => 'string']);
$schema = $schemaTool->getSchemaFromMetadata([$metadata]);
$this->assertTrue($schema->hasTable('first_entity'));
$table = $schema->getTable('first_entity');
$this->assertTrue($table->hasColumn('discriminator'));
$column = $table->getColumn('discriminator');
$this->assertEquals(255, $column->getLength());
}
}
/**
@@ -187,3 +208,45 @@ class UniqueConstraintAnnotationModel
*/
private $hash;
}
/**
* @Entity
* @Table(name="first_entity")
*/
class FirstEntity
{
/**
* @Id
* @Column(name="id")
*/
public $id;
/**
* @OneToOne(targetEntity="SecondEntity")
* @JoinColumn(name="id", referencedColumnName="fist_entity_id")
*/
public $secondEntity;
/**
* @Column(name="name")
*/
public $name;
}
/**
* @Entity
* @Table(name="second_entity")
*/
class SecondEntity
{
/**
* @Id
* @Column(name="fist_entity_id")
*/
public $fist_entity_id;
/**
* @Column(name="name")
*/
public $name;
}

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;
@@ -47,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();
@@ -392,6 +399,88 @@ class UnitOfWorkTest extends \Doctrine\Tests\OrmTestCase
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);
}
}
/**
@@ -498,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);
}
}

View File

@@ -309,6 +309,12 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
protected function tearDown()
{
$conn = static::$_sharedConn;
// In case test is skipped, tearDown is called, but no setup may have run
if ( ! $conn) {
return;
}
$platform = $conn->getDatabasePlatform();
$this->_sqlLoggerStack->enabled = false;