在PHP面向对象编程里,final 这个关键词扮演着“终结者”的角色。它只能用在两个地方——类和类方法(从PHP8.1 开始也支持类常量)。一旦某个类被标记为 final,它就不能被继承;一旦某个方法被标记为 final,子类就不能重写它。
这个机制的意图很明确:当你的某个类或方法已经达到了设计的终点,不希望任何人通过继承去修改它的行为时,就用 final 把它锁死。这在框架核心代码和安全性要求高的业务逻辑中尤其常见。
两类用法:锁类与锁方法
final 关键词有两种基本的使用方式:
-
修饰类:阻止这个类被任何其他类继承(extends)。
-
修饰方法:允许类被继承,但子类不能重写(override)这个被标记的方法。
两者的共同点都是“到此为止,不能再改”。
Final 类:禁止继承
当你把一个类声明为 final 时,你在向所有开发者传递一个信号:这个类的设计已经完成了,它的行为不应该被任何子类改变。任何试图 extends 它的代码都会触发致命错误。
看下面这个例子,我们有一个 MainClass,用它来计算两个数的和,但不想让任何子类去篡改这个计算逻辑:
final class MainClass {
function displayResult($num1, $num2) {
$result = $num1 + $num2;
echo "Sum of given numbers = " . $result;
}
}
class SubClass extends MainClass {
function displayResult($num1, $num2) {
$product = $num1 * $num2;
echo "Multiplication of given numbers = " . $product;
}
}
$obj = new SubClass();
$obj->displayResult(20, 20);
运行这段代码会直接得到一个致命错误:
Fatal error: Class SubClass may not inherit from final class (MainClass)
这个错误消息十分直白——MainClass 是 final 的,你无法继承它。相比之下,普通类对继承是开放的,任何类随时可以 extends 它,方法也能被随意重写。final 类则把这个大门彻底焊死了。我自己在维护一些老项目时,就曾因为某个核心类被下游同事意外继承并修改了行为而踩过坑,后来在重构时直接把那几个不该被扩展的类加上了 final,减少了后续类似问题的发生。
Final 方法:允许继承,禁止重写
相比于 final 类的绝对封闭,final 方法更加灵活。加了 final 的方法,所有子类都必须原封不动地继承它,不能用自己的版本去覆盖。这在你想保护某个核心算法不被改动的场景下很实用——类本身可以被扩展来添加新功能,但关键的方法逻辑是锁定的。
class BaseClass {
final function calculate($val1, $val2) {
$sum = $val1 + $val2;
echo "Sum of given numbers = " . $sum;
}
}
class ChildClass extends BaseClass {
function calculate($x, $y) {
$mult = $x * $y;
echo "Multiplication of given numbers = " . $mult;
}
}
$obj = new ChildClass();
$obj->calculate(10, 10);
输出:
Fatal error: Cannot override final method BaseClass::calculate()
这里 BaseClass 本身不是 final 的,所以 ChildClass 可以顺利继承它。但一旦 ChildClass 试图定义自己的 calculate() 方法,PHP 就直接报错,因为父类的 calculate() 被 final 锁定了。
这里有一个值得注意的细节:普通方法可以被子类重写(override),而且PHP不会给出任何警告,这是面向对象多态的基础。但 final 方法主动放弃了这种灵活性,换取的是行为一致性——无论谁继承了这个类,calculate() 的行为都不会变。在写支付模块、权限校验这类对逻辑一致性要求极高的代码时,用 final 方法保护核心流程是一种比较稳健的做法。
Final 常量(PHP 8.1 及以上)
从PHP8.1 版本开始,final 的作用范围扩展到了类常量。你可以用 final 声明一个常量,阻止子类去重定义它。这个特性在管理应用程序版本号、配置键名等全局固定值时很实用:
class AppSettings {
final public const VERSION = "1.0.0";
}
class CustomSettings extends AppSettings {
public const VERSION = "2.0.0";
}
输出:
Fatal error: CustomSettings::VERSION cannot override final constant AppSettings::VERSION
这保证了 VERSION 这个关键值在整个继承体系中的一致性。如果常规常量被允许在子类中重新定义,深层继承的项目里就可能出现同一个常量名指向不同值的情况,排查起来相当耗时。final 常量直接从语法层面杜绝了这种隐患。如果你使用的PHP版本在 8.1 及以上,对于系统级配置常量,我建议优先考虑加上 final。
什么时候该用 final?
final 是一把双刃剑。用得好可以保护核心逻辑不被意外篡改,用得过度则会让代码丧失灵活性,变成一块无法扩展的铁板。
这里是我个人的判断原则:
-
使用
final类的场景:这个类代表的实体在设计上已经终结,比如一个成熟的工具类、一个不应该再有变体的值对象。常见的例子包括StringHelper、MathUtils这类纯函数的类。如果你不确定未来是否需要扩展,就先不用final,因为一旦加上就不可逆。 -
使用
final方法的场景:在一个允许被扩展的类中,某些核心方法(比如支付回调处理、权限校验流程、关键算法)必须保证行为不变。子类可以去加新方法或者修改其他未锁定的方法,但不能动这些final标记的核心。 -
不要用
final的场景:当你设计一个需要被广泛扩展的框架基类或抽象类时,final会严重限制使用者的自由。除非有明确的安全或业务理由,否则框架的钩子方法和模板方法不宜设为final。
相比继承后重写导致的潜在错误(可能静默运行但逻辑不对),final 的致命错误是立即可见的,调试成本反而更低。这也是为什么在一些严格控制代码行为的团队中,final 会被视为一种代码质量工具来使用。
本节课程知识要点
-
final关键词可修饰类(禁止被继承)和方法(禁止被子类重写),PHP 8.1 起也支持修饰常量(禁止子类重定义)。 -
继承
final类或重写final方法都会触发致命错误,这种“锁定”保护了关键代码不被意外修改。 -
final方法所在的类本身仍可被继承,子类可以添加新方法,只是不能覆盖被标记的方法。 -
适合对成熟的工具类、核心算法、系统配置常量使用
final,在需要灵活扩展的框架基类中则应谨慎使用。