1
0
Fork 0
mirror of synced 2025-04-02 12:56:16 +03:00

Merge pull request from doctrine/DDC-551

DDC-551 - Filter branch
This commit is contained in:
Benjamin Eberlei 2011-12-18 08:03:23 -08:00
commit 003d1410b0
13 changed files with 1349 additions and 21 deletions

View file

@ -495,7 +495,32 @@ class Configuration extends \Doctrine\DBAL\Configuration
}
return $this->_attributes['classMetadataFactoryName'];
}
/**
* Add a filter to the list of possible filters.
*
* @param string $name The name of the filter.
* @param string $className The class name of the filter.
*/
public function addFilter($name, $className)
{
$this->_attributes['filters'][$name] = $className;
}
/**
* Gets the class name for a given filter name.
*
* @param string $name The name of the filter.
*
* @return string The class name of the filter, or null of it is not
* defined.
*/
public function getFilterClassName($name)
{
return isset($this->_attributes['filters'][$name]) ?
$this->_attributes['filters'][$name] : null;
}
/**
* Set default repository class.
*
@ -523,4 +548,4 @@ class Configuration extends \Doctrine\DBAL\Configuration
return isset($this->_attributes['defaultRepositoryClassName']) ?
$this->_attributes['defaultRepositoryClassName'] : 'Doctrine\ORM\EntityRepository';
}
}
}

View file

@ -27,7 +27,8 @@ use Closure, Exception,
Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\ORM\Mapping\ClassMetadataFactory,
Doctrine\ORM\Query\ResultSetMapping,
Doctrine\ORM\Proxy\ProxyFactory;
Doctrine\ORM\Proxy\ProxyFactory,
Doctrine\ORM\Query\FilterCollection;
/**
* The EntityManager is the central access point to ORM functionality.
@ -110,6 +111,13 @@ class EntityManager implements ObjectManager
*/
private $closed = false;
/**
* Collection of query filters.
*
* @var Doctrine\ORM\Query\FilterCollection
*/
private $filterCollection;
/**
* Creates a new EntityManager that operates on the given database connection
* and uses the given Configuration and EventManager implementations.
@ -788,4 +796,39 @@ class EntityManager implements ObjectManager
return new EntityManager($conn, $config, $conn->getEventManager());
}
/**
* Gets the enabled filters.
*
* @return FilterCollection The active filter collection.
*/
public function getFilters()
{
if (null === $this->filterCollection) {
$this->filterCollection = new FilterCollection($this);
}
return $this->filterCollection;
}
/**
* Checks whether the state of the filter collection is clean.
*
* @return boolean True, if the filter collection is clean.
*/
public function isFiltersStateClean()
{
return null === $this->filterCollection
|| $this->filterCollection->isClean();
}
/**
* Checks whether the Entity Manager has filters.
*
* @return True, if the EM has a filter collection.
*/
public function hasFilters()
{
return null !== $this->filterCollection;
}
}

View file

@ -72,6 +72,7 @@ use PDO,
* @author Roman Borschel <roman@code-factory.org>
* @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Alexander <iam.asm89@gmail.com>
* @since 2.0
*/
class BasicEntityPersister
@ -900,9 +901,19 @@ class BasicEntityPersister
$lockSql = ' ' . $this->_platform->getWriteLockSql();
}
$alias = $this->_getSQLTableAlias($this->_class->name);
if ($filterSql = $this->generateFilterConditionSQL($this->_class, $alias)) {
if ($conditionSql) {
$conditionSql .= ' AND ';
}
$conditionSql .= $filterSql;
}
return $this->_platform->modifyLimitQuery('SELECT ' . $this->_getSelectColumnListSQL()
. $this->_platform->appendLockHint(' FROM ' . $this->_class->getQuotedTableName($this->_platform) . ' '
. $this->_getSQLTableAlias($this->_class->name), $lockMode)
. $alias, $lockMode)
. $this->_selectJoinSql . $joinSql
. ($conditionSql ? ' WHERE ' . $conditionSql : '')
. $orderBySql, $limit, $offset)
@ -1014,14 +1025,20 @@ class BasicEntityPersister
$this->_selectJoinSql .= ' ' . $this->getJoinSQLForJoinColumns($assoc['joinColumns']);
$this->_selectJoinSql .= ' ' . $eagerEntity->getQuotedTableName($this->_platform) . ' ' . $this->_getSQLTableAlias($eagerEntity->name, $assocAlias) .' ON ';
$tableAlias = $this->_getSQLTableAlias($assoc['targetEntity'], $assocAlias);
foreach ($assoc['sourceToTargetKeyColumns'] AS $sourceCol => $targetCol) {
if ( ! $first) {
$this->_selectJoinSql .= ' AND ';
}
$this->_selectJoinSql .= $this->_getSQLTableAlias($assoc['sourceEntity']) . '.' . $sourceCol . ' = '
. $this->_getSQLTableAlias($assoc['targetEntity'], $assocAlias) . '.' . $targetCol;
. $tableAlias . '.' . $targetCol;
$first = false;
}
// Add filter SQL
if ($filterSql = $this->generateFilterConditionSQL($eagerEntity, $tableAlias)) {
$this->_selectJoinSql .= ' AND ' . $filterSql;
}
} else {
$eagerEntity = $this->_em->getClassMetadata($assoc['targetEntity']);
$owningAssoc = $eagerEntity->getAssociationMapping($assoc['mappedBy']);
@ -1521,10 +1538,16 @@ class BasicEntityPersister
$criteria = array_merge($criteria, $extraConditions);
}
$alias = $this->_getSQLTableAlias($this->_class->name);
$sql = 'SELECT 1 '
. $this->getLockTablesSql()
. ' WHERE ' . $this->_getSelectConditionSQL($criteria);
if ($filterSql = $this->generateFilterConditionSQL($this->_class, $alias)) {
$sql .= ' AND ' . $filterSql;
}
list($params, $types) = $this->expandParameters($criteria);
return (bool) $this->_conn->fetchColumn($sql, $params);
@ -1539,8 +1562,8 @@ class BasicEntityPersister
protected function getJoinSQLForJoinColumns($joinColumns)
{
// if one of the join columns is nullable, return left join
foreach($joinColumns as $joinColumn) {
if(isset($joinColumn['nullable']) && $joinColumn['nullable']){
foreach ($joinColumns as $joinColumn) {
if (isset($joinColumn['nullable']) && $joinColumn['nullable']) {
return 'LEFT JOIN';
}
}
@ -1562,4 +1585,26 @@ class BasicEntityPersister
substr($columnName . $this->_sqlAliasCounter++, -$this->_platform->getMaxIdentifierLength())
);
}
/**
* Generates the filter SQL for a given entity and table alias.
*
* @param ClassMetadata $targetEntity Metadata of the target entity.
* @param string $targetTableAlias The table alias of the joined/selected table.
*
* @return string The SQL query part to add to a query.
*/
protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
{
$filterClauses = array();
foreach ($this->_em->getFilters()->getEnabledFilters() as $filter) {
if ('' !== $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) {
$filterClauses[] = '(' . $filterExpr . ')';
}
}
$sql = implode(' AND ', $filterClauses);
return $sql ? "(" . $sql . ")" : ""; // Wrap again to avoid "X or Y and FilterConditionSQL"
}
}

