PHP分页类
一、实验简介
1.1 实验目的
本课程主要使用 PHP 实现分页类。分页功能在网站开发中应用非常广泛,相信大家都有用到过。本课程的目的就是带领大家通过实际的代码编写,让大家了解该功能实现的原理,当你们以后需要开发类似功能的时候,可以从中快速找到灵感。由于本课程主要是功能类的封装,所以在学习的过程中,你也可以进一步了解 PHP 面向对象编程的思想。
本次实验代码量可能较多,希望大家在学习过程中不要浮躁,每段代码都一定要在实验环境中自己动手敲,而不是一味地复制粘贴。希望大家能在本次实验中有所收获。
1.2 实验环境
实验楼在线环境:Ubuntu 14.04.5
MySQL 数据库,通过以下命令启动服务:
sudo service mysql start;
Apache 服务器,启动服务:
sudo service apache2 start;
代码编辑器:会员可使用 WebIDE 。普通用户可使用 gedit 或者 sublime 文本编辑器。
1.3 效果展示
1.4 代码说明
本实验代码参考 thinkphp 框架分页类代码,并在其基础上做了改动。如果大家有兴趣可以去查看框架源码中分页类部分。
二、项目文件结构
本次实验需要编写两个php文件: index.php和pager.php。 图中的index.html是/var/www/html
自带文件,而bootstrap.min.css我们会在接下来的教程中提供。
三、实验操作
闲话不多说,我们直接开始内容讲解。
3.1 分页类的封装
我们使用的是 Apache 服务器,所以为了访问的方便,我们需要将我们的代码文件放到它的根目录下。Apache 默认的web根目录位于 /var/www/html
。因为我们要在该目录下写入文件,所以首先我们需要修改 html
目录的权限:
sudo chmod -R 777 /var/www/html
文档以下内容如果不做特殊说明,我们的所有文件操作均位于 /var/www/html
目录,以下简称 html
目录。
在 html
目录下创建我们的分页类文件 pager.php
。
接下来开始具体的代码编写。
首先我们需要考虑分页操作有那些属性。
protected $currentPage; //当前页protected $lastPage; //最后一页protected $listRows; //每页展示的数量protected $hasNext; //是否有下一页protected $hasPre; //是否有前一页protected $options = [ //操作配置 'var_page' => 'page', //分页变量名 'path' => 'index.php', //分页路径名 'query' => [], //url中是否有其他参数 'fragment' =>'', //锚点位置];
在 pager.php
文件编辑如下:
<?php /** * 分页类 */class Pager{ protected $total; //数据集总数 protected $listRows; //每页展示的数量 protected $currentPage; //当前页 protected $lastPage; //最后一页 protected $hasNext; //是否有下一页 protected $hasPre; //是否有上一页 protected $options = [ //操作配置 'var_page' => 'page', 'path' => 'index.php', 'query' => [], 'fragment' =>'', ]; public function __construct($total,$listRows,$currentPage = null,$options = []) { $this->options = array_merge($this->options,$options); $this->options['path'] = $this->options['path'] != '' ? rtrim($this->options['path'],'') : $this->getCurrentPath(); //当前脚本路径 $this->listRows = $listRows; $this->total = $total; $this->lastPage = (int)ceil($total / $listRows); //总数/每页展示数 = 最后一页的页数 $this->currentPage = $this->getCurrentPage(); //获取当前页码 $this->hasNext = ($this->currentPage < $this->lastPage); //当前页小于下一页则表示有下一页 } }
大家可能对 $options
这个配置数组有点困惑,图解如下:
通过上面这张图,大家应该能够理解配置项的作用了。当然,这些都是可选的。
构造函数中,用到了一个函数 getCurrentPath
来获取当前路径信息,接下来在 Pager
类中添加该方法。
public static function getCurrentPath() { return $_SERVER['PHP_SELF']; //返回当前脚本路径 }
另外我们还用到了 getCurrentPage
函数来获取当前页码,在类中继续添加方法:
public static function getCurrentPage($varPage='page',$default=1) { $page = isset($_GET[$varPage]) ? (int)$_GET[$varPage] : $default; //通过$_GET从url中获取page页码,默认为1 return $page; }
通过以上的代码。我们得到了分页所需要的基本信息:总页数,每页展示数,当前页,是否有下一页,以及其他配置项。接下来继续加工这些信息。
-
将页码转为URL地址
protected function buildUrl($page) { if ($page <= 0) { $page = 1; } $parameters = [$this->options['var_page'] => $page]; //构造url参数,如 page=1 if (count($this->options['query']) > 0) { //如果还有其他url参数,添加参数数组 $parameters = array_merge($this->options['query'],$parameters); } $url = $this->options['path']; //分页的url地址 if (!empty($parameters)) { //处理参数。拼接参数 ?xx=aa&yy=bb $url .= '?' . urldecode(http_build_query($parameters,null,'&')); } return $url . $this->buildFragment(); //拼接锚点 # }
-
拼接锚点
protected function buildFragment() { //是否存在锚点信息,若有则拼接,如:#top return $this->options['fragment'] ? '#' . $this->options['fragment'] : '' ; }
通过上面两个方法,我们就可以将一个普通的页码转换为一个带有分页信息的url地址。
-
指定范围构造分页信息
分页时,我们可能会用到此方法,提供一段页码区间,自动生成这段区间的分页url。添加方法:
public function getUrlRange($start,$end) { $urls = []; //存储区间内urls for ($page=$start; $page <= $end; $page++) { $urls[$page] = $this->buildUrl($page); //每个页码都生成url } return $urls; }
通过此方法,我们可以批量生成一段区间内的url地址。
上述的几种方法,基本可以获取我们所以需要的任意分页url地址。接下来我们考虑一下我们要如何在页面上展示分页标签,以及如何将这些展示标签组合起来,形成一个漂亮的分页样式。
在第一节中,我展示了两张效果图,从中可以看出我们至少需要三种按钮样式:
包含:上一页+下一页的文本样式,具体页码的数字样式,以及省略的省略号样式。至于中间那个蓝色的按钮,也是属于数字一类的样式,只是标签的
class
和链接属性 不一样而已。接下来我们就考虑如何做出这样效果。 -
生成普通页码按钮
public function getPageLinkWrapper($page,$url) { if ($page == $this->currentPage()) { //当前页的页码按钮不可点击,是已激活按钮 return $this->getActiveWrapper($page); //提供页码,生成激活按钮 } return $this->getAvaliablePageWrapper($page,$url); //提供页码和链接其他可点击按钮 }
已上面的图片为例,如果当前页面是第七页,那么数字按钮 7 就不可点击,没有超链接属性,且处于激活状态,其他的数字按钮都是可点击的。
-
生成激活按钮
public function getActiveWrapper($text) { return '<li class="active"><span>'.$text.'</span></li>'; }
激活按钮没有
href
属性。所以只需要提供页码文字就行。比如提供页码 7 ,生成的按钮样式就是上图所展示的效果。 -
生成可点击按钮
public function getAvaliablePageWrapper($text,$url) { return '<li><a href="'.htmlentities($url).'">'.$text.'</a></li>'; }
通过它生成的按钮带有超链接属性,点击按钮就可以跳转到具体的url地址,这里就是跳转至具体的页码。
此方法的参数有一个
$text
,代表不只是可以提供页码数,也可以提供其他文字内容。比如如果有上一页,那么向getAvaliablePageWrapper
方法提供字符«
和 url地址,那么就可以生成可点击的上一页按钮样式,下一页按钮的生成方法也是类似。 -
生成禁用按钮
如果当前页是第一页,那么上一页的按钮就应该被禁用。同理,当处于最后一页时,下一页的按钮也应该被禁用。此方法就是用来生成这种按钮的,基本上只有上一页或下一页按钮才会用到。
public function getDisabledTextWrapper($text) { return '<li class="disable"><span>'.$text.'</span></li>'; }
上面三种生成按钮的方法比较通用,涵盖我们需要的主要类型,接下来我们利用上面的方法再具体的生成需要的按钮。
-
生成省略号按钮
当页码过多,我们采用省略号的方法来展示,如上效果图;
protected function getDots() { return $this->getDisabledTextWrapper('...'); //不可点击按钮 }
-
生成上一页按钮
public function getPreviousButton($text='«') { if ($this->currentPage() <= 1) { //根据当前页判断生成上一页按钮的样式 return $this->getDisabledTextWrapper($text); //第一页,上一页不可点击 } $url = $this->buildUrl($this->currentPage() - 1); //大于第一页,上一页=当前页-1,根据页码再生成url return $this->getPageLinkWrapper($text,$url); //将文字和url传入,生成按钮 }
-
生成下一页按钮
public function getNextButton($text='»') { if (!$this->hasNext) { //判断是否有下一页 return $this->getDisabledTextWrapper($text); //无下一页,生成禁用按钮样式 } $url = $this->buildUrl($this->currentPage() + 1); //有下一页,下一页=当前页+1,获取url return $this->getPageLinkWrapper($text,$url); //传入文本和url,生成按钮 }
-
批量生成按钮
public function getUrlLinks($urls) { $html = ''; foreach ($urls as $key => $value) { $html .= $this->getPageLinkWrapper($key, $value); //将传入的urls批量生成普通页码按钮,并组装 } return $html; }
好了,只要通过以上的方法,我们可以生成任意样式的按钮,基本可以满足我们的需求,接下来就是一个麻烦的问题:如何将页码按钮美观的展示出来,如何设置展示效果。我再放几张效果图:
大家应该看出来的了。随着分页数的不用,展示效果也有所不同,随着当前页的变动,展示效果也可能会发生变化。当然,这就需要我们仔细的琢磨一下。
从上面的展示效果中,页码按钮大致可以分为三种排列方式:
左
,左中右
,左右
。只要当前页码或总的页数满足一定的条件,就改变显示效果。接下来开始编码实现,请看代码:public function getLinks() { $block = [ 'first' => null, //头部,最左边的部分 'slider' => null, //滑块部分,中间的部分,跟随页码变动 'last' => null, //尾部,最右边的部分 ]; $side = 3; //当前页码两边各有多少个页码 上图:当前第八页,左边567,右边9 10 11 $window = $side * 2; //头部和尾部的页码数,上图,当前第八页,头部12.. 尾部 ... 15 16 if ($this->lastPage < $window + 6) { //如果总页数小于12 $block['first'] = $this->getUrlRange(1,$this->lastPage); //头部页码 1---总页数 } elseif ($this->currentPage <= $window) { //如果当前页小于等于6,头部页码 1---8 $block['first'] = $this->getUrlRange(1,$window + 2); } elseif ($this->currentPage > ($this->lastPage - $window)) { //如果当前页大于最后一页-$window $block['first'] = $this->getUrlRange(1,2); $block['last'] = $this->getUrlRange($this->lastPage - ($window + 2),$this->lastPage); } else { //拥有头部,滑块,尾部 $block['first'] = $this->getUrlRange(1,2); $block['slider'] = $this->getUrlRange($this->currentPage - $side,$this->currentPage + $side); $block['last'] = $this->getUrlRange($this->lastPage - 1,$this->lastPage); } $html = ''; if (is_array($block['first'])) { //将头部中的url地址转为按钮 $html .= $this->getUrlLinks($block['first']); //批量生成按钮获得html代码 } if (is_array($block['slider'])) { //滑块部分,包含省略号和滑块按钮 $html .= $this->getDots(); $html .= $this->getUrlLinks($block['slider']); } if (is_array($block['last'])) { //尾部部分,包含省略号和尾部按钮 $html .= $this->getDots(); $html .= $this->getUrlLinks($block['last']); } return $html; }
看完上面的代码,我估计你多少有点晕,这是正常的。这段代码比较费解,如果你有时间研究,你可以仔细琢磨一下,暂时看不懂的,也没有关系,可以先不急着理解这段代码,到时候通过最终的效果,一边查看展示效果,一边理解代码的意思。
-
分页数:16
-
分页数:11
-
渲染内容
public function render() { if ($this->hasPages()) { return sprintf('<ul class="pagination">%s %s %s</ul>', $this->getPreviousButton(), $this->getLinks(), $this->getNextButton() ); } }
通过这个方法,得到我们的分页渲染结果。
Pager
分页类中的方法已经全部写完。可以供我们使用了。接下来我们就来测试一下效果。
3.2 分页测试
首先,在数据库中准备分页所使用的示例数据。确保数据库服务已开启,若未开启,使用以下命令开始数据库服务;
sudo service mysql start;
进入数据库服务器,创建数据库和数据表;
mysql -u root
在线环境中mysql数据库默认没有密码,所以可以直接进入。
创建 company 数据库:
create database company;
为了大家操作方便,可以直接复制下面的内容,直接在mysql命令行里运行:
SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for employee -- ---------------------------- DROP TABLE IF EXISTS `employee`; CREATE TABLE `employee` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `gender` enum('Male','Female') NOT NULL DEFAULT 'Male', `age` int(11) NOT NULL, `avatar` varchar(255) NOT NULL, `title` varchar(255) NOT NULL, `depart` varchar(255) NOT NULL, `create_time` datetime NOT NULL, `update_time` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
接下来向数据库中填充数据。
在 html
目录创建 index.php
,作为测试文件;
首先我们使用php代码,向数据库填充数据。index.php 编辑如下:
<?php$mysqli = new mysqli('127.0.0.1','root','','company') or die('Connection Error'); $time = date('Y-m-d H:i:s',time()); $sql1 = "INSERT INTO employee(name,gender,age,avatar,title,depart,update_time,create_time) VALUES('XiaoMing','Male',20,'https://dn-simplecloud.shiyanlou.com/gravatar57FFHPK4T8G8.jpg?imageView2/1/w/30/h/30','Manager','Shiyanlou','$time','$time')";for ($i=0; $i < 30; $i++) { $mysqli->query($sql1); }
打开浏览器,输入地址:localhost/index.php
。如果没有报错,那就说明数据填充成功了。在数据库命令行中查询记录:
select * from employee;
就能看到我们刚才的数据了。
编辑 index.php
如下,可直接复制,替换之前的内容:
<?php require 'pager.php'; $mysqli = new mysqli('127.0.0.1','root','','company') or die('Connection Error'); $listRow = 2; //每页展示数量$sql = 'SELECT id FROM employee;'; $data = $mysqli->query($sql); $total = $data->num_rows; //获取记录总数$Pager = new Pager($total,$listRow,0,['query'=>['name'=>'admin'],'fragment'=>'top']); //实例化分页类$sql = "SELECT * FROM employee LIMIT ".($Pager->getCurrentPage()-1) * $listRow ."," . $listRow; //获取定量的数据条数$data = $mysqli->query($sql); ?><!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>分页类</title> <link href="bootstrap.min.css" rel="stylesheet"> </head> <body style="height: 800px"> <div class="container" style="text-align: center;display: flex;flex-direction: column;justify-content: center;height: 100%"> <table class="table table-bordered"> <caption>员工表</caption> <thead> <tr> <th>Portrait</th> <th>ID</th> <th>Name</th> <th>Gender</th> <th>Age</th> <th>Title</th> <th>Department</th> <th>create_time</th> <th>update_time</th> </tr> </thead> <tbody> <?php while ($row = mysqli_fetch_object($data)) { ?> <tr> <td><img src= "<?php echo $row->avatar; ?>" alt=""></td> <td><?php echo $row->id ?></td> <td><?php echo $row->name ?></td> <td><?php echo $row->gender ?></td> <td><?php echo $row->age ?></td> <td><?php echo $row->title ?></td> <td><?php echo $row->depart ?></td> <td><?php echo $row->create_time ?></td> <td><?php echo $row->update_time ?></td> </tr> <?php } ?> </tbody> </table> <div class="page"> <hr> <?php echo $Pager->render() ?> </div> </div> </body> </html>
保存退出,在浏览器中输入地址:localhost/index.php
。如果一切正常的话,你应该就可以看到和我的效果图十分类似的分页效果(需包含bootstrap.min.css 文件)。
在命令行中输入如下命令来获取bootstrap.min.css 文件:
wget http://labfile.oss.aliyuncs.com/courses/743/bootstrap.min.css
请确保css文件和php文件都在/var/www/html文件夹下。
如果想获取index.php和pager.php来进行校对,请输入:
http://labfile.oss.aliyuncs.com/courses/743/index.phphttp://labfile.oss.aliyuncs.com/courses/743/pager.php
四、实验总结
本实验带领大家动手编码封装了一个美观实用的分页类。代码量有两百多行,如果你是认真的敲完了所有的代码,那么你一定从本实验中收获不少。其实代码总体结构不是很复杂,难点在于其中各个方法之间的调用关系和逻辑处理可能会让你一时不能理解。如果暂时理解有困难的同学,希望能多花一点时间理解。
希望你能从本课程中学习到如何完整的封装一个分页类。如果文档中有任何问题或者你在学习中有任何困惑,欢迎到评论区留言,我会及时为你解答。