1
0
mirror of synced 2024-12-14 15:16:04 +03:00

Merge pull request #381 from doctrine/DDC-1637

[DDC-1637] Collection Filtering API
This commit is contained in:
Benjamin Eberlei 2012-07-09 08:16:31 -07:00
commit 113c6f51c2
11 changed files with 746 additions and 76 deletions

View File

@ -22,6 +22,11 @@ namespace Doctrine\ORM;
use Doctrine\DBAL\LockMode;
use Doctrine\Common\Persistence\ObjectRepository;
use Doctrine\Common\Collections\Selectable;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\ExpressionBuilder;
/**
* An EntityRepository serves as a repository for entities with generic as well as
* business specific methods for retrieving entities.
@ -35,8 +40,13 @@ use Doctrine\Common\Persistence\ObjectRepository;
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
*/
class EntityRepository implements ObjectRepository
class EntityRepository implements ObjectRepository, Selectable
{
/**
* @var Doctrine\Common\Collections\ExpressionBuilder
*/
private static $expressionBuilder;
/**
* @var string
*/
@ -308,4 +318,32 @@ class EntityRepository implements ObjectRepository
{
return $this->_class;
}
/**
* Select all elements from a selectable that match the expression and
* return a new collection containing these elements.
*
* @param \Doctrine\Common\Collections\Criteria $criteria
*
* @return \Doctrine\Common\Collections\Collection
*/
public function matching(Criteria $criteria)
{
$persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
return new ArrayCollection($persister->loadCriteria($criteria));
}
/**
* Return Builder object that helps with building criteria expressions.
*
* @return \Doctrine\Common\Collections\ExpressionBuilder
*/
public function expr()
{
if (self::$expressionBuilder === null) {
self::$expressionBuilder = new ExpressionBuilder();
}
return self::$expressionBuilder;
}
}

View File

@ -19,10 +19,15 @@
namespace Doctrine\ORM;
use Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\Common\Collections\Collection,
Doctrine\Common\Collections\ArrayCollection,
Closure;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Selectable;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\ExpressionBuilder;
use Closure;
/**
* A PersistentCollection represents a collection of elements that have persistent state.
@ -39,8 +44,13 @@ use Doctrine\ORM\Mapping\ClassMetadata,
* @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
* @todo Design for inheritance to allow custom implementations?
*/
final class PersistentCollection implements Collection
final class PersistentCollection implements Collection, Selectable
{
/**
* @var Doctrine\Common\Collections\ExpressionBuilder
*/
static private $expressionBuilder;
/**
* A snapshot of the collection at the moment it was fetched from the database.
* This is used to create a diff of the collection at commit time.
@ -789,4 +799,51 @@ final class PersistentCollection implements Collection
$this->changed();
}
/**
* Select all elements from a selectable that match the expression and
* return a new collection containing these elements.
*
* @param \Doctrine\Common\Collections\Criteria $criteria
* @return Collection
*/
public function matching(Criteria $criteria)
{
if ($this->initialized) {
return $this->coll->matching($criteria);
}
if ($this->association['type'] !== ClassMetadata::ONE_TO_MANY) {
throw new \RuntimeException("Matching Criteria on PersistentCollection only works on OneToMany assocations at the moment.");
}
$targetClass = $this->em->getClassMetadata(get_class($this->owner));
$id = $targetClass->getSingleIdReflectionProperty()->getValue($this->owner);
$builder = $this->expr();
$ownerExpression = $builder->eq($this->backRefFieldName, $id);
$expression = $criteria->getWhereExpression();
$expression = $expression ? $builder->andX($expression, $ownerExpression) : $ownerExpression;
$criteria->where($expression);
$persister = $this->em->getUnitOfWork()->getEntityPersister($this->association['targetEntity']);
return new ArrayCollection($persister->loadCriteria($criteria));
}
/**
* Return Builder object that helps with building criteria expressions.
*
* @return \Doctrine\Common\Collections\ExpressionBuilder
*/
public function expr()
{
if (self::$expressionBuilder === null) {
self::$expressionBuilder = new ExpressionBuilder();
}
return self::$expressionBuilder;
}
}