View file

@ -31,6 +31,7 @@ use Doctrine\ORM\ORMException,
*
* @author Roman Borschel <roman@code-factory.org>
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Alexander <iam.asm89@gmail.com>
* @since 2.0
* @see http://martinfowler.com/eaaCatalog/classTableInheritance.html
*/
@ -374,6 +375,15 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
$conditionSql = $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))) {
if ($conditionSql) {
$conditionSql .= ' AND ';
}
$conditionSql .= $filterSql;
}
$orderBy = ($assoc !== null && isset($assoc['orderBy'])) ? $assoc['orderBy'] : $orderBy;
$orderBySql = $orderBy ? $this->_getOrderBySQL($orderBy, $baseTableAlias) : '';
@ -473,4 +483,5 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
$value = $this->fetchVersionValue($this->_getVersionedClassMetadata(), $id);
$this->_class->setFieldValue($entity, $this->_class->versionField, $value);
}
}

View file

@ -21,7 +21,8 @@
namespace Doctrine\ORM\Persisters;
use Doctrine\ORM\PersistentCollection,
use Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\ORM\PersistentCollection,
Doctrine\ORM\UnitOfWork;
/**
@ -29,6 +30,7 @@ use Doctrine\ORM\PersistentCollection,
*
* @author Roman Borschel <roman@code-factory.org>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Alexander <iam.asm89@gmail.com>
* @since 2.0
*/
class ManyToManyPersister extends AbstractCollectionPersister
@ -215,10 +217,16 @@ class ManyToManyPersister extends AbstractCollectionPersister
? $id[$class->getFieldForColumn($joinColumns[$joinTableColumn])]
: $id[$class->fieldNames[$joinColumns[$joinTableColumn]]];
}
list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($mapping);
if ($filterSql) {
$whereClauses[] = $filterSql;
}
$sql = 'SELECT COUNT(*)'
. ' FROM ' . $class->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform())
. ' WHERE ' . implode(' AND ', $whereClauses);
. ' FROM ' . $class->getQuotedJoinTableName($mapping, $this->_conn->getDatabasePlatform()) . ' t'
. $joinTargetEntitySQL
. ' WHERE ' . implode(' AND ', $whereClauses);
return $this->_conn->fetchColumn($sql, $params);
}
@ -250,7 +258,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
return false;
}
list($quotedJoinTable, $whereClauses, $params) = $this->getJoinTableRestrictions($coll, $element);
list($quotedJoinTable, $whereClauses, $params) = $this->getJoinTableRestrictions($coll, $element, true);
$sql = 'SELECT 1 FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
@ -271,7 +279,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
return false;
}
list($quotedJoinTable, $whereClauses, $params) = $this->getJoinTableRestrictions($coll, $element);
list($quotedJoinTable, $whereClauses, $params) = $this->getJoinTableRestrictions($coll, $element, false);
$sql = 'DELETE FROM ' . $quotedJoinTable . ' WHERE ' . implode(' AND ', $whereClauses);
@ -281,9 +289,10 @@ class ManyToManyPersister extends AbstractCollectionPersister
/**
* @param \Doctrine\ORM\PersistentCollection $coll
* @param object $element
* @param boolean $addFilters Whether the filter SQL should be included or not.
* @return array
*/
private function getJoinTableRestrictions(PersistentCollection $coll, $element)
private function getJoinTableRestrictions(PersistentCollection $coll, $element, $addFilters)
{
$uow = $this->_em->getUnitOfWork();
$mapping = $coll->getMapping();
@ -321,7 +330,73 @@ class ManyToManyPersister extends AbstractCollectionPersister
? $sourceId[$sourceClass->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])]
: $sourceId[$sourceClass->fieldNames[$mapping['relationToSourceKeyColumns'][$joinTableColumn]]];
}
if ($addFilters) {
list($joinTargetEntitySQL, $filterSql) = $this->getFilterSql($mapping);
if ($filterSql) {
$quotedJoinTable .= ' t ' . $joinTargetEntitySQL;
$whereClauses[] = $filterSql;
}
}
return array($quotedJoinTable, $whereClauses, $params);
}
}
/**
* Generates the filter SQL for a given mapping.
*
* This method is not used for actually grabbing the related entities
* but when the extra-lazy collection methods are called on a filtered
* association. This is why besides the many to many table we also
* have to join in the actual entities table leading to additional
* JOIN.
*
* @param array $targetEntity Array containing mapping information.
*
* @return string The SQL query part to add to a query.
*/
public function getFilterSql($mapping)
{
$targetClass = $this->_em->getClassMetadata($mapping['targetEntity']);
$targetClass = $this->_em->getClassMetadata($targetClass->rootEntityName);
// A join is needed if there is filtering on the target entity
$joinTargetEntitySQL = '';
if ($filterSql = $this->generateFilterConditionSQL($targetClass, 'te')) {
$joinTargetEntitySQL = ' JOIN '
. $targetClass->getQuotedTableName($this->_conn->getDatabasePlatform()) . ' te'
. ' ON';
$joinTargetEntitySQLClauses = array();
foreach ($mapping['relationToTargetKeyColumns'] as $joinTableColumn => $targetTableColumn) {
$joinTargetEntitySQLClauses[] = ' t.' . $joinTableColumn . ' = ' . 'te.' . $targetTableColumn;
}
$joinTargetEntitySQL .= implode(' AND ', $joinTargetEntitySQLClauses);
}
return array($joinTargetEntitySQL, $filterSql);
}
/**
* Generates the filter SQL for a given entity and table alias.
*
* @param ClassMetadata $targetEntity Metadata of the target entity.
* @param string $targetTableAlias The table alias of the joined/selected table.
*
* @return string The SQL query part to add to a query.
*/
protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
{
$filterClauses = array();
foreach ($this->_em->getFilters()->getEnabledFilters() as $filter) {
if ($filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) {
$filterClauses[] = '(' . $filterExpr . ')';
}
}
$sql = implode(' AND ', $filterClauses);
return $sql ? "(" . $sql . ")" : "";
}
}

