Compare commits

..

60 Commits

Author SHA1 Message Date
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
40 changed files with 873 additions and 89 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
+6
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
+1 -2
View File
@@ -24,8 +24,7 @@
},
"require-dev": {
"symfony/yaml": "~2.3|~3.0",
"phpunit/phpunit": "~4.0",
"satooshi/php-coveralls": "dev-master"
"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->getTableName());
}
/**
* 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) {
@@ -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);
}
}
+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) {
+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;
}
+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) {
+30 -13
View File
@@ -2407,17 +2407,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)) {
@@ -3431,8 +3422,6 @@ class UnitOfWork implements PropertyChangedListener
);
$managedCol->setOwner($managedCopy, $assoc2);
$prop->setValue($managedCopy, $managedCol);
$this->originalEntityData[spl_object_hash($entity)][$name] = $managedCol;
}
if ($assoc2['isCascadeMerge']) {
@@ -3471,4 +3460,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.3';
const VERSION = '2.5.5-DEV';
/**
* Compares a Doctrine version with the current one.
@@ -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;
}
@@ -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]));
@@ -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);
}
}
}
@@ -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,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');
@@ -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
@@ -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
@@ -12,8 +12,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;
/**
@@ -321,6 +326,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 +364,34 @@ 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');
}
}
/**
@@ -284,6 +284,11 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
'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',
),
);
/**
@@ -544,6 +549,12 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
$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();
}