PHP - 对象实例化上下文 - 奇怪的行为 - 这是PHP错误吗?

时间:2012-02-24 05:18:52

标签: php class inheritance object instantiation

我不是在问一个典型的问题,为什么有些代码失败了,但我问的是为什么它有效。在编码时我和我合作过,我需要它失败。

案例

  • 带有受保护构造函数的基本抽象类,声明为abstract
  • 父类使用公共构造函数(Over ridding)扩展抽象类
  • 子类使用受保护的构造函数扩展相同的抽象类

      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在这种非常具体的情况下会以这种方式运行。

提前致谢

4 个答案:

答案 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)

编辑:构造函数行为不同...即使没有抽象类也可以工作但我发现this test测试相同的情况,看起来这是技术上的限制 - 下面解释的东西不起作用现在是施工人员。

没有错误。您需要了解访问属性是否适用于对象的上下文。扩展类时,您的类将能够在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行