File indexing completed on 2024-12-22 05:36:53

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Paginator
0017  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0018  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0019  * @version    $Id$
0020  */
0021 
0022 /**
0023  * @see Zend_Paginator_Adapter_Interface
0024  */
0025 // require_once 'Zend/Paginator/Adapter/Interface.php';
0026 
0027 /**
0028  * @see Zend_Db
0029  */
0030 // require_once 'Zend/Db.php';
0031 
0032 /**
0033  * @see Zend_Db_Select
0034  */
0035 // require_once 'Zend/Db/Select.php';
0036 
0037 /**
0038  * @category   Zend
0039  * @package    Zend_Paginator
0040  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0041  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0042  */
0043 class Zend_Paginator_Adapter_DbSelect implements Zend_Paginator_Adapter_Interface
0044 {
0045     /**
0046      * Name of the row count column
0047      *
0048      * @var string
0049      */
0050     const ROW_COUNT_COLUMN = 'zend_paginator_row_count';
0051 
0052     /**
0053      * The COUNT query
0054      *
0055      * @var Zend_Db_Select
0056      */
0057     protected $_countSelect = null;
0058 
0059     /**
0060      * Database query
0061      *
0062      * @var Zend_Db_Select
0063      */
0064     protected $_select = null;
0065 
0066     /**
0067      * Total item count
0068      *
0069      * @var integer
0070      */
0071     protected $_rowCount = null;
0072 
0073     /**
0074      * Identifies this adapter for caching purposes.  This value will remain constant for
0075      * the entire life of this adapter regardless of how many different pages are queried.
0076      *
0077      * @var string
0078      */
0079     protected $_cacheIdentifier = null;
0080 
0081     /**
0082      * Constructor.
0083      *
0084      * @param Zend_Db_Select $select The select query
0085      */
0086     public function __construct(Zend_Db_Select $select)
0087     {
0088         $this->_select = $select;
0089         $this->_cacheIdentifier = md5($select->assemble());
0090     }
0091 
0092     /**
0093      * Returns the cache identifier.
0094      * 
0095      * @return string
0096      */
0097     public function getCacheIdentifier()
0098     {
0099         return $this->_cacheIdentifier;
0100     }
0101     
0102     /**
0103      * Sets the total row count, either directly or through a supplied
0104      * query.  Without setting this, {@link getPages()} selects the count
0105      * as a subquery (SELECT COUNT ... FROM (SELECT ...)).  While this
0106      * yields an accurate count even with queries containing clauses like
0107      * LIMIT, it can be slow in some circumstances.  For example, in MySQL,
0108      * subqueries are generally slow when using the InnoDB storage engine.
0109      * Users are therefore encouraged to profile their queries to find
0110      * the solution that best meets their needs.
0111      *
0112      * @param  Zend_Db_Select|integer $totalRowCount Total row count integer
0113      *                                               or query
0114      * @return Zend_Paginator_Adapter_DbSelect $this
0115      * @throws Zend_Paginator_Exception
0116      */
0117     public function setRowCount($rowCount)
0118     {
0119         if ($rowCount instanceof Zend_Db_Select) {
0120             $columns = $rowCount->getPart(Zend_Db_Select::COLUMNS);
0121 
0122             $countColumnPart = empty($columns[0][2])
0123                              ? $columns[0][1]
0124                              : $columns[0][2];
0125 
0126             if ($countColumnPart instanceof Zend_Db_Expr) {
0127                 $countColumnPart = $countColumnPart->__toString();
0128             }
0129 
0130             $rowCountColumn = $this->_select->getAdapter()->foldCase(self::ROW_COUNT_COLUMN);
0131 
0132             // The select query can contain only one column, which should be the row count column
0133             if (false === strpos($countColumnPart, $rowCountColumn)) {
0134                 /**
0135                  * @see Zend_Paginator_Exception
0136                  */
0137                 // require_once 'Zend/Paginator/Exception.php';
0138 
0139                 throw new Zend_Paginator_Exception('Row count column not found');
0140             }
0141 
0142             $result = $rowCount->query(Zend_Db::FETCH_ASSOC)->fetch();
0143 
0144             $this->_rowCount = count($result) > 0 ? $result[$rowCountColumn] : 0;
0145         } else if (is_integer($rowCount)) {
0146             $this->_rowCount = $rowCount;
0147         } else {
0148             /**
0149              * @see Zend_Paginator_Exception
0150              */
0151             // require_once 'Zend/Paginator/Exception.php';
0152 
0153             throw new Zend_Paginator_Exception('Invalid row count');
0154         }
0155 
0156         return $this;
0157     }
0158 
0159     /**
0160      * Returns an array of items for a page.
0161      *
0162      * @param  integer $offset Page offset
0163      * @param  integer $itemCountPerPage Number of items per page
0164      * @return array
0165      */
0166     public function getItems($offset, $itemCountPerPage)
0167     {
0168         $this->_select->limit($itemCountPerPage, $offset);
0169 
0170         return $this->_select->query()->fetchAll();
0171     }
0172 
0173     /**
0174      * Returns the total number of rows in the result set.
0175      *
0176      * @return integer
0177      */
0178     public function count()
0179     {
0180         if ($this->_rowCount === null) {
0181             $this->setRowCount(
0182                 $this->getCountSelect()
0183             );
0184         }
0185 
0186         return $this->_rowCount;
0187     }
0188 
0189     /**
0190      * Get the COUNT select object for the provided query
0191      *
0192      * TODO: Have a look at queries that have both GROUP BY and DISTINCT specified.
0193      * In that use-case I'm expecting problems when either GROUP BY or DISTINCT
0194      * has one column.
0195      *
0196      * @return Zend_Db_Select
0197      */
0198     public function getCountSelect()
0199     {
0200         /**
0201          * We only need to generate a COUNT query once. It will not change for
0202          * this instance.
0203          */
0204         if ($this->_countSelect !== null) {
0205             return $this->_countSelect;
0206         }
0207 
0208         $rowCount = clone $this->_select;
0209         $rowCount->__toString(); // Workaround for ZF-3719 and related
0210 
0211         $db = $rowCount->getAdapter();
0212 
0213         $countColumn = $db->quoteIdentifier($db->foldCase(self::ROW_COUNT_COLUMN));
0214         $countPart   = 'COUNT(1) AS ';
0215         $groupPart   = null;
0216         $unionParts  = $rowCount->getPart(Zend_Db_Select::UNION);
0217 
0218         /**
0219          * If we're dealing with a UNION query, execute the UNION as a subquery
0220          * to the COUNT query.
0221          */
0222         if (!empty($unionParts)) {
0223             $expression = new Zend_Db_Expr($countPart . $countColumn);
0224 
0225             $rowCount = $db
0226                             ->select()
0227                             ->bind($rowCount->getBind())
0228                             ->from($rowCount, $expression);
0229         } else {
0230             $columnParts = $rowCount->getPart(Zend_Db_Select::COLUMNS);
0231             $groupParts  = $rowCount->getPart(Zend_Db_Select::GROUP);
0232             $havingParts = $rowCount->getPart(Zend_Db_Select::HAVING);
0233             $isDistinct  = $rowCount->getPart(Zend_Db_Select::DISTINCT);
0234 
0235             /**
0236              * If there is more than one column AND it's a DISTINCT query, more
0237              * than one group, or if the query has a HAVING clause, then take
0238              * the original query and use it as a subquery os the COUNT query.
0239              */
0240             if (($isDistinct && ((count($columnParts) == 1 && $columnParts[0][1] == Zend_Db_Select::SQL_WILDCARD) 
0241                  || count($columnParts) > 1)) || count($groupParts) > 1 || !empty($havingParts)) {
0242                 $rowCount->reset(Zend_Db_Select::ORDER);
0243                 $rowCount = $db
0244                                ->select()
0245                                ->bind($rowCount->getBind())
0246                                ->from($rowCount);
0247             } else if ($isDistinct) {
0248                 $part = $columnParts[0];
0249 
0250                 if ($part[1] !== Zend_Db_Select::SQL_WILDCARD && !($part[1] instanceof Zend_Db_Expr)) {
0251                     $column = $db->quoteIdentifier($part[1], true);
0252 
0253                     if (!empty($part[0])) {
0254                         $column = $db->quoteIdentifier($part[0], true) . '.' . $column;
0255                     }
0256 
0257                     $groupPart = $column;
0258                 }
0259             } else if (!empty($groupParts)) {
0260                 $groupPart = $db->quoteIdentifier($groupParts[0], true);
0261             }
0262 
0263             /**
0264              * If the original query had a GROUP BY or a DISTINCT part and only
0265              * one column was specified, create a COUNT(DISTINCT ) query instead
0266              * of a regular COUNT query.
0267              */
0268             if (!empty($groupPart)) {
0269                 $countPart = 'COUNT(DISTINCT ' . $groupPart . ') AS ';
0270             }
0271 
0272             /**
0273              * Create the COUNT part of the query
0274              */
0275             $expression = new Zend_Db_Expr($countPart . $countColumn);
0276 
0277             $rowCount->reset(Zend_Db_Select::COLUMNS)
0278                      ->reset(Zend_Db_Select::ORDER)
0279                      ->reset(Zend_Db_Select::LIMIT_OFFSET)
0280                      ->reset(Zend_Db_Select::GROUP)
0281                      ->reset(Zend_Db_Select::DISTINCT)
0282                      ->reset(Zend_Db_Select::HAVING)
0283                      ->columns($expression);
0284         }
0285 
0286         $this->_countSelect = $rowCount;
0287 
0288         return $rowCount;
0289     }
0290 }