《零基础学 PHP:从入门到实战》模块十:从应用到精通——掌握PHP进阶技术与现代化开发实战-1

第1章:面向对象编程(OOP):构建可复用的代码蓝图

章节介绍

章节学习目标

通过本章的学习,你将能够:

  1. 理解面向对象编程(OOP)的三大核心特性(封装、继承、多态)及其优势。
  2. 熟练地在PHP中定义类(Class)、创建对象(Object),并理解它们之间的关系。
  3. 掌握类属性与方法的定义,以及通过访问控制修饰符(public, protected, private)实现封装。
  4. 学会使用构造函数和析构函数管理对象的生命周期。
  5. 运用继承机制扩展已有类的功能,并理解方法重写与parent关键字的使用。
  6. 了解抽象类与接口在实现多态和制定契约中的作用。
  7. 初步认识魔术方法,并能在简单场景下使用__construct__toString等方法。

在整个教程中的作用

本章是本模块乃至从"入门"迈向"实战"的思维转型关键点 。在此之前,我们编写的都是"过程式"的脚本------代码从上到下执行,数据与操作数据的函数分离。面向对象编程(OOP)则提供了一种全新的、更贴近现实世界模型的编程范式。它将数据(属性)和对数据的操作(方法) 捆绑在一起,形成一个独立的单元------对象。掌握OOP,意味着你将能够:

  • 构建更复杂、更易维护的系统:通过类和对象来映射现实中的实体(如用户、文章、订单)。
  • 提高代码的复用性:通过继承,可以轻松创建新类,复用已有类的代码。
  • 增强代码的灵活性与可扩展性:多态和接口允许你编写更通用、更松耦合的代码。
  • 为学习现代化PHP框架铺平道路:所有主流的PHP框架(如Laravel, ThinkPHP, Yii)都深度建立在OOP思想之上。本章是理解和运用这些框架的基石。

与前面章节的衔接

你已经掌握了PHP的基础语法、数组、函数以及MySQL的基本操作。你将发现,OOP中的"方法"本质上是定义在类内部的函数,"属性"则是与类绑定的变量。本章将带你以新的视角重新组织这些已知的代码元素,构建结构更清晰、职责更分明的程序。

本章主要内容概览

我们将从"类"与"对象"这一基本概念出发,逐步深入OOP的各个核心机制。首先,我们会创建一个基础的User类,定义其属性和方法。然后,通过继承创建更特殊的Admin类。接着,探讨抽象类和接口如何定义规范。最后,简要介绍一些有用的魔术方法。本章的实践将贯穿始终,最终你将拥有一个可用的、面向对象设计的UserAdmin类,为后续构建完整的用户系统打下坚实基础。

核心概念讲解

1. 类(Class)与对象(Object):蓝图与实体

  • 概念 是对象的抽象"蓝图"或"模板",它定义了一类事物共有的属性 (是什么)和行为 (能做什么)。对象是类的一个具体"实例",是根据类这个蓝图创建出来的、实实在在存在的东西。
  • 类比汽车设计图是一个 ,它规定了颜色、型号、引擎等属性和启动、加速等方法。根据这张设计图生产出来的每一辆具体的轿车(比如你的那辆白色轿车)就是一个对象
  • 在PHP中的关系 :使用class关键字定义类,使用new关键字根据类创建对象。
php 复制代码
    // 定义"汽车"类(蓝图)
class Car {
        // ... 属性和方法
}
    // 创建具体的汽车对象(实体)
$myCar = new Car();
    $yourCar = new Car();
复制代码
`$myCar`和`$yourCar`是两个独立的对象,但它们都遵循`Car`类的结构。

2. 属性与方法:状态与行为

  • 属性:也称为成员变量,用于描述对象的状态或特征(例如:用户的名字、文章的标题)。在类内部定义。
  • 方法:也称为成员函数,用于定义对象的行为或能执行的操作(例如:用户登录、保存文章)。在类内部定义。
  • $this关键字 :在类的方法内部,$this是一个特殊的变量,它指向当前调用该方法的对象实例本身 。通过$this->属性名可以访问当前对象的属性。

3. 访问控制修饰符:实现封装

封装是OOP的一大支柱,它意味着将对象的内部细节隐藏起来,只通过一个受控的接口(通常是公共方法)与外部交互。这提高了安全性和代码的易维护性。PHP通过三个关键字实现:

  • public(公有):属性和方法可以在任何地方(类内部、外部、子类)被访问。这是最开放的权限。
  • protected(受保护):属性和方法可以在声明它的类以及其子类(继承类)内部被访问,但不能在类的外部直接访问。
  • private(私有) :属性和方法能在声明它的类内部被访问。即使是它的子类也无法直接访问。
  • 最佳实践 :属性应尽量声明为privateprotected,然后通过公共的getter(如getName())和setter(如setName($name))方法来提供对它们的访问和修改。这被称为"封装",它允许你在读取或修改属性时加入验证逻辑。

