1
0
Fork 0
mirror of synced 2025-04-01 12:26:11 +03:00

Merge pull request #937 from goetas/contains-key

Extra-lazy for containsKey on collections
This commit is contained in:
Benjamin Eberlei 2014-02-08 16:28:44 +01:00
commit 50ba19d91b
4 changed files with 243 additions and 23 deletions

View file

@ -471,6 +471,13 @@ final class PersistentCollection implements Collection, Selectable
*/
public function containsKey($key)
{
if (! $this->initialized && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY
&& isset($this->association['indexBy'])) {
$persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
return $this->coll->containsKey($key) || $persister->containsKey($this, $key);
}
$this->initialize();
return $this->coll->containsKey($key);
@ -776,7 +783,7 @@ final class PersistentCollection implements Collection, Selectable
public function next()
{
$this->initialize();
return $this->coll->next();
}

View file

@ -265,6 +265,15 @@ class ManyToManyPersister extends AbstractCollectionPersister
return $this->em->getUnitOfWork()->getEntityPersister($mapping['targetEntity'])->getManyToManyCollection($mapping, $coll->getOwner(), $offset, $length);
}
/**
* {@inheritdoc}
*/
public function containsKey(PersistentCollection $coll, $key)
{
list($quotedJoinTable, $whereClauses, $params) = $this->getJoinTableRestrictionsWithKey($coll, $key, true);
$sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
return (bool) $this->conn->fetchColumn($sql, $params);
}
/**
* {@inheritdoc}
@ -319,6 +328,78 @@ class ManyToManyPersister extends AbstractCollectionPersister
return (bool) $this->conn->executeUpdate($sql, $params);
}
/**
* @param \Doctrine\ORM\PersistentCollection $coll
* @param string $key
* @param boolean $addFilters Whether the filter SQL should be included or not.
*
* @return array
*/
private function getJoinTableRestrictionsWithKey(PersistentCollection $coll, $key, $addFilters)
{
$uow = $this->em->getUnitOfWork();
$filterMapping = $coll->getMapping();
$mapping = $filterMapping;
$indexBy = $mapping['indexBy'];
$id = $uow->getEntityIdentifier($coll->getOwner());
$targetEntity = $this->em->getClassMetadata($mapping['targetEntity']);
if (! $mapping['isOwningSide']) {
$associationSourceClass = $this->em->getClassMetadata($mapping['targetEntity']);
$mapping = $associationSourceClass->associationMappings[$mapping['mappedBy']];
$joinColumns = $mapping['joinTable']['joinColumns'];
$relationMode = 'relationToTargetKeyColumns';
} else {
$joinColumns = $mapping['joinTable']['inverseJoinColumns'];
$associationSourceClass = $this->em->getClassMetadata($mapping['sourceEntity']);
$relationMode = 'relationToSourceKeyColumns';
}
$quotedJoinTable = $this->quoteStrategy->getJoinTableName($mapping, $associationSourceClass, $this->platform). ' t';
$whereClauses = array();
$params = array();
$joinNeeded = !in_array($indexBy, $targetEntity->identifier);
if ($joinNeeded) { // extra join needed if indexBy is not a @id
$joinConditions = array();
foreach ($joinColumns as $joinTableColumn) {
$joinConditions[] = 't.' . $joinTableColumn['name'] . ' = tr.' . $joinTableColumn['referencedColumnName'];
}
$tableName = $this->quoteStrategy->getTableName($targetEntity, $this->platform);
$quotedJoinTable .= ' JOIN ' . $tableName . ' tr ON ' . implode(' AND ', $joinConditions);
$whereClauses[] = 'tr.' . $targetEntity->getColumnName($indexBy) . ' = ?';
$params[] = $key;
}
foreach ($mapping['joinTableColumns'] as $joinTableColumn) {
if (isset($mapping[$relationMode][$joinTableColumn])) {
$whereClauses[] = 't.' . $joinTableColumn . ' = ?';
$params[] = $targetEntity->containsForeignIdentifier
? $id[$targetEntity->getFieldForColumn($mapping[$relationMode][$joinTableColumn])]
: $id[$targetEntity->fieldNames[$mapping[$relationMode][$joinTableColumn]]];
} elseif (!$joinNeeded) {
$whereClauses[] = 't.' . $joinTableColumn . ' = ?';
$params[] = $key;
}
}
if ($addFilters) {
list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($filterMapping);
if ($filterSql) {
$quotedJoinTable .= ' ' . $joinTargetEntitySQL;
$whereClauses[] = $filterSql;
}
}
return array($quotedJoinTable, $whereClauses, $params);
}
/**
* @param \Doctrine\ORM\PersistentCollection $coll
* @param object $element

View file

@ -131,6 +131,45 @@ class OneToManyPersister extends AbstractCollectionPersister
* {@inheritdoc}
*/
public function count(PersistentCollection $coll)
{
list($quotedJoinTable, $whereClauses, $params) = $this->getJoinTableRestrictions($coll, true);
$sql = 'SELECT count(*) FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
return $this->conn->fetchColumn($sql, $params);
}
/**
* {@inheritdoc}
*/
public function slice(PersistentCollection $coll, $offset, $length = null)
{
$mapping = $coll->getMapping();
$uow = $this->em->getUnitOfWork();
$persister = $uow->getEntityPersister($mapping['targetEntity']);
return $persister->getOneToManyCollection($mapping, $coll->getOwner(), $offset, $length);
}
/**
* {@inheritdoc}
*/
public function containsKey(PersistentCollection $coll, $key)
{
list($quotedJoinTable, $whereClauses, $params) = $this->getJoinTableRestrictions($coll, true);
$mapping = $coll->getMapping();
$sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']);
$whereClauses[] = $sourceClass->getColumnName($mapping['indexBy']) . ' = ?';
$params[] = $key;
$sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
return (bool) $this->conn->fetchColumn($sql, $params);
}
private function getJoinTableRestrictions(PersistentCollection $coll, $addFilters)
{
$mapping = $coll->getMapping();
$targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
@ -149,30 +188,18 @@ class OneToManyPersister extends AbstractCollectionPersister
: $id[$sourceClass->fieldNames[$joinColumn['referencedColumnName']]];
}
$filterTargetClass = $this->em->getClassMetadata($targetClass->rootEntityName);
foreach ($this->em->getFilters()->getEnabledFilters() as $filter) {
if ($filterExpr = $filter->addFilterConstraint($filterTargetClass, 't')) {
$whereClauses[] = '(' . $filterExpr . ')';
if ($addFilters) {
$filterTargetClass = $this->em->getClassMetadata($targetClass->rootEntityName);
foreach ($this->em->getFilters()->getEnabledFilters() as $filter) {
if ($filterExpr = $filter->addFilterConstraint($filterTargetClass, 't')) {
$whereClauses[] = '(' . $filterExpr . ')';
}
}
}
$sql = 'SELECT count(*)'
. ' FROM ' . $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' t'
. ' WHERE ' . implode(' AND ', $whereClauses);
$quotedJoinTable = $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' t';
return $this->conn->fetchColumn($sql, $params);
}
/**
* {@inheritdoc}
*/
public function slice(PersistentCollection $coll, $offset, $length = null)
{
$mapping = $coll->getMapping();
$uow = $this->em->getUnitOfWork();
$persister = $uow->getEntityPersister($mapping['targetEntity']);
return $persister->getOneToManyCollection($mapping, $coll->getOwner(), $offset, $length);
return array($quotedJoinTable, $whereClauses, $params);
}
/**
@ -200,7 +227,7 @@ class OneToManyPersister extends AbstractCollectionPersister
// only works with single id identifier entities. Will throw an
// exception in Entity Persisters if that is not the case for the
// 'mappedBy' field.
$id = current( $uow->getEntityIdentifier($coll->getOwner()));
$id = current($uow->getEntityIdentifier($coll->getOwner()));
return $persister->exists($element, array($mapping['mappedBy'] => $id));
}

View file

@ -14,6 +14,7 @@ require_once __DIR__ . '/../../TestInit.php';
class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
private $userId;
private $userId2;
private $groupId;
private $articleId;
@ -35,6 +36,7 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
$class = $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsGroup');
$class->associationMappings['users']['fetch'] = ClassMetadataInfo::FETCH_EXTRA_LAZY;
$class->associationMappings['users']['indexBy'] = 'username';
$this->loadFixture();
}
@ -539,7 +541,6 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
/* @var $user CmsUser */
$queryCount = $this->getCurrentQueryCount();
$phonenumber = $user->phonenumbers->get($this->phonenumber);
$this->assertFalse($user->phonenumbers->isInitialized());
@ -576,6 +577,106 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertNull($user->articles->get(-1));
}
public function testContainsKeyIndexByOneToMany()
{
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId);
/* @var $user CmsUser */
$queryCount = $this->getCurrentQueryCount();
$contains = $user->articles->containsKey($this->topic);
$this->assertTrue($contains);
$this->assertFalse($user->articles->isInitialized());
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
}
public function testContainsKeyIndexByManyToMany()
{
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId2);
$group = $this->_em->find('Doctrine\Tests\Models\CMS\CmsGroup', $this->groupId);
$queryCount = $this->getCurrentQueryCount();
$contains = $user->groups->containsKey($group->name);
$this->assertTrue($contains, "The item is not into collection");
$this->assertFalse($user->groups->isInitialized(), "The collection must not be initialized");
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
}
public function testContainsKeyIndexByManyToManyNonOwning()
{
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId2);
$group = $this->_em->find('Doctrine\Tests\Models\CMS\CmsGroup', $this->groupId);
$queryCount = $this->getCurrentQueryCount();
$contains = $group->users->containsKey($user->username);
$this->assertTrue($contains, "The item is not into collection");
$this->assertFalse($group->users->isInitialized(), "The collection must not be initialized");
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
}
public function testContainsKeyIndexByWithPkManyToMany()
{
$class = $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser');
$class->associationMappings['groups']['indexBy'] = 'id';
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId2);
$queryCount = $this->getCurrentQueryCount();
$contains = $user->groups->containsKey($this->groupId);
$this->assertTrue($contains, "The item is not into collection");
$this->assertFalse($user->groups->isInitialized(), "The collection must not be initialized");
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
}
public function testContainsKeyIndexByWithPkManyToManyNonOwning()
{
$class = $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsGroup');
$class->associationMappings['users']['indexBy'] = 'id';
$group = $this->_em->find('Doctrine\Tests\Models\CMS\CmsGroup', $this->groupId);
$queryCount = $this->getCurrentQueryCount();
$contains = $group->users->containsKey($this->userId2);
$this->assertTrue($contains, "The item is not into collection");
$this->assertFalse($group->users->isInitialized(), "The collection must not be initialized");
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
}
public function testContainsKeyNonExistentIndexByOneToMany()
{
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId2);
$queryCount = $this->getCurrentQueryCount();
$contains = $user->articles->containsKey("NonExistentTopic");
$this->assertFalse($contains);
$this->assertFalse($user->articles->isInitialized());
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
}
public function testContainsKeyNonExistentIndexByManyToMany()
{
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId2);
$queryCount = $this->getCurrentQueryCount();
$contains = $user->groups->containsKey("NonExistentTopic");
$this->assertFalse($contains);
$this->assertFalse($user->groups->isInitialized());
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
}
private function loadFixture()
{
$user1 = new \Doctrine\Tests\Models\CMS\CmsUser();
@ -646,14 +747,18 @@ class ExtraLazyCollectionTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->_em->persist($phonenumber1);
$this->_em->persist($phonenumber2);
$user1->addPhonenumber($phonenumber1);
$this->_em->flush();
$this->_em->clear();
$this->articleId = $article1->id;
$this->userId = $user1->getId();
$this->userId2 = $user2->getId();
$this->groupId = $group1->id;
$this->topic = $article1->topic;
$this->phonenumber = $phonenumber1->phonenumber;
}
}