访问控制与继承
[TOC]
本文验证分析得到的结论
- 类内调用,不受访问控制约束限制。类间调用,则受访问控制约束限制。
- 只有 public 修饰的成员,才能在类外被访问。即实例化对象只可以直接访问被 public 修饰的成员。(注意:当类中声明魔术方法 __get (), 实例化对象访问类未定义或私有的成员是间接访问。)
子类与父类中同时定义一个同名的方法。
- 父类中的同名方法被 private 修饰,子类中的同名方法与父类的同名方法,不构成重写(overriding)关系。并且①如果父类中有其他公有方法调用此同名方法②子类不重写调用此同名方法的公有方法,那么子类的实例化对象访问这个公有方法,公有方法里面调用的还是父类的同名方法。(其他文章参考:从父类继承的函数中的$ this仍然指向php中的父对象?)
- 父类中的同名方法被 public 或 protected 修饰,子类中的同名方法与父类的同名方法,构成重写(overriding)关系,但要求修饰子类的同名方法的访问修饰符的作用范围 >= 修饰子类的同名方法的访问修饰符的作用范围,否则报致命错误。一般代码编辑器也会提示。
- $this 是一个伪变量,表示对主叫对象的引用。当一个方法在类定义内部被调用时,可使用 $this。
__CLASS__
魔术常量,表示当前类。作用与 get_class() 相同,要想获取主叫对象的所属类的类名,可使用 get_called_class() 函数, 或者 get_class($this) 。
访问控制
类中的成员(属性或类常量或方法)前无任何访问修饰符修饰时,默认被 pubic 修饰。
访问控制修饰符 | 作用范围 | 是否参与继承 |
---|---|---|
public | 当前类内部、子类内部、当前类外部、子类外部 | YES |
protected | 当前类内部、子类内部 | YES |
private | 只能在当前类内部被访问 | NO |
注意:
只有参与继承(被 public 或 protected 修饰)的成员方法,才可以 overriding(重写)。因为 private 修饰的私有方法对于子类来说,是不可见的,即***几乎可以认为私有方法不参与继承***。此外,php 中的 (overload)重载,并不是真正意义上的、像其他语言(如java)所描述的那种概念。
PHP 重载。
示例代码
下面的代码写在名为 base.php 的文件中,运行环境 PHP 8。
<?php
class Super
{
public $name; //姓名
public $gender; //性别
protected $birth; //出生日期
private $telephone; //手机号码
// 构造方法
public function __construct($name, $gender, $birth, $telephone)
{
$this->name = $name;
$this->gender = $gender;
$this->birth = $birth;
$this->telephone = $telephone;
}
// 私有方法
private function printHello() {
echo __CLASS__ . ' hello' . PHP_EOL;
}
// 声明一个公有方法,调用上面的私有方法 printHello
public function printTest() {
$this->printHello();
}
}
class child extends Super
{
// 声明一个公有的与父类中私有方法 printHello 同名的方法 printHello,看是否构成对的父类中的同名方法的重写
private function printHello() {
echo "阿凡提de小毛驴";
}
}
$init = ['绘梨衣', '女', '2003-08-12', '158xxxx0812'];
$super = new Super(...$init);
$child = new child(...$init);
echo $super->printTest();
echo $child->printTest();
运行上面的代码,结果输出:
[Running] php "base.php"
Super hello
Super hello
[Done] exited with code=0 in 0.086 seconds
重点分析
将上面的代码精简一下, 重点分析子类继承父类的公有或受保护的方法中,调用父类的私有方法,而后又在子类中声明同名的私有方法, $this 引用的疑问
。
第一种情况:子类直接继承父类,子类内部不实现任何方法。
<?php class Super { // 私有方法 printHello private function printHello() { echo get_called_class() . ' hello' . PHP_EOL; } // 声明一个公有方法,调用上面的私有方法 printHello public function printTest() { var_dump(get_class($this)); var_dump(get_class_methods($this)); $this->printHello(); } } class child extends Super { } $super = new Super(); echo $super->printTest(); echo '------------------------------------'.PHP_EOL; $child = new child(); echo $child->printTest();
输出的结果如下。
从输出结果来看,我们知道子类继承了父类的全部方法,包括 Super 私有的方法 printHello()
。显然这并不符合我们的预期,因为在其他强类型面向对象语言中,private 修饰的方法被描述为不可继承的。这里我们先可以认为这是因为 php 底层实现导致的原因,先不予考虑,也几乎认为在 php 中,私有方法不能被继承。对这里有疑问的同学可先参考这篇文章,PHP内核探索:继承,多态与抽象类。基于这样的考虑,我认为子类中只存在一个公有方法 printTest(),在类外,Child 的实例化的对象 $child 在调用 printTest() 时,会由于 Child 中没有可访问的 printHello() 方法,发出 Fatal Error。然而,和我预料的不一致。$child->printTest() 时,printTest() 内部的 $this->printHello() 访问的是父类的私有方法 printHello(),且此时的 $this 是 $child 对象的引用。
这不禁让人疑惑,子类的实例化对象的引用 $this 怎么可以访问父类的私有方法?
这不科学。string(5) "Super" array(2) { [0] => string(10) "printHello" [1] => string(9) "printTest" } Super hello ------------------------------------ string(5) "child" array(2) { [0] => string(10) "printHello" [1] => string(9) "printTest" } child hello
第二种情况:子类声明一个与父类私有方法同名的 printHello() 方法
注意:此方法并不与父类中的 printHello() 构成重写关系。
<?php class Super { // 私有方法 printHello private function printHello() { echo get_called_class() . ' hello' . PHP_EOL; } // 声明一个公有方法,调用上面的私有方法 printHello public function printTest() { var_dump(get_class($this)); var_dump(get_class_methods($this)); $this->printHello(); } } class child extends Super { public function printHello() { echo "阿凡提de小毛驴"; } } $super = new Super(); echo $super->printTest(); echo '------------------------------------'.PHP_EOL; $child = new child(); echo $child->printTest();
结果输出如下。从结果可以看出,$child->printTest() 时,此时的 $this 是 $child 对象的引用,
printTest() 内部的 $this->printHello() 访问的依然是父类的私有方法 printHello(),而非子类中声明的 printHello()
。这又让人更加疑惑了。string(5) "Super" array(2) { [0] => string(10) "printHello" [1] => string(9) "printTest" } Super hello ------------------------------------ string(5) "child" array(2) { [0] => string(10) "printHello" [1] => string(9) "printTest" } child hello
第三种情况:子类重写父类公有方法 printTest() ,但函数体不变。
<?php class Super { // 私有方法 printHello private function printHello() { echo get_called_class() . ' hello' . PHP_EOL; } // 声明一个公有方法,调用上面的私有方法 printHello public function printTest() { var_dump(get_class($this)); var_dump(get_class_methods($this)); $this->printHello(); } } class child extends Super { public function printTest() { $this->printHello(); } }
结果输出如下。此时的结果是符合预期的,$child->printTest() 是发生致命错误。因为 Child 中并没有可以访问的 printHello() 方法。但即使这样,对比第一种情况,也出现了新的疑问?子类中重写的 printTest() 与 继承的 printTest() 实现上是一样的,为什么第一种代码不报错,而当前的代码会报错,是因为继承的 printTest() 和 重写的 printTest() 在内存中存储的位置不一样吗?
string(5) "Super" array(2) { [0] => string(10) "printHello" [1] => string(9) "printTest" } Super hello ------------------------------------ Fatal error: Uncaught Error: Call to private method Super::printHello() from context 'child' on line 26 Error: Call to private method Super::printHello() from context 'child'
第四种情况:子类声明一个与父类私有方法同名的 printHello() 方法,并且子类重写父类公有方法 printTest() ,但函数体不变。
<?php class Super { // 私有方法 printHello private function printHello() { echo get_called_class() . ' hello' . PHP_EOL; } // 声明一个公有方法,调用上面的私有方法 printHello public function printTest() { var_dump(get_class($this)); var_dump(get_class_methods($this)); $this->printHello(); } } class child extends Super { public function printHello() { echo "阿凡提de小毛驴"; } // 声明一个公有方法,调用上面的私有方法 printHello public function printTest() { $this->printHello(); } } $super = new Super(); echo $super->printTest(); echo '------------------------------------'.PHP_EOL; $child = new child(); echo $child->printTest();
此时结果输出如下。我想此种情况是最没有疑问的,也是符合预期的。
string(5) "Super" array(2) { [0] => string(10) "printHello" [1] => string(9) "printTest" } Super hello ------------------------------------ 阿凡提de小毛驴
此问题已在我在本网站发起的问答中得到了解答
参考文章:
php继承相关的一个问题
PHP静态绑定与动态绑定
静态绑定和动态绑定的区别
关于php面向对象动态绑定和静态绑定的理解
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。