4. 构造函数与析构函数:对象的生命

  • 构造函数 __construct() :这是一个特殊的魔术方法。当使用new关键字创建一个对象时,该方法会自动被调用。它通常用于执行对象的初始化工作,例如为属性设置初始值或进行必要的资源分配。
php 复制代码
    public function __construct($name) {
        $this->name = $name; // 创建对象时立即设置名字
echo "对象已创建。";
    }
  • 析构函数 __destruct() :这也是一个魔术方法。当对象被销毁(例如,脚本执行结束,或明确地将对象变量设为null)时,该方法会自动被调用。它通常用于执行清理工作,如关闭数据库连接、释放文件句柄等。

5. 继承(Inheritance):代码复用的利器

  • 概念 :允许一个类(子类、派生类)基于另一个类(父类、基类)来创建。子类将自动拥有 父类所有publicprotected的属性和方法,并可以添加自己新的属性和方法,或修改(重写)父类的方法。
  • 语法 :使用extends关键字。
php 复制代码
    class Admin extends User {
        // Admin 类继承了 User 类的所有 public 和 protected 成员
// 可以在此添加管理员特有的属性和方法
}
  • 方法重写:子类可以重新定义从父类继承来的方法,以提供更具体或不同的实现。这直接引出了"多态"的概念。
  • parent关键字 :在子类重写的方法中,如果需要调用父类被重写方法的原始版本,可以使用parent::方法名()

6. 多态(Polymorphism)、抽象类与接口:面向抽象编程

  • 多态 :"多态"意为"多种形态"。在OOP中,它指子类对象可以被当作父类对象来使用,并且同一个方法在不同的子类中可以有不同的行为。这通过方法重写来实现,增强了程序的灵活性和可扩展性。
  • 抽象类(Abstract Class)
  • 使用abstract关键字声明。
  • 不能被实例化 (即不能new AbstractClass()),只能被继承。
  • 可以包含抽象方法(使用abstract关键字声明,没有方法体{},以分号;结束)和普通方法。
  • 作用:为所有子类定义一个公共的模板或部分实现,并强制要求子类必须实现特定的抽象方法。
  • 接口(Interface)
  • 使用interface关键字声明。
  • 只能包含抽象方法(在PHP8之前)和常量。
  • 类使用implements关键字来实现接口,并且必须实现接口中定义的所有方法
  • 作用 :定义一套行为契约。一个类可以实现多个接口,从而承诺具备多种不同的能力。它比抽象类更纯粹地关注"能做什么",而不关心"是什么"。

7. 魔术方法(Magic Methods):PHP的语法糖

魔术方法是以双下划线__开头的方法,它们会在特定的时机被PHP自动调用。本章我们主要接触:

  • __construct(): 对象创建时调用。
  • __destruct(): 对象销毁时调用。
  • __toString(): 当对象被当作字符串处理时(如echo $obj;)调用,必须返回一个字符串。
  • __get($name) / __set($name, $value): 当访问或设置一个不可访问(如private)或不存在属性时被调用,可用于实现动态属性或访问控制。

代码示例

示例1:基础类与对象、属性与方法

php 复制代码
<?php
// 1. 定义一个基础的"用户"类 (User)
class User {
    // 2. 定义属性(成员变量)
// public: 在任何地方都可访问
public $username;
    // protected: 在本类和子类中可访问
protected $email;
    // private: 仅在本类中可访问
private $passwordHash;

    // 3. 定义方法(成员函数)
// 一个公共方法,用于设置用户信息
public function setInfo($username, $email, $password) {
        $this->username = $username; // 使用 $this 访问当前对象的属性
$this->email = $email;
        // 调用私有方法进行密码加密
$this->setPassword($password);
    }

    // 一个公共方法,用于获取用户信息(封装了protected属性email的访问)
public function getInfo() {
        // 注意:这里无法直接返回 $this->passwordHash,因为它是私有的,且不应暴露。
return [
            'username' => $this->username,
            'email' => $this->email,
        ];
    }

    // 一个私有方法,用于内部处理密码
private function setPassword($password) {
        // 使用 password_hash 安全地哈希密码(后续章节详解)
$this->passwordHash = password_hash($password, PASSWORD_DEFAULT);
        echo "密码已设置并哈希。<br>";
    }

    // 一个验证密码的公共方法
public function verifyPassword($password) {
        return password_verify($password, $this->passwordHash);
    }
}

// 4. 使用类创建对象(实例化)
$user1 = new User(); // 创建一个 User 对象
$user2 = new User(); // 创建另一个 User 对象
// 5. 调用对象的方法来操作对象
$user1->setInfo('张三', 'zhangsan@example.com', 'mySecret123');
$user2->setInfo('李四', 'lisi@example.com', 'anotherSecret456');

