CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
PHP Web 入门/课时/面向对象
RawBlame
课时目录12 节

PHP Web 入门

  1. 01PHP 是什么
  2. 02基础语法
  3. 03函数与命名空间
  4. 04面向对象
  5. 05Composer 依赖
  6. 06Web 请求处理
  7. 07PDO 与 MySQL
  8. 08REST API 风格
  9. 09PHP 8 新特性
  10. 10Docker 运行 PHP
  11. 11PHP vs Java 选型
  12. 12综合练习

面向对象

class、interface、trait。

35 分钟

面向对象

这一课你将能做到

  1. 定义带可见性、类型声明属性的类,并用构造器属性提升写出紧凑的 DTO
  2. 用接口约束行为、用 trait 复用实现,说清两者与抽象类的分工
  3. 掌握 readonly 属性(8.1)与 readonly 类(8.2),写出不可变对象

为什么要学

PHP 8 之后的面向对象已经和 Java 非常接近:接口、抽象类、trait、枚举、final、类型声明一应俱全,还多了构造器属性提升这种 Java 没有的「减法语法」。现代框架的每一层——Controller、Service、Repository——都是类和接口的组合。不懂 OOP,读 Laravel 或 Symfony 源码就是读天书;懂了 OOP,你会发现两个框架的分层思想和 Spring 高度同构。

人话:类是「数据 + 行为」的模具,接口是「承诺书」(你能干什么),trait 是「外挂技能包」(横向复制实现),继承是「父子传家」。这四个工具组合起来,就是工程化的全部骨架。

先建立图像

一个典型的 PHP 服务类分层:

UserRepository ──► 查库(依赖 PDO)
UserServiceImpl ──► 业务逻辑(依赖 UserRepository 接口)
UserController ──► 接 HTTP 请求(依赖 Service 接口)

接口 UserRepositoryInterface  ← 行为契约
类   MySqlUserRepository     ← 一种实现
trait  Loggable              ← 横切能力,多个类复用

和 Spring 的 Controller/Service/Repository 分层完全对位,区别只是装配方式:PHP 在构造函数里手动 new 或由容器注入,Spring 交给 IoC 容器。

跟着做

步骤 1:类与构造器属性提升

新建 Order.php:

<?php
declare(strict_types=1);

class Order
{
    // PHP 8 构造器属性提升:声明 + 赋值一行搞定
    public function __construct(
        private readonly int $id,
        private readonly string $product,
        private float $amount,
        private string $status = 'pending',
    ) {
        if ($amount < 0) {
            throw new \InvalidArgumentException('金额不能为负');
        }
    }

    public function pay(): void
    {
        if ($this->status !== 'pending') {
            throw new \RuntimeException('订单状态不允许支付');
        }
        $this->status = 'paid';
    }

    public function status(): string
    {
        return $this->status;
    }
}

$order = new Order(1, '机械键盘', 399.00);
$order->pay();
echo $order->status(), PHP_EOL;    // paid
// $order->amount = -1;            // private,外部不可直接改

对比 Java:相当于把字段声明、构造参数、赋值语句三件事压缩成一组带修饰符的参数。readonly 修饰的属性初始化后不可再改。

步骤 2:继承与可见性

<?php
declare(strict_types=1);

class Animal
{
    public function __construct(protected string $name) {}

    public function introduce(): string
    {
        return "我是 {$this->name}";
    }
}

class Dog extends Animal
{
    public function introduce(): string   // 方法重写
    {
        return parent::introduce() . ',汪!';
    }
}

echo (new Dog('旺财'))->introduce(), PHP_EOL;   // 我是 旺财,汪!

可见性三层:public 谁都能访问, 本类和子类, 仅本类。属性不写修饰符是非法的(PHP 8 起强制),这堵死了老代码的「隐式 public」。

protected
private

步骤 3:接口 + 类型提示实现多态

<?php
declare(strict_types=1);

interface PaymentGateway
{
    public function charge(float $amount): string;
}

class AliPay implements PaymentGateway
{
    public function charge(float $amount): string
    {
        return "支付宝收款 {$amount} 元";
    }
}

class WeChatPay implements PaymentGateway
{
    public function charge(float $amount): string
    {
        return "微信收款 {$amount} 元";
    }
}

function checkout(PaymentGateway $gateway, float $amount): void
{
    echo $gateway->charge($amount), PHP_EOL;
}

checkout(new AliPay(), 99.0);     // 支付宝收款 99 元
checkout(new WeChatPay(), 99.0);  // 微信收款 99 元

checkout 只依赖接口,新增支付渠道零改动——这就是「依赖接口而非实现」,与 Java 面向接口编程完全一致。

步骤 4:trait 横向复用

<?php
declare(strict_types=1);

trait Loggable
{
    public function log(string $message): void
    {
        echo '[' . date('H:i:s') . '] ' . static::class . ": {$message}\n";
    }
}

class OrderService
{
    use Loggable;

    public function create(): void
    {
        $this->log('订单已创建');
    }
}

class StockService
{
    use Loggable;

    public function deduct(): void
    {
        $this->log('库存已扣减');
    }
}

(new OrderService())->create();    // [10:30:01] OrderService: 订单已创建
(new StockService())->deduct();    // [10:30:01] StockService: 库存已扣减

