PHP 方法重写
概述
方法重写(Method Overriding)允许子类提供父类方法的替代实现。重写时必须遵循 PHP 的签名兼容性规则,包括参数类型、返回值类型和可见性约束。
版本要求
- PHP 7.4+:参数类型逆变
- PHP 8.0+:返回值类型协变
- PHP 8.3+:
#[\Override]注解
基础概念
重写规则
子类重写方法时必须满足以下条件:
- 参数数量:不能减少必选参数
- 参数类型:可以与父类相同或更宽泛(逆变)
- 返回类型:可以与父类相同或更具体(协变)
- 可见性:不能比父类更严格
语法与代码
基本重写
php
<?php
declare(strict_types=1);
class Animal
{
public function speak(): string
{
return 'Some sound';
}
}
class Dog extends Animal
{
public function speak(): string
{
return 'Woof!';
}
}
echo (new Dog())->speak(); // Woof!参数放宽规则
子类方法可以将父类的必选参数变为可选参数,或添加新的可选参数。
php
<?php
declare(strict_types=1);
class Processor
{
public function process(string $data): string
{
return strtoupper($data);
}
}
class ExtendedProcessor extends Processor
{
// OK: 添加可选参数
public function process(string $data, bool $encode = false): string
{
$result = strtoupper($data);
return $encode ? base64_encode($result) : $result;
}
}协变返回类型(PHP 8.0+)
子类方法的返回类型可以比父类更具体(子类型)。
php
<?php
declare(strict_types=1);
class Animal
{
public function getOwner(): ?Person
{
return null;
}
}
class Dog extends Animal
{
// 协变:返回更具体的 DogOwner
public function getOwner(): ?DogOwner
{
return null;
}
}
class Person {}
class DogOwner extends Person {}#[\Override] 注解(PHP 8.3+)
#[\Override] 注解明确标记方法意图重写父类方法。
php
<?php
declare(strict_types=1);
class BaseService
{
protected function execute(): void
{
echo "BaseService::execute\n";
}
}
class UserService extends BaseService
{
#[\Override]
protected function execute(): void
{
parent::execute();
echo "UserService::execute\n";
}
// Error: #[\Override] 目标方法不存在于父类
// #[\Override]
// protected function notInParent(): void {}
}参数逆变
子类方法的参数类型可以比父类更宽泛。
php
<?php
declare(strict_types=1);
class AnimalShelter
{
public function adopt(Dog $dog): void
{
echo "Adopted a dog\n";
}
}
class GeneralShelter extends AnimalShelter
{
// 逆变:接受更宽泛的 Animal
public function adopt(Animal $animal): void
{
echo "Adopted an animal\n";
}
}
class Animal {}
class Dog extends Animal {}详细说明
签名不兼容的例子
php
<?php
declare(strict_types=1);
class Base
{
public function foo(int $a = 5): void {}
}
// Fatal: 移除参数
// class Bad1 extends Base { public function foo(): void {} }
// Fatal: 可选参数变必选
// class Bad2 extends Base { public function foo(int $a): void {} }命名参数与重写
重写方法时不要重命名参数,否则会导致命名参数调用时出错。
php
<?php
declare(strict_types=1);
class A
{
public function test(string $foo, string $bar): void {}
}
class B extends A
{
// 不推荐重命名参数
public function test(string $a, string $b): void {}
}
$obj = new B();
// Fatal error: Unknown named parameter $foo
// $obj->test(foo: 'hello', bar: 'world');实战示例
模板方法模式
php
<?php
declare(strict_types=1);
abstract class DataExporter
{
final public function export(array $data): string
{
$formatted = $this->formatData($data);
$encoded = $this->encode($formatted);
return $this->addHeaders($encoded);
}
abstract protected function formatData(array $data): string;
abstract protected function encode(string $data): string;
protected function addHeaders(string $data): string
{
return $data;
}
}
class JsonExporter extends DataExporter
{
#[\Override]
protected function formatData(array $data): string
{
return json_encode($data, JSON_PRETTY_PRINT);
}
#[\Override]
protected function encode(string $data): string
{
return base64_encode($data);
}
}注意事项
- 构造函数不受约束:子类构造函数可以完全不同
- private 方法不受约束:子类定义同名 private 方法不视为重写
- 命名参数重命名危险:保持参数名称一致
- #[\Override] 推荐使用:PHP 8.3+ 明确重写意图
最佳实践
- PHP 8.3+ 使用 #[\Override]:防止意外的方法签名错误
- 保持参数名称一致:避免命名参数调用时的错误
- 使用 parent:: 保留父类逻辑:扩展而非替换
- final 防止关键方法被重写:保证核心行为一致
进阶用法
调试与测试技巧
php
<?php
declare(strict_types=1);
// 单元测试辅助函数
function createTestResource(): mixed
{
return match (true) {
default => new stdClass(),
};
}
// 调试输出函数
function debugOutput(mixed , string = ''): void
{
= ? ": " : '';
.= print_r(, true);
fwrite(STDERR, . "\n");
}
// 性能基准测试
function benchmark(callable , int = 1000): float
{
= hrtime(true);
for ($i = 0; $i < $iterations; $i++) {
$fn();
}
return (hrtime(true) - $start) / 1e9;
}日志记录实践
php
<?php
declare(strict_types=1);
/**
* 简易日志记录器
*/
class SimpleLogger
{
private string $logFile;
private string $level = 'INFO';
public function __construct(string $logFile)
{
$this->logFile = $logFile;
}
public function info(string $message, array $context = []): void
{
$this->log('INFO', $message, $context);
}
public function warning(string $message, array $context = []): void
{
$this->log('WARNING', $message, $context);
}
public function error(string $message, array $context = []): void
{
$this->log('ERROR', $message, $context);
}
private function log(string $level, string $message, array $context): void
{
$timestamp = date('Y-m-d H:i:s');
$contextStr = $context ? ' ' . json_encode($context, JSON_UNESCAPED_UNICODE) : '';
$line = "[{$timestamp}] [{$level}] {$message}{$contextStr}\n";
file_put_contents($this->logFile, $line, FILE_APPEND | LOCK_EX);
}
}配置与环境检测
php
<?php
declare(strict_types=1);
// 环境检测工具
class EnvironmentChecker
{
public static function checkRequirements(array $requirements): array
{
$results = [];
foreach ($requirements as $name => $check) {
$results[$name] = is_callable($check) ? $check() : false;
}
return $results;
}
public static function getSystemInfo(): array
{
return [
'php_version' => PHP_VERSION,
'os' => PHP_OS,
'sapi' => PHP_SAPI,
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'loaded_extensions' => get_loaded_extensions(),
];
}
}常见问题排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 连接超时 | 网络问题/配置错误 | 检查配置,增加超时时间 |
| 权限不足 | 文件/目录权限 | 使用 chmod/chown 修正 |
| 性能下降 | 索引缺失/数据量大 | 添加索引,优化查询 |
| 数据不一致 | 并发冲突/事务残留 | 使用锁机制和事务 |
| 内存溢出 | 大数据集/未释放资源 | 增大内存限制,分批处理 |
故障排除步骤
- 检查错误日志和异常信息
- 确认配置和环境是否正确
- 使用调试工具逐步排查
- 参考官方文档查找已知问题
版本兼容性说明
| 功能 | 最低版本 | 说明 |
|---|---|---|
| 基础功能 | PHP 8.1 | 本文档基准版本 |
| 只读属性 | PHP 8.1 | public readonly 修饰符 |
| 枚举类型 | PHP 8.1 | enum 类型和 match 表达式 |
| Fiber | PHP 8.1 | 协程/轻量级并发 |
| 命名参数 | PHP 8.0 | foo(arg_name: value) |
| 联合类型 | PHP 8.0 | `int |
| Null 安全运算符 | PHP 8.0 | $obj?->method() |
| 析构器 promotion | PHP 8.0 | __construct(public $x) |
php
<?php
declare(strict_types=1);
// 版本兼容性检测
function ensureVersion(string $minVersion): void
{
if (version_compare(PHP_VERSION, $minVersion, '<')) {
throw new RuntimeException(
sprintf('需要 PHP %s+, 当前版本: %s', $minVersion, PHP_VERSION)
);
}
}
ensureVersion('8.1.0');