// 6. 访问对象的属性(public属性可以直接访问)
echo "用户1的用户名是: " . $user1->username . "<br>"; // 输出:张三
// 7. 尝试访问 protected 和 private 属性(会导致错误)
// echo $user1->email; // Fatal error: Cannot access protected property
// echo $user1->passwordHash; // Fatal error: Cannot access private property

// 8. 通过公共方法安全地获取信息
print_r($user1->getInfo());
echo "<br>";
// 输出: Array ( [username] => 张三 [email] => zhangsan@example.com )

// 9. 验证密码
$isCorrect = $user1->verifyPassword('mySecret123');
echo $isCorrect ? '密码正确<br>' : '密码错误<br>'; // 输出:密码正确
$isCorrect = $user1->verifyPassword('wrongPassword');
echo $isCorrect ? '密码正确<br>' : '密码错误<br>'; // 输出:密码错误
?>

示例2:构造函数、析构函数与封装实践

php 复制代码
<?php
class Product {
    // 私有属性,通过公共方法访问(封装)
private $name;
    private $price;
    private $category;

    // 构造函数:在创建对象时自动调用,用于初始化
public function __construct($name, $price, $category = '未分类') {
        $this->setName($name);
        $this->setPrice($price);
        $this->category = $category;
        echo "产品【{$this->name}】对象已创建。<br>";
    }

    // 析构函数:在对象销毁时自动调用
public function __destruct() {
        echo "产品【{$this->name}】对象即将被销毁。<br>";
    }

    // Getter 方法:获取私有属性 name
    public function getName() {
        return $this->name;
    }

    // Setter 方法:设置私有属性 name,并可加入验证逻辑
public function setName($name) {
        if (empty(trim($name))) {
            throw new InvalidArgumentException('产品名不能为空。');
        }
        $this->name = htmlspecialchars(trim($name), ENT_QUOTES, 'UTF-8'); // 简单XSS防护
}

    // Getter 和 Setter 方法 for price
    public function getPrice() {
        return number_format($this->price, 2); // 格式化输出
}
    public function setPrice($price) {
        if (!is_numeric($price) || $price < 0) {
            throw new InvalidArgumentException('价格必须是非负数字。');
        }
        $this->price = floatval($price);
    }

    // 一个业务方法
public function getDescription() {
        return "产品:{$this->name}, 价格:¥{$this->getPrice()}, 分类:{$this->category}";
    }
}

// 测试代码
echo "脚本开始...<br>";
try {
    $product1 = new Product('PHP教程书', 59.80, '图书');
    echo $product1->getDescription() . "<br>"; // 输出:产品:PHP教程书, 价格:¥59.80, 分类:图书
// 使用 setter 修改属性
$product1->setPrice(55.00);
    echo "打折后价格: " . $product1->getPrice() . "<br>"; // 输出:55.00

    // 尝试设置非法值(会抛出异常)
// $product1->setPrice(-10);

    // 第二个对象,生命周期更短
$product2 = new Product('马克杯', 25.50);
    unset($product2); // 手动销毁对象,会立即触发析构函数
// 输出:产品【马克杯】对象即将被销毁。
} catch (Exception $e) {
    echo '错误: ' . $e->getMessage() . '<br>';
}
echo "脚本结束...<br>";
// 脚本结束时,$product1 的析构函数也会被自动调用
?>

预期输出:

复制代码
脚本开始...
产品【PHP教程书】对象已创建。
产品:PHP教程书, 价格:¥59.80, 分类:图书
打折后价格: 55.00
产品【马克杯】对象已创建。
产品【马克杯】对象即将被销毁。
脚本结束...
产品【PHP教程书】对象即将被销毁。

示例3:继承、方法重写与 parent 关键字

php 复制代码
<?php
// 父类:普通员工
class Employee {
    protected $name;
    protected $baseSalary = 3000; // 基本工资
public function __construct($name) {
        $this->name = $name;
    }

    public function work() {
        return "{$this->name} 正在处理日常工作。<br>";
    }

    // 计算月薪的方法
public function calculateSalary() {
        return $this->baseSalary;
    }

    public function getInfo() {
        return "员工:{$this->name}, 月薪:{$this->calculateSalary()}<br>";
    }
}

// 子类1:经理,继承自 Employee
class Manager extends Employee {
    private $bonus = 2000; // 经理特有奖金
// 重写(Override)父类的 calculateSalary 方法
public function calculateSalary() {
        // 使用 parent 调用父类的方法,然后加上奖金
$base = parent::calculateSalary();
        return $base + $this->bonus;
    }

    // 经理特有的方法
public function holdMeeting() {
        return "{$this->name} 经理正在主持会议。<br>";
    }

    // 重写 work 方法,展现多态
public function work() {
        return "{$this->name} 正在统筹部门工作和分配任务。<br>";
    }
}

