2025 PHP7/8 实战入门:15 天精通现代 Web 开发——第 8 课:面向对象基础

第 8 课:面向对象基础

一、学习目标

  1. 理解面向对象编程(OOP)的核心思想(封装、继承、多态)
  2. 掌握 PHP 类与对象的定义、实例化及基本使用
  3. 熟练运用 PHP7 类型声明与 PHP8 构造器属性提升简化类设计
  4. 能够基于 OOP 思想编写可维护的基础类(如用户类、商品类)

二、核心知识点

(一)OOP 核心概念与优势
  1. 三大核心特性

    • 封装:将数据(属性)和操作数据的方法封装在类中,对外隐藏实现细节
    • 继承:子类可继承父类的属性和方法,减少代码重复
    • 多态:不同类的对象可通过相同的方法名实现不同的功能
  2. 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(); // 调用对象方法
    ?>
    
(二)类与对象的基础语法
  1. 类的定义

    • 关键字:class
    • 结构:包含属性(变量)和方法(函数)
    • 命名规范:首字母大写,采用驼峰命名法(如UserProductManager

    示例(基础类定义):

    <?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);
        }
    }
    ?>
    
  2. 对象的实例化与使用

    • 实例化:用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>"; // 失败
    ?>
    
  3. 访问控制修饰符

    • 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 新特性
  1. PHP7 标量类型声明与返回值类型声明

    • 可在类的方法参数和返回值前指定标量类型(intstringbool等)
    • 配合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
    ?>
    
  2. 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
    ?>
    
  3. 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
    ?>
    
(四)类的特殊方法
  1. 构造函数(__construct()

    • 对象实例化时自动调用,用于初始化属性
    • 支持默认参数和可变参数(PHP7+)
    • PHP8 支持构造器属性提升(见上文)
  2. 析构函数(__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);
    // 或脚本结束时自动销毁
    ?>
    
  3. 魔术方法(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
    ?>
    

三、注意事项

  1. 属性与方法命名冲突

    • 类中属性和方法不能重名(如不能同时有$user属性和user()方法)
    • 避免使用 PHP 关键字作为类名、属性名或方法名(如classfunctionpublic等)
  2. 构造函数兼容性

    • 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,姓名:张三
    ?>
    
  3. 性能相关

    • 避免过度使用魔术方法(如__get()/__set()),会降低代码执行效率
    • 对于简单场景,优先使用公共属性或显式的 getter/setter 方法(如getName()/setName()
    • PHP8 的构造器属性提升不会影响性能,反而因代码简化提升维护效率

四、实战练习

  1. 创建day8文件夹,新建Product.php文件:

    • 定义Product类,包含以下功能:
      • 属性:id(int,只读)、name(string)、price(float)、stock(int,库存)
      • 构造函数:使用 PHP8 构造器属性提升,初始化idnamepricestock默认值为 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 个商品对象,调用所有方法并验证结果
  2. 新建UserManager.php文件:

    • 定义UserManager类,实现用户管理基础功能:
      • 属性:users(private array,存储 User 对象数组)
      • 方法:
        • addUser(User $user): void:添加用户到users数组(需避免重复 ID)
        • getUserById(int $id): ?User:根据 ID 获取用户对象,不存在返回 null
        • getUserList(): array:返回所有用户对象数组
        • deleteUser(int $id): bool:根据 ID 删除用户,成功返回 true,失败返回 false
    • 定义User类(使用 PHP7 标量类型声明),包含id(int)、name(string)、email(string)属性及对应的 getter 方法
    • 编写测试代码,添加 3 个用户,测试查询、删除、列表展示功能
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Anson Jiang

感谢客官老爷打赏的咖啡钱:-)

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值