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

[DDC-551] Moved SQLFilter logic to a separate FilterCollection class

This commit is contained in:
Alexander 2011-09-15 21:34:51 +02:00
parent ed0fb4ece7
commit 63a3fb5ad8
9 changed files with 223 additions and 131 deletions

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,36 +111,12 @@ class EntityManager implements ObjectManager
*/
private $closed = false;
/* Filters data */
/**
* Instances of enabled filters.
* Collection of query filters.
*
* @var array
* @var Doctrine\ORM\Query\FilterCollection
*/
private $enabledFilters = array();
/**
* @var string The filter hash from the last time the query was parsed.
*/
private $filterHash;
/* 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;
/**
* @var integer $state The current state of this filter
*/
private $filtersState = self::FILTERS_STATE_CLEAN;
/* End filters data */
private $filterCollection;
/**
* Creates a new EntityManager that operates on the given database connection
@ -771,55 +748,13 @@ class EntityManager implements ObjectManager
return new EntityManager($conn, $config, $conn->getEventManager());
}
/** @return SQLFilter[] */
public function getEnabledFilters()
public function getFilters()
{
return $this->enabledFilters;
}
/** Throws exception if filter does not exist. No-op if the filter is alrady enabled.
* @return SQLFilter */
public function enableFilter($name)
{
if(null === $filterClass = $this->config->getFilterClassName($name)) {
throw new \InvalidArgumentException("Filter '" . $name . "' does not exist.");
if(null === $this->filterCollection) {
$this->filterCollection = new FilterCollection($this);
}
if(!isset($this->enabledFilters[$name])) {
$this->enabledFilters[$name] = new $filterClass($this);
// 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];
}
/** Disable the filter, looses the state */
public function disableFilter($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;
}
/** throws exception if not in enabled filters */
public function getFilter($name)
{
if(!isset($this->enabledFilters[$name])) {
throw new \InvalidArgumentException("Filter '" . $name . "' is not enabled.");
}
return $this->enabledFilters[$name];
return $this->filterCollection;
}
/**
@ -827,31 +762,12 @@ class EntityManager implements ObjectManager
*/
public function isFiltersStateClean()
{
return self::FILTERS_STATE_CLEAN === $this->filtersState;
return null === $this->filterCollection
|| $this->filterCollection->setFiltersStateDirty();
}
public function setFiltersStateDirty()
public function hasFilters()
{
$this->filtersState = self::FILTERS_STATE_DIRTY;
}
/**
* Generates a string of currently enabled filters to use for the cache id.
*
* @return string
*/
public function getFilterHash()
{
// 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;
return null !== $this->filterCollection;
}
}

View file

@ -1448,7 +1448,7 @@ class BasicEntityPersister
$filterSql = '';
$first = true;
foreach($this->_em->getEnabledFilters() as $filter) {
foreach($this->_em->getFilters()->getEnabledFilters() as $filter) {
if("" !== $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) {
if ( ! $first) $sql .= ' AND '; else $first = false;
$filterSql .= '(' . $filterExpr . ')';

View file

@ -318,7 +318,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
// Get the SQL for the filters
$filterSql = '';
foreach($this->_em->getEnabledFilters() as $filter) {
foreach($this->_em->getFilters()->getEnabledFilters() as $filter) {
if("" !== $filterExpr = $filter->addFilterConstraint($targetClass, 'te')) {
$filterSql .= ' AND (' . $filterExpr . ')';
}

View file

@ -146,7 +146,7 @@ class OneToManyPersister extends AbstractCollectionPersister
$sql = "SELECT count(*) FROM " . $targetClass->getQuotedTableName($this->_conn->getDatabasePlatform()) . " t WHERE " . $where;
// Apply the filters
foreach($this->_em->getEnabledFilters() as $filter) {
foreach($this->_em->getFilters()->getEnabledFilters() as $filter) {
if("" !== $filterExpr = $filter->addFilterConstraint($targetClass, 't')) {
$sql .= ' AND (' . $filterExpr . ')';
}

View file

@ -561,7 +561,7 @@ final class Query extends AbstractQuery
return md5(
$this->getDql() . var_export($this->_hints, true) .
$this->_em->getFilterHash() .
($this->_em->hasFilters() ? $this->_em->getFilters()->getHash() : '') .
'&firstResult=' . $this->_firstResult . '&maxResult=' . $this->_maxResults .
'&hydrationMode='.$this->_hydrationMode.'DOCTRINE_QUERY_CACHE_SALT'
);

View file

@ -51,7 +51,7 @@ abstract class SQLFilter
ksort($this->parameters);
// The filter collection of the EM is now dirty
$this->em->setFiltersStateDirty();
$this->em->getFilters()->setFiltersStateDirty();
return $this;
}

View file

@ -0,0 +1,153 @@
<?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
{
private $config;
private $em;
private $filters;
/**
* Instances of enabled filters.
*
* @var array
*/
private $enabledFilters = array();
/**
* @var string The filter hash from the last time the query was parsed.
*/
private $filterHash;
/* 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;
/**
* @var integer $state The current state of this filter
*/
private $filtersState = self::FILTERS_STATE_CLEAN;
final public function __construct(EntityManager $em)
{
$this->em = $em;
$this->config = $em->getConfiguration();
}
/** @return SQLFilter[] */
public function getEnabledFilters()
{
return $this->enabledFilters;
}
/** Throws exception if filter does not exist. No-op if the filter is alrady enabled.
* @return SQLFilter */
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];
}
/** Disable the filter, looses the state */
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;
}
/** throws exception if not in enabled filters */
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;
}
public function setFiltersStateDirty()
{
$this->filtersState = self::FILTERS_STATE_DIRTY;
}
}

View file

@ -354,11 +354,13 @@ class SqlWalker implements TreeWalker
{
$filterSql = '';
$first = true;
foreach($this->_em->getEnabledFilters() as $filter) {
if("" !== $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) {
if ( ! $first) $sql .= ' AND '; else $first = false;
$filterSql .= '(' . $filterExpr . ')';
if($this->_em->hasFilters()) {
$first = true;
foreach($this->_em->getFilters()->getEnabledFilters() as $filter) {
if("" !== $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) {
if ( ! $first) $sql .= ' AND '; else $first = false;
$filterSql .= '(' . $filterExpr . ')';
}
}
}

View file

@ -58,17 +58,17 @@ class SQLFilterTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->configureFilters($em);
// Enable an existing filter
$filter = $em->enableFilter("locale");
$filter = $em->getFilters()->enable("locale");
$this->assertTrue($filter instanceof \Doctrine\Tests\ORM\Functional\MyLocaleFilter);
// Enable the filter again
$filter2 = $em->enableFilter("locale");
$filter2 = $em->getFilters()->enable("locale");
$this->assertEquals($filter, $filter2);
// Enable a non-existing filter
$exceptionThrown = false;
try {
$filter = $em->enableFilter("foo");
$filter = $em->getFilters()->enable("foo");
} catch (\InvalidArgumentException $e) {
$exceptionThrown = true;
}
@ -80,14 +80,14 @@ class SQLFilterTest extends \Doctrine\Tests\OrmFunctionalTestCase
$em = $this->_getEntityManager();
// No enabled filters
$this->assertEquals(array(), $em->getEnabledFilters());
$this->assertEquals(array(), $em->getFilters()->getEnabledFilters());
$this->configureFilters($em);
$filter = $em->enableFilter("locale");
$filter = $em->enableFilter("soft_delete");
$filter = $em->getFilters()->enable("locale");
$filter = $em->getFilters()->enable("soft_delete");
// Two enabled filters
$this->assertEquals(2, count($em->getEnabledFilters()));
$this->assertEquals(2, count($em->getFilters()->getEnabledFilters()));
}
@ -97,16 +97,16 @@ class SQLFilterTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->configureFilters($em);
// Enable the filter
$filter = $em->enableFilter("locale");
$filter = $em->getFilters()->enable("locale");
// Disable it
$this->assertEquals($filter, $em->disableFilter("locale"));
$this->assertEquals(0, count($em->getEnabledFilters()));
$this->assertEquals($filter, $em->getFilters()->disable("locale"));
$this->assertEquals(0, count($em->getFilters()->getEnabledFilters()));
// Disable a non-existing filter
$exceptionThrown = false;
try {
$filter = $em->disableFilter("foo");
$filter = $em->getFilters()->disable("foo");
} catch (\InvalidArgumentException $e) {
$exceptionThrown = true;
}
@ -115,7 +115,7 @@ class SQLFilterTest extends \Doctrine\Tests\OrmFunctionalTestCase
// Disable a non-enabled filter
$exceptionThrown = false;
try {
$filter = $em->disableFilter("locale");
$filter = $em->getFilters()->disable("locale");
} catch (\InvalidArgumentException $e) {
$exceptionThrown = true;
}
@ -128,15 +128,15 @@ class SQLFilterTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->configureFilters($em);
// Enable the filter
$filter = $em->enableFilter("locale");
$filter = $em->getFilters()->enable("locale");
// Get the filter
$this->assertEquals($filter, $em->getFilter("locale"));
$this->assertEquals($filter, $em->getFilters()->getFilter("locale"));
// Get a non-enabled filter
$exceptionThrown = false;
try {
$filter = $em->getFilter("soft_delete");
$filter = $em->getFilters()->getFilter("soft_delete");
} catch (\InvalidArgumentException $e) {
$exceptionThrown = true;
}
@ -171,6 +171,19 @@ class SQLFilterTest extends \Doctrine\Tests\OrmFunctionalTestCase
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
@ -185,6 +198,11 @@ class SQLFilterTest extends \Doctrine\Tests\OrmFunctionalTestCase
->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);
@ -213,11 +231,14 @@ class SQLFilterTest extends \Doctrine\Tests\OrmFunctionalTestCase
public function testSQLFilterToString()
{
$filter = new MyLocaleFilter($this->getMockEntityManager());
$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($this->getMockEntityManager());
$filter2 = new MyLocaleFilter($em);
$filter2->setParameter('foo', 'bar', \Doctrine\DBAL\Types\Type::STRING);
$filter2->setParameter('locale', 'en', \Doctrine\DBAL\Types\Type::STRING);
@ -242,7 +263,7 @@ class SQLFilterTest extends \Doctrine\Tests\OrmFunctionalTestCase
$conf = $this->_em->getConfiguration();
$conf->addFilter("locale", "\Doctrine\Tests\ORM\Functional\MyLocaleFilter");
$this->_em->enableFilter("locale");
$this->_em->getFilters()->enable("locale");
$query->getResult();
$this->assertEquals(2, count($cache->getIds()));
@ -259,7 +280,7 @@ class SQLFilterTest extends \Doctrine\Tests\OrmFunctionalTestCase
$conf = $this->_em->getConfiguration();
$conf->addFilter("country", "\Doctrine\Tests\ORM\Functional\CMSCountryFilter");
$this->_em->enableFilter("country")
$this->_em->getFilters()->enable("country")
->setParameter("country", "en", \Doctrine\DBAL\Types\Type::getType(\Doctrine\DBAL\Types\Type::STRING)->getBindingType());
$this->assertNotEquals($firstSQLQuery, $query->getSQL());
@ -277,7 +298,7 @@ class SQLFilterTest extends \Doctrine\Tests\OrmFunctionalTestCase
$conf = $this->_em->getConfiguration();
$conf->addFilter("country", "\Doctrine\Tests\ORM\Functional\CMSCountryFilter");
$this->_em->enableFilter("country")->setParameter("country", "Germany", \Doctrine\DBAL\Types\Type::getType(\Doctrine\DBAL\Types\Type::STRING)->getBindingType());
$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()));
@ -293,7 +314,7 @@ class SQLFilterTest extends \Doctrine\Tests\OrmFunctionalTestCase
$conf = $this->_em->getConfiguration();
$conf->addFilter("group_prefix", "\Doctrine\Tests\ORM\Functional\CMSGroupPrefixFilter");
$this->_em->enableFilter("group_prefix")->setParameter("prefix", "bar_%", \Doctrine\DBAL\Types\Type::getType(\Doctrine\DBAL\Types\Type::STRING)->getBindingType());
$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()));
@ -310,7 +331,7 @@ class SQLFilterTest extends \Doctrine\Tests\OrmFunctionalTestCase
$conf = $this->_em->getConfiguration();
$conf->addFilter("group_prefix", "\Doctrine\Tests\ORM\Functional\CMSGroupPrefixFilter");
$this->_em->enableFilter("group_prefix")->setParameter("prefix", "bar_%", \Doctrine\DBAL\Types\Type::getType(\Doctrine\DBAL\Types\Type::STRING)->getBindingType());
$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()));
@ -329,7 +350,7 @@ class SQLFilterTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
$conf = $this->_em->getConfiguration();
$conf->addFilter("article_topic", "\Doctrine\Tests\ORM\Functional\CMSArticleTopicFilter");
$this->_em->enableFilter("article_topic")->setParameter("topic", "Test1", \Doctrine\DBAL\Types\Type::getType(\Doctrine\DBAL\Types\Type::STRING)->getBindingType());
$this->_em->getFilters()->enable("article_topic")->setParameter("topic", "Test1", \Doctrine\DBAL\Types\Type::getType(\Doctrine\DBAL\Types\Type::STRING)->getBindingType());
}
public function testOneToMany_ExtraLazyCountWithFilter()
@ -376,7 +397,7 @@ class SQLFilterTest extends \Doctrine\Tests\OrmFunctionalTestCase
{
$conf = $this->_em->getConfiguration();
$conf->addFilter("group_prefix", "\Doctrine\Tests\ORM\Functional\CMSGroupPrefixFilter");
$this->_em->enableFilter("group_prefix")->setParameter("prefix", "foo%", \Doctrine\DBAL\Types\Type::getType(\Doctrine\DBAL\Types\Type::STRING)->getBindingType());
$this->_em->getFilters()->enable("group_prefix")->setParameter("prefix", "foo%", \Doctrine\DBAL\Types\Type::getType(\Doctrine\DBAL\Types\Type::STRING)->getBindingType());
}
public function testManyToMany_ExtraLazyCountWithFilter()