Merge pull request #381 from doctrine/DDC-1637
[DDC-1637] Collection Filtering API
This commit is contained in:
commit
113c6f51c2
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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))) {
|
||||
|
@ -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} */
|
||||
|
102
lib/Doctrine/ORM/Persisters/SqlExpressionVisitor.php
Normal file
102
lib/Doctrine/ORM/Persisters/SqlExpressionVisitor.php
Normal 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 '?';
|
||||
}
|
||||
}
|
||||
|
126
lib/Doctrine/ORM/Persisters/SqlValueVisitor.php
Normal file
126
lib/Doctrine/ORM/Persisters/SqlValueVisitor.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user