news 2026/9/24 6:37:30

Yii 2 分页实战指南:掌握 Pagination 对象、LinkPager 组件与 REST 分页响应

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Yii 2 分页实战指南:掌握 Pagination 对象、LinkPager 组件与 REST 分页响应
  • 后端
  • Web框架

【免费下载链接】yii2

Yii 2: The Fast, Secure and Professional PHP Framework

项目地址:https://gitcode.com/gh_mirrors/yi/yii2
点击查看免费下载

分页(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();

这段代码的执行流程非常清晰:

  1. 构造查询Article::find()->where(['status' => 1])只构建查询条件,不立即执行;
  2. 统计总数$query->count()生成SELECT COUNT(*)语句取得总条数,作为totalCount
  3. 创建分页对象new Pagination(['totalCount' => $count])
  4. 取当前页数据$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() 中。结合源码可以了解到它的默认行为与常用可配置项:

属性默认值说明
maxButtonCount10最多同时显示的页码按钮数量,超出时自动收拢为窗口(首/尾页附近展开)
nextPageLabel»"下一页"按钮标签,设为false可隐藏
prevPageLabel«"上一页"按钮标签,设为false可隐藏
firstPageLabelfalse"首页"按钮标签,默认不显示
lastPageLabelfalse"末页"按钮标签,默认不显示
hideOnSinglePagetrue只有一页时自动隐藏整个分页条
options['class' => 'pagination']分页条外层容器(默认<ul>)的 HTML 属性
activePageCssClassactive当前选中页按钮的 CSS 类
disabledPageCssClassdisabled禁用状态按钮的 CSS 类
registerLinkTagsfalse是否在页面<head>中注册prev/next/first/last<link rel>标签

从 renderPageButtons() 可以看到,当pageCount < 2hideOnSinglePagetrue时组件直接返回空字符串;按钮窗口的计算则由 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()一次性生成selffirstlastprevnext五个导航链接,这正是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() 中自动将分页计算出的limitoffset应用到查询上,同时把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信封时,序列化结果中会附带导航链接与totalCountpageCountcurrentPageperPage元信息(见 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=100pageSize=10时,setPage(999, true)会被收敛为第 9 页(最后一页);
  • 总页数计算testPageCount):pageCount = ceil(totalCount / pageSize),如totalCount=15pageSize=2时总页数为 8;
  • 偏移与限制testGetOffsettestGetLimit):pageSize=2page=2offset为 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。掌握三个核心属性(totalCountpageSizepage)、两套渲染方式(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

项目地址:https://gitcode.com/gh_mirrors/yi/yii2
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 6:36:47

38,关卡管理器初始化改为c++

整体方案总结&#xff08;AMyLevelManager&#xff09; 架构目标BeginPlay 在 C 完成初始化逻辑&#xff0c;获取 GameInstance、PostProcessVolume、播放背景音乐、开启定时器。TimeCount 是纯 C 普通成员函数&#xff0c;不暴露给蓝图&#xff0c;定时器触发 TimeCount。Time…

作者头像 李华
网站建设 2026/9/24 6:35:47

中小制造ERP实操避坑指南:GICESY车间落地5大痛点解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 6:33:26

自用-提取红外相机相关的关键信息

例如&#xff1a;7050_IMAG0014_S10_N01 位置为CSV文件的第五列 提取编码SXX和NXXlibrary(readr) library(stringr) library(dplyr)# CSV文件路径 input_file <- "C:/Users/HUAWEI/Desktop/我的论文/红外相机文件信息汇总.csv" output_file <- "E:/开题中…

作者头像 李华
网站建设 2026/9/24 6:31:27

G6 Fruchterman 力导向布局实战指南:从基础均匀分布到聚类布局

数据可视化前端图表库 【免费下载链接】G6 ♾ A Graph Visualization Framework in JavaScript. 项目地址&#xff1a; https://gitcode.com/gh_mirrors/g6/G6 点击查看 免费下载 Fruchterman&#xff08;FR&#xff09;力导向布局是图可视化中最经典的布局算法之一&#xff0…

作者头像 李华