在 PHP 面向对象代码里,self 和 static 经常一起出现,尤其在继承、工厂方法、单例和 Active Record 这类写法中最容易让人踩坑。它们表面上都能写在类方法里,但真正决定结果的不是“写法像不像”,而是绑定发生在编译期还是运行期;顺着这个线索看,哪些场景该用 self、哪些场景必须交给 static,就会清楚很多。
为什么同样的调用方式,返回的类却不一样?
先看一个最典型的例子:
class A {
public static function getSelf() {
return new self();
}
public static function getStatic() {
return new static();
}
}
class B extends A {}
echo get_class(B::getSelf()); // 输出:A
echo get_class(B::getStatic()); // 输出:B
这段代码最容易让人误判的地方在于:两个方法都是通过 B 调用,为什么一个拿到 A,另一个却拿到 B?
关键不在调用方式,而在关键字绑定到谁。self 会直接指向“定义当前方法的类”,也就是这里的 A;static 则会保留“实际是谁发起了这次调用”,因此 B::getStatic() 最终返回的是 B 的实例。
如果只记一句话,可以这样理解:self 看定义位置,static 看调用者。
本质区别:早期绑定与后期静态绑定
self:在定义时就确定目标
self 采用的是早期绑定,可以把它理解为编译阶段就已经把目标类写死了。无论这个方法后来被父类自己调用,还是被子类继承后调用,self 都不会改指向。

因此,只要方法定义在父类中,self 指向的就始终是父类本身。这种行为的好处是稳定、直接、可预测,特别适合那些“不希望子类改变返回类型或访问目标”的逻辑。
static:在运行时根据调用者决定
static 使用的是后期静态绑定,也就是常说的 Late Static Binding。这是 PHP 5.3 引入的重要特性,它允许父类中的静态上下文在运行时感知“当前究竟是哪个类在调用”。

也正因为如此,static 更适合写在希望被继承、被扩展的基类方法中。父类只提供通用逻辑,真正返回哪个类的实例、读取哪个类的静态属性,则交给实际调用方决定。
从设计角度看,self 更偏向固定行为,static 更偏向多态扩展。
不只静态方法:实例方法和静态调用里都能看出差异
实例方法中,规则并不会改变
很多人第一次接触这两个关键字时,会以为它们只在静态方法里才有意义。实际上,放到实例方法中,绑定规则仍然完全一致:
class ParentClass {
public function createWithSelf() {
return new self();
}
public function createWithStatic() {
return new static();
}
}
class ChildClass extends ParentClass {}
$child = new ChildClass();
echo get_class($child->createWithSelf()); // 输出:ParentClass
echo get_class($child->createWithStatic()); // 输出:ChildClass
这里虽然是通过 $child 这个子类对象去调用实例方法,但 createWithSelf() 仍然会返回 ParentClass,因为它只认方法定义处;而 createWithStatic() 会根据实际调用类返回 ChildClass。
这说明问题的核心从来不是“静态方法还是实例方法”,而是绑定策略本身。
访问静态属性和静态方法时,区别同样明显
当你不是用它们来 new 对象,而是访问静态属性时,这种差异也会直接体现出来:
class Base {
public static $value = 'Base';
public static function showSelf() {
echo self::$value;
}
public static function showStatic() {
echo static::$value;
}
}
class Derived extends Base {
public static $value = 'Derived';
}
Derived::showSelf(); // 输出:Base
Derived::showStatic(); // 输出:Derived
当子类覆盖了父类的静态属性后,self::$value 依然读取的是父类里的值;static::$value 则会落到子类当前版本上。
这也是很多框架基类喜欢用 static 的原因之一:同一段父类逻辑,可以自然适配不同子类的静态配置。
实际开发怎么选:哪些场景该用 self,哪些该用 static?
需要固定行为时,用 self
如果你的目标是让行为保持稳定,不因为继承而变化,那么 self 往往更合适。一个常见例子就是单例模式:
class Database {
private static $instance;
private function __construct() {}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
}
这里使用 self 的意图很明确:getInstance() 要管理的是这个类自己对应的实例,而不是把实例化结果交给可能存在的子类。也就是说,设计者想要的是“确定性”,不是“可扩展性”。
凡是类似这种场景,例如内部工具类、明确不打算给子类改写结果的工厂入口、需要严格锁定返回类型的公共方法,都更适合选择 self。
需要继承扩展时,用 static
如果父类方法本来就是为子类复用而写,希望调用结果能够跟着子类变化,那么就应该优先考虑 static。例如工厂模式和 Active Record 风格的模型基类:
abstract class Model {
public static function find($id) {
$instance = new static();
// 从数据库加载数据
return $instance;
}
}
class User extends Model {}
$user = User::find(1); // 返回User实例,而不是Model实例
这里的核心诉求不是“在父类里创建父类对象”,而是“让父类提供通用查询流程,返回结果由具体模型类决定”。如果把 new static() 改成 new self(),这个基类就会失去面向子类扩展的意义。
所以,当你在写一个可复用基类、抽象模型、通用工厂,或者任何依赖多态的结构时,static 往往才是正确选择。
版本要求、性能差异与常见误区
后期静态绑定依赖 PHP 5.3 及以上
static 这种后期静态绑定能力并不是所有老版本 PHP 都支持。它要求环境至少是 PHP 5.3。如果项目仍运行在 PHP 5.2 或更早版本,这套行为就不能成立。
放到现在看,大多数项目早已越过这个门槛,但如果你在维护老系统,还是要先确认运行环境。
性能不是选型重点
从原理上说,self 因为在编译时就确定目标,开销会比运行时判断调用者的 static 稍低一些。但在真实业务代码里,这个差距通常小到不足以成为设计依据。
相比那点理论性能收益,更重要的是语义是否准确:你到底是想锁定当前类,还是想把行为开放给子类。这里选错,后续维护成本往往远比性能损失大得多。
一个容易忽略的限制
还有一个常见错误是,把 static 用在类属性默认值中:
class Foo {
public $name = static::class; // 错误!
}
这种写法是不成立的。这里应该改用 self:
class Foo {
public $name = self::class; // 正确
}
如果你遇到类似场景,可以先判断这段值是否需要依赖运行时调用者。类属性默认值显然不属于这种情况,因此应当使用确定性更强的 self。
一份够用的判断规则
把前面的例子压缩成一句实用判断标准,可以得到下面这组规则:
| 特性 | self | static |
|---|---|---|
| 绑定方式 | 早期绑定(编译时) | 后期静态绑定(运行时) |
| 指向目标 | 定义该方法的类 | 实际调用的类 |
| 多态支持 | 不支持 | 支持 |
| 适用场景 | 单例模式、需要稳定行为时 | 工厂模式、需要多态性时 |
实际写代码时,可以直接按下面的思路判断:
- 希望行为固定、结果稳定,就选
self - 希望父类逻辑能对不同子类自动生效,就选
static - 设计可扩展基类时,优先检查是否应该使用
static - 实现单例或明确不打算开放多态时,
self更稳妥
self 和 static 的区别,归根到底就是“是否把最终决定权交给调用者”。把这一点想明白,继承体系里的很多返回值问题、静态属性访问问题,基本都能更快定位。













