学习目标

  • 综合案例:学生信息管理
  • 工厂设计模式
  • 重载
  • 静态延时绑定
  • 变量序列化
  • 常用的魔术常量

综合案例:学生信息管理系统

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__:当前命名空间