// 子类2:销售,继承自 Employee
class Sales extends Employee {
    private $commissionRate = 0.1; // 佣金比例
private $salesAmount = 0; // 销售额
public function setSalesAmount($amount) {
        $this->salesAmount = $amount;
    }

    // 重写 calculateSalary 方法
public function calculateSalary() {
        $commission = $this->salesAmount * $this->commissionRate;
        return $this->baseSalary + $commission;
    }

    // 销售特有的方法
public function visitClient() {
        return "{$this->name} 销售正在拜访客户。<br>";
    }
}

// 测试代码
$emp = new Employee('小王');
echo $emp->work(); // 输出:小王 正在处理日常工作。
echo $emp->getInfo(); // 输出:员工:小王, 月薪:3000

$mgr = new Manager('张总');
echo $mgr->work(); // 输出:张总 正在统筹部门工作和分配任务。(调用了子类重写的方法)
echo $mgr->holdMeeting(); // 输出:张总 经理正在主持会议。
echo $mgr->getInfo(); // 输出:员工:张总, 月薪:5000(3000+2000)
$sal = new Sales('小李');
$sal->setSalesAmount(50000);
echo $sal->visitClient(); // 输出:小李 销售正在拜访客户。
echo $sal->getInfo(); // 输出:员工:小李, 月薪:8000(3000 + 50000*0.1)
// 多态的体现:将不同子类对象放入父类类型的数组中
$employees = [$emp, $mgr, $sal];
echo "<hr>所有员工汇报工作:<br>";
foreach ($employees as $employee) {
    // 虽然 $employee 在数组中声明为 Employee 类型,但实际调用的是各自子类重写的 work() 方法
echo $employee->work();
}
?>

示例4:抽象类与接口

php 复制代码
<?php
// 接口:定义"可支付"的契约
interface Payable {
    public function getPaymentAmount(); // 必须实现的方法
}

// 接口:定义"可打印信息"的契约
interface Printable {
    public function printInfo();
}

// 抽象类:为"文档"提供一个基础框架
abstract class Document {
    protected $title;
    protected $author;

    public function __construct($title, $author) {
        $this->title = $title;
        $this->author = $author;
    }

    // 抽象方法:子类必须实现如何获取内容
abstract public function getContent();

    // 普通方法:所有文档共享的打印标题的方法
public function printHeader() {
        echo "标题: {$this->title} <br>";
        echo "作者: {$this->author} <br>";
    }
}

// 具体类:电子书,继承自 Document,并实现两个接口
class EBook extends Document implements Payable, Printable {
    private $price;
    private $fileSize;

    public function __construct($title, $author, $price, $fileSize) {
        parent::__construct($title, $author); // 调用父类构造函数
$this->price = $price;
        $this->fileSize = $fileSize;
    }

    // 实现抽象方法
public function getContent() {
        return "这是一本名为《{$this->title}》的电子书,大小 {$this->fileSize}MB。<br>";
    }

    // 实现 Payable 接口的方法
public function getPaymentAmount() {
        return $this->price;
    }

    // 实现 Printable 接口的方法
public function printInfo() {
        $this->printHeader(); // 调用继承来的方法
echo "价格: ¥" . $this->getPaymentAmount() . "<br>";
        echo "内容摘要: " . $this->getContent();
    }
}

// 具体类:发票,只实现 Payable 接口(它不是一个Document)
class Invoice implements Payable {
    private $invoiceNumber;
    private $amount;

    public function __construct($number, $amount) {
        $this->invoiceNumber = $number;
        $this->amount = $amount;
    }

    // 实现 Payable 接口的方法
public function getPaymentAmount() {
        return $this->amount;
    }

    public function getDetails() {
        return "发票号:{$this->invoiceNumber}, 金额:¥{$this->amount}<br>";
    }
}

// 测试代码
$ebook = new EBook('零基础学PHP', '某作者', 68.5, 12.3);
$ebook->printInfo();
echo "<hr>";

$invoice = new Invoice('INV20231001', 2000);
echo $invoice->getDetails();
echo "应付金额:¥" . $invoice->getPaymentAmount() . "<br>";

// 类型提示展示接口的作用
function processPayment(Payable $item) {
    echo "正在处理支付,金额:¥" . $item->getPaymentAmount() . "<br>";
}

processPayment($ebook); // 可以传入 EBook
processPayment($invoice); // 也可以传入 Invoice
// processPayment(new Document(...)); // 错误!Document 没有实现 Payable 接口
?>

示例5:魔术方法 __toString 和简单的 __get/__set

php 复制代码
<?php
class Config {
    private $settings = [];

    // 当尝试设置一个不存在的属性时调用
public function __set($name, $value) {
        $this->settings[$name] = $value;
        echo "动态设置了属性 '{$name}' 的值为 '{$value}'。<br>";
    }

    // 当尝试获取一个不存在的属性时调用
public function __get($name) {
        if (array_key_exists($name, $this->settings)) {
            return $this->settings[$name];
        } else {
            return "属性 '{$name}' 不存在。<br>";
        }
    }

