У меня есть следующая иерархия классов:
class O_Base {...} class O extends O_Base {...} abstract class A_Abstract { public function save(O_Base $obj) {...} } class A extends A_Abstract { public function save(O $obj) { echo 'save!'; } } $o = new O; $a = new A; $a->save($o);
Когда я запускаю этот код, я получаю сообщение:
Строгие стандарты: декларация A :: save () должна быть совместима с A_Abstract :: save (O_Base $ obj) в .php в строке 21
Я знаю об уровне ошибки E_STRICT, но я не могу найти (и понять) причину такого поведения. Кто-нибудь может мне помочь?
Ваш код является явным нарушением принципа замены Лискова . Для абстрактного класса требуется, чтобы экземпляр O_Base
передавался методу save
, поэтому все дочерние A_Abstract
должны быть определены таким образом, чтобы они могли принимать все экземпляры O_Base
. Класс child реализует версию save
которая дополнительно ограничивает API. см. другой пример здесь
В вашем коде A
нарушает контракт, который Abstract_A
обеспечивает / описывает. Как и в реальной жизни, если вы подписываете контракт, вы должны согласиться с определенными условиями, и все согласны с терминологией, которую вы собираетесь использовать. Вот почему большинство контрактов начинается, называя стороны, а затем что-то говорит так: «Отныне г-н Х будет называться сотрудником» .
Эти термины, как и ваши абстрактные намеки типа, не подлежат обсуждению , так что в дальнейшем по линии вы не можете сказать: «О, ну … что вы называете расходами, я называю стандартную заработную плату»
Хорошо, я перестану использовать эти аналогии с половиной арсенала и просто использую простой пример, чтобы проиллюстрировать, почему то, что вы делаете, по праву не допускается.
Учти это:
abstract class Foo { abstract public function save(Guaranteed $obj); //or worse still: final public function doStuff(Guaranteed $obj) { $obj->setSomething('to string'); return $this->save($obj);//<====!!!! } } class FBar extends Foo { public function save(Guaranteed $obj) { return $obj->setFine(true); } } class FBar2 extends Foo { public function save(ChildOfGuaranteed $obj) {//FAIL: This method is required by Foo::doStuff to accept ALL instances of Guaranteed } }
См. Здесь, в этом случае вполне допустимый абстрактный класс вызывает метод save
с экземпляром Guaranteed
. Если вам разрешено вводить более строгий тип-подсказку в дочерних элементах этого класса, вы можете легко сломать этот метод doStuff
. Чтобы помочь вам защитить себя от этих видов ран, нанесенных самим себе, детям не следует применять более строгие типы методов, которые они наследуют от своих родительских классов.
Также рассмотрите сценарий, в котором мы перебираем некоторые экземпляры и проверяем, есть ли у них этот метод save
, основанный на том, что эти экземпляры являются instanceof Foo
:
$arg = new OtherChildOfGuaranteed; $array = array( 'fb' => new FBar, 'fb2' => new FBar2 ); foreach($array as $k => $class) { if ($class instanceof Foo) $class->save($arg); }
Теперь это будет нормально работать, если вы просто намекаете на Guaranteed
в сигнатуре метода. Но во втором случае мы немного изменили тип-подсказку, и этот код приведет к фатальной ошибке. Получайте удовольствие отладки этого в более сложном проекте …
PHP очень прощает, если не слишком прощает в большинстве случаев, но не здесь. Вместо того, чтобы вы почесывали голову, пока ваш слух не выпадет, PHP очень разумно дает вам голову, говоря, что реализация вашего метода нарушает контракт, поэтому вы должны исправить эту проблему.
Теперь быстрый способ обхода (и это тоже широко используется):
class FBar2 extends Foo { /** * FBar2 save implementation requires instance of ChildOfGuaranteed * Signature enforced by Foo * @return Fbar2 * @throw InvalidArgumentException **/ public function save(Guaranteed $obj) { if (!$obj instanceof ChildOfGuaranteed) throw new InvalidArgumentException(__METHOD__.' Expects instance of ChildOfGuaranteed, you passed '.get_class($obj)); //do save here... } }
Таким образом, вы просто оставляете абстрактный тип-подсказку как есть , но используйте докблоки для документирования своего кода
Вы видите разницу между сигнатурами функций?
// A_Abstract public function save(O_Base $obj) {...} // A public function save(O $obj) {
Они несовместимы.
Изменить A_Abstract на
abstract class A_Abstract { public function save(O $obj) {...} }
Независимо от того, что O
расширен от O_Base
, они представляют собой два разных объекта, а поскольку A
расширяет A_Abstract
функции сохранения должны быть совместимы друг с другом, то есть вам нужно передать один и тот же объект в O
У меня была схожая проблема.
Для меня объяснение намного проще, без крутых теорий: в PHP приведенный выше код делает переопределение метода. Но идея такого кода в моем случае была перегрузка метода, которая невозможна в PHP таким образом.
Таким образом, для создания требуемой логики (требуемой вашим приложением ) вам необходимо использовать обходные пути для создания совместимых сигнатур методов (путем наследования типов параметров или внесения некоторых параметров по выбору).