Intereting Posts
PHP, отображающий html-адрес электронной почты на странице html Есть ли стандарт PHPCS, предназначенный для PHP docblocks? Невозможно вставить путь для нескольких изображений в таблице базы данных с помощью codeigniter Физическое расстояние между двумя местами Получение ошибки при использовании COLLATE в запросе Mysql повышение скорости проверки наличия файлов проверить, завершена ли загрузка Как я могу узнать, на какой странице установлено приложение Facebook или какая страница загружает мое приложение? Facebook API – Как получить доступ к моему фотоальбому? Смешивание php и javascript – при каких условиях? ejabberdctl не работает с PHP Как я могу вернуть представление из вызова ajax в Laravel 5 Не удается подключиться к MySQL на Live с Laravel 5 Мой запрос sql для получения значений средней строки с использованием функции GROUP BY Как сохранить несколько повторяемых полей как массив в базе данных?

Строгие стандарты PHP: декларация должна быть совместимой

У меня есть следующая иерархия классов:

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 таким образом.

Таким образом, для создания требуемой логики (требуемой вашим приложением ) вам необходимо использовать обходные пути для создания совместимых сигнатур методов (путем наследования типов параметров или внесения некоторых параметров по выбору).