From f1a793f2eeb1d7936dfdec11bdeb9fae436277e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Gallego?= Date: Thu, 19 Dec 2013 20:07:50 +0100 Subject: [PATCH] Initial work for efficient counting on criteria --- lib/Doctrine/ORM/EntityRepository.php | 2 +- lib/Doctrine/ORM/LazyCriteriaCollection.php | 376 ++++++++++++++++++ .../ORM/Persisters/BasicEntityPersister.php | 45 +++ 3 files changed, 422 insertions(+), 1 deletion(-) create mode 100644 lib/Doctrine/ORM/LazyCriteriaCollection.php diff --git a/lib/Doctrine/ORM/EntityRepository.php b/lib/Doctrine/ORM/EntityRepository.php index beca25f93..a2d7fd8db 100644 --- a/lib/Doctrine/ORM/EntityRepository.php +++ b/lib/Doctrine/ORM/EntityRepository.php @@ -301,6 +301,6 @@ class EntityRepository implements ObjectRepository, Selectable { $persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName); - return new ArrayCollection($persister->loadCriteria($criteria)); + return new LazyCriteriaCollection($this->_em, $persister, $criteria); } } diff --git a/lib/Doctrine/ORM/LazyCriteriaCollection.php b/lib/Doctrine/ORM/LazyCriteriaCollection.php new file mode 100644 index 000000000..a545c0ad7 --- /dev/null +++ b/lib/Doctrine/ORM/LazyCriteriaCollection.php @@ -0,0 +1,376 @@ +. + */ + +namespace Doctrine\ORM; + +use Closure; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\Common\Collections\Criteria; +use Doctrine\ORM\Persisters\BasicEntityPersister; + +/** + * A lazy collection that allow a fast count when using criteria object + * + * @since 2.5 + * @author Michaƫl Gallego + */ +class LazyCriteriaCollection implements Collection +{ + /** + * @var EntityManager + */ + protected $entityManager; + + /** + * @var BasicEntityPersister + */ + protected $entityPersister; + + /** + * @var Criteria + */ + protected $criteria; + + /** + * @var ArrayCollection + */ + protected $collection; + + /** + * @var bool + */ + protected $initialized = false; + + /** + * Allow to cache the count + * + * @var int + */ + protected $count; + + /** + * @param EntityManager $entityManager + * @param BasicEntityPersister $entityPersister + * @param Criteria $criteria + */ + public function __construct(EntityManager $entityManager, BasicEntityPersister $entityPersister, Criteria $criteria) + { + $this->entityManager = $entityManager; + $this->entityPersister = $entityPersister; + $this->criteria = $criteria; + } + + /** + * Do an efficient count on the collection + * + * @return int + */ + public function count() + { + if (null !== $this->count) { + return $this->count; + } + + //$exp = $this->criteria->expr()->eq('username', 'bar'); + //$this->criteria = new Criteria($exp); + + $this->count = $this->entityPersister->count($this->criteria); + + return $this->count; + } + + /** + * {@inheritDoc} + */ + function add($element) + { + $this->initialize(); + return $this->collection->add($element); + } + + /** + * {@inheritDoc} + */ + function clear() + { + $this->initialize(); + $this->collection->clear(); + } + + /** + * {@inheritDoc} + */ + function contains($element) + { + $this->initialize(); + return $this->collection->contains($element); + } + + /** + * {@inheritDoc} + */ + function isEmpty() + { + $this->initialize(); + return $this->collection->isEmpty(); + } + + /** + * {@inheritDoc} + */ + function remove($key) + { + $this->initialize(); + return $this->collection->remove($key); + } + + /** + * {@inheritDoc} + */ + function removeElement($element) + { + $this->initialize(); + return $this->collection->removeElement($element); + } + + /** + * {@inheritDoc} + */ + function containsKey($key) + { + $this->initialize(); + return $this->collection->containsKey($key); + } + + /** + * {@inheritDoc} + */ + function get($key) + { + $this->initialize(); + return $this->collection->get($key); + } + + /** + * {@inheritDoc} + */ + function getKeys() + { + $this->initialize(); + return $this->collection->getKeys(); + } + + /** + * {@inheritDoc} + */ + function getValues() + { + $this->initialize(); + return $this->collection->getValues(); + } + + /** + * {@inheritDoc} + */ + function set($key, $value) + { + $this->initialize(); + $this->collection->set($key, $value); + } + + /** + * {@inheritDoc} + */ + function toArray() + { + $this->initialize(); + return $this->collection->toArray(); + } + + /** + * {@inheritDoc} + */ + function first() + { + $this->initialize(); + return $this->collection->first(); + } + + /** + * {@inheritDoc} + */ + function last() + { + $this->initialize(); + return $this->collection->last(); + } + + /** + * {@inheritDoc} + */ + function key() + { + $this->initialize(); + return $this->collection->key(); + } + + /** + * {@inheritDoc} + */ + function current() + { + $this->initialize(); + return $this->collection->current(); + } + + /** + * {@inheritDoc} + */ + function next() + { + $this->initialize(); + return $this->collection->next(); + } + + /** + * {@inheritDoc} + */ + function exists(Closure $p) + { + $this->initialize(); + return $this->collection->exists($p); + } + + /** + * {@inheritDoc} + */ + function filter(Closure $p) + { + $this->initialize(); + return $this->collection->filter($p); + } + + /** + * {@inheritDoc} + */ + function forAll(Closure $p) + { + $this->initialize(); + return $this->collection->forAll($p); + } + + /** + * {@inheritDoc} + */ + function map(Closure $func) + { + $this->initialize(); + return $this->collection->map($func); + } + + /** + * {@inheritDoc} + */ + function partition(Closure $p) + { + $this->initialize(); + return $this->collection->partition($p); + } + + /** + * {@inheritDoc} + */ + function indexOf($element) + { + $this->initialize(); + return $this->collection->indexOf($element); + } + + /** + * {@inheritDoc} + */ + function slice($offset, $length = null) + { + $this->initialize(); + return $this->collection->slice($offset, $length); + } + + /** + * {@inheritDoc} + */ + public function getIterator() + { + $this->initialize(); + return $this->collection->getIterator(); + } + + /** + * {@inheritDoc} + */ + public function offsetExists($offset) + { + $this->initialize(); + return $this->collection->offsetExists($offset); + } + + /** + * {@inheritDoc} + */ + public function offsetGet($offset) + { + $this->initialize(); + return $this->collection->offsetGet($offset); + } + + /** + * {@inheritDoc} + */ + public function offsetSet($offset, $value) + { + $this->initialize(); + return $this->collection->offsetSet($offset, $value); + } + + /** + * {@inheritDoc} + */ + public function offsetUnset($offset) + { + $this->initialize(); + return $this->collection->offsetUnset($offset); + } + + /** + * Initialize the collection + * + * @return void + */ + protected function initialize() + { + if ($this->initialized) { + return; + } + + $elements = $this->entityPersister->loadCriteria($this->criteria); + + $this->collection = new ArrayCollection($elements); + $this->initialized = true; + } +} diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 63c20d56d..f13a01479 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -810,6 +810,21 @@ class BasicEntityPersister implements EntityPersister $hydrator->hydrateAll($stmt, $this->rsm, array(Query::HINT_REFRESH => true)); } + /** + * @param array|Criteria $criteria + * @return int + */ + public function count($criteria = array()) + { + $sql = $this->getCountSQL($criteria); + + list($values, $types) = ($criteria instanceof Criteria) + ? $this->expandCriteriaParameters($criteria) + : $this->expandParameters($criteria); + + return $this->conn->fetchColumn($sql, $values); + } + /** * {@inheritdoc} */ @@ -1066,6 +1081,36 @@ class BasicEntityPersister implements EntityPersister return $this->platform->modifyLimitQuery($query, $limit, $offset) . $lockSql; } + /** + * Get the COUNT SQL to count entities (optionally based on a criteria) + * + * @param array|Criteria $criteria + * @return string + */ + public function getCountSQL($criteria = array()) + { + $tableName = $this->quoteStrategy->getTableName($this->class, $this->platform); + $tableAlias = $this->getSQLTableAlias($this->class->name); + + $conditionSql = ($criteria instanceof Criteria) + ? $this->getSelectConditionCriteriaSQL($criteria) + : $this->getSelectConditionSQL($criteria); + + $filterSql = $this->generateFilterConditionSQL($this->class, $tableAlias); + + if ('' !== $filterSql) { + $conditionSql = $conditionSql + ? $conditionSql . ' AND ' . $filterSql + : $filterSql; + } + + $sql = 'SELECT COUNT(*) ' + . 'FROM ' . $tableName . ' ' . 't0' + . (empty($conditionSql) ? '' : ' WHERE ' . $conditionSql); + + return $sql; + } + /** * Gets the ORDER BY SQL snippet for ordered collections. *