- 后端
- Web框架
【免费下载链接】yii2
Yii 2: The Fast, Secure and Professional PHP Framework
分页(Pagination)是 Web 应用中最常见的需求之一:当数据量远超单页展示能力时,将数据拆分到多页、每页只加载一小部分,既保证了页面响应速度,也降低了数据库压力。本篇技术指南以 Yii 2 框架的分页方案为核心,完整讲解[[yii\data\Pagination]]对象的三大核心属性、与数据库查询的配合方式、LinkPager分页组件以及手动构建分页链接的方法,并结合框架源码与测试用例说明其底层原理。读完本文,你将能在控制器与视图中独立实现一套完整、可定制、与 Yii 2 深度集成的前后端分页方案。
一、为什么需要分页:Yii 2 的分页抽象
当数据量大到无法在单页中合理展示时,业界通用做法是将其拆分为多个页面,每个页面只展示一部分数据,这就是分页。Yii 2 使用yii\data\Pagination对象来集中表达一套分页方案的全部信息:总共有多少条数据、每页展示多少条、当前处于第几页。它位于 framework/data/Pagination.php,实现Linkable接口,既能被视图组件消费,也能直接输出导航链接。
这个对象的设计目标是"一次创建、多处复用":同一个Pagination实例既可以传给数据库查询计算OFFSET/LIMIT,也可以传给分页组件渲染按钮,还可以用来生成任意页面的 URL,避免了在控制器、视图、模型层各自维护页码状态。
二、Pagination 对象的三大核心属性
Pagination对象用三个属性完整描述一个分页方案,这也是官方文档重点强调的三个概念:
| 属性 | 说明 | 默认值 |
|---|---|---|
totalCount | 数据条目总数,通常远大于单页展示的条目数 | 0 |
pageSize | 每页包含的数据条目数 | 20 |
page | 当前页码(基于 0,即 0 表示第一页) | 0 |
在源码中,这三者分别对应 Pagination.php 的public $totalCount = 0;、L134 的public $defaultPageSize = 20;,以及 getPage() 中从请求参数解析当前页码的逻辑。
需要特别强调的是page是从 0 开始计数的:page = 0是第一页,page = 1是第二页。这与 URL 中用户看到的页码(从 1 开始)存在差异,Yii 会在生成 URL 时自动完成+1转换(详见第五节)。
三、实战:在控制器中结合数据库查询实现分页
3.1 核心代码:count + offset + limit 三步走
将Pagination用于数据库查询是官方文档给出的标准用法,核心思路是"先数总数,再切分页"。以下代码完整继承自官方指南(参见 docs/guide/output-pagination.md):
use yii\data\Pagination; // 创建一个 DB 查询来获得所有 status 为 1 的文章 $query = Article::find()->where(['status' => 1]); // 得到文章的总数(注意:此时尚未真正从数据库拉取文章数据) $count = $query->count(); // 使用总数来创建分页对象 $pagination = new Pagination(['totalCount' => $count]); // 使用分页对象计算出的 offset/limit 来填充查询并取得当前页数据 $articles = $query->offset($pagination->offset) ->limit($pagination->limit) ->all();这段代码的执行流程非常清晰:
- 构造查询:
Article::find()->where(['status' => 1])只构建查询条件,不立即执行; - 统计总数:
$query->count()生成SELECT COUNT(*)语句取得总条数,作为totalCount; - 创建分页对象:
new Pagination(['totalCount' => $count]); - 取当前页数据:
$pagination->offset和$pagination->limit会根据当前页码实时计算,$query->offset(...)->limit(...)->all()最终执行SELECT * ... LIMIT offset, limit。
$pagination->offset与$pagination->limit是只读属性,其计算逻辑位于 getOffset() 与 getLimit():
getOffset()返回当前页码 × 每页条数,即$this->getPage() * $pageSize;getLimit()直接返回$pageSize;当每页条数小于 1(即"无限分页")时返回-1,对应 SQL 中不限制返回行数的语义。
3.2 完整的控制器动作示例
上述逻辑在真实控制器中通常会与视图渲染结合。Pagination类自身的文档注释(见 Pagination.php)给出了一个完整范例:
public function actionIndex() { $query = Article::find()->where(['status' => 1]); $countQuery = clone $query; $pages = new Pagination(['totalCount' => $countQuery->count()]); $models = $query->offset($pages->offset) ->limit($pages->limit) ->all(); return $this->render('index', [ 'models' => $models, 'pages' => $pages, ]); }这里使用clone $query克隆查询对象用于计数,避免count()对原查询对象造成状态污染,是官方推荐的安全写法。随后将$models与$pages一并传入视图。
3.3 当前页是如何确定的
在 3.1 的例子中,究竟返回哪一页的数据?答案取决于请求中是否带有名为page的查询参数。默认情况下,Pagination会尝试从page参数读取当前页码;若参数缺失则回退为 0(第一页)。该逻辑体现在 getPage() 中:
public function getPage($recalculate = false) { if ($this->_page === null || $recalculate) { $page = (int) $this->getQueryParam($this->pageParam, 1) - 1; $this->setPage($page, true); } return $this->_page; }注意两个细节:
- 请求参数中的
page按从 1 开始解析,内部减 1 后转为从 0 开始的页码; - 解析出的页码会经过 setPage() 的范围校验:当页码超出总页数时自动收敛到最后一页,小于 0 时收敛为 0,避免出现"越界页"或"负页"。
四、用 LinkPager 组件渲染分页按钮
数据取回后,还需要让用户能够切换页面。Yii 2 提供了[[yii\widgets\LinkPager]]组件,它接收一个Pagination对象,自动渲染一栏可点击的页码按钮。官方文档给出的最小用法如下:
use yii\widgets\LinkPager; echo LinkPager::widget([ 'pagination' => $pagination, ]);LinkPager的实现位于 framework/widgets/LinkPager.php,其核心渲染逻辑在 renderPageButtons() 中。结合源码可以了解到它的默认行为与常用可配置项:
| 属性 | 默认值 | 说明 |
|---|---|---|
maxButtonCount | 10 | 最多同时显示的页码按钮数量,超出时自动收拢为窗口(首/尾页附近展开) |
nextPageLabel | » | "下一页"按钮标签,设为false可隐藏 |
prevPageLabel | « | "上一页"按钮标签,设为false可隐藏 |
firstPageLabel | false | "首页"按钮标签,默认不显示 |
lastPageLabel | false | "末页"按钮标签,默认不显示 |
hideOnSinglePage | true | 只有一页时自动隐藏整个分页条 |
options | ['class' => 'pagination'] | 分页条外层容器(默认<ul>)的 HTML 属性 |
activePageCssClass | active | 当前选中页按钮的 CSS 类 |
disabledPageCssClass | disabled | 禁用状态按钮的 CSS 类 |
registerLinkTags | false | 是否在页面<head>中注册prev/next/first/last的<link rel>标签 |
从 renderPageButtons() 可以看到,当pageCount < 2且hideOnSinglePage为true时组件直接返回空字符串;按钮窗口的计算则由 getPageRange() 完成,它基于maxButtonCount以当前页为中心截取页码范围,并在接近首尾时自动平移窗口。
另外,LinkPager的 init() 会强制校验pagination属性必须被设置,否则抛出InvalidConfigException。
五、手动构建分页 URL:createUrl 详解
某些场景下(如自定义 UI、生成分享链接、REST 响应),你需要自己创建指向特定页面的 URL。此时可使用[[yii\data\Pagination::createUrl()]],它接收一个页码参数并返回格式正确的 URL。官方文档示例:
// 指定要创建的 URL 应使用的路由;不指定则使用当前请求的路由 $pagination->route = 'article/index'; // 显示: /index.php?r=article%2Findex&page=101 echo $pagination->createUrl(100); // 显示: /index.php?r=article%2Findex&page=102 echo $pagination->createUrl(101);注意:createUrl()接收的是从 0 开始的内部页码,但 URL 中输出的page参数是从 1 开始的用户页码——传入 100 会生成page=101。这个+1转换在 createUrl() 中完成:
if ($page > 0 || $page == 0 && $this->forcePageParam) { $params[$this->pageParam] = $page + 1; } else { unset($params[$this->pageParam]); }结合 createUrl() 的完整实现,其行为还有以下几点值得掌握:
- 路由来源:
route属性未设置时,自动使用当前请求的路由Yii::$app->controller->getRoute(); - 保留查询参数:URL 会保留当前请求中的其他查询参数(如搜索关键词
q),params属性可显式指定参与 URL 构建的参数集;params未设置时默认取$_GET; - 强制页码参数:
forcePageParam默认为true,即使当前是第一页也会输出page=1;设为false后第一页 URL 将不含page参数(测试用例见 PaginationTest.php); - 每页条数参数:当
pageSize与默认值不一致时,URL 中会附带per-page参数(对应pageSizeParam属性),从而允许用户通过 URL 调整每页数量; - 绝对 URL:第三个参数
$absolute = true时返回绝对地址,常用于 REST 场景。
getLinks()方法(Pagination.php)会基于createUrl()一次性生成self、first、last、prev、next五个导航链接,这正是LinkPager注册<link>标签及 REST 序列化器输出_links的数据来源。
六、自定义页码参数名:pageParam
如果项目出于 SEO 或路由规范考虑,不希望使用默认的page查询参数,可以通过pageParam属性重命名。官方文档给出的提示如下:
Tip: 创建分页对象时,可以通过配置
[[yii\data\Pagination::pageParam|pageParam]]属性来自定义查询参数page的名字。
$pagination = new Pagination([ 'totalCount' => $count, 'pageParam' => 'p', // URL 中显示 ?p=2 而非 ?page=2 ]);该属性定义于 Pagination.php:public $pageParam = 'page';。与之配套的还有pageSizeParam(默认per-page),用于自定义每页条数参数名。修改后,getPage()的解析、createUrl()的 URL 生成都会统一使用新的参数名,全程无缝切换。
七、分页与数据提供器、REST 的联动
7.1 ActiveDataProvider 自动分页
在实际项目中,更常见的做法是使用ActiveDataProvider让框架自动完成分页。yii\data\ActiveDataProvider内部会创建并持有Pagination实例,并在 prepareModels() 中自动将分页计算出的limit与offset应用到查询上,同时把totalCount回填给分页对象。因此配置化地声明即可获得分页能力:
$provider = new ActiveDataProvider([ 'query' => Article::find()->where(['status' => 1]), 'pagination' => [ 'pageSize' => 20, ], ]);这种模式下无需手动编写count()与offset()/limit(),LinkPager直接消费$provider->getPagination()即可。
7.2 REST 响应中的分页头信息
在 RESTful API 场景下,yii\rest\Serializer会利用Pagination在响应中输出标准化的分页信息(见 framework/rest/Serializer.php):
- HTTP 响应头:
X-Pagination-Total-Count(总数)、X-Pagination-Page-Count(总页数)、X-Pagination-Current-Page(当前页,从 1 开始)、X-Pagination-Per-Page(每页条数)以及Link头中的first/last/prev/next链接(见 addPaginationHeaders()); - 响应体:当设置了
_links与_meta信封时,序列化结果中会附带导航链接与totalCount、pageCount、currentPage、perPage元信息(见 serializePagination())。
这些输出全部复用同一个Pagination对象的getLinks()、getPageCount()、getPageSize()等方法,充分体现了该对象"一处计算、多方消费"的设计价值。
八、源码级验证:Pagination 的边界行为
框架测试套件 tests/framework/data/PaginationTest.php 对上述行为做了系统验证,可作为理解边界语义的权威参考:
- URL 生成(
testCreateUrl):createUrl(2)生成page=3,证明内部页码到 URL 页码的+1转换;传入pageSize时 URL 附带per-page;传入自定义params时保留额外查询参数; - 页码校验(
testValidatePage):totalCount=100、pageSize=10时,setPage(999, true)会被收敛为第 9 页(最后一页); - 总页数计算(
testPageCount):pageCount = ceil(totalCount / pageSize),如totalCount=15、pageSize=2时总页数为 8; - 偏移与限制(
testGetOffset、testGetLimit):pageSize=2、page=2时offset为 4;pageSize小于 1 时limit返回-1表示不限制; - 导航链接(
testGetLinks):getLinks()仅在第一页省略prev、在最后一页省略next,且永远包含self/first/last(当总页数大于 0 时)。
九、小结
Yii 2 的分页体系以yii\data\Pagination对象为核心,向上对接数据库查询(offset/limit)、分页组件(LinkPager)、数据提供器(ActiveDataProvider)与 REST 序列化器,向下则通过createUrl()统一输出符合路由规范的页码 URL。掌握三个核心属性(totalCount、pageSize、page)、两套渲染方式(LinkPager组件与手动createUrl)以及pageParam/forcePageParam/pageSizeLimit等定制开关,即可在 Web 与 REST 场景中构建健壮、可定制且与框架深度一致的分页能力。
本文对应的官方指南英文版位于 docs/guide/output-pagination.md,中文版位于 docs/guide-zh-CN/output-pagination.md,核心实现可进一步研读 framework/data/Pagination.php 与 framework/widgets/LinkPager.php。
- 后端
- Web框架
【免费下载链接】yii2
Yii 2: The Fast, Secure and Professional PHP Framework
相关推荐
Yii 2 数据分页实战指南:Pagination 对象与 LinkPager 分页组件全解析
Yii 2 数据分页实战指南:Pagination 对象与 LinkPager 分页组件全解析 本文以 Yii 2 官方指南 Pagination https:
后端Web框架Yii 2 数据分页完整指南:Pagination 对象与 LinkPager 组件的实战用法
Yii 2 数据分页完整指南:Pagination 对象与 LinkPager 组件的实战用法 当单页需要展示的数据量过大时,常见的做法是将数据拆分到多个页面,
后端Web框架Yii 2 分页(Pagination)完全指南:从 DB 查询分页到 LinkPager 分页按钮
Yii 2 分页(Pagination)完全指南:从 DB 查询分页到 LinkPager 分页按钮 本指南围绕 Yii 2 框架的 yii\data\Pagi
后端Web框架
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考