    // 当对象被当作字符串使用时调用
public function __toString() {
        return "当前配置信息: " . json_encode($this->settings, JSON_UNESCAPED_UNICODE);
    }
}

class SimpleUser {
    private $data = ['name' => '访客', 'role' => 'guest'];

    public function __toString() {
        return "用户[名称:{$this->data['name']}, 角色:{$this->data['role']}]";
    }
}

// 测试 __set 和 __get
$config = new Config();
$config->siteName = '我的PHP站'; // 触发 __set
$config->adminEmail = 'admin@example.com'; // 触发 __set

echo "站点名称: " . $config->siteName . "<br>"; // 触发 __get,输出:我的PHP站
echo "不存在的属性: " . $config->nonExistent . "<br>"; // 触发 __get,输出:属性 'nonExistent' 不存在。
echo $config . "<br><br>"; // 触发 __toString

// 测试 __toString
$user = new SimpleUser();
echo $user; // 输出: 用户[名称:访客, 角色:guest]
// 等价于 echo $user->__toString();
?>

实战项目:面向对象的用户与权限管理系统(V1.0)

项目需求分析和技术方案

  • 目标:运用本章所学的OOP知识,构建一个结构清晰的用户系统模型。为后续章节集成数据库和会话控制打下基础。
  • 核心实体
  1. User(用户基类):包含所有用户的公共属性(ID、用户名、邮箱、密码哈希)和行为(登录验证、信息获取)。
  2. Admin(管理员类) :继承自User,添加管理员特有的属性(如权限级别)和行为(如管理用户)。
  3. Guest(访客类) :继承自User,代表未登录用户,权限最低。
  • 技术要求
  • 严格使用private/protected修饰符进行封装。
  • User类使用构造函数初始化。
  • Admin类重写User类的某些方法以体现多态。
  • User类实现__toString()魔术方法,便于调试输出。

分步骤实现代码和详细说明

步骤1:创建基类 User

php 复制代码
<?php
// User.class.php
class User {
    // 私有属性,外部不能直接访问,实现封装
private $id;
    private $username;
    private $email;
    private $passwordHash;
    protected $role = 'user'; // 角色,protected允许子类访问和修改
// 构造函数:创建用户对象时必须提供基本信息
public function __construct($id, $username, $email, $password) {
        $this->id = $id;
        $this->setUsername($username);
        $this->setEmail($email);
        $this->setPassword($password); // 通过方法设置,以便进行哈希处理
echo "用户 '{$this->username}' 对象已创建。<br>";
    }

    // Getter 方法
public function getId() { return $this->id; }
    public function getUsername() { return $this->username; }
    public function getEmail() { return $this->email; }
    public function getRole() { return $this->role; }

    // Setter 方法(加入简单验证)
public function setUsername($username) {
        if (strlen(trim($username)) < 3) {
            throw new InvalidArgumentException('用户名至少3个字符。');
        }
        $this->username = trim($username);
    }

    public function setEmail($email) {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('邮箱格式不正确。');
        }
        $this->email = $email;
    }

    // 密码设置和验证
private function setPassword($password) {
        if (strlen($password) < 6) {
            throw new InvalidArgumentException('密码至少6位。');
        }
        $this->passwordHash = password_hash($password, PASSWORD_DEFAULT);
    }

    public function verifyPassword($password) {
        return password_verify($password, $this->passwordHash);
    }

    // 用户行为
public function login($inputPassword) {
        if ($this->verifyPassword($inputPassword)) {
            return "用户 '{$this->username}' 登录成功。<br>";
        } else {
            return "密码错误,登录失败。<br>";
        }
    }

    public function displayProfile() {
        return "ID: {$this->id}, 用户名: {$this->username}, 邮箱: {$this->email}, 角色: {$this->role}<br>";
    }

    // 魔术方法:当对象被当作字符串时
public function __toString() {
        return "[User] {$this->username} ({$this->email})";
    }
}
?>

步骤2:创建子类 Admin

php 复制代码
<?php
// Admin.class.php
require_once 'User.class.php';

class Admin extends User {
    private $permissionLevel; // 管理员特有属性
// 构造函数,调用父类构造函数并初始化特有属性
public function __construct($id, $username, $email, $password, $permissionLevel = 1) {
        parent::__construct($id, $username, $email, $password); // 必须先调用父类构造
$this->role = 'admin'; // 修改继承来的 protected 属性
$this->setPermissionLevel($permissionLevel);
    }

    public function getPermissionLevel() { return $this->permissionLevel; }
    public function setPermissionLevel($level) {
        if (!is_int($level) || $level < 1) {
            throw new InvalidArgumentException('权限级别必须为正整数。');
        }
        $this->permissionLevel = $level;
    }