View file

@ -1,7 +1,5 @@
<?php
/*
* $Id$
*
* 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
@ -29,6 +27,7 @@ use Doctrine\ORM\PersistentCollection,
*
* @author Roman Borschel <roman@code-factory.org>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Alexander <iam.asm89@gmail.com>
* @since 2.0
*/
class OneToManyPersister extends AbstractCollectionPersister
@ -120,10 +119,16 @@ class OneToManyPersister extends AbstractCollectionPersister
: $id[$sourceClass->fieldNames[$joinColumn['referencedColumnName']]];
}
foreach ($this->_em->getFilters()->getEnabledFilters() as $filter) {
if ($filterExpr = $filter->addFilterConstraint($targetClass, 't')) {
$whereClauses[] = '(' . $filterExpr . ')';
}
}
$sql = 'SELECT count(*)'
. ' FROM ' . $targetClass->getQuotedTableName($this->_conn->getDatabasePlatform())
. ' FROM ' . $targetClass->getQuotedTableName($this->_conn->getDatabasePlatform()) . ' t'
. ' WHERE ' . implode(' AND ', $whereClauses);
return $this->_conn->fetchColumn($sql, $params);
}

View file

@ -27,6 +27,7 @@ use Doctrine\ORM\Mapping\ClassMetadata;
*
* @author Roman Borschel <roman@code-factory.org>
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Alexander <iam.asm89@gmail.com>
* @since 2.0
* @link http://martinfowler.com/eaaCatalog/singleTableInheritance.html
*/
@ -131,4 +132,14 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
return $conditionSql;
}
/** {@inheritdoc} */
protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
{
// Ensure that the filters are applied to the root entity of the inheritance tree
$targetEntity = $this->_em->getClassMetadata($targetEntity->rootEntityName);
// we dont care about the $targetTableAlias, in a STI there is only one table.
return parent::generateFilterConditionSQL($targetEntity, $targetTableAlias);
}
}

View file

