Странное поведение при переопределении приватных методов - PullRequest
18 голосов
/ 21 сентября 2010

Рассмотрим следующий фрагмент кода:

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

Теперь, изменив видимость метода 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).

Я ожидал, что все будет вести себя так же, как и тогда, когда оба объявлены public. Но хотя foo->call() и bar->callbar() по сути одно и то же, они дают разные результаты в зависимости от видимости m() в foo и bar. Почему это происходит?

Ответы [ 3 ]

25 голосов
/ 21 сентября 2010

Наследование / переопределение приватных методов

В 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. (Оба частные) Для bar->call() область действия call равна foo.Вызов $this->m() вызывает поиск в таблице методов bar для m, в результате чего получается bar::m().Однако область действия bar::m() отличается от области вызова, которая foo.Метод foo:m() обнаруживается при обходе иерархии и используется вместо него.
  2. (Private в foo, public в bar). Область действия call по-прежнему foo.Поиск дает общедоступный bar::m().Однако его область помечена как изменившаяся, поэтому в таблице функций области вызова foo выполняется поиск для метода m().Это дает закрытый метод foo:m() с той же областью, что и область вызова, поэтому он используется вместо этого.
  3. Ничего здесь не видно, ошибка из-за снижения видимости.
  4. (Оба общедоступных)область действия call по-прежнему foo.Поиск дает общедоступный bar::m().Область действия не помечена как измененная (они оба общедоступны), поэтому используется bar::m().
13 голосов
/ 21 сентября 2010

Закрытый метод не может быть переопределен, так как закрытый метод не виден даже его подклассам. Определение метода как защищенного означает, что он не виден вне самого класса или его подклассов.

Если у вас есть метод, который вы хотите использовать из родительского класса, но хотите, чтобы дети могли изменять его поведение, и не хотите, чтобы этот метод был доступен извне, используйте protected. Если вы хотите, чтобы функциональность в родительском классе не могла быть изменена каким-либо образом подклассами, определите метод как private.

РЕДАКТИРОВАТЬ: для уточнения, если у вас есть два метода с одинаковым именем в родительском и подклассе, и эти методы определены как частные, по существу, метод подкласса не имеет абсолютно никакого отношения к родительскому методу , Как уже говорилось, закрытый метод полностью невидим для подкласса.

Учтите это:

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

Звонок $bar->callz(); собирается произвести ОШИБКУ, потому что z вообще не существует в подклассе, даже как унаследованный метод.

3 голосов
/ 21 сентября 2010

Согласно руководству по PHP:

Участники, объявленные личными, могут только быть доступным для класса, который определяет член.

http://www.php.net/manual/en/language.oop5.visibility.php

EDIT

они дают разные результаты в зависимости на видимости m () в foo и бар. Почему это происходит?

Если m() в foo общедоступно, оно может быть переопределено. В этом случае m() из bar переопределяет m() в foo.

...