文章目录▼CloseOpen
- 先搞懂三个关键字的基础定义:别再“张冠李戴”
- Self:编译时就“锁死”的“当前类”
- parent:专门“找爸爸”的继承工具
- Static:运行时才“选对象”的“动态选手”
- 一张表分清三者区别:再也不混淆
- 实操中最容易踩的3个坑,我替你避了
- Self和Static的本质区别是什么?
- parent只能调用父类的方法吗?
- 什么时候应该优先用Static而不是Self?
- 用parent调用父类成员时,最容易踩什么坑?
- Static可以用来调用非静态成员吗?
先搞懂三个关键字的基础定义:别再“张冠李戴”
要分清它们,得先把“绑定时机”和“作用对象”这两个词吃透——简单说,“绑定时机”就是PHP什么时候确定要调用哪个类的成员,“作用对象”就是这个关键字针对的是哪个类。
Self:编译时就“锁死”的“当前类”
Self
是“当前类”的别名,注意“当前类”指的是写Self
的那个类,不管有没有继承,它都会在PHP编译代码的时候(也就是解析代码、还没执行的时候)就“锁死”——举个我朋友的例子:
class User {
public static $role = '普通用户';
public static function getRole() {
return Self::$role; // 这里的Self,永远指向User
}
}
class Admin extends User {
public static $role = '管理员'; // 子类覆盖了静态属性
}
echo Admin::getRole(); // 输出:普通用户(是不是和你想的不一样?)
为什么?因为getRole()
是User
类里写的,Self
在编译时就绑定了User
,所以不管子类Admin
怎么覆盖$role
,用Self
拿到的还是User
的$role
。我后来帮他改成Static
才解决问题——这个后面说。
再举个更日常的例子:你写了个BaseModel
类,里面有个静态方法getTableName()
返回'base_table'
,子类UserModel
继承后想返回'user_table'
,如果用Self::getTableName()
,结果还是'base_table'
——这就是Self
的“死性”,它只认“自己所在的类”。
parent:专门“找爸爸”的继承工具
parent
就简单多了,它是“父类”的别名,专门用来在子类中调用父类被覆盖的成员(比如方法、属性),而且是在运行时确定父类——比如我之前写电商系统的“订单类”时,父类Order
有个calculatePrice()
方法计算基础价格,子类VIPOrder
要加个折扣:
class Order {
protected function calculatePrice($amount) {
return $amount 10; // 基础价格:10元/件
}
}
class VIPOrder extends Order {
protected function calculatePrice($amount) {
$basePrice = parent::calculatePrice($amount); // 先调用父类的基础计算
return $basePrice 0.8; // VIP打8折
}
}
$vipOrder = new VIPOrder();
echo $vipOrder->calculatePrice(5); // 输出:40(5100.8)
这里parent::calculatePrice($amount)
就是明确调用父类Order
的方法——注意,parent
必须用在有继承关系的子类里,而且父类的成员得是protected
或public
(如果是private
,子类根本访问不到)。我之前踩过一个坑:父类的方法是private
,结果用parent
调用时报错“无法访问私有方法”,后来改成protected
才解决——这个细节你得记牢。
Static:运行时才“选对象”的“动态选手”
Static
是PHP5.3之后加的“延迟静态绑定”(Late Static Binding)特性,它的核心是“在运行时确定调用的类”——也就是根据“实际调用的那个子类”来决定要访问的成员,而不是写Static
的类。再看我朋友的例子,把Self
改成Static
:
class User {
public static $role = '普通用户';
public static function getRole() {
return Static::$role; // 这里的Static,看“谁调用它”
}
}
class Admin extends User {
public static $role = '管理员';
}
echo Admin::getRole(); // 输出:管理员(对了!)
为什么?因为Admin::getRole()
是Admin
类在调用,Static
会在运行时(代码执行的时候)“看”到实际调用的是Admin
,所以指向Admin
的$role
。这就是Static
的“活”——它能处理继承中的“动态调用”。
我之前做过一个“支付方式”的需求:父类Payment
有个getPaymentType()
方法返回'默认支付'
,子类WechatPay
返回'微信支付'
、Alipay
返回'支付宝支付'
,用Static
就能完美解决:
class Payment {
public static function getPaymentType() {
return Static::$type;
}
}
class WechatPay extends Payment {
public static $type = '微信支付';
}
class Alipay extends Payment {
public static $type = '支付宝支付';
}
echo WechatPay::getPaymentType(); // 微信支付
echo Alipay::getPaymentType(); // 支付宝支付
是不是比Self
灵活多了?PHP官方文档里对Static
的说明很准确:“用于在继承范围内引用静态调用的类”(参考链接:PHP手册-延迟静态绑定)——简单说,就是“继承里的动态静态调用,找Static
准没错”。
一张表分清三者区别:再也不混淆
光讲定义可能还是有点懵?我把三者的核心区别做成了表格,你保存下来,写代码时对着看就行:
关键字 | 绑定时机 | 作用对象 | 典型场景 |
---|---|---|---|
Self | 编译时 | 写Self的当前类 | 调用当前类的静态成员/常量(不涉及继承动态调用) |
parent | 运行时 | 子类的父类 | 子类中调用父类被覆盖的方法/属性 |
Static | 运行时 | 实际调用的类(子类) | 继承中的动态静态调用(需要根据子类调整) |
比如你要写一个“日志类”,父类BaseLog
有个writeLog()
方法写文件,子类DbLog
要同时写数据库——用parent::writeLog()
调用父类方法;如果父类有个静态属性$logPath
,子类要覆盖成自己的路径——用Static::$logPath
;如果只是当前类自己用的静态常量(比如const VERSION = '1.0'
)——用Self::VERSION
就够了。
实操中最容易踩的3个坑,我替你避了
最后再讲几个我亲测的“避坑技巧”,都是踩过才记住的:
Self
别用来做“继承动态调用”:如果你写的类会被继承,而且需要子类覆盖静态成员,绝对别用Self
——比如前面的User
和Admin
例子,用Self
只会拿到父类的值,改成Static
才对。 parent
要确认父类有对应成员:我之前写Cache
类时,子类RedisCache
想调用父类的getCache()
,结果父类的方法是private
,直接报错——父类的成员得是protected
或public
,parent
才能访问。 Static
只用于“静态成员”:别用Static
调用非静态方法,虽然PHP不会报错,但逻辑上不对——非静态方法用$this
更合适,Static
是给静态成员准备的。比如我最近写的一个“权限管理系统”,角色类Role
有个静态方法getPermissions()
,子类AdminRole
覆盖后返回更多权限,用Static::getPermissions()
就能正确拿到子类的权限列表,上线后没出过错——这就是“分清关键字”的好处。
如果你之前踩过这三个关键字的坑,或者按我讲的例子试了,欢迎在评论区告诉我结果——比如“用Static
解决了子类静态属性的问题”,或者“之前parent
调用错了父类方法”,咱们一起讨论。其实PHP的面向对象不难,难的是把这些“小关键字”的细节摸透——毕竟写对一行代码,比改半小时bug舒服多了~
很多人以为parent只能调用父类的方法,其实不是——它连父类的属性也能调,只不过得满足个小条件:父类的那个成员得是protected或者public。要是父类把属性或方法设成private,子类用parent调用直接就会报错,我之前就踩过这坑。比如我写过一个商品分类的父类Goods,里面有个protected的静态属性$category = ‘日用品’,子类Electronics想在自己的getFullCategory方法里先拿到父类的分类,再加上子类的细分(比如“电子产品”),就用了parent::$category,顺利拿到“日用品”后,再拼接成“日用品-电子产品”,结果完全符合预期。但后来我误把父类的$category改成private,子类再调用直接报“无法访问私有属性”,改回protected才解决问题。
再来说方法,我做电商订单系统时更常用parent调方法。比如父类Order有个public的calculateBasePrice方法,用来计算商品的基础总价(比如“商品数量×单价”),子类VIPOrder需要计算折扣后的价格,这时候就先用parent::calculateBasePrice拿到基础价,再乘以VIP专属的0.8折扣率。这里parent的作用很明确:子类要覆盖父类的方法,但还想复用父类的基础逻辑,不用重新写一遍计算基础价的代码。其实不管是属性还是方法,parent的核心就是“连接子类和父类的继承关系”——让子类能拿到父类里“允许被继承的东西”。但一定要记住,父类的成员不能是private,就像你想拿家里的东西,得是爸爸允许你碰的,要是他把东西锁在只有自己能开的柜子里,你肯定拿不到对吧?
还有个小细节要注意,parent不仅能调用“被子类覆盖的成员”,就算子类没覆盖父类的成员,也能用parent调用。比如父类有个public的getOrderId方法,子类没覆盖它,那子类里用parent::getOrderId和直接用self::getOrderId结果一样,但要是子类覆盖了,parent就会明确指向父类的版本。简单说,parent就是“不管子类有没有改,我就要父类的那个版本”——比如你想喝爸爸泡的茶,不管你自己会不会泡茶,直接找爸爸要他泡的,就是这个意思。
Self和Static的本质区别是什么?
Self是编译时绑定,指向“写Self的当前类”(比如父类里的Self永远指向父类);Static是运行时绑定,指向“实际调用的类”(比如子类调用时指向子类)。简单说,Self“锁死”在代码所在的类,Static“跟随”实际使用的类。
parent只能调用父类的方法吗?
不是,parent可以调用父类的方法和属性,但要满足一个前提:父类的成员必须是protected
或public
(private
成员子类无法访问)。比如子类可以用parent::$role
调用父类的protected静态属性,也能用parent::getName()
调用父类的public方法。
什么时候应该优先用Static而不是Self?
当你的类会被继承,且需要子类覆盖静态成员(比如静态属性、静态方法)时,一定要用Static。比如父类有个静态属性$logPath
,子类要改成自己的日志路径,用Static才能拿到子类的路径;用Self会永远拿到父类的路径,导致逻辑错误。
用parent调用父类成员时,最容易踩什么坑?
最常见的坑是父类成员权限错误——如果父类的方法/属性是private
,子类用parent调用会直接报错。解决办法:把父类成员改成protected
(允许子类访问)或public
(公开访问)。
Static可以用来调用非静态成员吗?
不 Static是为静态成员设计的(静态方法、静态属性属于“类”),非静态成员属于“对象”(需要用$this
调用)。虽然PHP不会强制报错,但逻辑上不符合面向对象的封装性,容易导致代码混乱。