【问题标题】:PHP - Object instantiation context - Odd behaviour - Is it PHP bug?PHP - 对象实例化上下文 - 奇怪的行为 - 是 PHP 错误吗?
【发布时间】:2012-03-14 14:37:15
【问题描述】:

我不是在问为什么某些代码失败的典型问题,而是在问它为什么有效。它在编码时对我有用,我需要它失败。

案例

  • 具有声明为抽象的受保护构造函数的基本抽象类
  • 父类使用公共构造函数扩展抽象类(重写)
  • 子类使用受保护的构造函数扩展同一个抽象类

      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?
    

问题

父类实例化子类对象,它可以工作!! 怎么会这样? 据我所知,如果对象的构造函数声明为受保护,则无法实例化对象,除非仅在内部或通过继承从任何子类中进行。

父类不是子类的子类,它没有从它继承一毛钱(但两者都扩展了相同的基本抽象类),那么为什么实例化不会失败?

编辑

这种情况只发生在具有抽象构造函数的抽象 BaseClass 上。如果 BaseClass 是具体的,或者如果其受保护的构造函数不是抽象的,则实例化会按预期失败。这是 PHP 错误吗? 为了我的理智,我真的需要解释一下为什么 PHP 在这种非常特殊的情况下会以这种方式运行。

提前致谢

【问题讨论】:

  • 我正在失去对基地、父母和孩子的追踪,以及哪个延伸了哪个。你能添加一些代码示例吗?
  • 当然,我会在几分钟内发布一个代码示例
  • 我的猜测:BaseClass 被假定为定义构造函数的位置(确实如此),因此根据继承规则ParentClass 可以访问它。漏洞?值得商榷。罕见的边缘案例?当然。
  • 据我所知,它不应该被实例化,因为 ChildClass 构造函数是受保护的
  • 您可以尝试设置私有构造函数而不是受保护的吗?有趣的是发生了什么变化。

标签: php class inheritance object instantiation


【解决方案1】:

为什么有效?

因为您已从ParentClass 内部授予对BaseClass 抽象方法的访问权限。这是从ChildClass 调用的完全相同的抽象方法,尽管它的实现是在其自身上定义的。

一切都依赖于具体方法和抽象方法之间的区别。

您可以这样想:抽象方法是具有多个实现的单个方法。另一方面,每一种具体的方法都是一种独特的方法。当它与父级同名时,它覆盖父级的名称(它不实现它)。

所以,当声明abstract 时,它总是被调用的基类方法。

想想声明abstract的方法:为什么不同实现的签名不能不同?为什么子类不能声明可见性较低的方法?

不管怎样,你刚刚发现了一个非常有趣的功能。或者,如果我上面的理解不正确,并且您的预期行为是真正预期的行为,那么您发现了一个错误。

【讨论】:

  • 你是说无关类可以相互实例化,只要它们继承同一个抽象基类,其构造函数声明为抽象,而不考虑任何子类的构造函数签名吗?
  • 这些类如何“不相关”,它们实现了相同的抽象类!此外,构造函数签名是相同的(它们必须是相同的,因为它们实现了相同的抽象方法)。无论如何,我认为这个答案实际上解释了这种行为,所以你肯定可以接受它。
  • @Snifff:可能他的意思是“不相关”而不是“不相关”。无论如何,您的评论仍然适用。
  • @AhmadFarouk:是的,任何子类的签名都应该被忽略——它必须与抽象类中声明的相同。它可能更明显。您认为“可见性获胜”,而似乎“具体性获胜”。要考虑的要点是 override(重载)与 implement 之间的区别。
  • 无论如何,考虑到研究 PHP 源代码的用户“outis”的最新发现,我更倾向于将其视为“错误”。事实上,这取决于开发人员来决定,因为我们对这两种行为都有很好的解释。对您来说,一个显然是“正确”和“预期”,而另一个是“错误”。在我看来,两者都是合理的。对我来说,他们可以改变行为以符合您的要求,这也很好。结论:这是一个选择的问题,而不是是否是错误(不排除确实是错误的可能性)。
【解决方案2】:

注意:以下是使用 PHP 5.3.8 测试的。其他版本可能会表现出不同的行为。

由于没有正式的 PHP 规范,因此无法从 应该 发生什么的角度来回答这个问题。我们能得到的最接近的是 PHP 手册中关于 protected 的声明:

声明为 protected 的成员只能在类本身内以及被继承的类和父类访问。

尽管该成员可能在ChildClass 中被覆盖(保留“受保护”说明符),但它最初是在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::__构造

致命错误:无法在第 18 行访问受保护的属性 MommasBoy::$_foo

将抽象 __construct 更改为具有空实现的具体函数会产生所需的行为。

abstract class BaseClass {
   protected function __construct() {}
}

但是,非魔法方法在亲属中是可见的,无论它们是否是抽象的(大多数魔法方法必须是公共的)。

<?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();

输出:

在 LatchkeyKid 中:
MommasBoy::abstract_protected
MommasBoy::混凝土

如果您忽略警告并将魔术方法(__construct__destruct__clone 除外)声明为 protected,则它们似乎可以在亲属中访问,与非魔术方法一样。

