Search code examples
phpdoctrine-ormpaginationdql

How to paginate a native query in Doctrine 2?


Doctrine 2 has the Doctrine\ORM\Tools\Pagination\Paginator class which can be used to paginate normal DQL queries.

However if I pass it a native query, I get this error:

Catchable fatal error: Argument 1 passed to Doctrine\ORM\Tools\Pagination\Paginator::cloneQuery() must be an instance of Doctrine\ORM\Query, instance of Doctrine\ORM\NativeQuery given

I've tried removing the type-hinting from the paginator class in the cloneQuery method, but this just gives further errors because other bits of the paginator class expect methods found in Query that aren't in NativeQuery.

Is there any easy way of paginating the native queries without needing to build a new paginator class or fetching every row from the database into an array?


Solution

  • I made my own paginator adapter class compatible with Zend_Paginator.

    Probably won't be the most flexible since it relies on there being a " FROM " near the start of the query (see the count() method) but it's a relatively quick and easy fix.

    /**
     * Paginate native doctrine 2 queries 
     */
    class NativePaginator implements Zend_Paginator_Adapter_Interface
    {
        /**
         * @var Doctrine\ORM\NativeQuery
         */
        protected $query;
        protected $count;
    
        /**
         * @param Doctrine\ORM\NativeQuery $query 
         */
        public function __construct($query)
        {
            $this->query = $query;
        }
    
        /**
         * Returns the total number of rows in the result set.
         *
         * @return integer
         */
        public function count()
        {
            if(!$this->count)
            {
                //change to a count query by changing the bit before the FROM
                $sql = explode(' FROM ', $this->query->getSql());
                $sql[0] = 'SELECT COUNT(*)';
                $sql = implode(' FROM ', $sql);
    
                $db = $this->query->getEntityManager()->getConnection();
                $this->count = (int) $db->fetchColumn($sql, $this->query->getParameters());
            }
    
            return $this->count;
        }
    
        /**
         * Returns an collection of items for a page.
         *
         * @param  integer $offset Page offset
         * @param  integer $itemCountPerPage Number of items per page
         * @return array
         */
        public function getItems($offset, $itemCountPerPage)
        {
            $cloneQuery = clone $this->query;
            $cloneQuery->setParameters($this->query->getParameters(), $this->query->getParameterTypes());
    
            foreach($this->query->getHints() as $name => $value)
            {
                $cloneQuery->setHint($name, $value);
            }
    
            //add on limit and offset
            $sql = $cloneQuery->getSQL();
            $sql .= " LIMIT $itemCountPerPage OFFSET $offset";
            $cloneQuery->setSQL($sql);
    
            return $cloneQuery->getResult();
        }
    }