Merge pull request #885 from bakura10/criteria-many-to-many
Add support for ManyToMany Criteria
This commit is contained in:
commit
44c1dae1b9
@ -20,6 +20,7 @@
|
||||
|
||||
namespace Doctrine\ORM\Cache\Persister;
|
||||
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\ORM\Cache\EntityCacheKey;
|
||||
use Doctrine\ORM\Cache\CollectionCacheKey;
|
||||
use Doctrine\ORM\Persisters\CollectionPersister;
|
||||
@ -272,4 +273,12 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
|
||||
{
|
||||
return $this->persister->slice($collection, $offset, $length);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function loadCriteria(PersistentCollection $collection, Criteria $criteria)
|
||||
{
|
||||
return $this->persister->loadCriteria($collection, $criteria);
|
||||
}
|
||||
}
|
||||
|
@ -869,8 +869,10 @@ final class PersistentCollection implements Collection, Selectable
|
||||
return $this->coll->matching($criteria);
|
||||
}
|
||||
|
||||
if ($this->association['type'] !== ClassMetadata::ONE_TO_MANY) {
|
||||
throw new \RuntimeException("Matching Criteria on PersistentCollection only works on OneToMany associations at the moment.");
|
||||
if ($this->association['type'] === ClassMetadata::MANY_TO_MANY) {
|
||||
$persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
|
||||
|
||||
return new ArrayCollection($persister->loadCriteria($this, $criteria));
|
||||
}
|
||||
|
||||
$builder = Criteria::expr();
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
namespace Doctrine\ORM\Persisters;
|
||||
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
|
||||
@ -51,7 +52,7 @@ abstract class AbstractCollectionPersister implements CollectionPersister
|
||||
* @var \Doctrine\DBAL\Platforms\AbstractPlatform
|
||||
*/
|
||||
protected $platform;
|
||||
|
||||
|
||||
/**
|
||||
* The quote strategy.
|
||||
*
|
||||
@ -203,6 +204,14 @@ abstract class AbstractCollectionPersister implements CollectionPersister
|
||||
throw new \BadMethodCallException("Selecting a collection by index is not supported by this CollectionPersister.");
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadCriteria(PersistentCollection $coll, Criteria $criteria)
|
||||
{
|
||||
throw new \BadMethodCallException("Filtering a collection by Criteria is not supported by this CollectionPersister.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the SQL statement used for deleting a row from the collection.
|
||||
*
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
namespace Doctrine\ORM\Persisters;
|
||||
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
|
||||
/**
|
||||
@ -136,4 +137,14 @@ interface CollectionPersister
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(PersistentCollection $collection, $index);
|
||||
|
||||
/**
|
||||
* Loads association entities matching the given Criteria object.
|
||||
*
|
||||
* @param \Doctrine\ORM\PersistentCollection $collection
|
||||
* @param \Doctrine\Common\Collections\Criteria $criteria
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function loadCriteria(PersistentCollection $collection, Criteria $criteria);
|
||||
}
|
||||
|
@ -19,8 +19,10 @@
|
||||
|
||||
namespace Doctrine\ORM\Persisters;
|
||||
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
|
||||
/**
|
||||
@ -54,7 +56,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
}
|
||||
|
||||
return 'DELETE FROM ' . $tableName
|
||||
. ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?';
|
||||
. ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -102,7 +104,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
}
|
||||
|
||||
return 'INSERT INTO ' . $joinTable . ' (' . implode(', ', $columns) . ')'
|
||||
. ' VALUES (' . implode(', ', array_fill(0, count($columns), '?')) . ')';
|
||||
. ' VALUES (' . implode(', ', array_fill(0, count($columns), '?')) . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -178,7 +180,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
}
|
||||
|
||||
return 'DELETE FROM ' . $joinTable
|
||||
. ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?';
|
||||
. ' WHERE ' . implode(' = ? AND ', $columns) . ' = ?';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -257,7 +259,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function slice(PersistentCollection $coll, $offset, $length = null)
|
||||
{
|
||||
@ -276,7 +278,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function contains(PersistentCollection $coll, $element)
|
||||
{
|
||||
@ -302,7 +304,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function removeElement(PersistentCollection $coll, $element)
|
||||
{
|
||||
@ -485,29 +487,12 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
return array('', '');
|
||||
}
|
||||
|
||||
$conditions = array();
|
||||
$association = $mapping;
|
||||
|
||||
if ( ! $mapping['isOwningSide']) {
|
||||
$class = $this->em->getClassMetadata($mapping['targetEntity']);
|
||||
$association = $class->associationMappings[$mapping['mappedBy']];
|
||||
}
|
||||
|
||||
// A join is needed if there is filtering on the target entity
|
||||
$tableName = $this->quoteStrategy->getTableName($rootClass, $this->platform);
|
||||
$joinSql = ' JOIN ' . $tableName . ' te' . ' ON';
|
||||
$joinColumns = $mapping['isOwningSide']
|
||||
? $association['joinTable']['inverseJoinColumns']
|
||||
: $association['joinTable']['joinColumns'];
|
||||
$tableName = $this->quoteStrategy->getTableName($rootClass, $this->platform);
|
||||
$joinSql = ' JOIN ' . $tableName . ' te' . ' ON';
|
||||
$onConditions = $this->getOnConditionSQL($mapping);
|
||||
|
||||
foreach ($joinColumns as $joinColumn) {
|
||||
$joinColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
|
||||
$refColumnName = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
|
||||
|
||||
$conditions[] = ' t.' . $joinColumnName . ' = ' . 'te.' . $refColumnName;
|
||||
}
|
||||
|
||||
$joinSql .= implode(' AND ', $conditions);
|
||||
$joinSql .= implode(' AND ', $onConditions);
|
||||
|
||||
return array($joinSql, $filterSql);
|
||||
}
|
||||
@ -531,6 +516,108 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
}
|
||||
|
||||
$sql = implode(' AND ', $filterClauses);
|
||||
return $sql ? "(" . $sql . ")" : "";
|
||||
return $sql ? '(' . $sql . ')' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate ON condition
|
||||
*
|
||||
* @param array $mapping
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getOnConditionSQL($mapping)
|
||||
{
|
||||
$association = $mapping;
|
||||
|
||||
if ( ! $mapping['isOwningSide']) {
|
||||
$class = $this->em->getClassMetadata($mapping['targetEntity']);
|
||||
$association = $class->associationMappings[$mapping['mappedBy']];
|
||||
}
|
||||
|
||||
$targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
|
||||
|
||||
$joinColumns = $mapping['isOwningSide']
|
||||
? $association['joinTable']['inverseJoinColumns']
|
||||
: $association['joinTable']['joinColumns'];
|
||||
|
||||
$conditions = array();
|
||||
|
||||
foreach ($joinColumns as $joinColumn) {
|
||||
$joinColumnName = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
|
||||
$refColumnName = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
|
||||
|
||||
$conditions[] = ' t.' . $joinColumnName . ' = ' . 'te.' . $refColumnName;
|
||||
}
|
||||
|
||||
return $conditions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function loadCriteria(PersistentCollection $coll, Criteria $criteria)
|
||||
{
|
||||
$mapping = $coll->getMapping();
|
||||
$owner = $coll->getOwner();
|
||||
$ownerMetadata = $this->em->getClassMetadata(get_class($owner));
|
||||
$whereClauses = $params = array();
|
||||
|
||||
foreach ($mapping['relationToSourceKeyColumns'] as $key => $value) {
|
||||
$whereClauses[] = sprintf('t.%s = ?', $key);
|
||||
$params[] = $ownerMetadata->getFieldValue($owner, $value);
|
||||
}
|
||||
|
||||
$parameters = $this->expandCriteriaParameters($criteria);
|
||||
|
||||
foreach ($parameters as $parameter) {
|
||||
list($name, $value) = $parameter;
|
||||
$whereClauses[] = sprintf('te.%s = ?', $name);
|
||||
$params[] = $value;
|
||||
}
|
||||
|
||||
$mapping = $coll->getMapping();
|
||||
$targetClass = $this->em->getClassMetadata($mapping['targetEntity']);
|
||||
$tableName = $this->quoteStrategy->getTableName($targetClass, $this->platform);
|
||||
$joinTable = $this->quoteStrategy->getJoinTableName($mapping, $ownerMetadata, $this->platform);
|
||||
$onConditions = $this->getOnConditionSQL($mapping);
|
||||
|
||||
$rsm = new Query\ResultSetMappingBuilder($this->em);
|
||||
$rsm->addRootEntityFromClassMetadata($mapping['targetEntity'], 'te');
|
||||
|
||||
$sql = 'SELECT ' . $rsm->generateSelectClause() . ' FROM ' . $tableName . ' te'
|
||||
. ' JOIN ' . $joinTable . ' t ON'
|
||||
. implode(' AND ', $onConditions)
|
||||
. ' WHERE ' . implode(' AND ', $whereClauses);
|
||||
|
||||
$stmt = $this->conn->executeQuery($sql, $params);
|
||||
$hydrator = $this->em->newHydrator(Query::HYDRATE_OBJECT);
|
||||
|
||||
return $hydrator->hydrateAll($stmt, $rsm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands Criteria Parameters by walking the expressions and grabbing all
|
||||
* parameters and types from it.
|
||||
*
|
||||
* @param \Doctrine\Common\Collections\Criteria $criteria
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function expandCriteriaParameters(Criteria $criteria)
|
||||
{
|
||||
$expression = $criteria->getWhereExpression();
|
||||
|
||||
if ($expression === null) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$valueVisitor = new SqlValueVisitor();
|
||||
|
||||
$valueVisitor->dispatch($expression);
|
||||
|
||||
list($values, $types) = $valueVisitor->getParamsAndTypes();
|
||||
|
||||
return $types;
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ abstract class AbstractCollectionPersisterTest extends OrmTestCase
|
||||
'removeElement',
|
||||
'removeKey',
|
||||
'get',
|
||||
'loadCriteria'
|
||||
);
|
||||
|
||||
/**
|
||||
@ -298,4 +299,4 @@ abstract class AbstractCollectionPersisterTest extends OrmTestCase
|
||||
|
||||
$this->assertEquals($element, $persister->get($collection, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Tests\Models\CMS\CmsUser,
|
||||
Doctrine\Tests\Models\CMS\CmsGroup,
|
||||
Doctrine\Common\Collections\ArrayCollection;
|
||||
@ -377,4 +378,25 @@ class ManyToManyBasicAssociationTest extends \Doctrine\Tests\OrmFunctionalTestCa
|
||||
$user = $this->_em->find(get_class($user), $user->id);
|
||||
$this->assertEquals(0, count($user->groups));
|
||||
}
|
||||
|
||||
public function testMatching()
|
||||
{
|
||||
$user = $this->addCmsUserGblancoWithGroups(2);
|
||||
$this->_em->clear();
|
||||
|
||||
$user = $this->_em->find(get_class($user), $user->id);
|
||||
|
||||
$groups = $user->groups;
|
||||
$this->assertFalse($user->groups->isInitialized(), "Pre-condition: lazy collection");
|
||||
|
||||
$criteria = Criteria::create()->where(Criteria::expr()->eq('name', (string) 'Developers_0'));
|
||||
$result = $groups->matching($criteria);
|
||||
|
||||
$this->assertCount(1, $result);
|
||||
|
||||
$firstGroup = $result->first();
|
||||
$this->assertEquals('Developers_0', $firstGroup->name);
|
||||
|
||||
$this->assertFalse($user->groups->isInitialized(), "Post-condition: matching does not initialize collection");
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user