第 8 课:面向对象基础
一、学习目标
- 理解面向对象编程(OOP)的核心思想(封装、继承、多态)
- 掌握 PHP 类与对象的定义、实例化及基本使用
- 熟练运用 PHP7 类型声明与 PHP8 构造器属性提升简化类设计
- 能够基于 OOP 思想编写可维护的基础类(如用户类、商品类)
二、核心知识点
(一)OOP 核心概念与优势
-
三大核心特性
- 封装:将数据(属性)和操作数据的方法封装在类中,对外隐藏实现细节
- 继承:子类可继承父类的属性和方法,减少代码重复
- 多态:不同类的对象可通过相同的方法名实现不同的功能
-
OOP vs 面向过程(POP)
- 面向过程:以 “步骤” 为核心,代码按执行顺序编写(适合简单脚本)
- 面向对象:以 “对象” 为核心,将功能模块化(适合复杂项目,便于维护和扩展)
示例(POP vs OOP 对比:用户信息展示):
<?php // 1. 面向过程(POP)写法 $user_name = "张三"; $user_age = 25; function showUserInfo($name, $age) { echo "姓名:{$name},年龄:{$age}"; } showUserInfo($user_name, $user_age); // 2. 面向对象(OOP)写法 class User { // 封装属性 public $name; public $age; // 封装方法 public function showInfo() { echo "姓名:{$this->name},年龄:{$this->age}"; } } $user = new User(); // 实例化对象 $user->name = "张三"; $user->age = 25; $user->showInfo(); // 调用对象方法 ?>
(二)类与对象的基础语法
-
类的定义
- 关键字:
class - 结构:包含属性(变量)和方法(函数)
- 命名规范:首字母大写,采用驼峰命名法(如
User、ProductManager)
示例(基础类定义):
<?php // 定义User类 class User { // 1. 属性(类的变量) public $id; // 公共属性(可在类外部访问) public $name; private $password; // 私有属性(仅能在类内部访问) // 2. 方法(类的函数) // 构造函数:对象实例化时自动调用(PHP7/8兼容写法) public function __construct(int $id, string $name, string $password) { $this->id = $id; // $this表示当前对象 $this->name = $name; $this->password = $this->encryptPassword($password); // 调用类内部方法 } // 公共方法:显示用户信息(隐藏密码) public function showInfo(): string { return "ID:{$this->id},姓名:{$this->name}"; } // 私有方法:密码加密(仅类内部使用) private function encryptPassword(string $password): string { return password_hash($password, PASSWORD_DEFAULT); // PHP内置密码哈希函数 } // 公共方法:验证密码 public function verifyPassword(string $input_password): bool { return password_verify($input_password, $this->password); } } ?> - 关键字:
-
对象的实例化与使用
- 实例化:用
new关键字创建对象(如$user = new User(1, "张三", "123456");) - 访问属性:
对象->属性名(如$user->name,私有属性不可直接访问) - 调用方法:
对象->方法名()(如$user->showInfo())
示例(对象使用):
<?php // 实例化User对象(调用构造函数) $user = new User(1, "张三", "123456"); // 访问公共属性 echo "用户名:{$user->name}<br>"; // 输出:用户名:张三 // echo $user->password; // 错误:私有属性不可在类外部访问 // 调用公共方法 echo $user->showInfo() . "<br>"; // 输出:ID:1,姓名:张三 // 验证密码 $input_pwd1 = "123456"; $input_pwd2 = "wrong_pwd"; echo "密码{$input_pwd1}验证:" . ($user->verifyPassword($input_pwd1) ? "成功" : "失败") . "<br>"; // 成功 echo "密码{$input_pwd2}验证:" . ($user->verifyPassword($input_pwd2) ? "成功" : "失败") . "<br>"; // 失败 ?> - 实例化:用
-
访问控制修饰符
- public:公共(默认),可在类内部、子类、类外部访问
- private:私有,仅可在类内部访问
- protected:受保护,可在类内部、子类访问,不可在类外部访问
对比示例:
<?php class ParentClass { public $public_attr = "公共属性"; protected $protected_attr = "受保护属性"; private $private_attr = "私有属性"; public function showAllAttr() { // 类内部可访问所有属性 echo "内部访问:{$this->public_attr},{$this->protected_attr},{$this->private_attr}<br>"; } } class ChildClass extends ParentClass { // 子类继承父类 public function showParentAttr() { // 子类可访问父类的public和protected属性,不可访问private属性 echo "子类访问:{$this->public_attr},{$this->protected_attr}<br>"; // echo $this->private_attr; // 错误:子类不可访问父类私有属性 } } // 实例化父类 $parent = new ParentClass(); $parent->showAllAttr(); // 输出:内部访问:公共属性,受保护属性,私有属性 echo "外部访问父类公共属性:{$parent->public_attr}<br>"; // echo $parent->protected_attr; // 错误:外部不可访问受保护属性 // echo $parent->private_attr; // 错误:外部不可访问私有属性 // 实例化子类 $child = new ChildClass(); $child->showParentAttr(); // 输出:子类访问:公共属性,受保护属性 echo "外部访问子类公共属性(继承自父类):{$child->public_attr}<br>"; ?>
(三)PHP7/8 OOP 新特性
-
PHP7 标量类型声明与返回值类型声明
- 可在类的方法参数和返回值前指定标量类型(
int、string、bool等) - 配合
declare(strict_types=1)开启严格模式,强制类型匹配(避免隐式转换)
示例:
<?php declare(strict_types=1); // 严格模式(必须放在文件第一行) class Product { public $id; public $name; public $price; // 构造函数参数指定标量类型 public function __construct(int $id, string $name, float $price) { $this->id = $id; $this->name = $name; $this->price = $price; } // 方法返回值指定类型(float) public function getDiscountPrice(float $discount_rate): float { if ($discount_rate < 0 || $discount_rate > 1) { throw new InvalidArgumentException("折扣率必须在0-1之间"); } return $this->price * (1 - $discount_rate); } } // 正确实例化(类型匹配) $product = new Product(1, "PHP编程书籍", 59.9); echo "原价:{$product->price}元<br>"; echo "8折后价格:" . $product->getDiscountPrice(0.2) . "元<br>"; // 输出:47.92元 // 错误实例化(类型不匹配,严格模式下报错) // $product2 = new Product("1", "书籍", "59.9"); // 错误:id必须为int,price必须为float ?> - 可在类的方法参数和返回值前指定标量类型(
-
PHP8 构造器属性提升
- 革命性简化:在构造函数参数前直接添加访问修饰符,自动生成类属性
- 无需手动定义属性和赋值,大幅减少重复代码
示例(PHP8 简化写法 vs 传统写法):
<?php // 1. 传统写法(PHP7及之前) class UserOld { public $id; public $name; private $email; public function __construct(int $id, string $name, string $email) { $this->id = $id; $this->name = $name; $this->email = $email; } } // 2. PHP8构造器属性提升(简化写法) class UserNew { // 构造函数参数直接定义属性,自动完成赋值 public function __construct( public int $id, public string $name, private string $email ) { // 无需手动赋值,PHP8自动处理 } // 访问私有属性email的方法 public function getEmail(): string { return $this->email; } } // 两种写法的使用方式一致 $user_old = new UserOld(1, "张三", "zhangsan@example.com"); $user_new = new UserNew(2, "李四", "lisi@example.com"); echo $user_old->name . "<br>"; // 张三 echo $user_new->name . "<br>"; // 李四 echo $user_new->getEmail() . "<br>"; // lisi@example.com ?> -
PHP8 只读属性(Readonly Properties)
- 关键字:
readonly(仅 PHP8.1 + 支持) - 作用:属性初始化后不可修改,确保数据不可变(适合常量数据)
- 注意:只读属性必须是类型化属性,且不能有默认值(需在构造函数中初始化)
示例:
<?php class Order { // 只读属性:订单ID初始化后不可修改 public readonly int $order_id; public readonly string $create_time; public float $amount; // 普通属性:可修改 public function __construct(int $order_id, float $amount) { $this->order_id = $order_id; $this->create_time = date('Y-m-d H:i:s'); $this->amount = $amount; } } $order = new Order(1001, 99.9); echo "订单ID:{$order->order_id}<br>"; echo "创建时间:{$order->create_time}<br>"; echo "订单金额:{$order->amount}元<br>"; // 修改普通属性(允许) $order->amount = 129.9; echo "修改后金额:{$order->amount}元<br>"; // 修改只读属性(错误,PHP8.1+报错) // $order->order_id = 1002; // 错误:Cannot modify readonly property Order::$order_id ?> - 关键字:
(四)类的特殊方法
-
构造函数(
__construct())- 对象实例化时自动调用,用于初始化属性
- 支持默认参数和可变参数(PHP7+)
- PHP8 支持构造器属性提升(见上文)
-
析构函数(
__destruct())- 对象销毁时自动调用(如脚本结束、
unset()对象) - 常用于释放资源(如关闭文件句柄、数据库连接)
示例:
<?php class FileLogger { private $handle; // 构造函数:打开日志文件 public function __construct(string $log_file) { $this->handle = fopen($log_file, 'a'); if (!$this->handle) { throw new Exception("无法打开日志文件:{$log_file}"); } echo "日志文件已打开<br>"; } // 写入日志 public function writeLog(string $message): void { $log_line = date('Y-m-d H:i:s') . " - {$message}\n"; fwrite($this->handle, $log_line); } // 析构函数:关闭文件句柄 public function __destruct() { if ($this->handle) { fclose($this->handle); echo "日志文件已关闭<br>"; } } } // 实例化对象(调用构造函数) $logger = new FileLogger('app.log'); $logger->writeLog("用户登录成功"); // 手动销毁对象(调用析构函数) unset($logger); // 或脚本结束时自动销毁 ?> - 对象销毁时自动调用(如脚本结束、
-
魔术方法(Magic Methods)
__get()/__set():访问私有 / 受保护属性时自动调用(用于实现属性的 “getter/setter”)__call():调用不存在的方法时自动调用__toString():对象被当作字符串时自动调用(如echo $object)
示例(
__get()/__set()实现属性封装):<?php class User { private $name; private $age; // 访问私有属性时调用 public function __get(string $property) { if (property_exists($this, $property)) { // 可添加访问控制逻辑(如只允许管理员访问age) if ($property === 'age' && $_GET['role'] !== 'admin') { return "无权访问年龄"; } return $this->$property; } throw new InvalidArgumentException("属性不存在:{$property}"); } // 设置私有属性时调用 public function __set(string $property, $value) { if (property_exists($this, $property)) { // 可添加数据验证逻辑 if ($property === 'age' && ($value < 0 || $value > 120)) { throw new InvalidArgumentException("年龄必须在0-120之间"); } $this->$property = $value; } else { throw new InvalidArgumentException("属性不存在:{$property}"); } } // 对象转字符串时调用 public function __toString(): string { return "User: {$this->name}, Age: {$this->age}"; } } $user = new User(); $user->name = "张三"; // 调用__set() $user->age = 25; // 调用__set() echo "姓名:{$user->name}<br>"; // 调用__get(),输出:张三 echo "年龄(角色=user):{$user->age}<br>"; // 输出:无权访问年龄 echo "年龄(角色=admin):" . (($_GET['role'] = 'admin') ? $user->age : '') . "<br>"; // 输出:25 echo $user; // 调用__toString(),输出:User: 张三, Age: 25 ?>
三、注意事项
-
属性与方法命名冲突
- 类中属性和方法不能重名(如不能同时有
$user属性和user()方法) - 避免使用 PHP 关键字作为类名、属性名或方法名(如
class、function、public等)
- 类中属性和方法不能重名(如不能同时有
-
构造函数兼容性
- PHP7 中若子类定义了构造函数,不会自动调用父类构造函数,需手动调用
parent::__construct() - PHP8 中此规则不变,但构造器属性提升可能导致父类属性被覆盖,需注意继承逻辑
示例(子类调用父类构造函数):
<?php class ParentUser { protected $id; public function __construct(int $id) { $this->id = $id; } } class ChildUser extends ParentUser { public $name; public function __construct(int $id, string $name) { // 必须先调用父类构造函数,再初始化子类属性 parent::__construct($id); $this->name = $name; } } $child = new ChildUser(1, "张三"); echo "ID:{$child->id},姓名:{$child->name}<br>"; // 输出:ID:1,姓名:张三 ?> - PHP7 中若子类定义了构造函数,不会自动调用父类构造函数,需手动调用
-
性能相关
- 避免过度使用魔术方法(如
__get()/__set()),会降低代码执行效率 - 对于简单场景,优先使用公共属性或显式的 getter/setter 方法(如
getName()/setName()) - PHP8 的构造器属性提升不会影响性能,反而因代码简化提升维护效率
- 避免过度使用魔术方法(如
四、实战练习
-
创建
day8文件夹,新建Product.php文件:- 定义
Product类,包含以下功能:- 属性:
id(int,只读)、name(string)、price(float)、stock(int,库存) - 构造函数:使用 PHP8 构造器属性提升,初始化
id、name、price,stock默认值为 100 - 方法:
getPriceWithTax(): float:返回含税费价格(税率 13%,即price * 1.13,返回值保留 2 位小数)reduceStock(int $num): bool:减少库存(num为减少数量,需验证num为正整数且不大于当前库存,成功返回 true,失败返回 false)__toString(): string:返回商品信息字符串(格式:“ID:1,名称:PHP 书籍,价格:59.90 元,库存:100”)
- 属性:
- 编写测试代码,实例化 2 个商品对象,调用所有方法并验证结果
- 定义
-
新建
UserManager.php文件:- 定义
UserManager类,实现用户管理基础功能:- 属性:
users(private array,存储 User 对象数组) - 方法:
addUser(User $user): void:添加用户到users数组(需避免重复 ID)getUserById(int $id): ?User:根据 ID 获取用户对象,不存在返回 nullgetUserList(): array:返回所有用户对象数组deleteUser(int $id): bool:根据 ID 删除用户,成功返回 true,失败返回 false
- 属性:
- 定义
User类(使用 PHP7 标量类型声明),包含id(int)、name(string)、email(string)属性及对应的 getter 方法 - 编写测试代码,添加 3 个用户,测试查询、删除、列表展示功能
- 定义
727

被折叠的 条评论
为什么被折叠?



