mirror of
https://github.com/doctrine/orm.git
synced 2026-04-01 03:42:27 +02:00
Compare commits
200 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3aa3f2d1d | ||
|
|
c83f479633 | ||
|
|
741f1db198 | ||
|
|
22546a3811 | ||
|
|
efa058bd8f | ||
|
|
767577cec6 | ||
|
|
c0f593e422 | ||
|
|
d89d238594 | ||
|
|
2337b7aedd | ||
|
|
9e6f061bfb | ||
|
|
bf1188127e | ||
|
|
c73ec2aa76 | ||
|
|
095611c4b6 | ||
|
|
96c6f4cf1d | ||
|
|
5cacb6e14f | ||
|
|
ab63628960 | ||
|
|
15731c7bde | ||
|
|
abb429a0c9 | ||
|
|
61cb03bf30 | ||
|
|
d6bcb5b1f8 | ||
|
|
bdae362777 | ||
|
|
59c5574554 | ||
|
|
9545bf9d8c | ||
|
|
5521d1f325 | ||
|
|
49694dc335 | ||
|
|
3155d970d3 | ||
|
|
09189fc021 | ||
|
|
5a0d3e5fb8 | ||
|
|
b9ba4e3207 | ||
|
|
d7919678e5 | ||
|
|
8b185eb822 | ||
|
|
693a0546d3 | ||
|
|
5c5c8fc487 | ||
|
|
85dc707cc8 | ||
|
|
64a1251b61 | ||
|
|
65ed6a2c2f | ||
|
|
d27a9fce7a | ||
|
|
11659f5cfe | ||
|
|
68dad26482 | ||
|
|
b3ceef0fb6 | ||
|
|
095b365146 | ||
|
|
7c1ebd99bc | ||
|
|
c06f19db8d | ||
|
|
0be9be4e24 | ||
|
|
b2bf5ee92e | ||
|
|
698bd813a2 | ||
|
|
b2ac8fdfd7 | ||
|
|
9c2b54b748 | ||
|
|
910784213f | ||
|
|
1d1de7de80 | ||
|
|
741da6eed7 | ||
|
|
ad5397b581 | ||
|
|
57bb46ca9d | ||
|
|
0416d5e036 | ||
|
|
a14432117a | ||
|
|
824f62d3bb | ||
|
|
c0f0fe060f | ||
|
|
caffbe04a2 | ||
|
|
d2c805b071 | ||
|
|
04fc7a9a1c | ||
|
|
149b8f4e09 | ||
|
|
48e8c02cb8 | ||
|
|
e218866a69 | ||
|
|
e6c434196c | ||
|
|
d52dbe62ac | ||
|
|
b0ede40f47 | ||
|
|
3645a9c44d | ||
|
|
39ce6f96a0 | ||
|
|
e43f5304ef | ||
|
|
67724eb7ae | ||
|
|
8d4bc0638d | ||
|
|
81186105b6 | ||
|
|
beef8acdf5 | ||
|
|
26fc8d60e6 | ||
|
|
12e8ab216a | ||
|
|
dac1a16964 | ||
|
|
d9821d3fda | ||
|
|
576a4d7e31 | ||
|
|
eaee924180 | ||
|
|
cf941ce54f | ||
|
|
cfb7461f51 | ||
|
|
569c08ce55 | ||
|
|
295523cdca | ||
|
|
25efabdb74 | ||
|
|
1d96178097 | ||
|
|
20cb50451d | ||
|
|
0ff512ba8f | ||
|
|
5e9014fd99 | ||
|
|
a90cd9dfe8 | ||
|
|
4d699789a2 | ||
|
|
1486c8f8e2 | ||
|
|
3dadfa49d5 | ||
|
|
9b36947a48 | ||
|
|
2122297fdb | ||
|
|
af99cba28c | ||
|
|
9bcee455ca | ||
|
|
73e4be7c7b | ||
|
|
d7026c46ec | ||
|
|
b7bfbb6adb | ||
|
|
c9161fcd6f | ||
|
|
147f8fffff | ||
|
|
e73428a051 | ||
|
|
a3d93afc4f | ||
|
|
b0e4e3eda4 | ||
|
|
95dcf51ad5 | ||
|
|
5365a418e9 | ||
|
|
47ce079b64 | ||
|
|
8e95672f49 | ||
|
|
e16de704a1 | ||
|
|
3e57c46afd | ||
|
|
5e702ad524 | ||
|
|
9e9864c684 | ||
|
|
88567ea395 | ||
|
|
bf18aac62d | ||
|
|
4d16e30a16 | ||
|
|
d592c14a6c | ||
|
|
da41161d73 | ||
|
|
33e23cdddb | ||
|
|
c47c23a101 | ||
|
|
7352b97b14 | ||
|
|
dce0aeaa15 | ||
|
|
a2c23fb9cb | ||
|
|
aa6dc9695d | ||
|
|
c4a2a348f4 | ||
|
|
6ab27993fc | ||
|
|
5eedccc22a | ||
|
|
592122fbcb | ||
|
|
aa5820309e | ||
|
|
b183818fa8 | ||
|
|
2af84c6025 | ||
|
|
f181cf6c6b | ||
|
|
62431ae477 | ||
|
|
0d93461e66 | ||
|
|
a5eb0f2e82 | ||
|
|
9a393ccba7 | ||
|
|
224ac9725e | ||
|
|
0af9ee0140 | ||
|
|
fecadf059c | ||
|
|
800215040a | ||
|
|
ec4dd4ab44 | ||
|
|
7378035f68 | ||
|
|
7fbcbfa271 | ||
|
|
4a38c96ec5 | ||
|
|
110d771883 | ||
|
|
996c5048ab | ||
|
|
cd746beae2 | ||
|
|
dfbc6bbea3 | ||
|
|
c4209b4654 | ||
|
|
6279c80e05 | ||
|
|
d05aa6a5e0 | ||
|
|
bc4ddbfb01 | ||
|
|
14499f5021 | ||
|
|
aae43cbb77 | ||
|
|
db6cb8dedc | ||
|
|
5092da074a | ||
|
|
1c6524db55 | ||
|
|
d9fc5388f1 | ||
|
|
d2e51eacff | ||
|
|
5a6ae4686f | ||
|
|
8b8a1cbe81 | ||
|
|
27a5284899 | ||
|
|
0086d17afe | ||
|
|
ab62167c8a | ||
|
|
6d43195669 | ||
|
|
7065ff0ac9 | ||
|
|
aa61328e90 | ||
|
|
62719f2a97 | ||
|
|
66770c5bfe | ||
|
|
42691c21b4 | ||
|
|
596e895763 | ||
|
|
d5c82094df | ||
|
|
4148220f9c | ||
|
|
e173c930ec | ||
|
|
7071984559 | ||
|
|
216c466233 | ||
|
|
65f5777e60 | ||
|
|
6e3ce26429 | ||
|
|
752d4f9eac | ||
|
|
2983081a60 | ||
|
|
464b5fdbfb | ||
|
|
04254a8e34 | ||
|
|
1d213e6733 | ||
|
|
bc82e94afc | ||
|
|
b9af1c8fa5 | ||
|
|
d606efd4eb | ||
|
|
581e1638a2 | ||
|
|
17ae8d1b2d | ||
|
|
3a058f8522 | ||
|
|
567220ef71 | ||
|
|
39098ce415 | ||
|
|
1eb9c8a7f6 | ||
|
|
f2f53ba9dc | ||
|
|
ebbc443ec3 | ||
|
|
16802d2614 | ||
|
|
ef73249bc7 | ||
|
|
ed637e51b9 | ||
|
|
a3ece3b419 | ||
|
|
2d1bc78749 | ||
|
|
eaf8b1c7ca | ||
|
|
8070b50150 |
22
.travis.yml
22
.travis.yml
@@ -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
|
||||
|
||||
@@ -23,19 +21,11 @@ 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
|
||||
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
|
||||
|
||||
10
UPGRADE.md
10
UPGRADE.md
@@ -1,5 +1,11 @@
|
||||
# Upgrade to 2.5
|
||||
|
||||
## Minor BC BREAK: query cache key time is now a float
|
||||
|
||||
As of 2.5.5, the `QueryCacheEntry#time` property will contain a float value
|
||||
instead of an integer in order to have more precision and also to be consistent
|
||||
with the `TimestampCacheEntry#time`.
|
||||
|
||||
## Minor BC BREAK: discriminator map must now include all non-transient classes
|
||||
|
||||
It is now required that you declare the root of an inheritance in the
|
||||
@@ -138,6 +144,10 @@ From now on, the resultset will look like this:
|
||||
...
|
||||
)
|
||||
|
||||
## Minor BC BREAK: added second parameter $indexBy in EntityRepository#createQueryBuilder method signature
|
||||
|
||||
Added way to access the underlying QueryBuilder#from() method's 'indexBy' parameter when using EntityRepository#createQueryBuilder()
|
||||
|
||||
# Upgrade to 2.4
|
||||
|
||||
## BC BREAK: Compatibility Bugfix in PersistentCollection#matching()
|
||||
|
||||
@@ -16,16 +16,15 @@
|
||||
"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.6-dev",
|
||||
"doctrine/common": ">=2.5-dev,<2.9-dev",
|
||||
"doctrine/cache": "~1.4",
|
||||
"symfony/console": "~2.5"
|
||||
"symfony/console": "~2.5|~3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/yaml": "~2.1",
|
||||
"phpunit/phpunit": "~4.0",
|
||||
"satooshi/php-coveralls": "dev-master"
|
||||
"symfony/yaml": "~2.3|~3.0",
|
||||
"phpunit/phpunit": "~4.0"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/yaml": "If you want to use YAML Metadata Mapping Driver"
|
||||
|
||||
@@ -28,7 +28,7 @@ use Doctrine\ORM\Cache\QueryCacheKey;
|
||||
use Doctrine\DBAL\Cache\QueryCacheProfile;
|
||||
|
||||
use Doctrine\ORM\Cache;
|
||||
use Doctrine\ORM\Query\QueryException;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
|
||||
/**
|
||||
* Base contract for ORM queries. Base class for Query and NativeQuery.
|
||||
@@ -993,32 +993,54 @@ abstract class AbstractQuery
|
||||
private function executeUsingQueryCache($parameters = null, $hydrationMode = null)
|
||||
{
|
||||
$rsm = $this->getResultSetMapping();
|
||||
$querykey = new QueryCacheKey($this->getHash(), $this->lifetime, $this->cacheMode ?: Cache::MODE_NORMAL);
|
||||
$queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion);
|
||||
$result = $queryCache->get($querykey, $rsm, $this->_hints);
|
||||
$queryKey = new QueryCacheKey(
|
||||
$this->getHash(),
|
||||
$this->lifetime,
|
||||
$this->cacheMode ?: Cache::MODE_NORMAL,
|
||||
$this->getTimestampKey()
|
||||
);
|
||||
|
||||
$result = $queryCache->get($queryKey, $rsm, $this->_hints);
|
||||
|
||||
if ($result !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $querykey);
|
||||
$this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $queryKey);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
$result = $this->executeIgnoreQueryCache($parameters, $hydrationMode);
|
||||
$cached = $queryCache->put($querykey, $rsm, $result, $this->_hints);
|
||||
$cached = $queryCache->put($queryKey, $rsm, $result, $this->_hints);
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $querykey);
|
||||
$this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $queryKey);
|
||||
|
||||
if ($cached) {
|
||||
$this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $querykey);
|
||||
$this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $queryKey);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Doctrine\ORM\Cache\TimestampCacheKey|null
|
||||
*/
|
||||
private function getTimestampKey()
|
||||
{
|
||||
$entityName = reset($this->_resultSetMapping->aliasMap);
|
||||
|
||||
if (empty($entityName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$metadata = $this->_em->getClassMetadata($entityName);
|
||||
|
||||
return new Cache\TimestampCacheKey($metadata->rootEntityName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the result cache id to use to store the result set cache entry.
|
||||
* Will return the configured id if it exists otherwise a hash will be
|
||||
|
||||
@@ -110,7 +110,9 @@ class CacheConfiguration
|
||||
public function getQueryValidator()
|
||||
{
|
||||
if ($this->queryValidator === null) {
|
||||
$this->queryValidator = new TimestampQueryCacheValidator();
|
||||
$this->queryValidator = new TimestampQueryCacheValidator(
|
||||
$this->cacheFactory->getTimestampRegion()
|
||||
);
|
||||
}
|
||||
|
||||
return $this->queryValidator;
|
||||
|
||||
@@ -73,7 +73,7 @@ class DefaultEntityHydrator implements EntityHydrator
|
||||
public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $entity)
|
||||
{
|
||||
$data = $this->uow->getOriginalEntityData($entity);
|
||||
$data = array_merge($data, $key->identifier); // why update has no identifier values ?
|
||||
$data = array_merge($data, $metadata->getIdentifierValues($entity)); // why update has no identifier values ?
|
||||
|
||||
foreach ($metadata->associationMappings as $name => $assoc) {
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
|
||||
/**
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @since 2.5
|
||||
*/
|
||||
abstract class AbstractCollectionPersister implements CachedCollectionPersister
|
||||
@@ -164,10 +165,18 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
|
||||
public function storeCollectionCache(CollectionCacheKey $key, $elements)
|
||||
{
|
||||
/* @var $targetPersister CachedEntityPersister */
|
||||
$associationMapping = $this->sourceEntity->associationMappings[$key->association];
|
||||
$targetPersister = $this->uow->getEntityPersister($this->targetEntity->rootEntityName);
|
||||
$targetRegion = $targetPersister->getCacheRegion();
|
||||
$targetHydrator = $targetPersister->getEntityHydrator();
|
||||
$entry = $this->hydrator->buildCacheEntry($this->targetEntity, $key, $elements);
|
||||
|
||||
// Only preserve ordering if association configured it
|
||||
if ( ! (isset($associationMapping['indexBy']) && $associationMapping['indexBy'])) {
|
||||
// Elements may be an array or a Collection
|
||||
$elements = array_values(is_array($elements) ? $elements : $elements->getValues());
|
||||
}
|
||||
|
||||
$entry = $this->hydrator->buildCacheEntry($this->targetEntity, $key, $elements);
|
||||
|
||||
foreach ($entry->identifiers as $index => $entityKey) {
|
||||
if ($targetRegion->contains($entityKey)) {
|
||||
|
||||
@@ -131,7 +131,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
$this->cacheLogger = $cacheConfig->getCacheLogger();
|
||||
$this->timestampRegion = $cacheFactory->getTimestampRegion();
|
||||
$this->hydrator = $cacheFactory->buildEntityHydrator($em, $class);
|
||||
$this->timestampKey = new TimestampCacheKey($this->class->getTableName());
|
||||
$this->timestampKey = new TimestampCacheKey($this->class->rootEntityName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -296,17 +296,16 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
* @param array $orderBy
|
||||
* @param integer $limit
|
||||
* @param integer $offset
|
||||
* @param integer $timestamp
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getHash($query, $criteria, array $orderBy = null, $limit = null, $offset = null, $timestamp = null)
|
||||
protected function getHash($query, $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
{
|
||||
list($params) = ($criteria instanceof Criteria)
|
||||
? $this->persister->expandCriteriaParameters($criteria)
|
||||
: $this->persister->expandParameters($criteria);
|
||||
|
||||
return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset . $timestamp);
|
||||
return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -377,17 +376,16 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
}
|
||||
|
||||
//handle only EntityRepository#findOneBy
|
||||
$timestamp = $this->timestampRegion->get($this->timestampKey);
|
||||
$query = $this->persister->getSelectSQL($criteria, null, null, $limit, null, $orderBy);
|
||||
$hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null);
|
||||
$hash = $this->getHash($query, $criteria, null, null, null);
|
||||
$rsm = $this->getResultSetMapping();
|
||||
$querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL);
|
||||
$queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey);
|
||||
$queryCache = $this->cache->getQueryCache($this->regionName);
|
||||
$result = $queryCache->get($querykey, $rsm);
|
||||
$result = $queryCache->get($queryKey, $rsm);
|
||||
|
||||
if ($result !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->queryCacheHit($this->regionName, $querykey);
|
||||
$this->cacheLogger->queryCacheHit($this->regionName, $queryKey);
|
||||
}
|
||||
|
||||
return $result[0];
|
||||
@@ -397,15 +395,15 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
return null;
|
||||
}
|
||||
|
||||
$cached = $queryCache->put($querykey, $rsm, array($result));
|
||||
$cached = $queryCache->put($queryKey, $rsm, array($result));
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
if ($result) {
|
||||
$this->cacheLogger->queryCacheMiss($this->regionName, $querykey);
|
||||
$this->cacheLogger->queryCacheMiss($this->regionName, $queryKey);
|
||||
}
|
||||
|
||||
if ($cached) {
|
||||
$this->cacheLogger->queryCachePut($this->regionName, $querykey);
|
||||
$this->cacheLogger->queryCachePut($this->regionName, $queryKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,32 +415,31 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
*/
|
||||
public function loadAll(array $criteria = array(), array $orderBy = null, $limit = null, $offset = null)
|
||||
{
|
||||
$timestamp = $this->timestampRegion->get($this->timestampKey);
|
||||
$query = $this->persister->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy);
|
||||
$hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null);
|
||||
$hash = $this->getHash($query, $criteria, null, null, null);
|
||||
$rsm = $this->getResultSetMapping();
|
||||
$querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL);
|
||||
$queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey);
|
||||
$queryCache = $this->cache->getQueryCache($this->regionName);
|
||||
$result = $queryCache->get($querykey, $rsm);
|
||||
$result = $queryCache->get($queryKey, $rsm);
|
||||
|
||||
if ($result !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->queryCacheHit($this->regionName, $querykey);
|
||||
$this->cacheLogger->queryCacheHit($this->regionName, $queryKey);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
$result = $this->persister->loadAll($criteria, $orderBy, $limit, $offset);
|
||||
$cached = $queryCache->put($querykey, $rsm, $result);
|
||||
$cached = $queryCache->put($queryKey, $rsm, $result);
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
if ($result) {
|
||||
$this->cacheLogger->queryCacheMiss($this->regionName, $querykey);
|
||||
$this->cacheLogger->queryCacheMiss($this->regionName, $queryKey);
|
||||
}
|
||||
|
||||
if ($cached) {
|
||||
$this->cacheLogger->queryCachePut($this->regionName, $querykey);
|
||||
$this->cacheLogger->queryCachePut($this->regionName, $queryKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,32 +513,34 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
*/
|
||||
public function loadCriteria(Criteria $criteria)
|
||||
{
|
||||
$orderBy = $criteria->getOrderings();
|
||||
$limit = $criteria->getMaxResults();
|
||||
$offset = $criteria->getFirstResult();
|
||||
$query = $this->persister->getSelectSQL($criteria);
|
||||
$timestamp = $this->timestampRegion->get($this->timestampKey);
|
||||
$hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null);
|
||||
$hash = $this->getHash($query, $criteria, $orderBy, $limit, $offset);
|
||||
$rsm = $this->getResultSetMapping();
|
||||
$querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL);
|
||||
$queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey);
|
||||
$queryCache = $this->cache->getQueryCache($this->regionName);
|
||||
$cacheResult = $queryCache->get($querykey, $rsm);
|
||||
$cacheResult = $queryCache->get($queryKey, $rsm);
|
||||
|
||||
if ($cacheResult !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->queryCacheHit($this->regionName, $querykey);
|
||||
$this->cacheLogger->queryCacheHit($this->regionName, $queryKey);
|
||||
}
|
||||
|
||||
return $cacheResult;
|
||||
}
|
||||
|
||||
$result = $this->persister->loadCriteria($criteria);
|
||||
$cached = $queryCache->put($querykey, $rsm, $result);
|
||||
$cached = $queryCache->put($queryKey, $rsm, $result);
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
if ($result) {
|
||||
$this->cacheLogger->queryCacheMiss($this->regionName, $querykey);
|
||||
$this->cacheLogger->queryCacheMiss($this->regionName, $queryKey);
|
||||
}
|
||||
|
||||
if ($cached) {
|
||||
$this->cacheLogger->queryCachePut($this->regionName, $querykey);
|
||||
$this->cacheLogger->queryCachePut($this->regionName, $queryKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ use Doctrine\ORM\Cache\EntityCacheKey;
|
||||
* Specific non-strict read/write cached entity persister
|
||||
*
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @since 2.5
|
||||
*/
|
||||
class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
@@ -78,13 +79,16 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
*/
|
||||
public function delete($entity)
|
||||
{
|
||||
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
|
||||
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
|
||||
$deleted = $this->persister->delete($entity);
|
||||
|
||||
if ($this->persister->delete($entity)) {
|
||||
if ($deleted) {
|
||||
$this->region->evict($key);
|
||||
}
|
||||
|
||||
$this->queuedCache['delete'][] = $key;
|
||||
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,6 +30,7 @@ use Doctrine\ORM\Cache\EntityCacheKey;
|
||||
* Specific read-write entity persister
|
||||
*
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @since 2.5
|
||||
*/
|
||||
class ReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
@@ -100,21 +101,24 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
*/
|
||||
public function delete($entity)
|
||||
{
|
||||
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
|
||||
$lock = $this->region->lock($key);
|
||||
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
|
||||
$lock = $this->region->lock($key);
|
||||
$deleted = $this->persister->delete($entity);
|
||||
|
||||
if ($this->persister->delete($entity)) {
|
||||
if ($deleted) {
|
||||
$this->region->evict($key);
|
||||
}
|
||||
|
||||
if ($lock === null) {
|
||||
return;
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
$this->queuedCache['delete'][] = array(
|
||||
'lock' => $lock,
|
||||
'key' => $key
|
||||
);
|
||||
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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];
|
||||
@@ -357,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 {
|
||||
|
||||
@@ -122,6 +122,9 @@ class SimpleObjectHydrator extends AbstractHydrator
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if value is null before conversion (because some types convert null to something else)
|
||||
$valueIsNull = null === $value;
|
||||
|
||||
// Convert field to a valid PHP value
|
||||
if (isset($cacheKeyInfo['type'])) {
|
||||
$type = $cacheKeyInfo['type'];
|
||||
@@ -131,7 +134,7 @@ class SimpleObjectHydrator extends AbstractHydrator
|
||||
$fieldName = $cacheKeyInfo['fieldName'];
|
||||
|
||||
// Prevent overwrite in case of inherit classes using same property name (See AbstractHydrator)
|
||||
if ( ! isset($data[$fieldName]) || $value !== null) {
|
||||
if ( ! isset($data[$fieldName]) || ! $valueIsNull) {
|
||||
$data[$fieldName] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -940,8 +940,13 @@ class ClassMetadataInfo implements ClassMetadata
|
||||
continue;
|
||||
}
|
||||
|
||||
$parentReflFields[$property] = $reflService->getAccessibleProperty($this->name, $property);
|
||||
$this->reflFields[$property] = $reflService->getAccessibleProperty($this->name, $property);
|
||||
$fieldRefl = $reflService->getAccessibleProperty(
|
||||
isset($embeddedClass['declared']) ? $embeddedClass['declared'] : $this->name,
|
||||
$property
|
||||
);
|
||||
|
||||
$parentReflFields[$property] = $fieldRefl;
|
||||
$this->reflFields[$property] = $fieldRefl;
|
||||
}
|
||||
|
||||
foreach ($this->fieldMappings as $field => $mapping) {
|
||||
|
||||
@@ -514,9 +514,8 @@ class YamlDriver extends FileDriver
|
||||
if ( ! isset($joinColumnElement['name'])) {
|
||||
$joinColumnElement['name'] = $joinColumnName;
|
||||
}
|
||||
$joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement);
|
||||
}
|
||||
|
||||
$joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumnElement);
|
||||
}
|
||||
|
||||
if (isset($joinTableElement['inverseJoinColumns'])) {
|
||||
@@ -524,9 +523,8 @@ class YamlDriver extends FileDriver
|
||||
if ( ! isset($joinColumnElement['name'])) {
|
||||
$joinColumnElement['name'] = $joinColumnName;
|
||||
}
|
||||
$joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement);
|
||||
}
|
||||
|
||||
$joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumnElement);
|
||||
}
|
||||
|
||||
$mapping['joinTable'] = $joinTable;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -965,7 +965,7 @@ class Parser
|
||||
$schemaName = $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
|
||||
}
|
||||
|
||||
$exists = class_exists($schemaName, true);
|
||||
$exists = class_exists($schemaName, true) || interface_exists($schemaName, true);
|
||||
|
||||
if ( ! $exists) {
|
||||
$this->semanticalError("Class '$schemaName' is not defined.", $this->lexer->token);
|
||||
|
||||
@@ -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).
|
||||
@@ -438,8 +438,8 @@ class ResultSetMappingBuilder extends ResultSetMapping
|
||||
$sql .= $class->fieldMappings[$this->fieldMappings[$columnName]]['columnName'];
|
||||
} else if (isset($this->metaMappings[$columnName])) {
|
||||
$sql .= $this->metaMappings[$columnName];
|
||||
} else if (isset($this->discriminatorColumn[$columnName])) {
|
||||
$sql .= $this->discriminatorColumn[$columnName];
|
||||
} else if (isset($this->discriminatorColumns[$dqlAlias])) {
|
||||
$sql .= $this->discriminatorColumns[$dqlAlias];
|
||||
}
|
||||
|
||||
$sql .= " AS " . $columnName;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -2264,7 +2282,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
|
||||
|
||||
@@ -84,10 +84,6 @@ class MetadataFilter extends \FilterIterator implements \Countable
|
||||
);
|
||||
}
|
||||
|
||||
if ($pregResult === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($pregResult) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -438,7 +438,10 @@ class LimitSubqueryOutputWalker extends SqlWalker
|
||||
// Field was declared in a parent class, so we need to get the proper SQL table alias
|
||||
// for the joined parent table.
|
||||
$otherClassMetadata = $this->em->getClassMetadata($fieldMapping['declared']);
|
||||
$sqlTableAliasForFieldAlias = $this->getSQLTableAlias($otherClassMetadata->getTableName(), $dqlAliasForFieldAlias);
|
||||
if (!$otherClassMetadata->isMappedSuperclass) {
|
||||
$sqlTableAliasForFieldAlias = $this->getSQLTableAlias($otherClassMetadata->getTableName(), $dqlAliasForFieldAlias);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Compose search/replace patterns
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace Doctrine\ORM\Tools;
|
||||
|
||||
use Doctrine\ORM\ORMException;
|
||||
use Doctrine\DBAL\Schema\Comparator;
|
||||
use Doctrine\DBAL\Schema\Index;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
use Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector;
|
||||
@@ -274,6 +275,15 @@ class SchemaTool
|
||||
|
||||
if (isset($class->table['uniqueConstraints'])) {
|
||||
foreach ($class->table['uniqueConstraints'] as $indexName => $indexData) {
|
||||
$uniqIndex = new Index($indexName, $indexData['columns'], true, false, [], isset($indexData['options']) ? $indexData['options'] : []);
|
||||
|
||||
foreach ($table->getIndexes() as $tableIndexName => $tableIndex) {
|
||||
if ($tableIndex->isFullfilledBy($uniqIndex)) {
|
||||
$table->dropIndex($tableIndexName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$table->addUniqueIndex($indexData['columns'], is_numeric($indexName) ? null : $indexName, isset($indexData['options']) ? $indexData['options'] : array());
|
||||
}
|
||||
}
|
||||
@@ -335,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;
|
||||
|
||||
@@ -733,7 +733,6 @@ class UnitOfWork implements PropertyChangedListener
|
||||
|
||||
// Look for changes in associations of the entity
|
||||
foreach ($class->associationMappings as $field => $assoc) {
|
||||
|
||||
if (($val = $class->reflFields[$field]->getValue($entity)) === null) {
|
||||
continue;
|
||||
}
|
||||
@@ -799,7 +798,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
// Only MANAGED entities that are NOT SCHEDULED FOR INSERTION OR DELETION are processed here.
|
||||
$oid = spl_object_hash($entity);
|
||||
|
||||
if ( ! isset($this->entityInsertions[$oid]) && ! isset($this->entityDeletions[$oid]) && isset($this->entityStates[$oid])) {
|
||||
if ( ! isset($this->entityInsertions[$oid]) && ! isset($this->entityDeletions[$oid]) && isset($this->entityStates[$oid])) {
|
||||
$this->computeChangeSet($class, $entity);
|
||||
}
|
||||
}
|
||||
@@ -826,10 +825,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
if ($value instanceof PersistentCollection && $value->isDirty()) {
|
||||
$coid = spl_object_hash($value);
|
||||
|
||||
if ($assoc['isOwningSide']) {
|
||||
$this->collectionUpdates[$coid] = $value;
|
||||
}
|
||||
|
||||
$this->collectionUpdates[$coid] = $value;
|
||||
$this->visitedCollections[$coid] = $value;
|
||||
}
|
||||
|
||||
@@ -1803,7 +1799,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
* @throws OptimisticLockException If the entity uses optimistic locking through a version
|
||||
* attribute and the version check against the managed copy fails.
|
||||
* @throws ORMInvalidArgumentException If the entity instance is NEW.
|
||||
* @throws EntityNotFoundException
|
||||
* @throws EntityNotFoundException if an assigned identifier is used in the entity, but none is provided
|
||||
*/
|
||||
private function doMerge($entity, array &$visited, $prevManagedCopy = null, $assoc = null)
|
||||
{
|
||||
@@ -1835,6 +1831,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
if ( ! $id) {
|
||||
$managedCopy = $this->newInstance($class);
|
||||
|
||||
$this->mergeEntityStateIntoManagedCopy($entity, $managedCopy);
|
||||
$this->persistNew($class, $managedCopy);
|
||||
} else {
|
||||
$flatId = ($class->containsForeignIdentifier)
|
||||
@@ -1866,31 +1863,16 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$managedCopy = $this->newInstance($class);
|
||||
$class->setIdentifierValues($managedCopy, $id);
|
||||
|
||||
$this->mergeEntityStateIntoManagedCopy($entity, $managedCopy);
|
||||
$this->persistNew($class, $managedCopy);
|
||||
}
|
||||
}
|
||||
|
||||
if ($class->isVersioned) {
|
||||
$reflField = $class->reflFields[$class->versionField];
|
||||
$managedCopyVersion = $reflField->getValue($managedCopy);
|
||||
$entityVersion = $reflField->getValue($entity);
|
||||
|
||||
// Throw exception if versions don't match.
|
||||
if ($managedCopyVersion != $entityVersion) {
|
||||
throw OptimisticLockException::lockFailedVersionMismatch($entity, $entityVersion, $managedCopyVersion);
|
||||
} else {
|
||||
$this->ensureVersionMatch($class, $entity, $managedCopy);
|
||||
$this->mergeEntityStateIntoManagedCopy($entity, $managedCopy);
|
||||
}
|
||||
}
|
||||
|
||||
$visited[$oid] = $managedCopy; // mark visited
|
||||
|
||||
if (!($entity instanceof Proxy && ! $entity->__isInitialized())) {
|
||||
if ($managedCopy instanceof Proxy && ! $managedCopy->__isInitialized()) {
|
||||
$managedCopy->__load();
|
||||
}
|
||||
|
||||
$this->mergeEntityStateIntoManagedCopy($entity, $managedCopy);
|
||||
}
|
||||
|
||||
if ($class->isChangeTrackingDeferredExplicit()) {
|
||||
$this->scheduleForDirtyCheck($entity);
|
||||
}
|
||||
@@ -1908,6 +1890,45 @@ class UnitOfWork implements PropertyChangedListener
|
||||
return $managedCopy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassMetadata $class
|
||||
* @param object $entity
|
||||
* @param object $managedCopy
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws OptimisticLockException
|
||||
*/
|
||||
private function ensureVersionMatch(ClassMetadata $class, $entity, $managedCopy)
|
||||
{
|
||||
if (! ($class->isVersioned && $this->isLoaded($managedCopy) && $this->isLoaded($entity))) {
|
||||
return;
|
||||
}
|
||||
|
||||
$reflField = $class->reflFields[$class->versionField];
|
||||
$managedCopyVersion = $reflField->getValue($managedCopy);
|
||||
$entityVersion = $reflField->getValue($entity);
|
||||
|
||||
// Throw exception if versions don't match.
|
||||
if ($managedCopyVersion == $entityVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw OptimisticLockException::lockFailedVersionMismatch($entity, $entityVersion, $managedCopyVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if an entity is loaded - must either be a loaded proxy or not a proxy
|
||||
*
|
||||
* @param object $entity
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isLoaded($entity)
|
||||
{
|
||||
return !($entity instanceof Proxy) || $entity->__isInitialized();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets/adds associated managed copies into the previous entity's association field
|
||||
*
|
||||
@@ -2395,17 +2416,8 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$this->commitOrderCalculator->clear();
|
||||
}
|
||||
} else {
|
||||
$visited = array();
|
||||
|
||||
foreach ($this->identityMap as $className => $entities) {
|
||||
if ($className !== $entityName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
$this->doDetach($entity, $visited, false);
|
||||
}
|
||||
}
|
||||
$this->clearIdentityMapForEntityName($entityName);
|
||||
$this->clearEntityInsertionsForEntityName($entityName);
|
||||
}
|
||||
|
||||
if ($this->evm->hasListeners(Events::onClear)) {
|
||||
@@ -3357,6 +3369,14 @@ class UnitOfWork implements PropertyChangedListener
|
||||
*/
|
||||
private function mergeEntityStateIntoManagedCopy($entity, $managedCopy)
|
||||
{
|
||||
if (! $this->isLoaded($entity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->isLoaded($managedCopy)) {
|
||||
$managedCopy->__load();
|
||||
}
|
||||
|
||||
$class = $this->em->getClassMetadata(get_class($entity));
|
||||
|
||||
foreach ($this->reflectionPropertiesGetter->getProperties($class->name) as $prop) {
|
||||
@@ -3419,8 +3439,6 @@ class UnitOfWork implements PropertyChangedListener
|
||||
);
|
||||
$managedCol->setOwner($managedCopy, $assoc2);
|
||||
$prop->setValue($managedCopy, $managedCol);
|
||||
|
||||
$this->originalEntityData[spl_object_hash($entity)][$name] = $managedCol;
|
||||
}
|
||||
|
||||
if ($assoc2['isCascadeMerge']) {
|
||||
@@ -3459,4 +3477,32 @@ class UnitOfWork implements PropertyChangedListener
|
||||
{
|
||||
$this->hydrationCompleteHandler->hydrationComplete();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $entityName
|
||||
*/
|
||||
private function clearIdentityMapForEntityName($entityName)
|
||||
{
|
||||
if (! isset($this->identityMap[$entityName])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$visited = [];
|
||||
|
||||
foreach ($this->identityMap[$entityName] as $entity) {
|
||||
$this->doDetach($entity, $visited, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $entityName
|
||||
*/
|
||||
private function clearEntityInsertionsForEntityName($entityName)
|
||||
{
|
||||
foreach ($this->entityInsertions as $hash => $entity) {
|
||||
if (get_class($entity) === $entityName) {
|
||||
unset($this->entityInsertions[$hash]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ class Version
|
||||
/**
|
||||
* Current Doctrine Version
|
||||
*/
|
||||
const VERSION = '2.5.1';
|
||||
const VERSION = '2.5.8';
|
||||
|
||||
/**
|
||||
* Compares a Doctrine version with the current one.
|
||||
|
||||
@@ -33,7 +33,7 @@ class State
|
||||
protected $country;
|
||||
|
||||
/**
|
||||
* @Cache
|
||||
* @Cache("NONSTRICT_READ_WRITE")
|
||||
* @OneToMany(targetEntity="City", mappedBy="state")
|
||||
*/
|
||||
protected $cities;
|
||||
|
||||
@@ -26,7 +26,7 @@ class Traveler
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @Cache()
|
||||
* @Cache("NONSTRICT_READ_WRITE")
|
||||
* @OneToMany(targetEntity="Travel", mappedBy="traveler", cascade={"persist", "remove"}, orphanRemoval=true)
|
||||
*
|
||||
* @var \Doctrine\Common\Collections\Collection
|
||||
|
||||
@@ -6,17 +6,17 @@ class CompanyContractListener
|
||||
{
|
||||
public $postPersistCalls;
|
||||
public $prePersistCalls;
|
||||
|
||||
|
||||
public $postUpdateCalls;
|
||||
public $preUpdateCalls;
|
||||
|
||||
|
||||
public $postRemoveCalls;
|
||||
public $preRemoveCalls;
|
||||
|
||||
public $preFlushCalls;
|
||||
|
||||
|
||||
public $postLoadCalls;
|
||||
|
||||
|
||||
/**
|
||||
* @PostPersist
|
||||
*/
|
||||
@@ -80,5 +80,4 @@ class CompanyContractListener
|
||||
{
|
||||
$this->postLoadCalls[] = func_get_args();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
78
tests/Doctrine/Tests/Models/DDC3711/DDC3711EntityA.php
Normal file
78
tests/Doctrine/Tests/Models/DDC3711/DDC3711EntityA.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author Marc Pantel <pantel.m@gmail.com>
|
||||
*/
|
||||
namespace Doctrine\Tests\Models\DDC3711;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
class DDC3711EntityA
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $id1;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $id2;
|
||||
|
||||
/**
|
||||
* @var ArrayCollection
|
||||
*/
|
||||
private $entityB;
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getId1()
|
||||
{
|
||||
return $this->id1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $id1
|
||||
*/
|
||||
public function setId1($id1)
|
||||
{
|
||||
$this->id1 = $id1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getId2()
|
||||
{
|
||||
return $this->id2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $id2
|
||||
*/
|
||||
public function setId2($id2)
|
||||
{
|
||||
$this->id2 = $id2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArrayCollection
|
||||
*/
|
||||
public function getEntityB()
|
||||
{
|
||||
return $this->entityB;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ArrayCollection $entityB
|
||||
*
|
||||
* @return DDC3711EntityA
|
||||
*/
|
||||
public function addEntityB($entityB)
|
||||
{
|
||||
$this->entityB[] = $entityB;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
75
tests/Doctrine/Tests/Models/DDC3711/DDC3711EntityB.php
Normal file
75
tests/Doctrine/Tests/Models/DDC3711/DDC3711EntityB.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author Marc Pantel <pantel.m@gmail.com>
|
||||
*/
|
||||
namespace Doctrine\Tests\Models\DDC3711;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
class DDC3711EntityB
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $id1;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $id2;
|
||||
|
||||
/**
|
||||
* @var ArrayCollection
|
||||
*/
|
||||
private $entityA;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId1()
|
||||
{
|
||||
return $this->id1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id1
|
||||
*/
|
||||
public function setId1($id1)
|
||||
{
|
||||
$this->id1 = $id1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId2()
|
||||
{
|
||||
return $this->id2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id2
|
||||
*/
|
||||
public function setId2($id2)
|
||||
{
|
||||
$this->id2 = $id2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArrayCollection
|
||||
*/
|
||||
public function getEntityA()
|
||||
{
|
||||
return $this->entityA;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ArrayCollection $entityA
|
||||
*/
|
||||
public function addEntityA($entityA)
|
||||
{
|
||||
$this->entityA[] = $entityA;
|
||||
}
|
||||
|
||||
}
|
||||
25
tests/Doctrine/Tests/Models/DDC3899/DDC3899Contract.php
Normal file
25
tests/Doctrine/Tests/Models/DDC3899/DDC3899Contract.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Models\DDC3899;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @Table(name="dc3899_contracts")
|
||||
* @InheritanceType("SINGLE_TABLE")
|
||||
* @DiscriminatorColumn(name="discr", type="string")
|
||||
* @DiscriminatorMap({
|
||||
* "fix" = "DDC3899FixContract",
|
||||
* "flexible" = "DDC3899FlexContract"
|
||||
* })
|
||||
*/
|
||||
abstract class DDC3899Contract
|
||||
{
|
||||
/** @Id @Column(type="integer") */
|
||||
public $id;
|
||||
|
||||
/** @Column(type="boolean") */
|
||||
public $completed = false;
|
||||
|
||||
/** @ManyToOne(targetEntity="DDC3899User", inversedBy="contract") */
|
||||
public $user;
|
||||
}
|
||||
12
tests/Doctrine/Tests/Models/DDC3899/DDC3899FixContract.php
Normal file
12
tests/Doctrine/Tests/Models/DDC3899/DDC3899FixContract.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Models\DDC3899;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class DDC3899FixContract extends DDC3899Contract
|
||||
{
|
||||
/** @column(type="integer") */
|
||||
public $fixPrice = 0;
|
||||
}
|
||||
15
tests/Doctrine/Tests/Models/DDC3899/DDC3899FlexContract.php
Normal file
15
tests/Doctrine/Tests/Models/DDC3899/DDC3899FlexContract.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Models\DDC3899;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class DDC3899FlexContract extends DDC3899Contract
|
||||
{
|
||||
/** @column(type="integer") */
|
||||
public $hoursWorked = 0;
|
||||
|
||||
/** @column(type="integer") */
|
||||
public $pricePerHour = 0;
|
||||
}
|
||||
16
tests/Doctrine/Tests/Models/DDC3899/DDC3899User.php
Normal file
16
tests/Doctrine/Tests/Models/DDC3899/DDC3899User.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Models\DDC3899;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @Table(name="dc3899_users")
|
||||
*/
|
||||
class DDC3899User
|
||||
{
|
||||
/** @Id @Column(type="integer") */
|
||||
public $id;
|
||||
|
||||
/** @OneToMany(targetEntity="DDC3899Contract", mappedBy="user") */
|
||||
public $contracts;
|
||||
}
|
||||
@@ -9,6 +9,8 @@ namespace Doctrine\Tests\Models\GeoNames;
|
||||
*/
|
||||
class Country
|
||||
{
|
||||
const CLASSNAME = __CLASS__;
|
||||
|
||||
/**
|
||||
* @Id
|
||||
* @Column(type="string", length=2)
|
||||
|
||||
17
tests/Doctrine/Tests/Models/Issue5989/Issue5989Employee.php
Normal file
17
tests/Doctrine/Tests/Models/Issue5989/Issue5989Employee.php
Normal file
@@ -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;
|
||||
}
|
||||
17
tests/Doctrine/Tests/Models/Issue5989/Issue5989Manager.php
Normal file
17
tests/Doctrine/Tests/Models/Issue5989/Issue5989Manager.php
Normal file
@@ -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;
|
||||
}
|
||||
26
tests/Doctrine/Tests/Models/Issue5989/Issue5989Person.php
Normal file
26
tests/Doctrine/Tests/Models/Issue5989/Issue5989Person.php
Normal file
@@ -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;
|
||||
}
|
||||
37
tests/Doctrine/Tests/Models/VersionedManyToOne/Article.php
Normal file
37
tests/Doctrine/Tests/Models/VersionedManyToOne/Article.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Models\VersionedManyToOne;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @Table(name="versioned_many_to_one_article")
|
||||
*/
|
||||
class Article
|
||||
{
|
||||
const CLASSNAME = __CLASS__;
|
||||
|
||||
/**
|
||||
* @Id
|
||||
* @Column(name="id", type="integer")
|
||||
* @GeneratedValue(strategy="AUTO")
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* @Column(name="name")
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @ManyToOne(targetEntity="Category", cascade={"merge", "persist"})
|
||||
*/
|
||||
public $category;
|
||||
|
||||
/**
|
||||
* Version column
|
||||
*
|
||||
* @Column(type="integer", name="version")
|
||||
* @Version
|
||||
*/
|
||||
public $version;
|
||||
}
|
||||
27
tests/Doctrine/Tests/Models/VersionedManyToOne/Category.php
Normal file
27
tests/Doctrine/Tests/Models/VersionedManyToOne/Category.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\Models\VersionedManyToOne;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @Table(name="versioned_many_to_one_category")
|
||||
*/
|
||||
class Category
|
||||
{
|
||||
const CLASSNAME = __CLASS__;
|
||||
|
||||
/**
|
||||
* @Id
|
||||
* @Column(name="id", type="integer")
|
||||
* @GeneratedValue(strategy="AUTO")
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* Version column
|
||||
*
|
||||
* @Column(type="integer", name="version")
|
||||
* @Version
|
||||
*/
|
||||
public $version;
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Doctrine\Tests\ORM\Cache;
|
||||
|
||||
use Doctrine\Tests\DoctrineTestCase;
|
||||
use Doctrine\ORM\Cache\CacheConfiguration;
|
||||
use Doctrine\Tests\DoctrineTestCase;
|
||||
|
||||
/**
|
||||
* @group DDC-2183
|
||||
@@ -64,6 +64,11 @@ class CacheConfigTest extends DoctrineTestCase
|
||||
|
||||
public function testSetGetQueryValidator()
|
||||
{
|
||||
$factory = $this->getMock('Doctrine\ORM\Cache\CacheFactory');
|
||||
$factory->method('getTimestampRegion')->willReturn($this->getMock('Doctrine\ORM\Cache\TimestampRegion'));
|
||||
|
||||
$this->config->setCacheFactory($factory);
|
||||
|
||||
$validator = $this->getMock('Doctrine\ORM\Cache\QueryCacheValidator');
|
||||
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\TimestampQueryCacheValidator', $this->config->getQueryValidator());
|
||||
@@ -72,4 +77,4 @@ class CacheConfigTest extends DoctrineTestCase
|
||||
|
||||
$this->assertEquals($validator, $this->config->getQueryValidator());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ class DefaultEntityHydratorTest extends OrmTestCase
|
||||
$this->assertArrayHasKey('name', $cache->data);
|
||||
$this->assertArrayHasKey('country', $cache->data);
|
||||
$this->assertEquals(array(
|
||||
'id' => 11,
|
||||
'id' => 12,
|
||||
'name' => 'Bar',
|
||||
'country' => new AssociationCacheEntry(Country::CLASSNAME, array('id' => 11)),
|
||||
), $cache->data);
|
||||
@@ -147,9 +147,39 @@ class DefaultEntityHydratorTest extends OrmTestCase
|
||||
$this->assertArrayHasKey('name', $cache->data);
|
||||
$this->assertArrayHasKey('country', $cache->data);
|
||||
$this->assertEquals(array(
|
||||
'id' => 11,
|
||||
'id' => 12,
|
||||
'name' => 'Bar',
|
||||
'country' => new AssociationCacheEntry(Country::CLASSNAME, array('id' => 11)),
|
||||
), $cache->data);
|
||||
}
|
||||
}
|
||||
|
||||
public function testCacheEntryWithWrongIdentifierType()
|
||||
{
|
||||
$proxy = $this->em->getReference(Country::CLASSNAME, 11);
|
||||
$entity = new State('Bat', $proxy);
|
||||
$uow = $this->em->getUnitOfWork();
|
||||
$entityData = array('id'=> 12, 'name'=>'Bar', 'country' => $proxy);
|
||||
$metadata = $this->em->getClassMetadata(State::CLASSNAME);
|
||||
$key = new EntityCacheKey($metadata->name, array('id'=>'12'));
|
||||
|
||||
$entity->setId(12);
|
||||
|
||||
$uow->registerManaged($entity, array('id'=>12), $entityData);
|
||||
|
||||
$cache = $this->structure->buildCacheEntry($metadata, $key, $entity);
|
||||
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\CacheEntry', $cache);
|
||||
$this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheEntry', $cache);
|
||||
|
||||
$this->assertArrayHasKey('id', $cache->data);
|
||||
$this->assertArrayHasKey('name', $cache->data);
|
||||
$this->assertArrayHasKey('country', $cache->data);
|
||||
$this->assertSame($entity->getId(), $cache->data['id']);
|
||||
$this->assertEquals(array(
|
||||
'id' => 12,
|
||||
'name' => 'Bar',
|
||||
'country' => new AssociationCacheEntry(Country::CLASSNAME, array('id' => 11)),
|
||||
), $cache->data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -386,7 +386,7 @@ class DefaultQueryCacheTest extends OrmTestCase
|
||||
array('id'=>2, 'name' => 'Bar')
|
||||
);
|
||||
|
||||
$entry->time = time() - 100;
|
||||
$entry->time = microtime(true) - 100;
|
||||
|
||||
$this->region->addReturn('get', $entry);
|
||||
$this->region->addReturn('get', new EntityCacheEntry(Country::CLASSNAME, $entities[0]));
|
||||
|
||||
@@ -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!";
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\ORM\OptimisticLockException;
|
||||
use Doctrine\ORM\ORMException;
|
||||
use Doctrine\Tests\Models\VersionedManyToOne\Article;
|
||||
use Doctrine\Tests\Models\VersionedManyToOne\Category;
|
||||
|
||||
/**
|
||||
* @group MergeVersionedOneToMany
|
||||
*/
|
||||
class MergeVersionedManyToOneTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp()
|
||||
{
|
||||
$this->useModelSet('versioned_many_to_one');
|
||||
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* This test case asserts that a detached and unmodified entity could be merge without firing
|
||||
* OptimisticLockException.
|
||||
*/
|
||||
public function testSetVersionOnCreate()
|
||||
{
|
||||
$category = new Category();
|
||||
$article = new Article();
|
||||
|
||||
$article->name = 'Article';
|
||||
$article->category = $category;
|
||||
|
||||
$this->_em->persist($article);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$articleMerged = $this->_em->merge($article);
|
||||
|
||||
$articleMerged->name = 'Article Merged';
|
||||
|
||||
$this->_em->flush();
|
||||
$this->assertEquals(2, $articleMerged->version);
|
||||
}
|
||||
}
|
||||
@@ -791,4 +791,20 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
|
||||
$this->assertSQLEquals('u.id AS id0, u.status AS status1, u.username AS username2, u.name AS name3, u.email_id AS email_id4', (string)$rsm);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-3899
|
||||
*/
|
||||
public function testGenerateSelectClauseWithDiscriminatorColumn()
|
||||
{
|
||||
$rsm = new ResultSetMappingBuilder($this->_em, ResultSetMappingBuilder::COLUMN_RENAMING_INCREMENT);
|
||||
$rsm->addEntityResult('Doctrine\Tests\Models\DDC3899\DDC3899User', 'u');
|
||||
$rsm->addJoinedEntityResult('Doctrine\Tests\Models\DDC3899\DDC3899FixContract', 'c', 'u', 'contracts');
|
||||
$rsm->addFieldResult('u', $this->platform->getSQLResultCasing('id'), 'id');
|
||||
$rsm->setDiscriminatorColumn('c', $this->platform->getSQLResultCasing('discr'));
|
||||
|
||||
$selectClause = $rsm->generateSelectClause(array('u' => 'u1', 'c' => 'c1'));
|
||||
|
||||
$this->assertSQLEquals('u1.id as id, c1.discr as discr', $selectClause);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,12 +45,12 @@ class QueryCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
$query->setQueryCacheDriver($cache);
|
||||
|
||||
$query->getResult();
|
||||
$this->assertEquals(2, $this->getCacheSize($cache));
|
||||
$this->assertEquals(1, $this->getCacheSize($cache));
|
||||
|
||||
$query->setHint('foo', 'bar');
|
||||
|
||||
$query->getResult();
|
||||
$this->assertEquals(3, $this->getCacheSize($cache));
|
||||
$this->assertEquals(2, $this->getCacheSize($cache));
|
||||
|
||||
return $query;
|
||||
}
|
||||
@@ -105,16 +105,16 @@ class QueryCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
|
||||
$query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux');
|
||||
|
||||
$cache = new \Doctrine\Common\Cache\ArrayCache();
|
||||
$cache = $this->getMock('Doctrine\Common\Cache\Cache');
|
||||
|
||||
$query->setQueryCacheDriver($cache);
|
||||
|
||||
$users = $query->getResult();
|
||||
$cache
|
||||
->expects(self::once())
|
||||
->method('save')
|
||||
->with(self::isType('string'), self::isInstanceOf('Doctrine\ORM\Query\ParserResult'));
|
||||
|
||||
$data = $this->cacheDataReflection->getValue($cache);
|
||||
$this->assertEquals(2, count($data));
|
||||
|
||||
$this->assertInstanceOf('Doctrine\ORM\Query\ParserResult', array_pop($data));
|
||||
$query->getResult();
|
||||
}
|
||||
|
||||
public function testQueryCache_HitDoesNotSaveParserResult()
|
||||
|
||||
@@ -141,7 +141,7 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
|
||||
$this->assertEquals(0, $this->getCacheSize($cache));
|
||||
$query->getResult();
|
||||
$this->assertEquals(2, $this->getCacheSize($cache));
|
||||
$this->assertEquals(1, $this->getCacheSize($cache));
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
@@ -343,18 +343,18 @@ class SQLFilterTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
$query->setQueryCacheDriver($cache);
|
||||
|
||||
$query->getResult();
|
||||
$this->assertEquals(2, sizeof($cacheDataReflection->getValue($cache)));
|
||||
$this->assertEquals(1, sizeof($cacheDataReflection->getValue($cache)));
|
||||
|
||||
$conf = $this->_em->getConfiguration();
|
||||
$conf->addFilter("locale", "\Doctrine\Tests\ORM\Functional\MyLocaleFilter");
|
||||
$this->_em->getFilters()->enable("locale");
|
||||
|
||||
$query->getResult();
|
||||
$this->assertEquals(3, sizeof($cacheDataReflection->getValue($cache)));
|
||||
$this->assertEquals(2, sizeof($cacheDataReflection->getValue($cache)));
|
||||
|
||||
// Another time doesn't add another cache entry
|
||||
$query->getResult();
|
||||
$this->assertEquals(3, sizeof($cacheDataReflection->getValue($cache)));
|
||||
$this->assertEquals(2, sizeof($cacheDataReflection->getValue($cache)));
|
||||
}
|
||||
|
||||
public function testQueryGeneration_DependsOnFilters()
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\Tests\Models\Cache\Attraction;
|
||||
use Doctrine\Tests\Models\Cache\AttractionInfo;
|
||||
use Doctrine\Tests\Models\Cache\AttractionContactInfo;
|
||||
use Doctrine\Tests\Models\Cache\AttractionInfo;
|
||||
use Doctrine\Tests\Models\Cache\AttractionLocationInfo;
|
||||
|
||||
/**
|
||||
@@ -188,4 +188,47 @@ class SecondLevelCacheJoinTableInheritanceTest extends SecondLevelCacheAbstractT
|
||||
$this->assertInstanceOf(AttractionContactInfo::CLASSNAME, $entity->getInfos()->get(0));
|
||||
$this->assertEquals($this->attractionsInfo[0]->getFone(), $entity->getInfos()->get(0)->getFone());
|
||||
}
|
||||
}
|
||||
|
||||
public function testQueryCacheShouldBeEvictedOnTimestampUpdate()
|
||||
{
|
||||
$this->loadFixturesCountries();
|
||||
$this->loadFixturesStates();
|
||||
$this->loadFixturesCities();
|
||||
$this->loadFixturesAttractions();
|
||||
$this->loadFixturesAttractionsInfo();
|
||||
$this->evictRegions();
|
||||
$this->_em->clear();
|
||||
|
||||
$queryCount = $this->getCurrentQueryCount();
|
||||
$dql = 'SELECT attractionInfo FROM Doctrine\Tests\Models\Cache\AttractionInfo attractionInfo';
|
||||
|
||||
$result1 = $this->_em->createQuery($dql)
|
||||
->setCacheable(true)
|
||||
->getResult();
|
||||
|
||||
$this->assertCount(count($this->attractionsInfo), $result1);
|
||||
$this->assertEquals($queryCount + 5, $this->getCurrentQueryCount());
|
||||
|
||||
$contact = new AttractionContactInfo(
|
||||
'1234-1234',
|
||||
$this->_em->find(Attraction::CLASSNAME, $this->attractions[5]->getId())
|
||||
);
|
||||
|
||||
$this->_em->persist($contact);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$queryCount = $this->getCurrentQueryCount();
|
||||
|
||||
$result2 = $this->_em->createQuery($dql)
|
||||
->setCacheable(true)
|
||||
->getResult();
|
||||
|
||||
$this->assertCount(count($this->attractionsInfo) + 1, $result2);
|
||||
$this->assertEquals($queryCount + 6, $this->getCurrentQueryCount());
|
||||
|
||||
foreach ($result2 as $entity) {
|
||||
$this->assertInstanceOf(AttractionInfo::CLASSNAME, $entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\Tests\Models\Cache\City;
|
||||
use Doctrine\Tests\Models\Cache\ComplexAction;
|
||||
use Doctrine\Tests\Models\Cache\Country;
|
||||
use Doctrine\Tests\Models\Cache\State;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\Tests\Models\Cache\Token;
|
||||
use Doctrine\Tests\Models\Cache\Action;
|
||||
|
||||
@@ -98,6 +98,40 @@ class SecondLevelCacheManyToOneTest extends SecondLevelCacheAbstractTest
|
||||
$this->assertEquals($this->states[1]->getCountry()->getName(), $c4->getCountry()->getName());
|
||||
}
|
||||
|
||||
public function testInverseSidePutShouldEvictCollection()
|
||||
{
|
||||
$this->loadFixturesCountries();
|
||||
$this->loadFixturesStates();
|
||||
|
||||
$this->_em->clear();
|
||||
|
||||
$this->cache->evictEntityRegion(State::CLASSNAME);
|
||||
$this->cache->evictEntityRegion(Country::CLASSNAME);
|
||||
|
||||
//evict collection on add
|
||||
$c3 = $this->_em->find(State::CLASSNAME, $this->states[0]->getId());
|
||||
$prev = $c3->getCities();
|
||||
$count = $prev->count();
|
||||
$city = new City("Buenos Aires", $c3);
|
||||
|
||||
$c3->addCity($city);
|
||||
|
||||
$this->_em->persist($city);
|
||||
$this->_em->persist($c3);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$state = $this->_em->find(State::CLASSNAME, $c3->getId());
|
||||
$queryCount = $this->getCurrentQueryCount();
|
||||
|
||||
// Association was cleared from EM
|
||||
$this->assertNotEquals($prev, $state->getCities());
|
||||
|
||||
// New association has one more item (cache was evicted)
|
||||
$this->assertEquals($count + 1, $state->getCities()->count());
|
||||
$this->assertEquals($queryCount, $this->getCurrentQueryCount());
|
||||
}
|
||||
|
||||
public function testShouldNotReloadWhenAssociationIsMissing()
|
||||
{
|
||||
$this->loadFixturesCountries();
|
||||
|
||||
@@ -14,18 +14,18 @@ use Doctrine\Tests\Models\Cache\Traveler;
|
||||
*/
|
||||
class SecondLevelCacheOneToManyTest extends SecondLevelCacheAbstractTest
|
||||
{
|
||||
public function testShouldNotPutCollectionInverseSideOnPersist()
|
||||
public function testShouldPutCollectionInverseSideOnPersist()
|
||||
{
|
||||
$this->loadFixturesCountries();
|
||||
$this->loadFixturesStates();
|
||||
$this->loadFixturesCities();
|
||||
|
||||
$this->_em->clear();
|
||||
|
||||
$this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[0]->getId()));
|
||||
$this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[1]->getId()));
|
||||
|
||||
$this->assertFalse($this->cache->containsCollection(State::CLASSNAME, 'cities', $this->states[0]->getId()));
|
||||
$this->assertFalse($this->cache->containsCollection(State::CLASSNAME, 'cities', $this->states[1]->getId()));
|
||||
$this->assertTrue($this->cache->containsCollection(State::CLASSNAME, 'cities', $this->states[0]->getId()));
|
||||
$this->assertTrue($this->cache->containsCollection(State::CLASSNAME, 'cities', $this->states[1]->getId()));
|
||||
}
|
||||
|
||||
public function testPutAndLoadOneToManyRelation()
|
||||
@@ -187,6 +187,7 @@ class SecondLevelCacheOneToManyTest extends SecondLevelCacheAbstractTest
|
||||
$this->loadFixturesCountries();
|
||||
$this->loadFixturesStates();
|
||||
$this->loadFixturesCities();
|
||||
|
||||
$this->_em->clear();
|
||||
$this->secondLevelCacheLogger->clearStats();
|
||||
|
||||
@@ -247,8 +248,8 @@ class SecondLevelCacheOneToManyTest extends SecondLevelCacheAbstractTest
|
||||
$this->_em->remove($city0);
|
||||
$this->_em->persist($state);
|
||||
$this->_em->flush();
|
||||
|
||||
$this->_em->clear();
|
||||
|
||||
$this->secondLevelCacheLogger->clearStats();
|
||||
|
||||
$queryCount = $this->getCurrentQueryCount();
|
||||
@@ -261,19 +262,19 @@ class SecondLevelCacheOneToManyTest extends SecondLevelCacheAbstractTest
|
||||
$this->assertInstanceOf(City::CLASSNAME, $city1);
|
||||
$this->assertEquals($entity->getCities()->get(1)->getName(), $city1->getName());
|
||||
|
||||
$this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount());
|
||||
$this->assertEquals(2, $this->secondLevelCacheLogger->getHitCount());
|
||||
$this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(State::CLASSNAME)));
|
||||
$this->assertEquals(0, $this->secondLevelCacheLogger->getRegionHitCount($this->getCollectionRegion(State::CLASSNAME, 'cities')));
|
||||
$this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getCollectionRegion(State::CLASSNAME, 'cities')));
|
||||
|
||||
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
|
||||
$this->assertEquals($queryCount, $this->getCurrentQueryCount());
|
||||
|
||||
$state->getCities()->remove(0);
|
||||
|
||||
$this->_em->remove($city1);
|
||||
$this->_em->persist($state);
|
||||
$this->_em->flush();
|
||||
|
||||
$this->_em->clear();
|
||||
|
||||
$this->secondLevelCacheLogger->clearStats();
|
||||
|
||||
$queryCount = $this->getCurrentQueryCount();
|
||||
@@ -281,9 +282,9 @@ class SecondLevelCacheOneToManyTest extends SecondLevelCacheAbstractTest
|
||||
|
||||
$this->assertCount(0, $state->getCities());
|
||||
|
||||
$this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount());
|
||||
$this->assertEquals(2, $this->secondLevelCacheLogger->getHitCount());
|
||||
$this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(State::CLASSNAME)));
|
||||
$this->assertEquals(0, $this->secondLevelCacheLogger->getRegionHitCount($this->getCollectionRegion(State::CLASSNAME, 'cities')));
|
||||
$this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getCollectionRegion(State::CLASSNAME, 'cities')));
|
||||
}
|
||||
|
||||
public function testOneToManyWithEmptyRelation()
|
||||
@@ -346,11 +347,12 @@ class SecondLevelCacheOneToManyTest extends SecondLevelCacheAbstractTest
|
||||
public function testCacheInitializeCollectionWithNewObjects()
|
||||
{
|
||||
$this->_em->clear();
|
||||
|
||||
$this->evictRegions();
|
||||
|
||||
$traveler = new Traveler("Doctrine Bot");
|
||||
|
||||
for ($i=0; $i<3; ++$i) {
|
||||
for ($i = 0; $i < 3; ++$i) {
|
||||
$traveler->getTravels()->add(new Travel($traveler));
|
||||
}
|
||||
|
||||
@@ -373,7 +375,7 @@ class SecondLevelCacheOneToManyTest extends SecondLevelCacheAbstractTest
|
||||
$this->assertFalse($entity->getTravels()->isInitialized());
|
||||
$this->assertCount(4, $entity->getTravels());
|
||||
$this->assertTrue($entity->getTravels()->isInitialized());
|
||||
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
|
||||
$this->assertEquals($queryCount, $this->getCurrentQueryCount());
|
||||
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
@@ -1035,4 +1035,37 @@ class SecondLevelCacheQueryCacheTest extends SecondLevelCacheAbstractTest
|
||||
->setCacheable(true)
|
||||
->getResult();
|
||||
}
|
||||
}
|
||||
|
||||
public function testQueryCacheShouldBeEvictedOnTimestampUpdate()
|
||||
{
|
||||
$this->loadFixturesCountries();
|
||||
$this->_em->clear();
|
||||
|
||||
$queryCount = $this->getCurrentQueryCount();
|
||||
$dql = 'SELECT country FROM Doctrine\Tests\Models\Cache\Country country';
|
||||
|
||||
$result1 = $this->_em->createQuery($dql)
|
||||
->setCacheable(true)
|
||||
->getResult();
|
||||
|
||||
$this->assertCount(2, $result1);
|
||||
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
|
||||
|
||||
$this->_em->persist(new Country('France'));
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$queryCount = $this->getCurrentQueryCount();
|
||||
|
||||
$result2 = $this->_em->createQuery($dql)
|
||||
->setCacheable(true)
|
||||
->getResult();
|
||||
|
||||
$this->assertCount(3, $result2);
|
||||
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
|
||||
|
||||
foreach ($result2 as $entity) {
|
||||
$this->assertInstanceOf(Country::CLASSNAME, $entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,4 +210,45 @@ class SecondLevelCacheSingleTableInheritanceTest extends SecondLevelCacheAbstrac
|
||||
$this->assertEquals($this->attractions[0]->getName(), $entity->getAttractions()->get(0)->getName());
|
||||
$this->assertEquals($this->attractions[1]->getName(), $entity->getAttractions()->get(1)->getName());
|
||||
}
|
||||
}
|
||||
|
||||
public function testQueryCacheShouldBeEvictedOnTimestampUpdate()
|
||||
{
|
||||
$this->loadFixturesCountries();
|
||||
$this->loadFixturesStates();
|
||||
$this->loadFixturesCities();
|
||||
$this->loadFixturesAttractions();
|
||||
$this->_em->clear();
|
||||
|
||||
$queryCount = $this->getCurrentQueryCount();
|
||||
$dql = 'SELECT attraction FROM Doctrine\Tests\Models\Cache\Attraction attraction';
|
||||
|
||||
$result1 = $this->_em->createQuery($dql)
|
||||
->setCacheable(true)
|
||||
->getResult();
|
||||
|
||||
$this->assertCount(count($this->attractions), $result1);
|
||||
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
|
||||
|
||||
$contact = new Beach(
|
||||
'Botafogo',
|
||||
$this->_em->find(City::CLASSNAME, $this->cities[1]->getId())
|
||||
);
|
||||
|
||||
$this->_em->persist($contact);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$queryCount = $this->getCurrentQueryCount();
|
||||
|
||||
$result2 = $this->_em->createQuery($dql)
|
||||
->setCacheable(true)
|
||||
->getResult();
|
||||
|
||||
$this->assertCount(count($this->attractions) + 1, $result2);
|
||||
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
|
||||
|
||||
foreach ($result2 as $entity) {
|
||||
$this->assertInstanceOf(Attraction::CLASSNAME, $entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
65
tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3146Test.php
Normal file
65
tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3146Test.php
Normal 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');
|
||||
}
|
||||
}
|
||||
93
tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3303Test.php
Normal file
93
tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3303Test.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
30
tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3711Test.php
Normal file
30
tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3711Test.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* @author Marc Pantel <pantel.m@gmail.com>
|
||||
*/
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\Tests\ORM\Mapping\YamlMappingDriverTest;
|
||||
|
||||
class DDC3711Test extends YamlMappingDriverTest
|
||||
{
|
||||
public function testCompositeKeyForJoinTableInManyToManyCreation()
|
||||
{
|
||||
$yamlDriver = $this->_loadDriver();
|
||||
|
||||
$em = $this->_getTestEntityManager();
|
||||
$em->getConfiguration()->setMetadataDriverImpl($yamlDriver);
|
||||
$factory = new \Doctrine\ORM\Mapping\ClassMetadataFactory();
|
||||
$factory->setEntityManager($em);
|
||||
|
||||
$entityA = new ClassMetadata('Doctrine\Tests\Models\DDC3711\DDC3711EntityA');
|
||||
$entityA = $factory->getMetadataFor('Doctrine\Tests\Models\DDC3711\DDC3711EntityA');
|
||||
|
||||
$this->assertEquals(array('link_a_id1' => "id1", 'link_a_id2' => "id2"), $entityA->associationMappings['entityB']['relationToSourceKeyColumns']);
|
||||
$this->assertEquals(array('link_b_id1' => "id1", 'link_b_id2' => "id2"), $entityA->associationMappings['entityB']['relationToTargetKeyColumns']);
|
||||
|
||||
}
|
||||
}
|
||||
35
tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3967Test.php
Normal file
35
tests/Doctrine/Tests/ORM/Functional/Ticket/DDC3967Test.php
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
193
tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php
Normal file
193
tests/Doctrine/Tests/ORM/Functional/Ticket/GH5762Test.php
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
152
tests/Doctrine/Tests/ORM/Functional/Ticket/GH6362Test.php
Normal file
152
tests/Doctrine/Tests/ORM/Functional/Ticket/GH6362Test.php
Normal 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;
|
||||
}
|
||||
81
tests/Doctrine/Tests/ORM/Functional/Ticket/GH6464Test.php
Normal file
81
tests/Doctrine/Tests/ORM/Functional/Ticket/GH6464Test.php
Normal 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__;
|
||||
}
|
||||
51
tests/Doctrine/Tests/ORM/Functional/Ticket/Issue5989Test.php
Normal file
51
tests/Doctrine/Tests/ORM/Functional/Ticket/Issue5989Test.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\Tests\Models\Issue5989\Issue5989Employee;
|
||||
use Doctrine\Tests\Models\Issue5989\Issue5989Manager;
|
||||
use Doctrine\Tests\Models\Issue5989\Issue5989Person;
|
||||
|
||||
/**
|
||||
* @group issue-5989
|
||||
*/
|
||||
class Issue5989Test extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
{
|
||||
public function setUp()
|
||||
{
|
||||
$this->useModelSet('issue5989');
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
public function testSimpleArrayTypeHydratedCorrectlyInJoinedInheritance()
|
||||
{
|
||||
$manager = new Issue5989Manager();
|
||||
|
||||
$managerTags = ['tag1', 'tag2'];
|
||||
$manager->tags = $managerTags;
|
||||
$this->_em->persist($manager);
|
||||
|
||||
$employee = new Issue5989Employee();
|
||||
|
||||
$employeeTags =['tag2', 'tag3'];
|
||||
$employee->tags = $employeeTags;
|
||||
$this->_em->persist($employee);
|
||||
|
||||
$this->_em->flush();
|
||||
|
||||
$managerId = $manager->id;
|
||||
$employeeId = $employee->id;
|
||||
|
||||
// clear entity manager so that $repository->find actually fetches them and uses the hydrator
|
||||
// instead of just returning the existing managed entities
|
||||
$this->_em->clear();
|
||||
|
||||
$repository = $this->_em->getRepository(Issue5989Person::CLASSNAME);
|
||||
|
||||
$manager = $repository->find($managerId);
|
||||
$employee = $repository->find($employeeId);
|
||||
|
||||
static::assertEquals($managerTags, $manager->tags);
|
||||
static::assertEquals($employeeTags, $employee->tags);
|
||||
}
|
||||
}
|
||||
@@ -86,4 +86,34 @@ class SimpleObjectHydratorTest extends HydrationTestCase
|
||||
$hydrator = new \Doctrine\ORM\Internal\Hydration\SimpleObjectHydrator($this->_em);
|
||||
$hydrator->hydrateAll($stmt, $rsm);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group issue-5989
|
||||
*/
|
||||
public function testNullValueShouldNotOverwriteFieldWithSameNameInJoinedInheritance()
|
||||
{
|
||||
$rsm = new ResultSetMapping;
|
||||
$rsm->addEntityResult('Doctrine\Tests\Models\Issue5989\Issue5989Person', 'p');
|
||||
$rsm->addFieldResult('p', 'p__id', 'id');
|
||||
$rsm->addFieldResult('p', 'm__tags', 'tags', 'Doctrine\Tests\Models\Issue5989\Issue5989Manager');
|
||||
$rsm->addFieldResult('p', 'e__tags', 'tags', 'Doctrine\Tests\Models\Issue5989\Issue5989Employee');
|
||||
$rsm->addMetaResult('p', 'discr', 'discr', false, 'string');
|
||||
$resultSet = array(
|
||||
array(
|
||||
'p__id' => '1',
|
||||
'm__tags' => 'tag1,tag2',
|
||||
'e__tags' => null,
|
||||
'discr' => 'manager'
|
||||
),
|
||||
);
|
||||
|
||||
$expectedEntity = new \Doctrine\Tests\Models\Issue5989\Issue5989Manager();
|
||||
$expectedEntity->id = 1;
|
||||
$expectedEntity->tags = ['tag1', 'tag2'];
|
||||
|
||||
$stmt = new HydratorMockStatement($resultSet);
|
||||
$hydrator = new \Doctrine\ORM\Internal\Hydration\SimpleObjectHydrator($this->_em);
|
||||
$result = $hydrator->hydrateAll($stmt, $rsm);
|
||||
$this->assertEquals($result[0], $expectedEntity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ class AnnotationDriverTest extends AbstractMappingDriverTest
|
||||
{
|
||||
$cm = new ClassMetadata('stdClass');
|
||||
$cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService);
|
||||
$reader = new \Doctrine\Common\Annotations\AnnotationReader(new \Doctrine\Common\Cache\ArrayCache());
|
||||
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
|
||||
$annotationDriver = new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($reader);
|
||||
|
||||
$this->setExpectedException('Doctrine\ORM\Mapping\MappingException');
|
||||
|
||||
@@ -49,7 +49,7 @@ abstract class AbstractDriverTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
$this->setExpectedException(
|
||||
'Doctrine\Common\Persistence\Mapping\MappingException',
|
||||
"No mapping file found named '".$this->dir."/Foo".$this->getFileExtension()."' for class 'MyNamespace\MySubnamespace\Entity\Foo'."
|
||||
"No mapping file found named"
|
||||
);
|
||||
|
||||
$driver = $this->getDriver(array(
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
Doctrine\Tests\Models\DDC3711\DDC3711EntityA:
|
||||
type: entity
|
||||
table: ddc3711.entityA
|
||||
id:
|
||||
id1:
|
||||
type: int
|
||||
id2:
|
||||
type: int
|
||||
manyToMany:
|
||||
entityB:
|
||||
targetEntity: Doctrine\Tests\Models\DDC3711\DDC3711EntityB
|
||||
joinTable:
|
||||
name: link
|
||||
joinColumns:
|
||||
link_a_id1:
|
||||
referencedColumnName: id1
|
||||
link_a_id2:
|
||||
referencedColumnName: id2
|
||||
inverseJoinColumns:
|
||||
link_b_id1:
|
||||
referencedColumnName: id1
|
||||
link_b_id2:
|
||||
referencedColumnName: id2
|
||||
@@ -0,0 +1,8 @@
|
||||
Doctrine\Tests\Models\DDC3711\DDC3711EntityB:
|
||||
type: entity
|
||||
table: ddc3711.entityB
|
||||
id:
|
||||
id1:
|
||||
type: int
|
||||
id2:
|
||||
type: int
|
||||
@@ -3,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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2242,6 +2242,27 @@ class SelectSqlGenerationTest extends \Doctrine\Tests\OrmTestCase
|
||||
'SELECT COUNT(c0_.name) AS sclr_0 FROM cms_users c0_ HAVING sclr_0 IS NULL'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* GitHub issue #4764: https://github.com/doctrine/doctrine2/issues/4764
|
||||
* @group DDC-3907
|
||||
* @dataProvider mathematicOperatorsProvider
|
||||
*/
|
||||
public function testHavingRegressionUsingVariableWithMathOperatorsExpression($operator)
|
||||
{
|
||||
$this->assertSqlGeneration(
|
||||
'SELECT COUNT(u.name) AS countName FROM Doctrine\Tests\Models\CMS\CmsUser u HAVING 1 ' . $operator . ' countName > 0',
|
||||
'SELECT COUNT(c0_.name) AS sclr_0 FROM cms_users c0_ HAVING 1 ' . $operator . ' sclr_0 > 0'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function mathematicOperatorsProvider()
|
||||
{
|
||||
return [['+'], ['-'], ['*'], ['/']];
|
||||
}
|
||||
}
|
||||
|
||||
class MyAbsFunction extends \Doctrine\ORM\Query\AST\Functions\FunctionNode
|
||||
|
||||
@@ -85,6 +85,6 @@ class RunDqlCommandTest extends OrmFunctionalTestCase
|
||||
))
|
||||
);
|
||||
|
||||
$this->assertStringMatchesFormat('string%sSELECT %a', $this->tester->getDisplay());
|
||||
$this->assertStringMatchesFormat('%Astring%sSELECT %a', $this->tester->getDisplay());
|
||||
}
|
||||
}
|
||||
|
||||
195
tests/Doctrine/Tests/ORM/Tools/Console/MetadataFilterTest.php
Normal file
195
tests/Doctrine/Tests/ORM/Tools/Console/MetadataFilterTest.php
Normal 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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -350,7 +350,26 @@ ORDER BY b.id DESC'
|
||||
$query->getSQL()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This tests ordering by property that has the 'declared' field.
|
||||
*/
|
||||
public function testLimitSubqueryOrderByFieldFromMappedSuperclass()
|
||||
{
|
||||
$this->entityManager->getConnection()->setDatabasePlatform(new MySqlPlatform());
|
||||
|
||||
// now use the third one in query
|
||||
$query = $this->entityManager->createQuery(
|
||||
'SELECT b FROM Doctrine\Tests\ORM\Tools\Pagination\Banner b ORDER BY b.id DESC'
|
||||
);
|
||||
$query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'Doctrine\ORM\Tools\Pagination\LimitSubqueryOutputWalker');
|
||||
|
||||
$this->assertEquals(
|
||||
'SELECT DISTINCT id_0 FROM (SELECT b0_.id AS id_0, b0_.name AS name_1 FROM Banner b0_) dctrn_result ORDER BY id_0 DESC',
|
||||
$query->getSQL()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests order by on a subselect expression (mysql).
|
||||
*/
|
||||
|
||||
@@ -168,4 +168,22 @@ class Avatar
|
||||
public $image_width;
|
||||
/** @Column(type="string", length=255) */
|
||||
public $image_alt_desc;
|
||||
}
|
||||
}
|
||||
|
||||
/** @MappedSuperclass */
|
||||
abstract class Identified
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private $id;
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
}
|
||||
|
||||
/** @Entity */
|
||||
class Banner extends Identified
|
||||
{
|
||||
/** @Column(type="string") */
|
||||
public $name;
|
||||
}
|
||||
|
||||
@@ -9,19 +9,19 @@ use Doctrine\ORM\Events;
|
||||
class ResolveTargetEntityListenerTest extends \Doctrine\Tests\OrmTestCase
|
||||
{
|
||||
/**
|
||||
* @var EntityManager
|
||||
* @var \Doctrine\ORM\EntityManager
|
||||
*/
|
||||
private $em = null;
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* @var ResolveTargetEntityListener
|
||||
*/
|
||||
private $listener = null;
|
||||
private $listener;
|
||||
|
||||
/**
|
||||
* @var ClassMetadataFactory
|
||||
*/
|
||||
private $factory = null;
|
||||
private $factory;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
@@ -106,6 +106,32 @@ class ResolveTargetEntityListenerTest extends \Doctrine\Tests\OrmTestCase
|
||||
$this->assertSame('Doctrine\Tests\ORM\Tools\TargetEntity', $meta['targetEntity']);
|
||||
$this->assertEquals(array('resolvetargetentity_id', 'targetinterface_id'), $meta['joinTableColumns']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group 1572
|
||||
* @group functional
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
public function testDoesResolveTargetEntitiesInDQLAlsoWithInterfaces()
|
||||
{
|
||||
$evm = $this->em->getEventManager();
|
||||
$this->listener->addResolveTargetEntity(
|
||||
'Doctrine\Tests\ORM\Tools\ResolveTargetInterface',
|
||||
'Doctrine\Tests\ORM\Tools\ResolveTargetEntity',
|
||||
array()
|
||||
);
|
||||
|
||||
$evm->addEventSubscriber($this->listener);
|
||||
|
||||
$this->assertStringMatchesFormat(
|
||||
'SELECT%AFROM ResolveTargetEntity%A',
|
||||
$this
|
||||
->em
|
||||
->createQuery('SELECT f FROM Doctrine\Tests\ORM\Tools\ResolveTargetInterface f')
|
||||
->getSQL()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface ResolveTargetInterface
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -116,6 +117,48 @@ class SchemaToolTest extends \Doctrine\Tests\OrmTestCase
|
||||
|
||||
$this->assertSame(array(), $customSchemaOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-3671
|
||||
*/
|
||||
public function testSchemaHasProperIndexesFromUniqueConstraintAnnotation()
|
||||
{
|
||||
$em = $this->_getTestEntityManager();
|
||||
$schemaTool = new SchemaTool($em);
|
||||
|
||||
$classes = [
|
||||
$em->getClassMetadata(__NAMESPACE__ . '\\UniqueConstraintAnnotationModel'),
|
||||
];
|
||||
|
||||
$schema = $schemaTool->getSchemaFromMetadata($classes);
|
||||
|
||||
$this->assertTrue($schema->hasTable('unique_constraint_annotation_table'));
|
||||
$table = $schema->getTable('unique_constraint_annotation_table');
|
||||
|
||||
$this->assertEquals(2, count($table->getIndexes()));
|
||||
$this->assertTrue($table->hasIndex('primary'));
|
||||
$this->assertTrue($table->hasIndex('uniq_hash'));
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -148,3 +191,62 @@ class GenerateSchemaEventListener
|
||||
$this->schemaCalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @Table(name="unique_constraint_annotation_table", uniqueConstraints={
|
||||
* @UniqueConstraint(name="uniq_hash", columns={"hash"})
|
||||
* })
|
||||
*/
|
||||
class UniqueConstraintAnnotationModel
|
||||
{
|
||||
/** @Id @Column */
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @Column(name="hash", type="string", length=8, nullable=false, unique=true)
|
||||
*/
|
||||
private $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
namespace Doctrine\Tests\ORM;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\EventManager;
|
||||
use Doctrine\Common\NotifyPropertyChanged;
|
||||
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
|
||||
use Doctrine\Common\PropertyChangedListener;
|
||||
use Doctrine\ORM\Events;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
use Doctrine\Tests\Mocks\ConnectionMock;
|
||||
@@ -12,8 +15,13 @@ use Doctrine\Tests\Mocks\DriverMock;
|
||||
use Doctrine\Tests\Mocks\EntityManagerMock;
|
||||
use Doctrine\Tests\Mocks\EntityPersisterMock;
|
||||
use Doctrine\Tests\Mocks\UnitOfWorkMock;
|
||||
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
|
||||
use Doctrine\Tests\Models\CMS\CmsUser;
|
||||
use Doctrine\Tests\Models\Forum\ForumAvatar;
|
||||
use Doctrine\Tests\Models\Forum\ForumUser;
|
||||
use Doctrine\Tests\Models\GeoNames\City;
|
||||
use Doctrine\Tests\Models\GeoNames\Country;
|
||||
use Doctrine\Tests\OrmTestCase;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
@@ -42,18 +50,22 @@ class UnitOfWorkTest extends \Doctrine\Tests\OrmTestCase
|
||||
*/
|
||||
private $_emMock;
|
||||
|
||||
protected function setUp() {
|
||||
/**
|
||||
* @var EventManager|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
private $eventManager;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
$this->_connectionMock = new ConnectionMock(array(), new DriverMock());
|
||||
$this->_emMock = EntityManagerMock::create($this->_connectionMock);
|
||||
$this->_connectionMock = new ConnectionMock([], new DriverMock());
|
||||
$this->eventManager = $this->getMockBuilder('Doctrine\Common\EventManager')->getMock();
|
||||
$this->_emMock = EntityManagerMock::create($this->_connectionMock, null, $this->eventManager);
|
||||
// SUT
|
||||
$this->_unitOfWork = new UnitOfWorkMock($this->_emMock);
|
||||
$this->_emMock->setUnitOfWork($this->_unitOfWork);
|
||||
}
|
||||
|
||||
protected function tearDown() {
|
||||
}
|
||||
|
||||
public function testRegisterRemovedOnNewEntityIsIgnored()
|
||||
{
|
||||
$user = new ForumUser();
|
||||
@@ -321,6 +333,28 @@ class UnitOfWorkTest extends \Doctrine\Tests\OrmTestCase
|
||||
$this->assertTrue($this->_unitOfWork->isInIdentityMap($entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group 5849
|
||||
* @group 5850
|
||||
*/
|
||||
public function testPersistedEntityAndClearManager()
|
||||
{
|
||||
$entity1 = new City(123, 'London');
|
||||
$entity2 = new Country(456, 'United Kingdom');
|
||||
|
||||
$this->_unitOfWork->persist($entity1);
|
||||
$this->assertTrue($this->_unitOfWork->isInIdentityMap($entity1));
|
||||
|
||||
$this->_unitOfWork->persist($entity2);
|
||||
$this->assertTrue($this->_unitOfWork->isInIdentityMap($entity2));
|
||||
|
||||
$this->_unitOfWork->clear(Country::CLASSNAME);
|
||||
$this->assertTrue($this->_unitOfWork->isInIdentityMap($entity1));
|
||||
$this->assertFalse($this->_unitOfWork->isInIdentityMap($entity2));
|
||||
$this->assertTrue($this->_unitOfWork->isScheduledForInsert($entity1));
|
||||
$this->assertFalse($this->_unitOfWork->isScheduledForInsert($entity2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data Provider
|
||||
*
|
||||
@@ -337,6 +371,116 @@ class UnitOfWorkTest extends \Doctrine\Tests\OrmTestCase
|
||||
[new ArrayCollection()],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @group 5689
|
||||
* @group 1465
|
||||
*/
|
||||
public function testObjectHashesOfMergedEntitiesAreNotUsedInOriginalEntityDataMap()
|
||||
{
|
||||
$user = new CmsUser();
|
||||
$user->name = 'ocramius';
|
||||
$mergedUser = $this->_unitOfWork->merge($user);
|
||||
|
||||
self::assertSame([], $this->_unitOfWork->getOriginalEntityData($user), 'No original data was stored');
|
||||
self::assertSame([], $this->_unitOfWork->getOriginalEntityData($mergedUser), 'No original data was stored');
|
||||
|
||||
|
||||
$user = null;
|
||||
$mergedUser = null;
|
||||
|
||||
// force garbage collection of $user (frees the used object hashes, which may be recycled)
|
||||
gc_collect_cycles();
|
||||
|
||||
$newUser = new CmsUser();
|
||||
$newUser->name = 'ocramius';
|
||||
|
||||
$this->_unitOfWork->persist($newUser);
|
||||
|
||||
self::assertSame([], $this->_unitOfWork->getOriginalEntityData($newUser), 'No original data was stored');
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-1955
|
||||
* @group 5570
|
||||
* @group 6174
|
||||
*/
|
||||
public function testMergeWithNewEntityWillPersistItAndTriggerPrePersistListenersWithMergedEntityData()
|
||||
{
|
||||
$entity = new EntityWithRandomlyGeneratedField();
|
||||
|
||||
$generatedFieldValue = $entity->generatedField;
|
||||
|
||||
$this
|
||||
->eventManager
|
||||
->expects(self::any())
|
||||
->method('hasListeners')
|
||||
->willReturnCallback(function ($eventName) {
|
||||
return $eventName === Events::prePersist;
|
||||
});
|
||||
$this
|
||||
->eventManager
|
||||
->expects(self::once())
|
||||
->method('dispatchEvent')
|
||||
->with(
|
||||
self::anything(),
|
||||
self::callback(function (LifecycleEventArgs $args) use ($entity, $generatedFieldValue) {
|
||||
/* @var $object EntityWithRandomlyGeneratedField */
|
||||
$object = $args->getObject();
|
||||
|
||||
self::assertInstanceOf('Doctrine\Tests\ORM\EntityWithRandomlyGeneratedField', $object);
|
||||
self::assertNotSame($entity, $object);
|
||||
self::assertSame($generatedFieldValue, $object->generatedField);
|
||||
|
||||
return true;
|
||||
})
|
||||
);
|
||||
|
||||
/* @var $object EntityWithRandomlyGeneratedField */
|
||||
$object = $this->_unitOfWork->merge($entity);
|
||||
|
||||
self::assertNotSame($object, $entity);
|
||||
self::assertInstanceOf('Doctrine\Tests\ORM\EntityWithRandomlyGeneratedField', $object);
|
||||
self::assertSame($object->generatedField, $entity->generatedField);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DDC-1955
|
||||
* @group 5570
|
||||
* @group 6174
|
||||
*/
|
||||
public function testMergeWithExistingEntityWillNotPersistItNorTriggerPrePersistListeners()
|
||||
{
|
||||
$persistedEntity = new EntityWithRandomlyGeneratedField();
|
||||
$mergedEntity = new EntityWithRandomlyGeneratedField();
|
||||
|
||||
$mergedEntity->id = $persistedEntity->id;
|
||||
$mergedEntity->generatedField = mt_rand(
|
||||
$persistedEntity->generatedField + 1,
|
||||
$persistedEntity->generatedField + 1000
|
||||
);
|
||||
|
||||
$this
|
||||
->eventManager
|
||||
->expects(self::any())
|
||||
->method('hasListeners')
|
||||
->willReturnCallback(function ($eventName) {
|
||||
return $eventName === Events::prePersist;
|
||||
});
|
||||
$this->eventManager->expects(self::never())->method('dispatchEvent');
|
||||
|
||||
$this->_unitOfWork->registerManaged(
|
||||
$persistedEntity,
|
||||
['id' => $persistedEntity->id],
|
||||
['generatedField' => $persistedEntity->generatedField]
|
||||
);
|
||||
|
||||
/* @var $merged EntityWithRandomlyGeneratedField */
|
||||
$merged = $this->_unitOfWork->merge($mergedEntity);
|
||||
|
||||
self::assertSame($merged, $persistedEntity);
|
||||
self::assertSame($persistedEntity->generatedField, $mergedEntity->generatedField);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -443,3 +587,61 @@ class VersionedAssignedIdentifierEntity
|
||||
*/
|
||||
public $version;
|
||||
}
|
||||
|
||||
/** @Entity */
|
||||
class EntityWithStringIdentifier
|
||||
{
|
||||
/**
|
||||
* @Id @Column(type="string")
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $id;
|
||||
}
|
||||
|
||||
/** @Entity */
|
||||
class EntityWithBooleanIdentifier
|
||||
{
|
||||
/**
|
||||
* @Id @Column(type="boolean")
|
||||
*
|
||||
* @var bool|null
|
||||
*/
|
||||
public $id;
|
||||
}
|
||||
|
||||
/** @Entity */
|
||||
class EntityWithCompositeStringIdentifier
|
||||
{
|
||||
/**
|
||||
* @Id @Column(type="string")
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $id1;
|
||||
|
||||
/**
|
||||
* @Id @Column(type="string")
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $id2;
|
||||
}
|
||||
|
||||
/** @Entity */
|
||||
class EntityWithRandomlyGeneratedField
|
||||
{
|
||||
/** @Id @Column(type="string") */
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
public $generatedField;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->id = uniqid('id', true);
|
||||
$this->generatedField = mt_rand(0, 100000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,6 +280,15 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
|
||||
'Doctrine\Tests\Models\Pagination\User',
|
||||
'Doctrine\Tests\Models\Pagination\User1',
|
||||
),
|
||||
'versioned_many_to_one' => array(
|
||||
'Doctrine\Tests\Models\VersionedManyToOne\Category',
|
||||
'Doctrine\Tests\Models\VersionedManyToOne\Article',
|
||||
),
|
||||
'issue5989' => array(
|
||||
'Doctrine\Tests\Models\Issue5989\Issue5989Person',
|
||||
'Doctrine\Tests\Models\Issue5989\Issue5989Employee',
|
||||
'Doctrine\Tests\Models\Issue5989\Issue5989Manager',
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -300,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;
|
||||
@@ -535,6 +550,17 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
|
||||
$conn->executeUpdate('DELETE FROM pagination_user');
|
||||
}
|
||||
|
||||
if (isset($this->_usedModelSets['versioned_many_to_one'])) {
|
||||
$conn->executeUpdate('DELETE FROM versioned_many_to_one_article');
|
||||
$conn->executeUpdate('DELETE FROM versioned_many_to_one_category');
|
||||
}
|
||||
|
||||
if (isset($this->_usedModelSets['issue5989'])) {
|
||||
$conn->executeUpdate('DELETE FROM issue5989_persons');
|
||||
$conn->executeUpdate('DELETE FROM issue5989_employees');
|
||||
$conn->executeUpdate('DELETE FROM issue5989_managers');
|
||||
}
|
||||
|
||||
$this->_em->clear();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user