学习目标
- 综合案例:学生信息管理
- 工厂设计模式
- 重载
- 静态延时绑定
- 变量序列化
- 常用的魔术常量
综合案例:学生信息管理系统
1、面向对象开发流程
- 面向过程是以过程(步骤)为中心的编程思想,面向对象是事务(对象)为中心的编程思想。
- 对象是专业对象,是一个功能方方面面的总和。例如:数据库对象、分页对象、图像处理等。
- 一个项目由若干个功能模块构成,包括:用户管理、新闻管理、产品管理、文章管理、学生管理等。
- 每个功能模块是一个对象,包括:用户对象、新闻对象、产品对象、文章对象、学生对象等。
- 每个对象对应一个类:包括:用户类、新闻类、产品类、文章类、学生类等。
- 当然,每个模块还有一些公共对象:数据库对象、分页对象、上传对象、图像处理、验证码对象等。
2、单例设计模式(三私一公)
- 一个类永远只能创建一个对象,不管用任何方法都无法创建第2个对象。该对象大家共享。
- 一私:私有的静态的保存对象的属性。
- 一私:私有的构造方法,阻止类外new对象。
- 一私:私有的克隆方法,阻止类外clone对象。
- 一公:公共的静态的创建对象的方法。
3、数据库工具类(./libs/Db.class.php)
<?php
// 定义一个数据库链接工具类(单例设计类)
class Db
{
// 定义私有属性
private $db_host;
private $db_username;
private $db_password;
private $sheet_name;
private $charset;
private $sql_link;
// 定义私有属性实例对象
private static $obj = null;
// 定义私有构造方法,防止类外new对象实例
private function __construct($info_arr = array())
{
// 传递变量
$this->db_host = $info_arr['host'];
$this->db_username = $info_arr['username'];
$this->db_password = $info_arr['password'];
$this->sheet_name = $info_arr['sheet_name'];
$this->charset = $info_arr['charset'];
// 启动各个服务
$this->connect_sql();
$this->select_sheet();
$this->set_charset();
}
// 定义私有克隆方法,防止克隆对象
private function __clone()
{
return "禁止克隆对象!!!";
}
// 定义共有创建对象方法
public static function create_obj($info_arr)
{
if (!(self::$obj instanceof self)) {
self::$obj = new self($info_arr);
}
return self::$obj;
}
// 链接数据库的方法
private function connect_sql()
{
if (!($this->sql_link = @mysqli_connect($this->db_host, $this->db_username, $this->db_password))) {
echo "链接数据库{$this->db_host}失败!!!";
die();
}
}
// 设置字符集的方法
private function select_sheet()
{
if (!mysqli_select_db($this->sql_link, $this->sheet_name)) {
echo "链接数据表" . $this->sheet_name . "失败!!!";
die();
}
}
// 选择数据表
private function set_charset()
{
mysqli_set_charset($this->sql_link, $this->charset);
}
// sql语句执行总函数
public function do_sql_query($sql_query, $type = 2)
{
// 对sql语句的首个单词做判断,需要根据返回结果选择不同的处理方法
if (substr(strtolower($sql_query), 0, 6) == 'select') {
// select语句需要对结果集二次处理
return $this->do_select_sql_query($sql_query, $type);
} else {
// 非select语句直接返回查询布尔值结果
return mysqli_query($this->sql_link, $sql_query);
}
}
// sql语句执行分函数:执行select语句(对返回结果集处理)
private function do_select_sql_query($sql_query, $type)
{
$result = mysqli_query($this->sql_link, $sql_query);
// 当用户查询语句错误时可能引起报错
$rows_num = @mysqli_num_rows($result);
// 定义返回结果集的类型
$return_type = array(
1 => MYSQLI_NUM,
2 => MYSQLI_ASSOC,
3 => MYSQLI_BOTH
);
if ($rows_num == 1) {
// 只从数据库获取了一行
$return_result = mysqli_fetch_all($result, $return_type[$type]);
// 对数组进行将维处理,方便类外操作
return $return_result[0];
} elseif ($rows_num > 1) {
// 获取了多行
$return_result = mysqli_fetch_all($result, $return_type[$type]);
return $return_result;
} else {
// $rows_num未赋值,说明数据库查询语句出现问题
echo "请检查SQL语句格式!!!";
die();
}
}
// 公共构析
public function __destruct()
{
mysqli_close($this->sql_link);
}
}
4、连接数据库的公共文件(./conn.php)
<?php
// 类的自动加载
spl_autoload_register(function ($className) {
// 这个数组中包含了所有可能的路径
$path_arr = array(
'./libs/' . $className . '.class.php',
'./public/' . $className . '.cla.php'
);
foreach ($path_arr as $path) {
if (file_exists($path)) {
// 找到路径时,立即退出函数,节省CPU资源
require_once($path);
return;
}
}
});
// 数据库信息
$info_arr = array(
'host' => 'localhost',
'username' => 'imageSystem',
'password' => 'imageSystem',
'sheet_name' => 'imageSystem',
'charset' => 'utf8'
);
// 创建数据库对象
$my_db = Db::create_obj($info_arr);
5、显示学生信息列表(./list.php)
<?php
// 连接公共导入文件
require_once("./conn.php");
// 获取页面地址栏参数,并进行分页计算
$countNum = isset($_GET['page']) ? $_GET['page'] : 1;
$showSize = 10;
$startRow = ($countNum - 1) * $showSize;
// 编写sql查询语句
$sqlQuery = "SELECT * FROM typecho_contents";
//对创建的数据库对象进行调用
$my_db->do_sql_query($sqlQuery);
$numberOfRows = $my_db->get_affect_num();
$pagesNum = ceil($numberOfRows / $showSize);
$sqlQuery .= " ORDER BY cid DESC LIMIT {$startRow}, {$showSize}";
$allRows = $my_db->do_sql_query($sqlQuery);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文章列表页面</title>
<style>
/* 分页按钮样式 */
.pagination a {
margin: auto 5px;
display: inline-block;
width: 20px;
height: 20px;
color: skyblue;
text-decoration: none;
}
</style>
<script>
// 自定义一个删除提示函数
function confirmDel(cid) {
if (window.confirm("确认删除cid为:" + cid + " 的文章?")) {
window.location.href = "./delete-article.php?cid=" + cid; // 用户确认删除时,浏览器窗口重定向
}
}
</script>
</head>
<body>
<div style="margin: 10px auto;text-align: center;">
<h2>文章信息管理</h2>
<a href="./add-article.php">添加文章</a>
目前共有<font color=red><?php echo $numberOfRows ?></font>篇文章
</div>
<table width="1000" border="1" align="center" rules="all" cellpadding="5">
<!-- 分页按钮html显示 -->
<tr class="pagination">
<td colspan="8" align="center" height="50">
<?php
// 对分页按钮进行处理
$startPageNum = $countNum - 5;
$EndPageNum = $countNum + 5;
// 两种特殊情况
if ($countNum <= 5) {
$startPageNum = 1;
$EndPageNum = $showSize;
}
if ($countNum > $pagesNum - 5) {
$EndPageNum = $pagesNum;
$startPageNum = $pagesNum - $showSize;
}
// 当分页数小于十时全部输出
if ($pagesNum < 10) {
$startPageNum = 1;
$EndPageNum = $pagesNum;
}
// 循环输出分页按钮
for ($i = $startPageNum; $i <= $EndPageNum; $i++) {
if ($i == $countNum) {
echo "<a href='?page={$i}' style='border: 2px solid red'>{$i}</a>";
} else {
echo "<a href='?page={$i}' style='border: 2px solid #ccc'>{$i}</a>";
}
}
?>
</td>
</tr>
<tr bgcolor="#ccc">
<th>cid</th>
<th>title</th>
<th>text</th>
<th>type</th>
<th>commentsNum</th>
<th>viewsNum</th>
<th>likes</th>
<th>操作选项</th>
</tr>
<!-- 循环结果集得到的二维数组 -->
<?php
foreach ($allRows as $row) {
?>
<tr align="center">
<td><?php echo $row['cid']; ?></td>
<td><a href="https://blog.manyacan.com/archives/<?php echo $row['cid']; ?>/" target="_blank"><?php echo $row['title']; ?></a></td>
<td><?php echo htmlentities(substr($row['text'], 0, 100), ENT_QUOTES, "UTF-8"); ?></td>
<td><?php echo $row['type']; ?></td>
<td><?php echo $row['commentsNum']; ?></td>
<td><?php echo $row['viewsNum']; ?></td>
<td><?php echo $row['likes']; ?></td>
<td><a href="#">修改</a> | <a href="#" onclick="confirmDel(<?php echo $row['cid']; ?>)">删除</a></td>
</tr>
<?php } ?>
<!-- 分页按钮html显示 -->
<tr class="pagination">
<td colspan="8" align="center" height="50">
<?php
for ($i = $startPageNum; $i <= $EndPageNum; $i++) {
if ($i == $countNum) {
echo "<a href='?page={$i}' style='border: 2px solid red'>{$i}</a>";
} else {
echo "<a href='?page={$i}' style='border: 2px solid #ccc'>{$i}</a>";
}
}
?>
</td>
</tr>
</table>
</body>
</html>
6、删除学生信息(./delete.php)
(1)列表页list.php
<script>
// 自定义一个删除提示函数
function confirmDel(cid) {
if (window.confirm("确认删除cid为:" + cid + " 的文章?")) {
window.location.href = "./delete-article.php?cid=" + cid; // 用户确认删除时,浏览器窗口重定向
}
}
</script>
...
...
...
<td><a href="#">修改</a> | <a href="#" onclick="confirmDel(<?php echo $row['cid']; ?>)">删除</a></td>
(2)删除页面delete.php
<?php
// 导入公共文件
require_once('./conn.php');
// 获取地址栏传递的id
$del_cid = $_GET['cid'];
// 编写sql语句
$sql_query = "DELETE FROM typecho_contents where cid={$del_cid}";
// echo $sql_query;
// 启动数据库实例的方法
if ($my_db->do_sql_query($sql_query)) {
echo "cid={$del_cid}的文章删除成功!";
// header('refresh:3;url=./index.php');
die();
} else {
echo "cid={$del_cid}的文章删除失败!";
// header('refresh:3;url=./index.php');
die();
}
7、创建分页类文件 ./libs/Pager.class.php
<?php
// 定义最终分页类【有BUG!!!】
final class Pager
{
// 定义私有属性
public $countNum; //地址栏参数
public $showSize; //每页文章数
public $startRow; //数据库获取起始行号
public $pagesNum;
public $numberOfRows;
private static $obj = null;
public function __construct($countNum, $showSize, $numberOfRows)
{
$this->countNum = $countNum;
$this->showSize = $showSize;
$this->numberOfRows = $numberOfRows;
$this->get_params();
}
public static function cteate_obj($countNum, $showSize, $numberOfRows)
{
if (!(self::$obj instanceof self)) {
self::$obj = new self($countNum, $showSize, $numberOfRows);
}
return self::$obj;
}
private function get_params()
{
$this->startRow = ($this->countNum - 1) * $this->showSize;
$this->pagesNum = ceil($$this->numberOfRows / $this->showSize);
}
public function get_sql_query()
{
$sql_query = "SELECT * FROM typecho_contents";
$sql_query .= " ORDER BY cid DESC LIMIT {$this->startRow}, {$this->showSize}";
return $sql_query;
}
public function make_btn()
{
// 对分页按钮进行处理
$startPageNum = $this->countNum - 5;
$EndPageNum = $this->countNum + 5;
// 两种特殊情况
if ($this->countNum <= 5) {
$startPageNum = 1;
$EndPageNum = $this->showSize;
}
if ($this->countNum > $this->pagesNum - 5) {
$EndPageNum = $this->pagesNum;
$startPageNum = $this->pagesNum - $this->showSize;
}
// 当分页数小于十时全部输出
if ($this->pagesNum < 10) {
$startPageNum = 1;
$EndPageNum = $this->pagesNum;
}
// 定义一个返回字符串
$end_str = '';
// 循环输出分页按钮
for ($i = $startPageNum; $i <= $EndPageNum; $i++) {
if ($i == $this->countNum) {
$end_str .= "<a href='?page={$i}' style='border: 2px solid red'>{$i}</a>";
} else {
$end_str .= "<a href='?page={$i}' style='border: 2px solid #ccc'>{$i}</a>";
}
}
return $end_str;
}
}
工厂设计模式
1、什么是工厂设计模式
- 根据传递不同的类名参数,返回不同类的对象;
- 工厂模式,就是生产各种的不同类的对象;
- 工厂模式,改变了在类外使用new关键字创建对象的方式,改成了在工厂类中创建类的对象。
- 在类的外部我们无法控制类的行为,但在类内部自己可以控制类的行为。
2、工厂设计模式的要求
- 一般情况下,定义一个工厂类;
- 工厂类中的方法,应该是公共的静态的方法;
- 该方法功能:就是根据传递的不同参数,去创建不同的类实例;
重载
1、什么是重载
- 在其它编程语言中,面向对象的重载是指,方法有相同的名称,但是参数列表不相同的情形,但PHP不支持同名函数或同名方法。
- PHP所提供的"重载"(overloading)是指动态地"创建"类属性和方法,我们是通过魔术方法来实现的。
- 当调用当前环境下未定义或不可见的类属性或方法时,重载方法会被调用。
- 所有的重载方法都必须被声明为 public。
属性重载只能在对象中进行。在静态方式中,这些魔术方法将不会被调用。所以这些方法都不能被
声明为 static。
- 这些魔术方法的参数都不能通过引用传递。
2、属性重载
PHP魔术方法:https://www.php.net/manual/zh/language.oop5.magic.php
(1)__get()魔术方法
描述:读取不可访问属性的值时,__get() 会被调用。
语法:**public mixed __get ( string $name )
<?php
class Student
{
private $name = '张三';
private $age = 22;
public function __get($name)
{
return $this->$name;
}
}
$stu_01 = new Student();
echo "学生{$stu_01->name}的年龄是{$stu_01->age}";
(2)__set()魔术方法
描述:在给不可访问属性赋值时,__set() 会被调用。
语法:public void __set ( string $name , mixed $value )
<?php
class Student
{
private $name = '张三';
private $age = 22;
public function __get($name)
{
return $this->$name;
}
public function __set($name, $value)
{
$this->$name = $value;
}
}
$stu_01 = new Student();
echo "学生{$stu_01->name}的年龄是{$stu_01->age}<br>";
$stu_01->name = '李四';
$stu_01->age = 20;
echo "学生{$stu_01->name}的年龄是{$stu_01->age}<br>";
(3)__isset()魔术方法
描述:当对不可访问属性调用 isset() 或 empty() 时,__isset()会被调用。
语法:public bool __isset ( string $name )
<?php
class Student
{
private $name = '张三';
private $age = 22;
public function __get($name)
{
return $this->$name;
}
public function __set($name, $value)
{
$this->$name = $value;
}
public function __isset($name)
{
return isset($this->$name);
}
}
$stu_01 = new Student();
// echo "学生{$stu_01->name}的年龄是{$stu_01->age}<br>";
// $stu_01->name = '李四';
// $stu_01->age = 20;
// echo "学生{$stu_01->name}的年龄是{$stu_01->age}<br>";
if (isset($stu_01->name)) {
echo true;
}
(4)__unset()魔术方法
描述:当对不可访问属性调用 unset() 时,__unset()会被调用。
语法:public void __unset ( string $name )
<?php
class Student
{
private $name = '张三';
private $age = 22;
public function __get($name)
{
return $this->$name;
}
public function __set($name, $value)
{
$this->$name = $value;
}
public function __unset($name)
{
unset($this->$name);
}
}
$stu_01 = new Student();
echo "学生{$stu_01->name}的年龄是{$stu_01->age}<br>"; // 学生张三的年龄是22
// $stu_01->name = '李四';
// $stu_01->age = 20;
// echo "学生{$stu_01->name}的年龄是{$stu_01->age}<br>";
unset($stu_01->name);
echo "学生{$stu_01->name}的年龄是{$stu_01->age}<br>"; // 学生的年龄是22
3、方法重载
(1)__call()魔术方法
描述:在对象中调用一个不可访问方法时,__call() 会被调用。
语法:**public mixed __call ( string $name , array $arguments )
(2)__callStatic()魔术方法
描述:用静态方式中调用一个不可访问方法时,__callStatic() 会被调用。
语法:public static mixed __callStatic ( string $name , array $arguments)
变量序列化
1、什么是变量序列化
- 序列化是将变量转换为可保存或传输的字符串的过程;
- 反序列化就是在适当的时候把这个字符串再转化成原来的变量使用;
- 这两个过程结合起来,可以轻松地存储和传输数据,使程序更具维护性;
- 序列化有利于存储或传递 PHP 的值,同时不丢失其类型和结构。
变量一旦离开内存就不是变量了,如何跨语言实现变量传递?例如:PHP与JS交互,这就要用到变量序列化。
2、序列化函数serialize()
- 描述:产生一个可存储的值的表示;
- 语法:string serialize ( mixed $value )
- 参数:$value可以是任何类型,除了资源外;
- 返回:返回序列化之后的字符串,可以存储于任何地方。
3、反序列化函数unserialize()
- 描述:从已存储的表示中创建 PHP 的值
- 语法:mixed unserialize ( string $str )
- 说明:对单一的已序列化的变量进行操作,将其转换回 PHP 的值。
- 参数:$str为序列化后的字符串;
返回:返回的是转换之后的值,可为 integer、float、string、array或 object。
如果传递的字符串不可序列化,则返回 FALSE,并产生一个 E_NOTICE。
4、对象序列化
- 对象的序列化过程,与其它变量数据一样;
- 对象序列化的内容只能包含成员属性;
当序列化对象时,serialize()函数会检查类中是否存在一个魔术方法
\__sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。
5、对象反序列化
- 对象的反序列化过程,与其它变量数据一样;
当对象反序列化时,unserialize()函数会检查类中是否存在一个__wakeup()方法。如果存在,则会先调用
\__wakeup 方法,预先准备对象需要的资源。 \__wakeup() 经常用在反序列化操作中,进行一些初始化操作,例如重新建立数据库连接,或执行其它初始化操作。
常用的魔术常量
- __LINE__:当前行号
- __FILE__:当前文件
- __DIR__:当前目录
- __FUNCTION__:当前函数
- __CLASS__:当前类
- __METHOD__:当前方法
- __NAMESPACE__:当前命名空间