View File

@ -19,21 +19,26 @@
namespace Doctrine\ORM\Persisters;
use PDO,
Doctrine\DBAL\LockMode,
Doctrine\DBAL\Types\Type,
Doctrine\DBAL\Connection,
Doctrine\ORM\ORMException,
Doctrine\ORM\OptimisticLockException,
Doctrine\ORM\EntityManager,
Doctrine\ORM\UnitOfWork,
Doctrine\ORM\Query,
Doctrine\ORM\PersistentCollection,
Doctrine\ORM\Mapping\MappingException,
Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\ORM\Events,
Doctrine\ORM\Event\LifecycleEventArgs,
Doctrine\Common\Util\ClassUtils;
use PDO;
use Doctrine\DBAL\LockMode;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Connection;
use Doctrine\ORM\ORMException;
use Doctrine\ORM\OptimisticLockException;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\UnitOfWork;
use Doctrine\ORM\Query;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Events;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Expr\Comparison;
/**
* A BasicEntityPersiter maps an entity to a single table in a relational database.
@ -78,6 +83,21 @@ use PDO,
*/
class BasicEntityPersister
{
/**
* @var array
*/
static private $comparisonMap = array(
Comparison::EQ => '= %s',
Comparison::IS => '= %s',
Comparison::NEQ => '!= %s',
Comparison::GT => '> %s',
Comparison::GTE => '>= %s',
Comparison::LT => '< %s',
Comparison::LTE => '<= %s',
Comparison::IN => 'IN (%s)',
Comparison::NIN => 'NOT IN (%s)',
);
/**
* Metadata object that describes the mapping of the mapped entity class.
*
@ -748,6 +768,52 @@ class BasicEntityPersister
$hydrator->hydrateAll($stmt, $this->_rsm, array(Query::HINT_REFRESH => true));
}
/**
* Load Entities matching the given Criteria object
*
* @param \Doctrine\Common\Collections\Criteria $criteria
*
* @return array
*/
public function loadCriteria(Criteria $criteria)
{
$orderBy = $criteria->getOrderings();
$limit = $criteria->getMaxResults();
$offset = $criteria->getFirstResult();
$sql = $this->_getSelectEntitiesSQL($criteria, null, 0, $limit, $offset, $orderBy);
list($params, $types) = $this->expandCriteriaParameters($criteria);
$stmt = $this->_conn->executeQuery($sql, $params, $types);
$hydrator = $this->_em->newHydrator(($this->_selectJoinSql) ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT);
return $hydrator->hydrateAll($stmt, $this->_rsm, array('deferEagerLoads' => true));
}
/**
* Expand Criteria Parameters by walking the expressions and grabbing all
* parameters and types from it.
*
* @param \Doctrine\Common\Collections\Criteria $criteria
*
* @return array(array(), array())
*/
private function expandCriteriaParameters(Criteria $criteria)
{
$expression = $criteria->getWhereExpression();
if ($expression === null) {
return array(array(), array());
}
$valueVisitor = new SqlValueVisitor($this->_class);
$valueVisitor->dispatch($expression);
return $valueVisitor->getParamsAndTypes();
}
/**
* Loads a list of entities by a list of field criteria.
*
@ -920,7 +986,7 @@ class BasicEntityPersister
/**
* Gets the SELECT SQL to select one or more entities by a set of field criteria.
*
* @param array $criteria
* @param array|\Doctrine\Common\Collections\Criteria $criteria
* @param AssociationMapping $assoc
* @param string $orderBy
* @param int $lockMode
@ -930,10 +996,12 @@ class BasicEntityPersister
* @return string
* @todo Refactor: _getSelectSQL(...)
*/
protected function _getSelectEntitiesSQL(array $criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null)
protected function _getSelectEntitiesSQL($criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null)
{
$joinSql = ($assoc != null && $assoc['type'] == ClassMetadata::MANY_TO_MANY) ? $this->_getSelectManyToManyJoinSQL($assoc) : '';
$conditionSql = $this->_getSelectConditionSQL($criteria, $assoc);
$conditionSql = ($criteria instanceof Criteria)
? $this->_getSelectConditionCriteriaSQL($criteria)
: $this->_getSelectConditionSQL($criteria, $assoc);
$orderBy = ($assoc !== null && isset($assoc['orderBy'])) ? $assoc['orderBy'] : $orderBy;
$orderBySql = $orderBy ? $this->_getOrderBySQL($orderBy, $this->_getSQLTableAlias($this->_class->name)) : '';
@ -1336,6 +1404,93 @@ class BasicEntityPersister
. $this->_getSQLTableAlias($this->_class->name);
}
/**
* Get the Select Where Condition from a Criteria object.
*
* @param \Doctrine\Common\Collections\Criteria $criteria
* @return string
*/
protected function _getSelectConditionCriteriaSQL(Criteria $criteria)
{
$expression = $criteria->getWhereExpression();
if ($expression === null) {
return '';
}
$visitor = new SqlExpressionVisitor($this);
return $visitor->dispatch($expression);
}
/**
* Get the SQL WHERE condition for matching a field with a given value.
*
* @param string $field
* @param mixed $value
* @param array|null $assoc
* @param string $comparison
*
* @return string
*/
public function getSelectConditionStatementSQL($field, $value, $assoc = null, $comparison = null)
{
$conditionSql = $this->getSelectConditionStatementColumnSQL($field, $assoc);
$placeholder = '?';
if (isset($this->_class->fieldMappings[$field]['requireSQLConversion'])) {
$type = Type::getType($this->_class->getTypeOfField($field));
$placeholder = $type->convertToDatabaseValueSQL($placeholder, $this->_platform);
}
$conditionSql .= ($comparison === null)
? ((is_array($value)) ? ' IN (?)' : (($value === null) ? ' IS NULL' : ' = ' . $placeholder))
: ' ' . sprintf(self::$comparisonMap[$comparison], $placeholder);
return $conditionSql;
}
/**
* Build the left-hand-side of a where condition statement.
*
* @param string $field
* @param array $assoc
*
* @return string
*/
protected function getSelectConditionStatementColumnSQL($field, $assoc = null)
{
switch (true) {
case (isset($this->_class->columnNames[$field])):
$className = (isset($this->_class->fieldMappings[$field]['inherited']))
? $this->_class->fieldMappings[$field]['inherited']
: $this->_class->name;
return $this->_getSQLTableAlias($className) . '.' . $this->quoteStrategy->getColumnName($field, $this->_class, $this->_platform);
case (isset($this->_class->associationMappings[$field])):
if ( ! $this->_class->associationMappings[$field]['isOwningSide']) {
throw ORMException::invalidFindByInverseAssociation($this->_class->name, $field);
}
$className = (isset($this->_class->associationMappings[$field]['inherited']))
? $this->_class->associationMappings[$field]['inherited']
: $this->_class->name;
return $this->_getSQLTableAlias($className) . '.' . $this->_class->associationMappings[$field]['joinColumns'][0]['name'];
case ($assoc !== null && strpos($field, " ") === false && strpos($field, "(") === false):
// very careless developers could potentially open up this normally hidden api for userland attacks,
// therefore checking for spaces and function calls which are not allowed.
// found a join column condition, not really a "field"
return $field;
}
throw ORMException::unrecognizedField($field);
}
/**
* Gets the conditional SQL fragment used in the WHERE clause when selecting
* entities in this persister.
@ -1353,42 +1508,9 @@ class BasicEntityPersister
foreach ($criteria as $field => $value) {
$conditionSql .= $conditionSql ? ' AND ' : '';
$placeholder = '?';
if (isset($this->_class->columnNames[$field])) {
$className = (isset($this->_class->fieldMappings[$field]['inherited']))
? $this->_class->fieldMappings[$field]['inherited']
: $this->_class->name;
$conditionSql .= $this->_getSQLTableAlias($className) . '.' . $this->quoteStrategy->getColumnName($field, $this->_class, $this->_platform);
if (isset($this->_class->fieldMappings[$field]['requireSQLConversion'])) {
$type = Type::getType($this->_class->getTypeOfField($field));
$placeholder = $type->convertToDatabaseValueSQL($placeholder, $this->_platform);
}
} else if (isset($this->_class->associationMappings[$field])) {
if ( ! $this->_class->associationMappings[$field]['isOwningSide']) {
throw ORMException::invalidFindByInverseAssociation($this->_class->name, $field);
}
$className = (isset($this->_class->associationMappings[$field]['inherited']))
? $this->_class->associationMappings[$field]['inherited']
: $this->_class->name;
$conditionSql .= $this->_getSQLTableAlias($className) . '.' . $this->_class->associationMappings[$field]['joinColumns'][0]['name'];
} else if ($assoc !== null && strpos($field, " ") === false && strpos($field, "(") === false) {
// very careless developers could potentially open up this normally hidden api for userland attacks,
// therefore checking for spaces and function calls which are not allowed.
// found a join column condition, not really a "field"
$conditionSql .= $field;
} else {
throw ORMException::unrecognizedField($field);
}
$conditionSql .= (is_array($value)) ? ' IN (?)' : (($value === null) ? ' IS NULL' : ' = ' . $placeholder);
$conditionSql .= $this->getSelectConditionStatementSQL($field, $value, $assoc);
}
return $conditionSql;
}

View File

@ -19,11 +19,14 @@
namespace Doctrine\ORM\Persisters;
use Doctrine\ORM\ORMException,
Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\DBAL\LockMode,
Doctrine\DBAL\Types\Type,
Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\ORMException;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\DBAL\LockMode;
use Doctrine\DBAL\Types\Type;
use Doctrine\Common\Collections\Criteria;
/**
* The joined subclass persister maps a single entity instance to several tables in the
@ -264,7 +267,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
/**
* {@inheritdoc}
*/
protected function _getSelectEntitiesSQL(array $criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null)
protected function _getSelectEntitiesSQL($criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null)
{
$idColumns = $this->_class->getIdentifierColumnNames();
$baseTableAlias = $this->_getSQLTableAlias($this->_class->name);
@ -373,7 +376,9 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
$joinSql .= ($assoc != null && $assoc['type'] == ClassMetadata::MANY_TO_MANY) ? $this->_getSelectManyToManyJoinSQL($assoc) : '';
$conditionSql = $this->_getSelectConditionSQL($criteria, $assoc);
$conditionSql = ($criteria instanceof Criteria)
? $this->_getSelectConditionCriteriaSQL($criteria)
: $this->_getSelectConditionSQL($criteria, $assoc);
// If the current class in the root entity, add the filters
if ($filterSql = $this->generateFilterConditionSQL($this->_em->getClassMetadata($this->_class->rootEntityName), $this->_getSQLTableAlias($this->_class->rootEntityName))) {

View File

@ -20,6 +20,7 @@
namespace Doctrine\ORM\Persisters;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\Common\Collections\Criteria;
/**
* Persister for entities that participate in a hierarchy mapped with the
@ -113,9 +114,27 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
{
$conditionSql = parent::_getSelectConditionSQL($criteria, $assoc);
// Append discriminator condition
if ($conditionSql) $conditionSql .= ' AND ';
if ($conditionSql) {
$conditionSql .= ' AND ';
}
return $conditionSql . $this->_getSelectConditionDiscriminatorValueSQL();
}
/** {@inheritdoc} */
protected function _getSelectConditionCriteriaSQL(Criteria $criteria)
{
$conditionSql = parent::_getSelectConditionCriteriaSQL($criteria);
if ($conditionSql) {
$conditionSql .= ' AND ';
}
return $conditionSql . $this->_getSelectConditionDiscriminatorValueSQL();
}
protected function _getSelectConditionDiscriminatorValueSQL()
{
$values = array();
if ($this->_class->discriminatorValue !== null) { // discriminators can be 0
@ -128,10 +147,8 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
$values[] = $this->_conn->quote($discrValues[$subclassName]);
}
$conditionSql .= $this->_getSQLTableAlias($this->_class->name) . '.' . $this->_class->discriminatorColumn['name']
. ' IN (' . implode(', ', $values) . ')';
return $conditionSql;
return $this->_getSQLTableAlias($this->_class->name) . '.' . $this->_class->discriminatorColumn['name']
. ' IN (' . implode(', ', $values) . ')';
}
/** {@inheritdoc} */

View File

@ -0,0 +1,102 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Persisters;
use Doctrine\Common\Collections\Expr\ExpressionVisitor;
use Doctrine\Common\Collections\Expr\Comparison;
use Doctrine\Common\Collections\Expr\Value;
use Doctrine\Common\Collections\Expr\CompositeExpression;
/**
* Visit Expressions and generate SQL WHERE conditions from them.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.3
*/
class SqlExpressionVisitor extends ExpressionVisitor
{
/**
* @var \Doctrine\ORM\Persisters\BasicEntityPersister
*/
private $persister;
/**
* @param \Doctrine\ORM\Persisters\BasicEntityPersister $persister
*/
public function __construct(BasicEntityPersister $persister)
{
$this->persister = $persister;
}
/**
* Convert a comparison expression into the target query language output
*
* @param \Doctrine\Common\Collections\Expr\Comparison $comparison
*
* @return mixed
*/
public function walkComparison(Comparison $comparison)
{
$field = $comparison->getField();
$value = $comparison->getValue()->getValue(); // shortcut for walkValue()
return $this->persister->getSelectConditionStatementSQL($field, $value, null, $comparison->getOperator());
}
/**
* Convert a composite expression into the target query language output
*
* @param \Doctrine\Common\Collections\Expr\CompositeExpression $expr
*
* @return mixed
*/
public function walkCompositeExpression(CompositeExpression $expr)
{
$expressionList = array();
foreach ($expr->getExpressionList() as $child) {
$expressionList[] = $this->dispatch($child);
}
switch($expr->getType()) {
case CompositeExpression::TYPE_AND:
return '(' . implode(' AND ', $expressionList) . ')';
case CompositeExpression::TYPE_OR:
return '(' . implode(' OR ', $expressionList) . ')';
default:
throw new \RuntimeException("Unknown composite " . $expr->getType());
}
}
/**
* Convert a value expression into the target query language part.
*
* @param \Doctrine\Common\Collections\Expr\Value $value
*
* @return mixed
*/
public function walkValue(Value $value)
{
return '?';
}
}

View File

@ -0,0 +1,126 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Persisters;
use Doctrine\Common\Collections\Expr\ExpressionVisitor;
use Doctrine\Common\Collections\Expr\Comparison;
use Doctrine\Common\Collections\Expr\Value;
use Doctrine\Common\Collections\Expr\CompositeExpression;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Connection;
/**
* Extract the values from a criteria/expression
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class SqlValueVisitor extends ExpressionVisitor
{
/**
* @var array
*/
private $values = array();
/**
* @var array
*/
private $types = array();
/**
* @var \Doctrine\ORM\Mapping\ClassMetadata
*/
private $class;
/**
* @param \Doctrine\ORM\Mapping\ClassMetadata
*/
public function __construct(ClassMetadata $class)
{
$this->class = $class;
}
/**
* Convert a comparison expression into the target query language output
*
* @param \Doctrine\Common\Collections\Expr\Comparison $comparison
*
* @return mixed
*/
public function walkComparison(Comparison $comparison)
{
$value = $comparison->getValue()->getValue();
$field = $comparison->getField();
$this->values[] = $value;
$this->types[] = $this->getType($field, $value);
}
/**
* Convert a composite expression into the target query language output
*
* @param \Doctrine\Common\Collections\Expr\CompositeExpression $expr
*
* @return mixed
*/
public function walkCompositeExpression(CompositeExpression $expr)
{
foreach ($expr->getExpressionList() as $child) {
$this->dispatch($child);
}
}
/**
* Convert a value expression into the target query language part.
*
* @param \Doctrine\Common\Collections\Expr\Value $value
*
* @return mixed
*/
public function walkValue(Value $value)
{
return;
}
/**
* Return the Parameters and Types necessary for matching the last visited expression.
*
* @return array
*/
public function getParamsAndTypes()
{
return array($this->values, $this->types);
}
private function getType($field, $value)
{
$type = isset($this->class->fieldMappings[$field])
? Type::getType($this->class->fieldMappings[$field]['type'])->getBindingType()
: \PDO::PARAM_STR;
if (is_array($value)) {
$type += Connection::ARRAY_PARAM_OFFSET;
}
return $type;
}
}

View File

@ -2,8 +2,6 @@
namespace Doctrine\Tests\ORM\Functional;
require_once __DIR__ . '/../../TestInit.php';
use Doctrine\Tests\Models\Company\CompanyPerson,
Doctrine\Tests\Models\Company\CompanyEmployee,
Doctrine\Tests\Models\Company\CompanyManager,
@ -13,6 +11,8 @@ use Doctrine\Tests\Models\Company\CompanyPerson,
Doctrine\Tests\Models\Company\CompanyRaffle,
Doctrine\Tests\Models\Company\CompanyCar;
use Doctrine\Common\Collections\Criteria;
/**
* Functional tests for the Class Table Inheritance mapping strategy.
*
@ -467,4 +467,31 @@ class ClassTableInheritanceTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertTrue($this->_em->getUnitOfWork()->getEntityPersister(get_class($manager))->exists($manager));
}
/**
* @group DDC-1637
*/
public function testMatching()
{
$manager = new CompanyManager();
$manager->setName('gblanco');
$manager->setSalary(1234);
$manager->setTitle('Awesome!');
$manager->setDepartment('IT');
$this->_em->persist($manager);
$this->_em->flush();
$repository = $this->_em->getRepository("Doctrine\Tests\Models\Company\CompanyEmployee");
$users = $repository->matching(new Criteria(
$repository->expr()->eq('department', 'IT')
));
$this->assertEquals(1, count($users));
$repository = $this->_em->getRepository("Doctrine\Tests\Models\Company\CompanyManager");
$users = $repository->matching(new Criteria(
$repository->expr()->eq('department', 'IT')
));
$this->assertEquals(1, count($users));
}
}

View File

@ -5,6 +5,7 @@ namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\Models\CMS\CmsAddress;
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
use Doctrine\Common\Collections\Criteria;
require_once __DIR__ . '/../../TestInit.php';
@ -558,5 +559,138 @@ class EntityRepositoryTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals(array(1,2,3), $query['params'][0]);
$this->assertEquals(\Doctrine\DBAL\Connection::PARAM_INT_ARRAY, $query['types'][0]);
}
/**
* @group DDC-1637
*/
public function testMatchingEmptyCriteria()
{
$this->loadFixture();
$repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$users = $repository->matching(new Criteria());
$this->assertEquals(4, count($users));
}
/**
* @group DDC-1637
*/
public function testMatchingCriteriaEqComparison()
{
$this->loadFixture();
$repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$users = $repository->matching(new Criteria(
$repository->expr()->eq('username', 'beberlei')
));
$this->assertEquals(1, count($users));
}
/**
* @group DDC-1637
*/
public function testMatchingCriteriaNeqComparison()
{
$this->loadFixture();
$repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$users = $repository->matching(new Criteria(
$repository->expr()->neq('username', 'beberlei')
));
$this->assertEquals(3, count($users));
}
/**
* @group DDC-1637
*/
public function testMatchingCriteriaInComparison()
{
$this->loadFixture();
$repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$users = $repository->matching(new Criteria(
$repository->expr()->in('username', array('beberlei', 'gblanco'))
));
$this->assertEquals(2, count($users));
}
/**
* @group DDC-1637
*/
public function testMatchingCriteriaNotInComparison()
{
$this->loadFixture();
$repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$users = $repository->matching(new Criteria(
$repository->expr()->notIn('username', array('beberlei', 'gblanco', 'asm89'))
));
$this->assertEquals(1, count($users));
}
/**
* @group DDC-1637
*/
public function testMatchingCriteriaLtComparison()
{
$firstUserId = $this->loadFixture();
$repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$users = $repository->matching(new Criteria(
$repository->expr()->lt('id', $firstUserId + 1)
));
$this->assertEquals(1, count($users));
}
/**
* @group DDC-1637
*/
public function testMatchingCriteriaLeComparison()
{
$firstUserId = $this->loadFixture();
$repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$users = $repository->matching(new Criteria(
$repository->expr()->lte('id', $firstUserId + 1)
));
$this->assertEquals(2, count($users));
}
/**
* @group DDC-1637
*/
public function testMatchingCriteriaGtComparison()
{
$firstUserId = $this->loadFixture();
$repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$users = $repository->matching(new Criteria(
$repository->expr()->gt('id', $firstUserId)
));
$this->assertEquals(3, count($users));
}
/**
* @group DDC-1637
*/
public function testMatchingCriteriaGteComparison()
{
$firstUserId = $this->loadFixture();
$repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser');
$users = $repository->matching(new Criteria(
$repository->expr()->gte('id', $firstUserId)
));
$this->assertEquals(4, count($users));
}
}