trait 是「把实现复制进类」:两个不相干的类都能 use Loggable 获得同一份方法。static::class 是后期静态绑定,返回实际调用者的类名。

讲清楚要点

  1. 接口管「能做什么」,trait 管「怎么做」,抽象类管「是什么」:接口只含方法签名和常量(PHP 8 后允许常量),抽象类可以有状态和构造器但只能单继承,trait 无状态纯实现且可多用。需要多继承能力时:继承一个抽象类 + use 多个 trait。
  2. 构造器属性提升是 PHP 8 的标配写法:参数带可见性修饰符即自动成为属性。注意规则:提升参数不能是可变参数、不能重复声明同名的提升属性;默认值写在参数上。
  3. self、static、$this 三兄弟:$this 指当前实例;self:: 编译期绑定到「定义该方法的类」;static:: 运行期绑定到「实际调用的类」(后期静态绑定),子类重写常量或静态方法时 static:: 才能拿到子类版本;new static() 也据此实例化子类。
  4. readonly 属性与 readonly 类:属性加 readonly 只能在声明处或构造器内赋值一次;PHP 8.2 可整类 readonly class(所有声明属性自动只读)。配合 promoted properties 就有了 PHP 版 record——但 PHP 不会自动生成 equals/hashCode。

你可能会懵的地方

  • 抽象类和接口怎么选? 要给子类提供公共状态、构造逻辑、部分实现 → 抽象类;只定义行为契约、希望类保留继承名额 → 接口。PHP 支持一个类实现多个接口,所以框架里契约几乎全是接口,抽象基类做模板方法。
  • trait 方法冲突了怎么办? 两个 trait 有同名方法时必须显式解决:use A, B { A::hello insteadof B; B::hello as aliased; }。冲突不解决直接编译报错,PHP 不会偷偷选一个。
  • PHP 有重载(overload)吗? 没有传统意义上的方法重载——同名方法只能定义一次。变通方案:默认参数、可变参数、命名参数模拟重载;__call 魔术方法做动态分发。构造器同样不能重载,静态工厂方法是主流替代。
  • new self() 和 new static() 区别? new self() 永远实例化「写这行代码的类」;new static() 实例化「运行时实际调用的类」。在父类工厂方法里想返回子类实例,必须用 new static()。
  • 属性不写可见性会怎样? PHP 8 起直接解析错误(老版本会被当作 public 并触发弃用警告)。所有属性必须显式 public/protected/private。

错法对照

错法问题正解
属性全 public,外部随意改状态失控,无法维护不变式private + getter/setter(按需暴露)
用继承复用工具方法继承树膨胀,父类被迫懂子类抽成 trait 或独立工具类
接口里写实现接口应纯契约(除非默认方法有明确理由)实现放抽象类或 trait
self::CONST 在子类拿到父类值self 是编译期绑定需要多态时用 static::CONST
构造器里做 IO/查库对象创建副作用不可控构造器只做赋值与校验,IO 交给方法
手写 $this->id = $id 三件套冗长重复构造器属性提升一行搞定

卡点急救

  1. Cannot declare class xxx, because the name is already in use:同类被加载两次。确认走 Composer 自动加载而不是手动 require,或检查是否 require 了两个内容不同但类名相同的文件。
  2. Typed property must not be accessed before initialization:声明了类型的属性没初始化就被读。检查构造器是否漏赋值,或属性标注了非可空类型却没默认值。
  3. trait 方法被类自身方法覆盖,以为 trait 生效:优先级是「类自身方法 > trait 方法 > 父类方法」。想让 trait 的实现生效,就别在类里写同名方法。

面试常问

  • 抽象类和接口的区别? 抽象类:单继承、可有属性/构造器/具体方法、表达「is-a」;接口:多实现、只含方法签名与常量(PHP 8.0 起支持常量)、表达「can-do」。工程上服务契约用接口,公共骨架用抽象类。
  • trait 和接口的区别? 接口只有契约没有实现,类 implements 后必须自己实现;trait 携带完整实现,use 后直接拥有方法体,解决 PHP 单继承下的横向代码复用。常见组合是「接口定义契约 + trait 提供默认实现」。
  • self 和 static 的区别(后期静态绑定)? self:: 绑定到方法定义所在的类,static:: 绑定到运行时实际调用的类。子类继承父类的自引用方法时,static:: 能取到子类的常量、静态属性与方法,new static() 也能实例化子类——这就是后期静态绑定(LSB)。

自测三问

  1. 构造器属性提升把 Java 里的几步操作压缩成了一步?写出对应的 Java 版本。
  2. 一个类能同时继承抽象类和使用多个 trait 吗?优先级顺序是什么?
  3. readonly 属性什么时候可以赋值?第二次赋值会发生什么?

小结一句话

PHP 8 的 OOP 用「接口立契约、trait 做横切、抽象类供骨架、构造器属性提升 + readonly 写紧凑不可变模型」,分层思想与 Spring 完全同构。

延伸

  • PHP 官方手册:类与对象
  • 下一课:Composer 依赖——用 composer.json 和 Packagist 管理整个项目。
加载本节练习…
← 上一课函数与命名空间下一课 →Composer 依赖