受保护的__clone__destruct 在亲属中是不可访问的,无论它们是否抽象。这让我相信 abstract __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 的特殊处理,所以显然还有更多。

Stas Malyshev(嗨,Stas!),其中一位 PHP 开发人员,查看了 __construct__clone__destruct 并说:

一般来说,在基类中定义的函数应该可以被所有人访问 那个阶级的[后代]。它背后的基本原理是,如果你定义 你的基类中的函数(甚至是抽象的),你说它会是 可用于此类的任何实例(包括扩展实例)。所以 这个类的任何后代都可以使用它。

[...] 我检查了为什么 ctor 行为不同,这是因为父 ctor 被认为是子ctor的原型(带有签名 强制执行等)仅当它被声明为抽象的或从 界面。因此,通过将 ctor 声明为抽象或使其成为 接口,你使它成为合同的一部分,因此所有人都可以访问 等级制度。如果您不这样做,则 ctor 与每个都完全无关 其他(这对于所有其他非静态方法都不同),因此 拥有父母 ctor 并没有说明孩子 ctor,所以父母 ctor 的可见性不会延续。所以对于ctor来说不是一个错误。 [注意:这类似于 J. Bruni 的回答。]

我仍然认为这很可能是 __clone 和 __destruct 的错误。

[...]

我已提交 bug #61782 以跟踪 __clone 和 __destruct 的问题。

【讨论】:

  • 受保护的属性被拒绝访问,而同时受保护的构造函数不是!!.. ChildClass 对象不应该首先被实例化!!根据 PHP 规范。
  • @Ahmad 可以这样想:abstract 声明将方法定义实现之间的代码分开。如果您曾经使用过诸如 C 头文件之类的东西,那么您就会知道其中的区别。 definition 函数定义了可见性。属性不能声明abstract,所以比较不算。
  • @outis .. 感谢您的解释,但我仍然不明白为什么 PHP 会这样工作:) .. 它只发生在具有抽象构造函数的抽象 BaseClass 上。这是唯一的情况,其他明智的实例化失败。我仍然需要知道为什么会这样,但感谢您的努力
【解决方案3】:

编辑:构造函数的行为不同......即使没有抽象类,它也可以工作,但我发现 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 方法(包括受保护的,因为 ParentClass 是 BaseClass 子类,子类可以看到其父类的受保护方法)。同样的行为适用于 constructors 和抽象类。

【讨论】:

  • 实际上没有,因为根据我的示例,如果 BaseClass 是具体的而不是抽象的,则对象实例化将失败..根据您的解释,它应该双向成功。如果 BaseClass 构造函数不是抽象的,它也会失败。因此会导致非常特殊的情况,即 BaseClass 是抽象的,并且它的构造函数也被声明为抽象
  • @AhmadFarouk 你是对的。但它应该在没有抽象类的情况下工作,但构造函数上下文似乎存在已知问题。更新了我的答案。
  • 根据我的理解,无论如何实例化都应该失败,因为 ParentClass 没有从 ChildClass 继承一分钱。除了我们现在讨论的情况之外,所有可能的情况都会发生预期的失败
  • @AhmadFarouk 我认为有两个方面:1)您不希望允许从另一个“非直接子”类实例化具有受保护构造函数的类 2)另一方面,您想要所有方法保持一致并以相同的方式行事。无论如何,抽象方法与否不应改变方法的可见性 - 提交错误报告。
  • @meze .. 你现在得到了我.. 抽象方法与否不应该改变方法访问(可见性)。所以受保护的访问修饰符是允许或拒绝访问的管理规则,而不是抽象声明。
【解决方案4】:

我想知道引擎盖下的抽象实现是否存在错误,或者是否存在我们遗漏的微妙问题。将 BaseClass 从抽象更改为具体会产生您所追求的致命错误(为我的理智重命名的类)

编辑:我同意@deceze 在他的 cmets 中所说的话,这是抽象实现的边缘案例,并且可能是一个错误。这至少是一种解决方法,它提供了预期的行为,但有一些丑陋的技术(伪装的抽象基类)。

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 上 第 46 行

【讨论】:

  • 这是我需要发生的致命错误。为什么如果声明为抽象基类不会发生这种情况。我真的需要解释一下为什么 PHP 在抽象基类方面的行为方式与具体类不同
  • 看看其他人在说什么,包括其他答案。这是在抽象上下文中使用受保护构造函数的边缘案例。确保您接受其中一个答案,并使您的答案朝着正确的方向前进:D
  • “预期行为”究竟是什么?目前的行为可以说是可以预期的。 :)
  • @deceze 只是让ChildClassComposed 的构造函数在从ChildClassComposer 中调用它时引发致命事件,尽管我同意;如果您知道自己在做什么,则可以预期当前行为,大声笑。只是想帮助 OP 获得 他的 期望的行为 :)
  • 我不是在寻求解决方法。我知道很多解决方法。我只需要知道为什么抽象类会这样工作。为了我的理智,我也需要解释一下,否则这将是一个 PHP 错误。
猜你喜欢
  • 1970-01-01
  • 2014-09-09
  • 1970-01-01
  • 1970-01-01
  • 2013-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-27
相关资源
最近更新 更多