    // 重写父类方法,展现多态
public function displayProfile() {
        $baseProfile = parent::displayProfile(); // 调用父类方法获取基础信息
return $baseProfile . "权限级别: {$this->permissionLevel}<br>";
    }

    // 管理员特有行为
public function manageUsers() {
        return "管理员 '{$this->getUsername()}' 正在管理用户列表。<br>";
    }

    // 重写 __toString
    public function __toString() {
        return "[Admin] {$this->getUsername()} (Lv.{$this->permissionLevel})";
    }
}
?>

步骤3:创建子类 Guest

php 复制代码
<?php
// Guest.class.php
require_once 'User.class.php';

class Guest extends User {
    // 访客的ID、用户名等是固定的或临时的
public function __construct() {
        // 调用父类构造,传入访客的默认信息
parent::__construct(0, 'Guest', 'guest@example.com', ''); // 密码为空或不验证
$this->role = 'guest';
    }

    // 访客无法登录
public function login($inputPassword) {
        return "访客无需登录,或请先注册。<br>";
    }

    // 重写 __toString
    public function __toString() {
        return "[Guest]";
    }
}
?>

步骤4:项目测试与演示

php 复制代码
<?php
// test_system.php
require_once 'User.class.php';
require_once 'Admin.class.php';
require_once 'Guest.class.php';

echo "<h3>用户系统测试</h3>";

try {
    // 1. 创建普通用户
$user1 = new User(1001, 'alice_wonder', 'alice@example.com', 'AlicePass123');
    echo $user1->displayProfile();
    echo $user1->login('AlicePass123');
    echo $user1->login('WrongPass');
    echo "字符串表示: " . $user1 . "<br><br>"; // 触发 __toString

    // 2. 创建管理员
$admin1 = new Admin(2001, 'sys_admin', 'admin@example.com', 'Admin@Secure456', 3);
    echo $admin1->displayProfile(); // 调用的是 Admin 类重写的方法
echo $admin1->login('Admin@Secure456');
    echo $admin1->manageUsers();
    echo "字符串表示: " . $admin1 . "<br><br>";

    // 3. 创建访客
$guest1 = new Guest();
    echo $guest1->displayProfile();
    echo $guest1->login('any'); // 调用 Guest 类重写的方法
echo "字符串表示: " . $guest1 . "<br><br>";

    // 4. 多态演示:将不同类型的用户放入数组
$users = [$user1, $admin1, $guest1];
    echo "<hr>所有用户自我介绍:<br>";
    foreach ($users as $u) {
        // 这里 $u 的类型是 User,但实际调用的是各自子类重写的 __toString 方法
echo $u . "<br>";
    }

} catch (InvalidArgumentException $e) {
    echo "创建用户时出错: " . $e->getMessage() . "<br>";
} catch (Exception $e) {
    echo "发生未知错误: " . $e->getMessage() . "<br>";
}
?>

项目部署指南

  1. 在你的PHP开发环境(如XAMPP的htdocs目录)中创建一个新文件夹,例如chapter01_project
  2. 将上述四个PHP文件(User.class.php, Admin.class.php, Guest.class.php, test_system.php)保存到该文件夹中。
  3. 通过浏览器访问http:// localhost/chapter01_project/test_system.php
  4. 观察页面输出,理解对象创建、方法调用和多态的效果。

项目扩展和优化建议

  1. 添加更多属性 :为User类添加registrationDate(注册日期)、lastLogin(最后登录)等属性。
  2. 实现更多子类 :例如Editor(编辑)类,继承User,拥有发布、编辑文章的权限,但没有管理员的管理权限。
  3. 引入接口 :定义一个Authenticatable(可认证)接口,包含login()verifyPassword()方法,让UserAdmin类实现它。再定义一个Authorizable(可授权)接口,包含hasPermission($permission)方法,让AdminEditor类实现。
  4. 完善验证 :在setEmail等方法中使用更严格的正则表达式验证。

最佳实践

1. 行业标准和开发规范(PSR)

  • PSR-1 基础编码规范 :类名使用StudlyCaps(大驼峰),如UserManager;方法名使用camelCase(小驼峰),如getUserName();属性名同样使用camelCase
  • PSR-12 编码风格扩展规范:详细规定了代码缩进(4个空格)、大括号位置、关键字大小写等。遵循统一的代码风格能极大提高团队协作效率和代码可读性。
  • 一个文件只定义一个类 :并将文件名命名为类名.class.php或直接是类名.php

2. 常见错误和避坑指南

  • 忘记使用 $this :在类的方法内部访问属性或调用其他方法时,必须使用$this->。直接写$username访问的是局部变量或全局变量,而不是对象的属性。
  • 混淆类与对象User是一个类,$user = new User() 中的 $user 才是对象。静态属性和方法属于类本身,而非对象。
  • 过度使用继承("is-a"关系) :继承应严格用于"是一个"的关系(如Admin是一个User)。对于"有一个"或"能做什么"的关系,应考虑使用组合或接口。例如,User有一个Profile(资料),应该将Profile作为User的一个属性对象,而不是让User继承Profile
  • 忽略访问控制 :将所有属性都设为public是懒惰和危险的做法。务必思考每个成员的访问权限,优先使用private,然后按需放宽至protected,最后才是public

