注意:以下内容已在PHP 5.3.8中测试。其他版本可能表现不同。
由于PHP没有正式规范,因此无法从“应该”发生的角度回答这个问题。我们能做到的最接近的是PHP手册关于
protected的声明:
“声明为protected的成员只能在类本身和继承和父类中访问。”
虽然成员可以在
ChildClass
中被覆盖(保持“protected”标识符),但它最初是在
BaseClass
中声明的,因此它仍然可在
BaseClass
的后代中看到。
与此解释直接相反的是,比较一下受保护属性的行为:
<?php
abstract class BaseClass {
protected $_foo = 'foo';
abstract protected function __construct();
}
class MommasBoy extends BaseClass {
protected $_foo = 'foobar';
protected function __construct(){
echo __METHOD__, "\n";
}
}
class LatchkeyKid extends BaseClass {
public function __construct() {
echo 'In ', __CLASS__, ":\n";
$kid = new MommasBoy();
echo $kid->_foo, "\n";
}
}
$obj = new LatchkeyKid();
输出:
在LatchkeyKid中:
MommasBoy::__construct
致命错误:无法访问MommasBoy::$_foo的受保护属性,位于-行18
将抽象的__construct
更改为具体函数并使用空实现可获得所需的行为。
abstract class BaseClass {
protected function __construct() {}
}
然而,非魔术方法在相关类中是可见的,无论它们是否是抽象的(大多数魔术方法必须是公共的)。
<?php
abstract class BaseClass {
abstract protected function abstract_protected();
protected function concrete() {}
}
class MommasBoy extends BaseClass {
protected function abstract_protected() {
return __METHOD__;
}
protected function concrete() {
return __METHOD__;
}
}
class LatchkeyKid extends BaseClass {
function abstract_protected() {}
public function __construct() {
echo 'In ', __CLASS__, ":\n";
$kid = new MommasBoy();
echo $kid->abstract_protected(), "\n", $kid->concrete(), "\n";
}
}
$obj = new LatchkeyKid();
输出:
在LatchkeyKid中:
MommasBoy::abstract_protected
MommasBoy::concrete
如果您忽略警告并将魔术方法(除了__construct
,__destruct
和__clone
)声明为protected
,则它们似乎可以像非魔术方法一样在相关类中访问。
__clone
和__destruct
被保护时,在相关类中也无法访问,无论它们是否是抽象的。这使我相信抽象的__construct
行为是一个错误。
<?php
abstract class BaseClass {
abstract protected function __clone();
}
class MommasBoy extends BaseClass {
protected function __clone() {
echo __METHOD__, "\n";
}
}
class LatchkeyKid extends BaseClass {
public function __construct() {
echo 'In ', __CLASS__, ": \n";
$kid = new MommasBoy();
$kid = clone $kid;
}
public function __clone() {}
}
$obj = new LatchkeyKid();
输出:
在LatchkeyKid中:
致命错误:从上下文'LatchkeyKid'调用受保护的MommasBoy::__clone(),位于 - 的第16行
访问__clone
在zend_vm_def.h(具体来说是ZEND_CLONE
操作码处理程序)中受到强制执行。这除了对方法的访问检查之外,可能就是它具有不同行为的原因。然而,我没有看到访问__destruct
的特殊处理,所以显然还有更多内容。
PHP开发人员之一Stas Malyshev(嗨,Stas!)研究了__construct
、__clone
和__destruct
,并发表了以下观点:
一般来说,定义在基类中的函数应该对该类的所有[后代]可访问。其背后的原理是,如果你在基类中定义了一个函数(即使是抽象的),那么你就表示它将对该类的任何实例(包括扩展的实例)都可用。因此,该类的任何后代都可以使用它。 [...] 我检查了为什么构造函数的行为不同,这是因为父级构造函数只有在被声明为抽象或从接口引入时才被认为是子级构造函数的原型(具有签名强制执行等)。因此,通过将构造函数声明为抽象或将其作为接口的一部分,您使其成为合同的一部分,从而可供所有层次结构访问。如果您不这样做,则构造函数彼此完全不相关(这与所有其他非静态方法不同),因此具有父构造函数并不意味着任何关于子构造函数的内容,因此父构造函数的可见性不会传递。因此,对于构造函数而言,这不是一个错误。[注意:这与J. Bruni的答案类似。] 我仍然认为__clone和__destruct最可能存在错误。 [...] 我已经提交
bug # 61782以跟踪__clone和__destruct的问题。
BaseClass
被假定为构造函数被定义的地方(它确实是),因此根据继承规则,ParentClass
有权访问它。这是个错误吗?有争议。罕见的边缘情况?当然。 - decezeBaseClass::__construct
被定义为protected
,ChildClass::__construct
继承了它而没有改变它,ParentClass::__construct
从BaseClass
继承,因此ParentClass
可以访问ChildClass::__construct
。 - deceze