Позднее статическое связывание в php 5.3

Пожалуйста, просмотрите приведенный ниже код, это из руководства по php.

<?php
class A {
    private function foo() {
        echo "success!\n";
    }
    public function test() {
        $this->foo();
        static::foo();
    }
}

class B extends A {
   /* foo() will be copied to B, hence its scope will still be A and
    * the call be successful */
}

class C extends A {
    private function foo() {
        /* original method is replaced; the scope of the new one is C */
    }
}

$b = new B();
$b->test();
$c = new C();
$c->test();   //fails
?> 

может ли кто-нибудь объяснить, что именно здесь происходит?

Почему foo() будет скопирован в B?


person Poonam Bhatt    schedule 17.06.2012    source источник
comment
foo() не будет скопирован, он наследуется своим родителем A. Я не понимаю вашей конкретной проблемы. Конечно, это не удается, потому что вы сделали свой метод foo в C закрытым. Сделайте это общедоступным, и это сработает.   -  person dan-lee    schedule 17.06.2012
comment
как получится, что foo будет унаследован? ... так как это частный метод... это пример из руководства по php   -  person Poonam Bhatt    schedule 17.06.2012
comment
Да просто недоступно.   -  person dan-lee    schedule 17.06.2012
comment
вся страница, из которой вы скопировали пример в Руководстве, объясняет Позднее статическое связывание. Не могли бы вы уточнить, что вы не понимаете или почему страница неясна для вас?   -  person Gordon    schedule 17.06.2012
comment
в приведенном выше примере... в классе B... написано, что foo() будет скопировано в B... вот что меня смущает...   -  person Poonam Bhatt    schedule 17.06.2012
comment
@PoonamBhatt см. главу на php.net/manual/en/language. oop5.visibility.php   -  person Gordon    schedule 17.06.2012
comment
тл; др. Если вы объявите C::foo защищенным, проблема исчезнет   -  person madfriend    schedule 17.06.2012


Ответы (2)


Теперь я вспомнил, почему позднее статическое связывание полезно. К сожалению, пример с php.net объяснен плохо. См. этот (измененный) пример:

<?php
class A {
    private function foo() {
        echo __CLASS__;
    }
    public function test() {
        $this->foo();
        static::foo();
    }
}

class B extends A {
    public function foo()
    {
        echo __CLASS__;
    }
}

$b = new B();
$b->test();
?>

Если вы запустите приведенный выше код, вы заметите, что он работает, и вы получите AB. Почему?

  • Это работает, потому что test() является общедоступным получателем для foo(), поэтому не имеет значения, вызываете ли вы test() из объекта типа A или из объекта типа B, который наследуется от A, потому что test() всегда будет иметь доступ к закрытым членам класса, в котором он определен.
  • В первом случае $this->foo(); всегда будет вызывать A::foo(), потому что привязка выполняется локально, внутри области действия A, что иногда весьма нежелательно. См. этот комментарий: http://www.php.net/manual/en/language.oop5.late-static-bindings.php#83502
  • Во втором случае static::foo(); указывает интерпретатору определить тип $b и посмотреть, в каком классе попытаться найти foo(). В этом случае B::foo() рассматривается как метод переопределения для A::foo(), поэтому, по сути, если B::foo() существует, он будет вызван, иначе интерпретатор будет искать A::foo().
  • Попробуйте пометить B::foo() как частный и запустить приведенный мной пример, чтобы посмотреть, что произойдет. Я думаю, что этот тест и мои разглагольствования выше прояснят вопрос для вас;)

Кроме того, я принимаю любые комментарии к моим пунктам выше, потому что я довольно давно не использовал PHP.

person Mihai Todor    schedule 17.06.2012
comment
Спойлер к моему вопросу: сначала может показаться немного странным, что он попытается получить доступ к B::foo() с точки зрения A (не забывайте, что test() определен в A), но в противном случае он нарушит инкапсуляцию, поэтому вот почему B::foo() должен быть общедоступным, чтобы работать как метод переопределения для A::foo(). - person Mihai Todor; 17.06.2012
comment
Второй пункт в вашем списке верен только для этого примера, а не вообще. Если A::foo() объявлен общедоступным или защищенным, результатом будет BB. Это связано с тем, что частные функции не могут быть перезаписаны. - person Niko; 17.06.2012
comment
Ну да. Надеюсь, мой пример не предполагает такого заблуждения :) Кстати, спасибо за правку. - person Mihai Todor; 17.06.2012

foo не копируется в B как таковой (он наследуется, но невидим; см. комментарий Гордона ниже). B наследует A->foo, который вызывает A->test. Чтобы продемонстрировать, посмотрите, что происходит, когда вы echo __CLASS__ из test и foo (и удаляете вызов static::foo(), который вызывает ошибку):

class A {
    private function foo() {
        echo __CLASS__."->foo\n";
        echo "success!\n";
    }
    public function test() {
        echo __CLASS__."->test\n";
        $this->foo();
    }
}

Вывод:

A->test
A->foo
success!
A->test
A->foo
success!

Это одна из основ наследования, поскольку она связана с сокрытием информации/инкапсуляция. Это позволяет вам делать такие вещи:

class ListOfThings {
    // internal structure (top secret!)
    private $_list = array();

    // add item to list
    public function add($item) {
        $this->_list[] = $item;
    }

    // return number of items in list
    public function count() {
        return count($this->_list);
    }
}

class ListOfStates extends ListOfThings {

    // do we have all 50 states?
    public function allStatesListed() {
        return $this->count() === 50;
    }

    // try to access internal structure of ListOfThings
    public function accessInternalStructure() {
        sort($this->_list);
    }
}

$states = new ListOfStates;
$states->add('ME');
$states->add('NH');
$states->add('VT');
$states->add('RI');
$states->add('CT');
var_dump($states->count());
var_dump($states->allStatesListed());
$states->accessInternalStructure();

Вывод:

int(5)
bool(false)

Warning: sort() expects parameter 1 to be array, null given...

Как видите, ListOfStates может использовать все общедоступные функции ListOfThings, хотя все эти функции зависят от частной переменной $_list. При этом ListOfStates не может напрямую манипулировать $_list; он может воздействовать на $_list только косвенно через общедоступные функции, определенные в ListOfThings.

Посетите страницу Видимость в документации по PHP, чтобы получить более подробную информацию о такие вещи.

person Jonathan S.    schedule 17.06.2012
comment
foo - это частный метод... и в соответствии с видимостью частный метод не наследуется дочернему классу - person Poonam Bhatt; 17.06.2012
comment
@Poonam это унаследовано. Но он не виден, поэтому вы не можете получить к нему доступ. См. codepad.viper-7.com/B7OWyr. - person Gordon; 17.06.2012
comment
@Gordon Гордон Это не очень хороший пример, потому что вы используете переменную, а не функцию. Переменные содержат данные, которые обязательно должны быть привязаны к экземпляру, так что разница есть. - person Niko; 17.06.2012