3. 性能优化技巧

  • 谨慎使用魔术方法__get__set__call 等魔术方法因为涉及动态解析,其性能开销远高于直接访问属性或调用方法。在性能关键的代码段中应避免过度使用。
  • 惰性加载(Lazy Loading):如果对象的某个属性(如关联的大对象)创建或获取成本很高,可以考虑在第一次访问时才进行初始化。
php 复制代码
    class User {
        private $profile = null;
        public function getProfile() {
            if ($this->profile === null) {
                $this->profile = new Profile($this->id); // 仅在使用时才创建
}
            return $this->profile;
        }
    }

4. 安全性考虑和建议(为后续章节铺垫)

虽然本章重点是OOP语法,但设计类时就必须将安全思想融入其中:

  • 封装是安全的第一道防线 :通过将敏感数据(如$passwordHash)设为private,防止它们被外部代码意外修改或直接读取。
  • 在 Setter 方法中进行输入验证与过滤 :如示例所示,在setEmail()setUsername()中加入格式验证。这是防御XSS、SQL注入等攻击的起点。后续章节我们会使用filter_var和预处理语句进行更严格的防护。
  • 密码永远不存储明文 :示例中使用了password_hash()password_verify()。这是PHP官方推荐的、安全的密码处理方式。绝对不要 使用md5()sha1()来哈希密码。
  • 面向对象的安全设计 :通过将权限检查(如$permissionLevel)和行为(如manageUsers())封装在对象内部,可以更集中地管理安全逻辑。例如,在Admin类的manageUsers()方法开始处,可以检查$permissionLevel是否足够高。

练习题与挑战

基础练习题

  1. 【难度:★☆☆】 类与对象创建
    • 题目 :请定义一个名为 Book 的类,它应包含以下私有 属性:title (书名), author (作者), price (价格)。为该类创建:
  • 一个公共的构造函数,用于初始化这三个属性。
  • 公共的 Getter 方法 (getTitle, getAuthor, getPrice)。
  • 一个公共方法 getInfo(),返回例如 "《书名》 by 作者,价格:XX元" 的字符串。
  • 要求 :实例化两个不同的 Book 对象,并调用它们的 getInfo() 方法输出信息。
  • 提示:注意构造函数参数与属性赋值的对应关系。
  1. 【难度:★☆☆】 封装与访问控制
    • 题目 :接上题,为 Book 类添加一个私有 属性 discount (折扣,范围0-1,例如0.8代表8折)。添加 setDiscount($rate) 方法,该方法应检查传入的折扣率是否在0到1之间,如果不是则抛出异常。修改 getPrice() 方法,使其返回应用折扣后的价格(原价 * 折扣)。
  • 要求 :创建一个 Book 对象,设置一个合法的折扣和一个非法的折扣(使用try-catch捕获异常),并分别输出打折前后的价格。
  • 提示 :在 setDiscount 中使用 if ($rate < 0 || $rate > 1) { ... } 进行判断。

进阶练习题

  1. 【难度:★★☆】 继承与方法重写
    • 题目 :基于第1题的 Book 类,创建一个子类 EBook (电子书)。EBook 类新增一个私有 属性 fileSize (文件大小,单位MB)。重写父类的 getInfo() 方法,在返回的信息末尾加上 ",文件大小:XX MB"。同时,EBookgetPrice() 方法默认打9折(即价格是原价的90%)。
  • 要求 :分别实例化一个 Book 对象和一个 EBook 对象,设置相同的书名、作者和原价,然后调用它们的 getInfo()getPrice() 方法,观察输出差异。
  • 提示 :在 EBook 的构造函数中调用 parent::__construct(),并在重写的 getPrice() 中使用 parent::getPrice() * 0.9。
  1. 【难度:★★☆】 多态与抽象类
    • 题目 :定义一个抽象类 Shape (形状),它有一个受保护 的属性 name (形状名) 和一个抽象方法 calculateArea() (计算面积)。创建两个子类 Circle (圆形) 和 Rectangle (矩形)。Circle 类需要属性 $radius (半径),面积公式为 π * r²。Rectangle 类需要属性 $width$height,面积公式为 宽 * 高。为每个子类实现 calculateArea() 方法。
  • 要求 :创建一个 Shape 类型的数组,放入一个 Circle 对象和一个 Rectangle 对象。遍历数组,调用每个元素的 calculateArea() 方法并输出结果。
  • 提示 :抽象类不能实例化。使用 M_PI 常量获取π的值。

