static::指向实际调用类(后期静态绑定),self::始终指向定义该方法的类;前者支持子类覆盖静态属性和方法实现多态,后者在继承中锁死父类上下文导致逻辑错误。

PHP里static::和self::到底绑到哪个类
延迟静态绑定不是让代码“等一会儿再执行”,而是解决self::在继承中永远指向定义它的类、无法响应子类上下文的问题。比如父类有个create()方法返回new self(),子类调用时拿到的仍是父类实例——类型错误、方法缺失、配置读错,全是因为self锁死了类名。
真正起作用的是static::和new static:它们在运行时才查当前调用类,不是编译期硬编码。这个“当前调用类”由上一个「非转发调用」决定,比如B::test()触发static::who(),那static就指B;但如果是A::foo()里调static::who(),那static就指A,跟谁继承无关。
-
self::→ 总是定义该行代码的类(写死在父类里,子类调也白调) -
static::→ 运行时实际发起调用的那个类(B::test()里的static::就是B) -
parent::和self::属于“转发调用”,不改变static指向 - 明确写类名的调用(如
A::foo())是非转发调用,会重置static上下文
什么时候必须用new static而不是new self
工厂方法、单例初始化、模板方法里创建本类实例,都得用new static。否则子类一调就崩:抽象基类Base里写return new self(),子类Child extends Base调Child::create()直接报错——因为new self试图实例化Base,而Base是抽象的。
换成new static后,Child::create()返回Child实例,类型对、方法可用、静态属性读取的是Child自己的值。注意两点:
立即学习“PHP免费学习笔记(深入)”;
-
new static要求子类构造函数不能是private,否则实例化失败(可设为protected) - PHP 5.3+才支持
new static,老版本需用new $calledClass配合get_called_class() - 如果基类构造函数带参数,子类必须兼容签名,否则
new static(...)传参会出错
static::访问静态属性或常量的典型陷阱
最常见误用是父类里写self::CONFIG想让子类覆盖,结果子类改了CONFIG常量,父类方法还是读父类的值。正确写法是static::CONFIG,但要注意:
- 子类必须用
const CONFIG = ...重定义,不能只改public static $CONFIG——static::只对常量和静态方法有效,对普通静态变量无效(得用static::$config) -
static::不能访问private成员,哪怕在同一个类族内;protected可以 - 如果子类没定义
static::PREFIX,运行时会报Access to undeclared static property,不像self::那样静默 fallback 到父类 - IDE 和静态分析工具(如 PHPStan)可能无法推断
static::的实际类型,需加注解@return static辅助
延迟绑定失效的几个真实场景
你以为用了static::就万事大吉?这几个地方它根本不工作:
- 在闭包里用
static::——闭包没有类上下文,static解析失败,必须显式传入类名或用get_called_class() - 用
forward_static_call()时,如果第一个参数是字符串数组['A', 'method'],它不会延续static上下文,得用forward_static_call_array([static::class, 'method'], $args) - trait 中的
static::行为取决于它被 use 进哪个类,但 trait 本身无上下文,容易误判 -
__CLASS__和get_class($this)不受延迟绑定影响,前者永远是定义处类名,后者才是运行时类名
真正难的不是语法,是设计基类时就想清楚:这个静态引用,到底该由父类拍板,还是留给子类定夺。一旦选错,debug时看到的永远是“为什么子类改了却没生效”,而不是“我该用哪个关键字”。



















