我不是在问一个典型的问题,为什么有些代码失败了,但我问的是为什么它有效。在编码时我和我合作过,我需要它失败。
案例
子类使用受保护的构造函数扩展相同的抽象类
abstract class BaseClass {
abstract protected function __construct();
}
class ChildClass extends BaseClass {
protected function __construct(){
echo 'It works';
}
}
class ParentClass extends BaseClass {
public function __construct() {
new ChildClass();
}
}
// $obj = new ChildClass(); // Will result in fatal error. Expected!
$obj = new ParentClass(); // that works!!WHY?
问题
父类实例化子类对象,它有效!! 怎么会这样呢? 据我所知,如果对象的构造函数声明为protected,则无法实例化对象,除了内部或通过继承从任何子类内部。
父类不是子类的子类,它不从它继承一角钱(但都扩展了相同的基本抽象类),那么如何实例化不会失败?
修改
这种情况只发生在一个抽象的BaseClass上,它也有一个抽象的构造函数。如果BaseClass是concerete,或者如果它的受保护构造函数不是抽象的,那么实例化会按预期失败..这是一个PHP错误吗? 为了我的理智,我需要解释为什么PHP在这种非常具体的情况下会以这种方式运行。
提前致谢
答案 0 :(得分:3)
注意:以下是使用PHP 5.3.8测试的。其他版本可能表现出不同的行为。
由于没有正式的PHP规范,因此从应该发生的角度来看,没有办法回答这个问题。我们可以得到的最接近的是PHP手册中关于protected的声明:
声明受保护的成员只能在类本身以及继承和父类中访问。
虽然成员可能会在ChildClass
中被覆盖(保留“受保护”的说明符),但它最初在BaseClass
中被声明,因此它在{的后代中仍然可见{1}}。
与此解释直接相反,比较受保护财产的行为:
BaseClass
输出:
In LatchkeyKid: MommasBoy::__construct Fatal error: Cannot access protected property MommasBoy::$_foo in - on line 18
将抽象<?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();
更改为具有空实现的具体函数会产生所需的行为。
__construct
然而,非魔法方法在亲属中是可见的,无论它们是否是抽象的(大多数魔术方法必须是公开的)。
abstract class BaseClass {
protected function __construct() {}
}
输出:
In LatchkeyKid: MommasBoy::abstract_protected MommasBoy::concrete
如果您忽略警告并将魔术方法(<?php
abstract class BaseClass {
abstract protected function abstract_protected();
protected function concrete() {}
}
class MommasBoy extends BaseClass {
/* accessible in relatives */
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();
,__construct
和__destruct
除外)声明为__clone
,则它们似乎可以在亲属中访问,如同非魔法。
受保护的protected
和__clone
无法在亲属中访问,无论他们是否是抽象的。这让我相信抽象__destruct
的行为是一个错误。
__construct
输出:
In LatchkeyKid: Fatal error: Call to protected MommasBoy::__clone() from context 'LatchkeyKid' in - on line 16
zend_vm_def.h(具体来说,ZEND_CLONE
操作码处理程序)强制执行对<?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();
的访问。这是对方法的访问检查的补充,这可能是它具有不同行为的原因。但是,我没有看到访问__clone
的特殊处理,所以显然还有更多。
PHP开发人员之一Stas Malyshev(嗨,Stas!)调查了__destruct
,__construct
和__clone
并说了这些话:
通常,所有人都可以访问基类中定义的函数 那个班级的[后代]。其背后的基本原理是,如果你定义 你的基类中的函数(甚至是抽象的),你会说它会 可用于此类的任何实例(包括扩展实例)。所以 这个类的任何后代都可以使用它。
[...]我检查了为什么ctor表现不同,这是因为父母ctor 被认为是儿童ctor的原型(签名 强制执行等)只有在宣布抽象或从中提出 接口。因此,通过将ctor声明为抽象或使其成为一部分 界面,你使它成为合同的一部分,因此所有人都可以访问 层次结构。如果你不这样做,ctors与每个人完全无关 其他(这与所有其他非静态方法不同)因此 有父母ctor没有说儿童ctor,所以父母 ctor的能见度不会延续。所以对于ctor来说不是一个bug。 [注意:这与J. Bruni的回答类似。]
我仍然认为这很可能是__clone和__destruct的错误。
[...]
我已提交bug #61782来跟踪__clone和__destruct的问题。
答案 1 :(得分:3)
为什么会这样?
因为从ParentClass
内部您已授予BaseClass
对抽象方法的访问权限。这是一个从ChildClass
调用的抽象方法,尽管它的实现是自己定义的。
所有都依赖于具体和抽象方法之间的区别。
您可以这样思考:抽象方法是具有多个实现的单个方法。另一方面,每种具体方法都是一种独特的方法。如果它的名称与其父级相同,则会覆盖父级的名称(它不会实施)。
因此,当声明abstract
时,它始终是被调用的基类方法。
考虑一个声明为abstract
的方法:为什么不同实现的签名不能有所区别?为什么子类不能声明具有较低可见性的方法?
无论如何,您刚刚找到了一个非常有趣的功能。或者,如果我上面的理解不正确,并且您的预期行为是真正预期的行为,那么您就发现了一个错误。
答案 2 :(得分:2)
没有错误。您需要了解访问属性是否适用于对象的上下文。扩展类时,您的类将能够在BaseClass的上下文中查看方法。 ChildClass和ParentClass都在BaseClass上下文中,因此他们可以看到所有BaseClass方法。你为什么需要它?对于多态性:
class BaseClass {
protected function a(){}
}
class ChildClass extends BaseClass {
protected function a(){
echo 'It works';
}
}
class ParentClass extends BaseClass {
public function b(BaseClass $a) {
$a->a();
}
public function a() {
}
}
无论你传递给ParentClass :: b()方法的子节点,你都能够访问BaseClass方法(包括protected,因为ParentClass是BaseClass子节点,子节点可以看到父节点的受保护方法)。相同的行为适用于构造函数和抽象类。
答案 3 :(得分:1)
我想知道是否有一些错误的抽象实现,或者如果有一个微妙的问题,我们就会失踪。将BaseClass从abstract更改为concrete会产生您之后遇到的致命错误(为了我的理智而重命名的类)
编辑:我同意@deceze在他的评论中所说的,它是抽象实现的边缘情况,可能是一个错误。这至少是一种解决方法,它提供了预期的行为,这是一种丑陋的技术(假设的抽象基类)。class BaseClass
{
protected function __construct()
{
die('Psuedo Abstract function; override in sub-class!');
}
}
class ChildClassComposed extends BaseClass
{
protected function __construct()
{
echo 'It works';
}
}
// Child of BaseClass, Composes ChildClassComposed
class ChildClassComposer extends BaseClass
{
public function __construct()
{
new ChildClassComposed();
}
}
PHP致命错误:调用受保护的ChildClassComposed :: __ construct() 来自上下文'ChildClassComposer' /Users/quickshiftin/junk-php/change-private-of-another-class.php on 第46行