综合挑战题

  1. 【难度:★★★】 综合应用:简单的购物车系统
    • 题目:设计一个面向对象的微型购物车系统。
  • Product 类:属性包括 id, name, price。有构造函数和Getter。
  • CartItem 类:代表购物车中的一项。属性包括 product (一个 Product 对象) 和 quantity (数量)。有方法 getTotalPrice() 返回该项总价(单价*数量)。
  • ShoppingCart 类:代表整个购物车。属性是一个 CartItem 对象的数组 $items。方法包括:
  • addItem(Product $product, $quantity=1):添加商品。如果购物车中已有该商品,则增加其数量;否则新建一个 CartItem 加入数组。
  • removeItem($productId):根据商品ID移除购物车中的对应项。
  • getTotal():计算并返回购物车内所有商品的总价。
  • displayCart():以清晰格式列出购物车中所有商品(名称、单价、数量、小计)和总价。
  • 要求 :编写测试代码,创建至少2种商品,添加到购物车中,进行增加数量、移除商品等操作,并最终调用 displayCart() 输出购物车详情。
  • 提示 :在 ShoppingCart::addItem 中遍历 $this->items,检查 $item->product->getId() 是否等于要添加商品的ID。

章节总结

本章重点知识回顾

本章我们系统地学习了PHP中面向对象编程(OOP)的核心知识,完成了从过程式思维到对象式思维的跨越:

  1. 核心概念 :理解了 作为蓝图和对象 作为实例的关系,掌握了使用new关键字实例化对象。
  2. 封装 :通过publicprotectedprivate三个访问控制修饰符,学会了如何隐藏对象的内部实现细节,并通过公共的Getter/Setter方法提供安全可控的访问接口。$this关键字是对象内部自我引用的关键。
  3. 对象生命周期 :使用构造函数__construct()进行对象初始化,使用析构函数__destruct()进行资源清理。
  4. 继承 :使用extends关键字实现代码复用,子类继承父类的属性和方法。掌握了方法重写 来修改继承的行为,并使用parent::调用父类方法。
  5. 多态:理解了子类对象可被视作父类类型,并通过重写的方法表现出不同行为,这增强了程序的灵活性。
  6. 抽象与接口 :学习了abstract class用于定义部分实现的模板和强制规范,学习了interface用于定义纯粹的行为契约。它们是实现高级多态和松耦合设计的重要工具。
  7. 魔术方法 :初步接触了__toString__get__set等魔术方法,它们为类提供了特殊的交互能力。

技能掌握要求

完成本章学习后,你应当能够:

  • 独立设计并实现一个符合封装要求的类,正确使用访问控制修饰符。
  • 根据"is-a"关系,使用继承来扩展已有类的功能。
  • 在适当场景下,运用抽象类或接口来设计程序结构。
  • 阅读并理解基于OOP编写的PHP代码。

进一步学习建议

  1. 巩固练习:务必亲手完成本章的练习题和实战项目,OOP的理解需要通过大量编码来内化。
  2. 阅读优秀代码:尝试在GitHub上寻找一些小型、结构清晰的PHP项目(如一个简单的博客引擎),阅读其源码,学习别人是如何组织类和设计对象间关系的。
  3. 预习下一章 :下一章我们将学习PDO ,这是与数据库交互的现代化、安全的方式。思考如何将本章创建的UserAdmin类与数据库表关联起来(例如,将属性值保存到数据库,或从数据库读取数据来创建对象)。这将是"对象关系映射(ORM)"思想的雏形。
  4. 探索更多OOP特性 :在掌握本章内容后,你可以自行了解静态属性和方法后期静态绑定Traits 等更进阶的OOP特性,它们将在你使用主流框架时经常遇到。
    恭喜你完成了PHP编程中至关重要的一章!面向对象的思想将伴随你整个开发生涯。准备好,在下一章中,我们将让这些"对象"与数据库安全地对话。
相关推荐
老华带你飞3 小时前
旅游|基于Java旅游信息推荐系统(源码+数据库+文档)
java·开发语言·数据库·vue.js·spring boot·后端·旅游
Jane-6667773 小时前
C语言——表达式、语句、函数
c语言·开发语言·算法
arron88993 小时前
C# 项目源码进行全面的技术架构和调用逻辑分析。以下是系统性的技术方案
开发语言·架构·c#
88号技师4 小时前
【2025年1区SCI】最新信号分解方法-JMD的参数优化:15种适应度函数-matlab代码
开发语言·matlab·故障诊断·信号分解
zmzb01034 小时前
C++课后习题训练记录Day44
开发语言·c++
smile_Iris4 小时前
Day 30 函数定义与参数
开发语言·python
老华带你飞4 小时前
医院挂号|基于Java医院挂号管理系统(源码+数据库+文档)
java·开发语言·数据库·vue.js·spring boot
豐儀麟阁贵4 小时前
9.6使用正则表达式
java·开发语言·数据库·mysql
kgduu4 小时前
go ethreum之Trie
开发语言·后端·golang