Подтвердить что ты не робот

Строгие стандарты 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, но я не могу найти (и понять) причину такого поведения. Кто-нибудь может мне помочь?

4b9b3361

Ответ 1

Ваш код является явным нарушением принципа замены Лискова. Для абстрактного класса требуется, чтобы экземпляр O_Base передавался методу save, поэтому все дочерние элементы A_Abstract должны быть определены таким образом, чтобы они могли принимать все экземпляры O_Base. Ваш дочерний класс реализует версию 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...
    }
}

Итак, вы просто оставляете абстрактный тип-подсказку как есть, но используйте докблоки для документирования вашего кода

Ответ 2

Вы видите разницу между сигнатурами функций?

// A_Abstract
public function save(O_Base $obj) {...}

// A
public function save(O $obj) {

Они несовместимы.

Ответ 3

Измените A_Abstract на

abstract class A_Abstract {
    public function save(O $obj) {...}
}

Независимо от того, что O расширяется от O_Base, это два разных объекта, а в качестве A extends A_Abstract функции сохранения должны быть совместимы друг с другом, то есть вам нужно передать тот же объект, O.

Ответ 4

У меня была схожая проблема.

Для меня объяснение намного проще, без крутых теорий: в PHP приведенный выше код делает метод переопределяющим. Но идея такого кода в моем случае была методом перегрузки, что невозможно в PHP таким образом.

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