匿名类如何重写父类方法并传递构造参数?

解读

国内面试官问这道题,通常想验证三件事:

  1. 你是否真的用过 PHP7 之后推出的匿名类,而不是“只听过”;
  2. 能否把“继承”“构造传参”“方法重写”三件事一次性写对,且语法不踩坑;
  3. 是否知道匿名类一样遵循普通类的继承规则(构造器不会自动向上转发,必须手动 parent::__construct)。

现场写不出来,会被直接判定为“没有 PHP7+ 实际项目经验”,在简历筛选环节就可能被刷掉。

知识点

  1. 匿名类语法:new class(…) extends ParentClass { … }
  2. 构造参数传递:在 new class(实参) 的小括号里写,对应匿名类构造器的形参列表。
  3. 重写规则:方法名、可见性必须一致;想调用父类被覆盖的方法,用 parent::method()。
  4. 匿名类一样支持 use 语句引入外部变量,但面试题侧重“继承+构造”,use 不是得分点。
  5. 运行期特性:匿名类会在内部生成一个带随机后缀的类名,但开发者无感知,调试时可通过 get_class() 观察。

答案

<?php
// 父类
class Logger
{
    protected string $channel;

    public function __construct(string $channel)
    {
        $this->channel = $channel;
    }

    public function log(string $msg): void
    {
        echo "[{$this->channel}] {$msg}\n";
    }
}

// 客户端代码:匿名类重写 log 并传递构造参数
$logger = new class('order') extends Logger
{
    // 显式写出构造器,把参数继续传给父类
    public function __construct(string $channel)
    {
        // 必须先调 parent,否则 $this->channel 未初始化
        parent::__construct($channel);
    }

    // 重写父类方法
    public function log(string $msg): void
    {
        // 先调父类,实现“装饰”效果
        parent::log($msg);
        echo '>>> 额外行为:记录时间 ' . date('Y-m-d H:i:s') . PHP_EOL;
    }
};

// 运行验证
$logger->log('订单创建成功');

输出:
[order] 订单创建成功

额外行为:记录时间 2025-06-10 14:22:38

关键点:

  • new class('order') 的小括号就是构造实参;
  • 匿名类内部必须再写一次 __construct 形参列表,并手动 parent::__construct($channel);
  • 重写方法时,签名必须与父类保持兼容(public 不能改成 protected,参数不能加类型限制之外的变更)。

拓展思考

  1. 如果父类构造器有 3 个以上参数,匿名类写法会显得冗长,实战里可用工厂方法封装:
    static function createOrderLogger(): Logger { return new class(...) extends Logger {...}; }
  2. 匿名类可以实现接口,也能同时继承父类;当接口与父类方法冲突时,优先满足接口的签名约束,这是面试的“陷阱”追问点。
  3. 在常驻进程(Swoole、WorkerMan)中,匿名类会被重复创建,导致 Opcache 生成多个匿名类缓存条目;高并发场景下建议改用普通具名类,避免内存碎片。
  4. 单元测试时,可用匿名类快速“打桩”外部依赖:
    $stub = new class implements PaymentInterface { public function pay($amount) { return true; } };
    这种写法在 TDD 代码评审中很常见,能体现你对语言特性的灵活运用。