View File

@ -151,6 +151,29 @@ class OneToManyBidirectionalAssociationTest extends \Doctrine\Tests\OrmFunctiona
$this->assertEquals(0, count($features));
}
/**
* @group DDC-1637
*/
public function testMatching()
{
$this->_createFixture();
$product = $this->_em->find('Doctrine\Tests\Models\ECommerce\ECommerceProduct', $this->product->getId());
$features = $product->getFeatures();
$results = $features->matching(new \Doctrine\Common\Collections\Criteria(
$features->expr()->eq('description', 'Model writing tutorial')
));
$this->assertInstanceOf('Doctrine\Common\Collections\Collection', $results);
$this->assertEquals(1, count($results));
$results = $features->matching(new \Doctrine\Common\Collections\Criteria());
$this->assertInstanceOf('Doctrine\Common\Collections\Collection', $results);
$this->assertEquals(2, count($results));
}
private function _createFixture()
{
$this->product->addFeature($this->firstFeature);

View File

@ -3,8 +3,7 @@
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\ORM\Mapping\ClassMetadata;
require_once __DIR__ . '/../../TestInit.php';
use Doctrine\Common\Collections\Criteria;
class SingleTableInheritanceTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
@ -335,6 +334,26 @@ class SingleTableInheritanceTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals(1, count($contracts), "There should be 1 entities related to " . $this->salesPerson->getId() . " for 'Doctrine\Tests\Models\Company\CompanyFlexUltraContract'");
}
/**
* @group DDC-1637
*/
public function testInheritanceMatching()
{
$this->loadFullFixture();
$repository = $this->_em->getRepository("Doctrine\Tests\Models\Company\CompanyContract");
$contracts = $repository->matching(new Criteria(
$repository->expr()->eq('salesPerson', $this->salesPerson->getId())
));
$this->assertEquals(3, count($contracts));
$repository = $this->_em->getRepository("Doctrine\Tests\Models\Company\CompanyFixContract");
$contracts = $repository->matching(new Criteria(
$repository->expr()->eq('salesPerson', $this->salesPerson->getId())
));
$this->assertEquals(1, count($contracts));
}
/**
* @group DDC-834
*/