重写私有方法时的奇怪行为

2022-08-30 19:02:48

请考虑以下代码段:

class foo {
    private function m() {
        echo 'foo->m() ';
    }
    public function call() {
        $this->m();
    }
}

class bar extends foo {
    private function m() {
        echo 'bar->m() ';
    }
    public function callbar() {
        $this->m();
    }
}

$bar = new bar;

$bar->call();
$bar->callbar();

现在,更改方法的可见性,我得到:
( for ,m()+public-private)

Visibility              bar->call()    bar->callbar() 
======================================================
-foo->m(), -bar->m()    foo->m()       bar->m()
-foo->m(), +bar->m()    foo->m()       bar->m()
+foo->m(), -bar->m()    ERROR          ERROR
+foo->m(), +bar->m()    bar->m()       bar->m()

(protected似乎表现得像)。public

我期望一切都像声明时一样。但是,尽管 和 本质上是相同的,但它们根据 in 和 的可见性产生不同的结果。为什么会发生这种情况?publicfoo->call()bar->callbar()m()foobar


答案 1

继承/重写私有方法

在 PHP 中,子类中的方法(包括私有方法)可以是:

  • 复制;保持原始功能的范围。
  • 已替换(如果需要,“已覆盖”)。

您可以使用以下代码看到这一点:

<?php
class A {
    //calling B::h, because static:: resolves to B::
    function callH() { static::h(); }
    private function h() { echo "in A::h"; }
}
class B extends A {
    //not necessary; just to make explicit what's happening
    function callH() { parent::callH(); }
}
$b = new B;
$b->callH();

现在,如果重写私有方法,则其新作用域将不是 A,而是 B,并且调用将失败,因为在作用域中运行:A::callH()A

<?php
class A {
    //calling B::h, because static:: resolves to B::
    function callH() { static::h(); }
    private function h() { echo "in A::h"; }
}
class B extends A {
    private function h() { echo "in B::h"; }
}
$b = new B;
$b->callH(); //fatal error; call to private method B::h() from context 'A'

调用方法

这里的规则如下:

  • 查看对象的实际类的方法表(在您的情况下为 )。bar
    • 如果这会产生私有方法
      • 如果定义方法的作用域与调用函数的作用域相同,并且与对象的类相同,请使用它。
      • 否则,请在父类中查找与调用函数的作用域相同且同名的私有方法。
      • 如果未找到满足上述要求之一的方法,则失败。
    • 如果这会产生公共/受保护的方法
      • 如果方法的范围被标记为已更改,则我们可能已使用公共/受保护方法覆盖了私有方法。因此,在这种情况下,如果另外,如果存在与为调用函数范围定义的名称相同的私有方法,请改用该方法。
      • 否则,请使用 found 方法。

结论

  1. (均为私人)对于 ,的作用域是 。调用 在 的方法表中引发查找 for ,生成私有 。但是,的作用域与调用作用域不同,后者 .该方法在向上遍历层次结构时找到,并改用。bar->call()callfoo$this->m()barmbar::m()bar::m()foofoo:m()
  2. (私人在, 公共在 )的范围仍然是 .查找将生成一个公共 .但是,其作用域被标记为已更改,因此在方法 的调用作用域的函数表中进行查找。这将生成与调用作用域具有相同作用域的私有方法,因此将改用该方法。foobarcallfoobar::m()foom()foo:m()
  3. 这里没什么可看的,错误是因为能见度降低了。
  4. (均为公共)的范围仍然是 .查找将生成一个公共 .它的范围没有被标记为已更改(它们都是公开的),因此被使用。callfoobar::m()bar::m()

答案 2

私有方法不可重写,因为私有方法甚至对其子类也不可见。将方法定义为受保护意味着它在类本身或其子类之外不可见。

如果您有一个要从父类中使用的方法,但希望子类能够修改其行为,并且不希望此方法在外部可用,请使用 。如果希望父类中的功能不能由子类以任何方式修改,请将该方法定义为 。protectedprivate

编辑:为了进一步澄清,如果您在父类和子类中有两个同名的方法,并且这些方法被定义为私有,则本质上子类方法与父方法完全没有关系。如前所述,私有方法对子类是完全不可见的。

请考虑以下情况:

class foo {
    private function m() {
        echo 'foo->m() ';
    }
    private function z() { echo "foo->z();"; }

    public function call() {
        $this->m();
    }
}

class bar extends foo {
    private function m() {
        echo 'bar->m() ';
    }
    public function callbar() {
        $this->m();
    }
    public function callz()
    {
       $this->z();
    }
}

呼叫 ;将产生一个 ERROR,因为 z 根本不存在于子类中,甚至不能作为继承方法。$bar->callz()


推荐