Pagination.php 12.4 KB
Newer Older
Qiang Xue committed
1 2 3
<?php
/**
 * @link http://www.yiiframework.com/
Qiang Xue committed
4
 * @copyright Copyright (c) 2008 Yii Software LLC
Qiang Xue committed
5 6 7
 * @license http://www.yiiframework.com/license/
 */

8
namespace yii\data;
Qiang Xue committed
9

Qiang Xue committed
10
use Yii;
11
use yii\base\Object;
Qiang Xue committed
12 13
use yii\web\Link;
use yii\web\Linkable;
14
use yii\web\Request;
Qiang Xue committed
15

Qiang Xue committed
16
/**
Qiang Xue committed
17
 * Pagination represents information relevant to pagination of data items.
Qiang Xue committed
18
 *
Qiang Xue committed
19
 * When data needs to be rendered in multiple pages, Pagination can be used to
Qiang Xue committed
20
 * represent information such as [[totalCount|total item count]], [[pageSize|page size]],
21
 * [[page|current page]], etc. These information can be passed to [[\yii\widgets\LinkPager|pagers]]
Qiang Xue committed
22
 * to render pagination buttons or links.
Qiang Xue committed
23
 *
Qiang Xue committed
24 25
 * The following example shows how to create a pagination object and feed it
 * to a pager.
Qiang Xue committed
26 27
 *
 * Controller action:
Qiang Xue committed
28 29 30 31
 *
 * ~~~
 * function actionIndex()
 * {
Alexander Makarov committed
32
 *     $query = Article::find()->where(['status' => 1]);
Qiang Xue committed
33
 *     $countQuery = clone $query;
Alexander Makarov committed
34
 *     $pages = new Pagination(['totalCount' => $countQuery->count()]);
Qiang Xue committed
35 36 37
 *     $models = $query->offset($pages->offset)
 *         ->limit($pages->limit)
 *         ->all();
Qiang Xue committed
38
 *
Alexander Makarov committed
39
 *     return $this->render('index', [
Qiang Xue committed
40
 *          'models' => $models,
Qiang Xue committed
41
 *          'pages' => $pages,
Alexander Makarov committed
42
 *     ]);
Qiang Xue committed
43
 * }
Qiang Xue committed
44
 * ~~~
Qiang Xue committed
45 46
 *
 * View:
Qiang Xue committed
47 48
 *
 * ~~~
resurtm committed
49
 * foreach ($models as $model) {
Qiang Xue committed
50 51
 *     // display $model here
 * }
Qiang Xue committed
52 53
 *
 * // display pagination
Alexander Makarov committed
54
 * echo LinkPager::widget([
gsd committed
55
 *     'pagination' => $pages,
Alexander Makarov committed
56
 * ]);
Qiang Xue committed
57
 * ~~~
Qiang Xue committed
58
 *
59 60 61
 * @property integer $limit The limit of the data. This may be used to set the LIMIT value for a SQL statement
 * for fetching the current page of data. Note that if the page size is infinite, a value -1 will be returned.
 * This property is read-only.
62 63
 * @property array $links The links for navigational purpose. The array keys specify the purpose of the links
 * (e.g. [[LINK_FIRST]]), and the array values are the corresponding URLs. This property is read-only.
64 65
 * @property integer $offset The offset of the data. This may be used to set the OFFSET value for a SQL
 * statement for fetching the current page of data. This property is read-only.
66
 * @property integer $page The zero-based current page number.
67
 * @property integer $pageCount Number of pages. This property is read-only.
68
 * @property integer $pageSize The number of items per page.
Qiang Xue committed
69 70
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
Qiang Xue committed
71
 * @since 2.0
Qiang Xue committed
72
 */