@ -198,7 +198,10 @@ final class Query extends AbstractQuery
*/
private function _parse()
{
if ($this->_state === self::STATE_CLEAN) {
// Return previous parser result if the query and the filter collection are both clean
if ($this->_state === self::STATE_CLEAN
&& $this->_em->isFiltersStateClean()
) {
return $this->_parserResult;
}
@ -623,6 +626,7 @@ final class Query extends AbstractQuery
return md5(
$this->getDql() . var_export($this->_hints, true) .
($this->_em->hasFilters() ? $this->_em->getFilters()->getHash() : '') .
'&firstResult=' . $this->_firstResult . '&maxResult=' . $this->_maxResults .
'&hydrationMode='.$this->_hydrationMode.'DOCTRINE_QUERY_CACHE_SALT'
);

View file

@ -0,0 +1,115 @@
<?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 LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Query\Filter;
use Doctrine\ORM\Mapping\ClassMetaData;
use Doctrine\ORM\EntityManager;
/**
* The base class that user defined filters should extend.
*
* Handles the setting and escaping of parameters.
*
* @author Alexander <iam.asm89@gmail.com>
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @abstract
*/
abstract class SQLFilter
{
/**
* The entity manager.
* @var EntityManager
*/
private $em;
/**
* Parameters for the filter.
* @var array
*/
private $parameters;
/**
* Constructs the SQLFilter object.
*
* @param EntityManager $em The EM
*/
final public function __construct(EntityManager $em)
{
$this->em = $em;
}
/**
* Sets a parameter that can be used by the filter.
*
* @param string $name Name of the parameter.
* @param string $value Value of the parameter.
* @param string $type Type of the parameter.
*
* @return SQLFilter The current SQL filter.
*/
final public function setParameter($name, $value, $type)
{
$this->parameters[$name] = array('value' => $value, 'type' => $type);
// Keep the parameters sorted for the hash
ksort($this->parameters);
// The filter collection of the EM is now dirty
$this->em->getFilters()->setFiltersStateDirty();
return $this;
}
/**
* Gets a parameter to use in a query.
*
* The function is responsible for the right output escaping to use the
* value in a query.
*
* @param string $name Name of the parameter.
*
* @return string The SQL escaped parameter to use in a query.
*/
final public function getParameter($name)
{
if (!isset($this->parameters[$name])) {
throw new \InvalidArgumentException("Parameter '" . $name . "' does not exist.");
}
return $this->em->getConnection()->quote($this->parameters[$name]['value'], $this->parameters[$name]['type']);
}
/**
* Returns as string representation of the SQLFilter parameters (the state).
*
* @return string String representation of the SQLFilter.
*/
final public function __toString()
{
return serialize($this->parameters);
}
/**
* Gets the SQL query part to add to a query.
*
* @return string The constraint SQL if there is available, empty string otherwise
*/
abstract public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias);
}

View file

@ -0,0 +1,198 @@
<?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 LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Query;
use Doctrine\ORM\Configuration,
Doctrine\ORM\EntityManager;
/**
* Collection class for all the query filters.
*
* @author Alexander <iam.asm89@gmail.com>
*/
class FilterCollection
{
/* Filter STATES */
/**
* A filter object is in CLEAN state when it has no changed parameters.
*/
const FILTERS_STATE_CLEAN = 1;
/**
* A filter object is in DIRTY state when it has changed parameters.
*/
const FILTERS_STATE_DIRTY = 2;
/**
* The used Configuration.
*
* @var Doctrine\ORM\Configuration
*/
private $config;
/**
* The EntityManager that "owns" this FilterCollection instance.
*
* @var Doctrine\ORM\EntityManager
*/
private $em;
/**
* Instances of enabled filters.
*
* @var array
*/
private $enabledFilters = array();
/**
* @var string The filter hash from the last time the query was parsed.
*/
private $filterHash;
/**
* @var integer $state The current state of this filter
*/
private $filtersState = self::FILTERS_STATE_CLEAN;
/**
* Constructor.
*
* @param EntityManager $em
*/
public function __construct(EntityManager $em)
{
$this->em = $em;
$this->config = $em->getConfiguration();
}
/**
* Get all the enabled filters.
*
* @return array The enabled filters.
*/
public function getEnabledFilters()
{
return $this->enabledFilters;
}
/**
* Enables a filter from the collection.
*
* @param string $name Name of the filter.
*
* @throws \InvalidArgumentException If the filter does not exist.
*
* @return SQLFilter The enabled filter.
*/
public function enable($name)
{
if (null === $filterClass = $this->config->getFilterClassName($name)) {
throw new \InvalidArgumentException("Filter '" . $name . "' does not exist.");
}
if (!isset($this->enabledFilters[$name])) {
$this->enabledFilters[$name] = new $filterClass($this->em);
// Keep the enabled filters sorted for the hash
ksort($this->enabledFilters);
// Now the filter collection is dirty
$this->filtersState = self::FILTERS_STATE_DIRTY;
}
return $this->enabledFilters[$name];
}
/**
* Disables a filter.
*
* @param string $name Name of the filter.
*
* @return SQLFilter The disabled filter.
*
* @throws \InvalidArgumentException If the filter does not exist.
*/
public function disable($name)
{
// Get the filter to return it
$filter = $this->getFilter($name);
unset($this->enabledFilters[$name]);
// Now the filter collection is dirty
$this->filtersState = self::FILTERS_STATE_DIRTY;
return $filter;
}
/**
* Get an enabled filter from the collection.
*
* @param string $name Name of the filter.
*
* @return SQLFilter The filter.
*
* @throws \InvalidArgumentException If the filter is not enabled.
*/
public function getFilter($name)
{
if (!isset($this->enabledFilters[$name])) {
throw new \InvalidArgumentException("Filter '" . $name . "' is not enabled.");
}
return $this->enabledFilters[$name];
}
/**
* @return boolean True, if the filter collection is clean.
*/
public function isClean()
{
return self::FILTERS_STATE_CLEAN === $this->filtersState;
}
/**
* Generates a string of currently enabled filters to use for the cache id.
*
* @return string
*/
public function getHash()
{
// If there are only clean filters, the previous hash can be returned
if (self::FILTERS_STATE_CLEAN === $this->filtersState) {
return $this->filterHash;
}
$filterHash = '';
foreach ($this->enabledFilters as $name => $filter) {
$filterHash .= $name . $filter;
}
return $filterHash;
}
/**
* Set the filter state to dirty.
*/
public function setFiltersStateDirty()
{
$this->filtersState = self::FILTERS_STATE_DIRTY;
}
}

View file

@ -33,6 +33,7 @@ use Doctrine\DBAL\LockMode,
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Alexander <iam.asm89@gmail.com>
* @since 2.0
* @todo Rename: SQLWalker
*/
@ -267,6 +268,11 @@ class SqlWalker implements TreeWalker
$sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
}
// Add filters on the root class
if ($filterSql = $this->generateFilterConditionSQL($parentClass, $tableAlias)) {
$sqlParts[] = $filterSql;
}
$sql .= implode(' AND ', $sqlParts);
}
@ -352,6 +358,50 @@ class SqlWalker implements TreeWalker
return (count($sqlParts) > 1) ? '(' . $sql . ')' : $sql;
}
/**
* Generates the filter SQL for a given entity and table alias.
*
* @param ClassMetadata $targetEntity Metadata of the target entity.
* @param string $targetTableAlias The table alias of the joined/selected table.
*
* @return string The SQL query part to add to a query.
*/
private function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
{
if (!$this->_em->hasFilters()) {
return '';
}
switch($targetEntity->inheritanceType) {
case ClassMetadata::INHERITANCE_TYPE_NONE:
break;
case ClassMetadata::INHERITANCE_TYPE_JOINED:
// The classes in the inheritance will be added to the query one by one,
// but only the root node is getting filtered
if ($targetEntity->name !== $targetEntity->rootEntityName) {
return '';
}
break;
case ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE:
// With STI the table will only be queried once, make sure that the filters
// are added to the root entity
$targetEntity = $this->_em->getClassMetadata($targetEntity->rootEntityName);
break;
default:
//@todo: throw exception?
return '';
break;
}
$filterClauses = array();
foreach ($this->_em->getFilters()->getEnabledFilters() as $filter) {
if ('' !== $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) {
$filterClauses[] = '(' . $filterExpr . ')';
}
}
return implode(' AND ', $filterClauses);
}
/**
* Walks down a SelectStatement AST node, thereby generating the appropriate SQL.
*
@ -802,6 +852,7 @@ class SqlWalker implements TreeWalker
$sql .= $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn;
}
}
} else if ($assoc['type'] == ClassMetadata::MANY_TO_MANY) {
// Join relation table
$joinTable = $assoc['joinTable'];
@ -867,6 +918,11 @@ class SqlWalker implements TreeWalker
}
}
// Apply the filters
if ($filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias)) {
$sql .= ' AND ' . $filterExpr;
}
// Handle WITH clause
if (($condExpr = $join->conditionalExpression) !== null) {
// Phase 2 AST optimization: Skip processment of ConditionalExpression
@ -1467,6 +1523,26 @@ class SqlWalker implements TreeWalker
$condSql = null !== $whereClause ? $this->walkConditionalExpression($whereClause->conditionalExpression) : '';
$discrSql = $this->_generateDiscriminatorColumnConditionSql($this->_rootAliases);
if ($this->_em->hasFilters()) {
$filterClauses = array();
foreach ($this->_rootAliases as $dqlAlias) {
$class = $this->_queryComponents[$dqlAlias]['metadata'];
$tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
if ($filterExpr = $this->generateFilterConditionSQL($class, $tableAlias)) {
$filterClauses[] = $filterExpr;
}
}
if (count($filterClauses)) {
if ($condSql) {
$condSql .= ' AND ';
}
$condSql .= implode(' AND ', $filterClauses);
}
}
if ($condSql) {
return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql);
}

View file

@ -0,0 +1,720 @@
<?php
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\ORM\Query\Filter\SQLFilter;
use Doctrine\ORM\Mapping\ClassMetaData;
use Doctrine\Common\Cache\ArrayCache;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
use Doctrine\Tests\Models\CMS\CmsAddress;
use Doctrine\Tests\Models\CMS\CmsGroup;
use Doctrine\Tests\Models\CMS\CmsArticle;
use Doctrine\Tests\Models\CMS\CmsComment;
use Doctrine\Tests\Models\Company\CompanyPerson;
use Doctrine\Tests\Models\Company\CompanyManager;
use Doctrine\Tests\Models\Company\CompanyEmployee;
use Doctrine\Tests\Models\Company\CompanyFlexContract;
use Doctrine\Tests\Models\Company\CompanyFlexUltraContract;
require_once __DIR__ . '/../../TestInit.php';
/**
* Tests SQLFilter functionality.
*
* @author Alexander <iam.asm89@gmail.com>
*/
class SQLFilterTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
private $userId, $userId2, $articleId, $articleId2;
private $groupId, $groupId2;
public function setUp()
{
$this->useModelSet('cms');
$this->useModelSet('company');
parent::setUp();
}
public function tearDown()
{
parent::tearDown();
$class = $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser');
$class->associationMappings['groups']['fetch'] = ClassMetadataInfo::FETCH_LAZY;
$class->associationMappings['articles']['fetch'] = ClassMetadataInfo::FETCH_LAZY;
}
public function testConfigureFilter()
{
$config = new \Doctrine\ORM\Configuration();
$config->addFilter("locale", "\Doctrine\Tests\ORM\Functional\MyLocaleFilter");
$this->assertEquals("\Doctrine\Tests\ORM\Functional\MyLocaleFilter", $config->getFilterClassName("locale"));
$this->assertNull($config->getFilterClassName("foo"));
}
public function testEntityManagerEnableFilter()
{
$em = $this->_getEntityManager();
$this->configureFilters($em);
// Enable an existing filter
$filter = $em->getFilters()->enable("locale");
$this->assertTrue($filter instanceof \Doctrine\Tests\ORM\Functional\MyLocaleFilter);
// Enable the filter again
$filter2 = $em->getFilters()->enable("locale");
$this->assertEquals($filter, $filter2);
// Enable a non-existing filter
$exceptionThrown = false;
try {
$filter = $em->getFilters()->enable("foo");
} catch (\InvalidArgumentException $e) {
$exceptionThrown = true;
}
$this->assertTrue($exceptionThrown);
}
public function testEntityManagerEnabledFilters()
{
$em = $this->_getEntityManager();
// No enabled filters
$this->assertEquals(array(), $em->getFilters()->getEnabledFilters());
$this->configureFilters($em);
$filter = $em->getFilters()->enable("locale");
$filter = $em->getFilters()->enable("soft_delete");
// Two enabled filters
$this->assertEquals(2, count($em->getFilters()->getEnabledFilters()));
}
public function testEntityManagerDisableFilter()
{
$em = $this->_getEntityManager();
$this->configureFilters($em);
// Enable the filter
$filter = $em->getFilters()->enable("locale");
// Disable it
$this->assertEquals($filter, $em->getFilters()->disable("locale"));
$this->assertEquals(0, count($em->getFilters()->getEnabledFilters()));
// Disable a non-existing filter
$exceptionThrown = false;
try {
$filter = $em->getFilters()->disable("foo");
} catch (\InvalidArgumentException $e) {
$exceptionThrown = true;
}
$this->assertTrue($exceptionThrown);
// Disable a non-enabled filter
$exceptionThrown = false;
try {
$filter = $em->getFilters()->disable("locale");
} catch (\InvalidArgumentException $e) {
$exceptionThrown = true;
}
$this->assertTrue($exceptionThrown);
}
public function testEntityManagerGetFilter()
{
$em = $this->_getEntityManager();
$this->configureFilters($em);
// Enable the filter
$filter = $em->getFilters()->enable("locale");
// Get the filter
$this->assertEquals($filter, $em->getFilters()->getFilter("locale"));
// Get a non-enabled filter
$exceptionThrown = false;
try {
$filter = $em->getFilters()->getFilter("soft_delete");
} catch (\InvalidArgumentException $e) {
$exceptionThrown = true;
}
$this->assertTrue($exceptionThrown);
}
protected function configureFilters($em)
{
// Add filters to the configuration of the EM
$config = $em->getConfiguration();
$config->addFilter("locale", "\Doctrine\Tests\ORM\Functional\MyLocaleFilter");
$config->addFilter("soft_delete", "\Doctrine\Tests\ORM\Functional\MySoftDeleteFilter");
}
protected function getMockConnection()
{
// Setup connection mock
$conn = $this->getMockBuilder('Doctrine\DBAL\Connection')
->disableOriginalConstructor()
->getMock();
return $conn;
}
protected function getMockEntityManager()
{
// Setup connection mock
$em = $this->getMockBuilder('Doctrine\ORM\EntityManager')
->disableOriginalConstructor()
->getMock();
return $em;
}
protected function addMockFilterCollection($em)
{
$filterCollection = $this->getMockBuilder('Doctrine\ORM\Query\FilterCollection')
->disableOriginalConstructor()
->getMock();
$em->expects($this->any())
->method('getFilters')
->will($this->returnValue($filterCollection));
return $filterCollection;
}
public function testSQLFilterGetSetParameter()
{
// Setup mock connection
$conn = $this->getMockConnection();
$conn->expects($this->once())
->method('quote')
->with($this->equalTo('en'))
->will($this->returnValue("'en'"));
$em = $this->getMockEntityManager($conn);
$em->expects($this->once())
->method('getConnection')
->will($this->returnValue($conn));
$filterCollection = $this->addMockFilterCollection($em);
$filterCollection
->expects($this->once())
->method('setFiltersStateDirty');
$filter = new MyLocaleFilter($em);
$filter->setParameter('locale', 'en', \Doctrine\DBAL\Types\Type::STRING);
$this->assertEquals("'en'", $filter->getParameter('locale'));
}
public function testSQLFilterAddConstraint()
{
// Set up metadata mock
$targetEntity = $this->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadata')
->disableOriginalConstructor()
->getMock();
$filter = new MySoftDeleteFilter($this->getMockEntityManager());
// Test for an entity that gets extra filter data
$targetEntity->name = 'MyEntity\SoftDeleteNewsItem';
$this->assertEquals('t1_.deleted = 0', $filter->addFilterConstraint($targetEntity, 't1_'));
// Test for an entity that doesn't get extra filter data
$targetEntity->name = 'MyEntity\NoSoftDeleteNewsItem';
$this->assertEquals('', $filter->addFilterConstraint($targetEntity, 't1_'));
}
public function testSQLFilterToString()
{
$em = $this->getMockEntityManager();
$filterCollection = $this->addMockFilterCollection($em);
$filter = new MyLocaleFilter($em);
$filter->setParameter('locale', 'en', \Doctrine\DBAL\Types\Type::STRING);
$filter->setParameter('foo', 'bar', \Doctrine\DBAL\Types\Type::STRING);
$filter2 = new MyLocaleFilter($em);
$filter2->setParameter('foo', 'bar', \Doctrine\DBAL\Types\Type::STRING);
$filter2->setParameter('locale', 'en', \Doctrine\DBAL\Types\Type::STRING);
$parameters = array(
'foo' => array('value' => 'bar', 'type' => \Doctrine\DBAL\Types\Type::STRING),
'locale' => array('value' => 'en', 'type' => \Doctrine\DBAL\Types\Type::STRING),
);
$this->assertEquals(serialize($parameters), ''.$filter);
$this->assertEquals(''.$filter, ''.$filter2);
}
public function testQueryCache_DependsOnFilters()
{
$cacheDataReflection = new \ReflectionProperty("Doctrine\Common\Cache\ArrayCache", "data");
$cacheDataReflection->setAccessible(true);
$query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux');
$cache = new ArrayCache();
$query->setQueryCacheDriver($cache);
$query->getResult();
$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(2, sizeof($cacheDataReflection->getValue($cache)));
// Another time doesn't add another cache entry
$query->getResult();
$this->assertEquals(2, sizeof($cacheDataReflection->getValue($cache)));
}
public function testQueryGeneration_DependsOnFilters()
{
$query = $this->_em->createQuery('select a from Doctrine\Tests\Models\CMS\CmsAddress a');
$firstSQLQuery = $query->getSQL();
$conf = $this->_em->getConfiguration();
$conf->addFilter("country", "\Doctrine\Tests\ORM\Functional\CMSCountryFilter");
$this->_em->getFilters()->enable("country")
->setParameter("country", "en", \Doctrine\DBAL\Types\Type::getType(\Doctrine\DBAL\Types\Type::STRING)->getBindingType());
$this->assertNotEquals($firstSQLQuery, $query->getSQL());
}
public function testToOneFilter()
{
//$this->_em->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger);
$this->loadFixtureData();
$query = $this->_em->createQuery('select ux, ua from Doctrine\Tests\Models\CMS\CmsUser ux JOIN ux.address ua');
// We get two users before enabling the filter
$this->assertEquals(2, count($query->getResult()));
$conf = $this->_em->getConfiguration();
$conf->addFilter("country", "\Doctrine\Tests\ORM\Functional\CMSCountryFilter");
$this->_em->getFilters()->enable("country")->setParameter("country", "Germany", \Doctrine\DBAL\Types\Type::getType(\Doctrine\DBAL\Types\Type::STRING)->getBindingType());
// We get one user after enabling the filter
$this->assertEquals(1, count($query->getResult()));
}
public function testManyToManyFilter()
{
$this->loadFixtureData();
$query = $this->_em->createQuery('select ux, ug from Doctrine\Tests\Models\CMS\CmsUser ux JOIN ux.groups ug');
// We get two users before enabling the filter
$this->assertEquals(2, count($query->getResult()));
$conf = $this->_em->getConfiguration();
$conf->addFilter("group_prefix", "\Doctrine\Tests\ORM\Functional\CMSGroupPrefixFilter");
$this->_em->getFilters()->enable("group_prefix")->setParameter("prefix", "bar_%", \Doctrine\DBAL\Types\Type::getType(\Doctrine\DBAL\Types\Type::STRING)->getBindingType());
// We get one user after enabling the filter
$this->assertEquals(1, count($query->getResult()));
}
public function testWhereFilter()
{
$this->loadFixtureData();
$query = $this->_em->createQuery('select ug from Doctrine\Tests\Models\CMS\CmsGroup ug WHERE 1=1');
// We get two users before enabling the filter
$this->assertEquals(2, count($query->getResult()));
$conf = $this->_em->getConfiguration();
$conf->addFilter("group_prefix", "\Doctrine\Tests\ORM\Functional\CMSGroupPrefixFilter");
$this->_em->getFilters()->enable("group_prefix")->setParameter("prefix", "bar_%", \Doctrine\DBAL\Types\Type::getType(\Doctrine\DBAL\Types\Type::STRING)->getBindingType());
// We get one user after enabling the filter
$this->assertEquals(1, count($query->getResult()));
}
private function loadLazyFixtureData()
{
$class = $this->_em->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser');
$class->associationMappings['articles']['fetch'] = ClassMetadataInfo::FETCH_EXTRA_LAZY;
$class->associationMappings['groups']['fetch'] = ClassMetadataInfo::FETCH_EXTRA_LAZY;
$this->loadFixtureData();
}
private function useCMSArticleTopicFilter()
{
$conf = $this->_em->getConfiguration();
$conf->addFilter("article_topic", "\Doctrine\Tests\ORM\Functional\CMSArticleTopicFilter");
$this->_em->getFilters()->enable("article_topic")->setParameter("topic", "Test1", \Doctrine\DBAL\Types\Type::getType(\Doctrine\DBAL\Types\Type::STRING)->getBindingType());
}
public function testOneToMany_ExtraLazyCountWithFilter()
{
$this->loadLazyFixtureData();
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId);
$this->assertFalse($user->articles->isInitialized());
$this->assertEquals(2, count($user->articles));
$this->useCMSArticleTopicFilter();
$this->assertEquals(1, count($user->articles));
}
public function testOneToMany_ExtraLazyContainsWithFilter()
{
$this->loadLazyFixtureData();
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId);
$filteredArticle = $this->_em->find('Doctrine\Tests\Models\CMS\CmsArticle', $this->articleId2);
$this->assertFalse($user->articles->isInitialized());
$this->assertTrue($user->articles->contains($filteredArticle));
$this->useCMSArticleTopicFilter();
$this->assertFalse($user->articles->contains($filteredArticle));
}
public function testOneToMany_ExtraLazySliceWithFilter()
{
$this->loadLazyFixtureData();
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId);
$this->assertFalse($user->articles->isInitialized());
$this->assertEquals(2, count($user->articles->slice(0,10)));
$this->useCMSArticleTopicFilter();
$this->assertEquals(1, count($user->articles->slice(0,10)));
}
private function useCMSGroupPrefixFilter()
{
$conf = $this->_em->getConfiguration();
$conf->addFilter("group_prefix", "\Doctrine\Tests\ORM\Functional\CMSGroupPrefixFilter");
$this->_em->getFilters()->enable("group_prefix")->setParameter("prefix", "foo%", \Doctrine\DBAL\Types\Type::getType(\Doctrine\DBAL\Types\Type::STRING)->getBindingType());
}
public function testManyToMany_ExtraLazyCountWithFilter()
{
$this->loadLazyFixtureData();
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId2);
$this->assertFalse($user->groups->isInitialized());
$this->assertEquals(2, count($user->groups));
$this->useCMSGroupPrefixFilter();
$this->assertEquals(1, count($user->groups));
}
public function testManyToMany_ExtraLazyContainsWithFilter()
{
$this->loadLazyFixtureData();
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId2);
$filteredArticle = $this->_em->find('Doctrine\Tests\Models\CMS\CmsGroup', $this->groupId2);
$this->assertFalse($user->groups->isInitialized());
$this->assertTrue($user->groups->contains($filteredArticle));
$this->useCMSGroupPrefixFilter();
$this->assertFalse($user->groups->contains($filteredArticle));
}
public function testManyToMany_ExtraLazySliceWithFilter()
{
$this->loadLazyFixtureData();
$user = $this->_em->find('Doctrine\Tests\Models\CMS\CmsUser', $this->userId2);
$this->assertFalse($user->groups->isInitialized());
$this->assertEquals(2, count($user->groups->slice(0,10)));
$this->useCMSGroupPrefixFilter();
$this->assertEquals(1, count($user->groups->slice(0,10)));
}
private function loadFixtureData()
{
$user = new CmsUser;
$user->name = 'Roman';
$user->username = 'romanb';
$user->status = 'developer';
$address = new CmsAddress;
$address->country = 'Germany';
$address->city = 'Berlin';
$address->zip = '12345';
$user->address = $address; // inverse side
$address->user = $user; // owning side!
$group = new CmsGroup;
$group->name = 'foo_group';
$user->addGroup($group);
$article1 = new CmsArticle;
$article1->topic = "Test1";
$article1->text = "Test";
$article1->setAuthor($user);
$article2 = new CmsArticle;
$article2->topic = "Test2";
$article2->text = "Test";
$article2->setAuthor($user);
$this->_em->persist($article1);
$this->_em->persist($article2);
$this->_em->persist($user);
$user2 = new CmsUser;
$user2->name = 'Guilherme';
$user2->username = 'gblanco';
$user2->status = 'developer';
$address2 = new CmsAddress;
$address2->country = 'France';
$address2->city = 'Paris';
$address2->zip = '12345';
$user->address = $address2; // inverse side
$address2->user = $user2; // owning side!
$user2->addGroup($group);
$group2 = new CmsGroup;
$group2->name = 'bar_group';
$user2->addGroup($group2);
$this->_em->persist($user2);
$this->_em->flush();
$this->_em->clear();
$this->userId = $user->getId();
$this->userId2 = $user2->getId();
$this->articleId = $article1->id;
$this->articleId2 = $article2->id;
$this->groupId = $group->id;
$this->groupId2 = $group2->id;
}
public function testJoinSubclassPersister_FilterOnlyOnRootTableWhenFetchingSubEntity()
{
$this->loadCompanyJoinedSubclassFixtureData();
// Persister
$this->assertEquals(2, count($this->_em->getRepository('Doctrine\Tests\Models\Company\CompanyManager')->findAll()));
// SQLWalker
$this->assertEquals(2, count($this->_em->createQuery("SELECT cm FROM Doctrine\Tests\Models\Company\CompanyManager cm")->getResult()));
// Enable the filter
$conf = $this->_em->getConfiguration();
$conf->addFilter("person_name", "\Doctrine\Tests\ORM\Functional\CompanyPersonNameFilter");
$this->_em->getFilters()
->enable("person_name")
->setParameter("name", "Guilh%", \Doctrine\DBAL\Types\Type::getType(\Doctrine\DBAL\Types\Type::STRING)->getBindingType());
$managers = $this->_em->getRepository('Doctrine\Tests\Models\Company\CompanyManager')->findAll();
$this->assertEquals(1, count($managers));
$this->assertEquals("Guilherme", $managers[0]->getName());
$this->assertEquals(1, count($this->_em->createQuery("SELECT cm FROM Doctrine\Tests\Models\Company\CompanyManager cm")->getResult()));
}
public function testJoinSubclassPersister_FilterOnlyOnRootTableWhenFetchingRootEntity()
{
$this->loadCompanyJoinedSubclassFixtureData();
$this->assertEquals(3, count($this->_em->getRepository('Doctrine\Tests\Models\Company\CompanyPerson')->findAll()));
$this->assertEquals(3, count($this->_em->createQuery("SELECT cp FROM Doctrine\Tests\Models\Company\CompanyPerson cp")->getResult()));
// Enable the filter
$conf = $this->_em->getConfiguration();
$conf->addFilter("person_name", "\Doctrine\Tests\ORM\Functional\CompanyPersonNameFilter");
$this->_em->getFilters()
->enable("person_name")
->setParameter("name", "Guilh%", \Doctrine\DBAL\Types\Type::getType(\Doctrine\DBAL\Types\Type::STRING)->getBindingType());
$persons = $this->_em->getRepository('Doctrine\Tests\Models\Company\CompanyPerson')->findAll();
$this->assertEquals(1, count($persons));
$this->assertEquals("Guilherme", $persons[0]->getName());
$this->assertEquals(1, count($this->_em->createQuery("SELECT cp FROM Doctrine\Tests\Models\Company\CompanyPerson cp")->getResult()));
}
private function loadCompanyJoinedSubclassFixtureData()
{
$manager = new CompanyManager;
$manager->setName('Roman');
$manager->setTitle('testlead');
$manager->setSalary(42);
$manager->setDepartment('persisters');
$manager2 = new CompanyManager;
$manager2->setName('Guilherme');
$manager2->setTitle('devlead');
$manager2->setSalary(42);
$manager2->setDepartment('parsers');
$person = new CompanyPerson;
$person->setName('Benjamin');
$this->_em->persist($manager);
$this->_em->persist($manager2);
$this->_em->persist($person);
$this->_em->flush();
$this->_em->clear();
}
public function testSingleTableInheritance_FilterOnlyOnRootTableWhenFetchingSubEntity()
{
$this->loadCompanySingleTableInheritanceFixtureData();
// Persister
$this->assertEquals(2, count($this->_em->getRepository('Doctrine\Tests\Models\Company\CompanyFlexUltraContract')->findAll()));
// SQLWalker
$this->assertEquals(2, count($this->_em->createQuery("SELECT cfc FROM Doctrine\Tests\Models\Company\CompanyFlexUltraContract cfc")->getResult()));
// Enable the filter
$conf = $this->_em->getConfiguration();
$conf->addFilter("completed_contract", "\Doctrine\Tests\ORM\Functional\CompletedContractFilter");
$this->_em->getFilters()
->enable("completed_contract");
$this->assertEquals(1, count($this->_em->getRepository('Doctrine\Tests\Models\Company\CompanyFlexUltraContract')->findAll()));
$this->assertEquals(1, count($this->_em->createQuery("SELECT cfc FROM Doctrine\Tests\Models\Company\CompanyFlexUltraContract cfc")->getResult()));
}
public function testSingleTableInheritance_FilterOnlyOnRootTableWhenFetchingRootEntity()
{
$this->loadCompanySingleTableInheritanceFixtureData();
$this->assertEquals(4, count($this->_em->getRepository('Doctrine\Tests\Models\Company\CompanyFlexContract')->findAll()));
$this->assertEquals(4, count($this->_em->createQuery("SELECT cfc FROM Doctrine\Tests\Models\Company\CompanyFlexContract cfc")->getResult()));
// Enable the filter
$conf = $this->_em->getConfiguration();
$conf->addFilter("completed_contract", "\Doctrine\Tests\ORM\Functional\CompletedContractFilter");
$this->_em->getFilters()
->enable("completed_contract");
$this->assertEquals(2, count($this->_em->getRepository('Doctrine\Tests\Models\Company\CompanyFlexContract')->findAll()));
$this->assertEquals(2, count($this->_em->createQuery("SELECT cfc FROM Doctrine\Tests\Models\Company\CompanyFlexContract cfc")->getResult()));
}
private function loadCompanySingleTableInheritanceFixtureData()
{
$contract1 = new CompanyFlexUltraContract;
$contract2 = new CompanyFlexUltraContract;
$contract2->markCompleted();
$contract3 = new CompanyFlexContract;
$contract4 = new CompanyFlexContract;
$contract4->markCompleted();
$this->_em->persist($contract1);
$this->_em->persist($contract2);
$this->_em->persist($contract3);
$this->_em->persist($contract4);
$this->_em->flush();
$this->_em->clear();
}
}
class MySoftDeleteFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
{
if ($targetEntity->name != "MyEntity\SoftDeleteNewsItem") {
return "";
}
return $targetTableAlias.'.deleted = 0';
}
}
class MyLocaleFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
{
if (!in_array("LocaleAware", $targetEntity->reflClass->getInterfaceNames())) {
return "";
}
return $targetTableAlias.'.locale = ' . $this->getParameter('locale'); // getParam uses connection to quote the value.
}
}
class CMSCountryFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
{
if ($targetEntity->name != "Doctrine\Tests\Models\CMS\CmsAddress") {
return "";
}
return $targetTableAlias.'.country = ' . $this->getParameter('country'); // getParam uses connection to quote the value.
}
}
class CMSGroupPrefixFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
{
if ($targetEntity->name != "Doctrine\Tests\Models\CMS\CmsGroup") {
return "";
}
return $targetTableAlias.'.name LIKE ' . $this->getParameter('prefix'); // getParam uses connection to quote the value.
}
}
class CMSArticleTopicFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
{
if ($targetEntity->name != "Doctrine\Tests\Models\CMS\CmsArticle") {
return "";
}
return $targetTableAlias.'.topic = ' . $this->getParameter('topic'); // getParam uses connection to quote the value.
}
}
class CompanyPersonNameFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias, $targetTable = '')
{
if ($targetEntity->name != "Doctrine\Tests\Models\Company\CompanyPerson") {
return "";
}
return $targetTableAlias.'.name LIKE ' . $this->getParameter('name');
}
}
class CompletedContractFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias, $targetTable = '')
{
if ($targetEntity->name != "Doctrine\Tests\Models\Company\CompanyContract") {
return "";
}
return $targetTableAlias.'.completed = 1';
}
}

View file

@ -99,4 +99,4 @@ class DDC633Patient
* @OneToOne(targetEntity="DDC633Appointment", mappedBy="patient")
*/
public $appointment;
}
}