73
class Pagination extends Object implements Linkable
Qiang Xue committed
74
{
75 76 77 78
    const LINK_NEXT = 'next';
    const LINK_PREV = 'prev';
    const LINK_FIRST = 'first';
    const LINK_LAST = 'last';
79

80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
    /**
     * @var string name of the parameter storing the current page index.
     * @see params
     */
    public $pageParam = 'page';
    /**
     * @var string name of the parameter storing the page size.
     * @see params
     */
    public $pageSizeParam = 'per-page';
    /**
     * @var boolean whether to always have the page parameter in the URL created by [[createUrl()]].
     * If false and [[page]] is 0, the page parameter will not be put in the URL.
     */
    public $forcePageParam = true;
    /**
     * @var string the route of the controller action for displaying the paged contents.
     * If not set, it means using the currently requested route.
     */
    public $route;
    /**
     * @var array parameters (name => value) that should be used to obtain the current page number
     * and to create new pagination URLs. If not set, all parameters from $_GET will be used instead.
     *
     * In order to add hash to all links use `array_merge($_GET, ['#' => 'my-hash'])`.
     *
     * The array element indexed by [[pageParam]] is considered to be the current page number (defaults to 0);
     * while the element indexed by [[pageSizeParam]] is treated as the page size (defaults to [[defaultPageSize]]).
     */
    public $params;
    /**
     * @var \yii\web\UrlManager the URL manager used for creating pagination URLs. If not set,
     * the "urlManager" application component will be used.
     */
    public $urlManager;
    /**
     * @var boolean whether to check if [[page]] is within valid range.
     * When this property is true, the value of [[page]] will always be between 0 and ([[pageCount]]-1).
     * Because [[pageCount]] relies on the correct value of [[totalCount]] which may not be available
     * in some cases (e.g. MongoDB), you may want to set this property to be false to disable the page
     * number validation. By doing so, [[page]] will return the value indexed by [[pageParam]] in [[params]].
     */
    public $validatePage = true;
    /**
     * @var integer total number of items.
     */
    public $totalCount = 0;
    /**
     * @var integer the default page size. This property will be returned by [[pageSize]] when page size
     * cannot be determined by [[pageSizeParam]] from [[params]].
     */
    public $defaultPageSize = 20;
    /**
     * @var array|boolean the page size limits. The first array element stands for the minimal page size, and the second
     * the maximal page size. If this is false, it means [[pageSize]] should always return the value of [[defaultPageSize]].
     */
    public $pageSizeLimit = [1, 50];
    /**
     * @var integer number of items on each page.
     * If it is less than 1, it means the page size is infinite, and thus a single page contains all items.
     */
    private $_pageSize;
Qiang Xue committed
142

143 144 145 146 147 148 149 150 151 152
    /**
     * @return integer number of pages
     */
    public function getPageCount()
    {
        $pageSize = $this->getPageSize();
        if ($pageSize < 1) {
            return $this->totalCount > 0 ? 1 : 0;
        } else {
            $totalCount = $this->totalCount < 0 ? 0 : (int) $this->totalCount;
Qiang Xue committed
153

154 155 156
            return (int) (($totalCount + $pageSize - 1) / $pageSize);
        }
    }
Qiang Xue committed
157

158
    private $_page;
Qiang Xue committed
159

160 161
    /**
     * Returns the zero-based current page number.
162
     * @param boolean $recalculate whether to recalculate the current page based on the page size and item count.
163 164 165 166 167 168 169 170
     * @return integer the zero-based current page number.
     */
    public function getPage($recalculate = false)
    {
        if ($this->_page === null || $recalculate) {
            $page = (int) $this->getQueryParam($this->pageParam, 1) - 1;
            $this->setPage($page, true);
        }
Qiang Xue committed
171

172 173
        return $this->_page;
    }
174

175 176
    /**
     * Sets the current page number.
177
     * @param integer $value the zero-based index of the current page.
178
     * @param boolean $validatePage whether to validate the page number. Note that in order
179
     * to validate the page number, both [[validatePage]] and this parameter must be true.
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
     */
    public function setPage($value, $validatePage = false)
    {
        if ($value === null) {
            $this->_page = null;
        } else {
            $value = (int) $value;
            if ($validatePage && $this->validatePage) {
                $pageCount = $this->getPageCount();
                if ($value >= $pageCount) {
                    $value = $pageCount - 1;
                }
            }
            if ($value < 0) {
                $value = 0;
            }
            $this->_page = $value;
        }
    }
199

200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
    /**
     * Returns the number of items per page.
     * By default, this method will try to determine the page size by [[pageSizeParam]] in [[params]].
     * If the page size cannot be determined this way, [[defaultPageSize]] will be returned.
     * @return integer the number of items per page.
     * @see pageSizeLimit
     */
    public function getPageSize()
    {
        if ($this->_pageSize === null) {
            if (empty($this->pageSizeLimit)) {
                $pageSize = $this->defaultPageSize;
                $this->setPageSize($pageSize);
            } else {
                $pageSize = (int) $this->getQueryParam($this->pageSizeParam, $this->defaultPageSize);
                $this->setPageSize($pageSize, true);
            }
        }
Qiang Xue committed
218

219 220
        return $this->_pageSize;
    }
Qiang Xue committed
221

222
    /**
223
     * @param integer $value the number of items per page.
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
     * @param boolean $validatePageSize whether to validate page size.
     */
    public function setPageSize($value, $validatePageSize = false)
    {
        if ($value === null) {
            $this->_pageSize = null;
        } else {
            $value = (int) $value;
            if ($validatePageSize && count($this->pageSizeLimit) === 2 && isset($this->pageSizeLimit[0], $this->pageSizeLimit[1])) {
                if ($value < $this->pageSizeLimit[0]) {
                    $value = $this->pageSizeLimit[0];
                } elseif ($value > $this->pageSizeLimit[1]) {
                    $value = $this->pageSizeLimit[1];
                }
            }
            $this->_pageSize = $value;
        }
    }
Qiang Xue committed
242

243 244 245
    /**
     * Creates the URL suitable for pagination with the specified page number.
     * This method is mainly called by pagers when creating URLs used to perform pagination.
246 247 248
     * @param integer $page the zero-based page number that the URL should point to.
     * @param boolean $absolute whether to create an absolute URL. Defaults to `false`.
     * @return string the created URL
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
     * @see params
     * @see forcePageParam
     */
    public function createUrl($page, $absolute = false)
    {
        if (($params = $this->params) === null) {
            $request = Yii::$app->getRequest();
            $params = $request instanceof Request ? $request->getQueryParams() : [];
        }
        if ($page > 0 || $page >= 0 && $this->forcePageParam) {
            $params[$this->pageParam] = $page + 1;
        } else {
            unset($params[$this->pageParam]);
        }
        $pageSize = $this->getPageSize();
        if ($pageSize != $this->defaultPageSize) {
            $params[$this->pageSizeParam] = $pageSize;
        } else {
            unset($params[$this->pageSizeParam]);
        }
        $params[0] = $this->route === null ? Yii::$app->controller->getRoute() : $this->route;
        $urlManager = $this->urlManager === null ? Yii::$app->getUrlManager() : $this->urlManager;
        if ($absolute) {
            return $urlManager->createAbsoluteUrl($params);
        } else {
            return $urlManager->createUrl($params);
        }
    }
277

278 279
    /**
     * @return integer the offset of the data. This may be used to set the
280
     * OFFSET value for a SQL statement for fetching the current page of data.
281 282 283 284
     */
    public function getOffset()
    {
        $pageSize = $this->getPageSize();
285

286 287 288 289 290
        return $pageSize < 1 ? 0 : $this->getPage() * $pageSize;
    }

    /**
     * @return integer the limit of the data. This may be used to set the
291 292
     * LIMIT value for a SQL statement for fetching the current page of data.
     * Note that if the page size is infinite, a value -1 will be returned.
293 294 295 296 297 298 299 300 301 302
     */
    public function getLimit()
    {
        $pageSize = $this->getPageSize();

        return $pageSize < 1 ? -1 : $pageSize;
    }

    /**
     * Returns a whole set of links for navigating to the first, last, next and previous pages.
303 304 305
     * @param boolean $absolute whether the generated URLs should be absolute.
     * @return array the links for navigational purpose. The array keys specify the purpose of the links (e.g. [[LINK_FIRST]]),
     * and the array values are the corresponding URLs.
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
     */
    public function getLinks($absolute = false)
    {
        $currentPage = $this->getPage();
        $pageCount = $this->getPageCount();
        $links = [
            Link::REL_SELF => $this->createUrl($currentPage, $absolute),
        ];
        if ($currentPage > 0) {
            $links[self::LINK_FIRST] = $this->createUrl(0, $absolute);
            $links[self::LINK_PREV] = $this->createUrl($currentPage - 1, $absolute);
        }
        if ($currentPage < $pageCount - 1) {
            $links[self::LINK_NEXT] = $this->createUrl($currentPage + 1, $absolute);
            $links[self::LINK_LAST] = $this->createUrl($pageCount - 1, $absolute);
        }

        return $links;
    }

    /**
     * Returns the value of the specified query parameter.
     * This method returns the named parameter value from [[params]]. Null is returned if the value does not exist.
329 330
     * @param string $name the parameter name
     * @param string $defaultValue the value to be returned when the specified parameter does not exist in [[params]].
331 332 333 334 335 336 337 338 339 340 341
     * @return string the parameter value
     */
    protected function getQueryParam($name, $defaultValue = null)
    {
        if (($params = $this->params) === null) {
            $request = Yii::$app->getRequest();
            $params = $request instanceof Request ? $request->getQueryParams() : [];
        }

        return isset($params[$name]) && is_scalar($params[$name]) ? $params[$name] : $defaultValue;
    }
Zander Baldwin committed
342
}