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

Интерфейсы PHP 7, подсказки типа возврата и

Я столкнулся с чем-то вроде проблемы с использованием типа возвращаемого типа в PHP 7. Я понимаю, что намек : self означает, что вы намерены реализовать сам класс реализации. Поэтому я использовал : self в своих интерфейсах, чтобы указать это, но когда я попытался реализовать интерфейс, у меня появились ошибки совместимости.

Ниже приведена простая демонстрация проблемы, с которой я столкнулся:

interface iFoo
{
    public function bar (string $baz) : self;
}

class Foo implements iFoo
{

    public function bar (string $baz) : self
    {
        echo $baz . PHP_EOL;
        return $this;
    }
}

(new Foo ()) -> bar ("Fred") 
    -> bar ("Wilma") 
    -> bar ("Barney") 
    -> bar ("Betty");

Ожидаемый результат:

Фред Вильма гулянка Бетти

Я действительно получаю:

PHP Неустранимая ошибка: декларация Foo:: bar (int $baz): Foo должен быть совместим с iFoo:: bar (int $baz): iFoo в test.php в строке 7

Дело в том, что Foo - это реализация iFoo, так как я могу сказать, что реализация должна быть полностью совместима с данным интерфейсом. Я мог бы, вероятно, исправить эту проблему, изменив либо интерфейс, либо реализующий класс (или оба), чтобы вернуть намек на интерфейс по имени вместо использования self, но я понимаю, что семантически self означает "вернуть экземпляр класса вы просто вызвали метод на". Поэтому изменение его в интерфейсе в теории означало бы, что я мог бы вернуть любой экземпляр того, что реализует интерфейс, когда мое намерение для вызванного экземпляра - это то, что будет возвращено.

Является ли это надзором в PHP или это преднамеренное дизайнерское решение? Если это первый, есть шанс увидеть его исправленным в PHP 7.1? Если нет, то каков правильный способ возврата, намекая, что ваш интерфейс ожидает, что вы вернете экземпляр, который вы только что назвали методом для цепочки?

4b9b3361

Ответ 1

self не относится к экземпляру, он относится к текущему классу. Интерфейс не имеет права указывать, что тот же экземпляр должен быть возвращен - использование self в том виде, в котором вы пытаетесь, будет только обеспечивать, чтобы возвращаемый экземпляр был одного класса.

Тем не менее, декларации возвращаемого типа в PHP должны быть инвариантными, а то, что вы пытаетесь, является ковариантным.

Ваше использование self эквивалентно:

interface iFoo
{
    public function bar (string $baz) : iFoo;
}

class Foo implements iFoo
{

    public function bar (string $baz) : Foo  {...}
}

который не разрешен.


Объявление типа декларации RFC :

Принуждение объявленного типа возврата во время наследования инвариантно; это означает, что, когда подтип переопределяет родительский метод, возвращаемый тип дочернего элемента должен точно соответствовать родительскому элементу и не может быть опущен. Если родительский элемент не объявляет тип возвращаемого значения, дочерний объект может объявить его.

...

Этот RFC первоначально предлагал ковариантные типы возврата, но был изменен на инвариантный из-за нескольких проблем. В некоторый момент в будущем можно добавить ковариантные типы возврата.


В настоящее время, по крайней мере, лучшее, что вы можете сделать, это:

interface iFoo
{
    public function bar (string $baz) : iFoo;
}

class Foo implements iFoo
{

    public function bar (string $baz) : iFoo  {...}
}

Ответ 2

Это также может быть решением, что вы явно не определяете тип возврата в интерфейсе, только в PHPDoc, а затем вы можете определить определенный тип возвращаемого значения в реализациях:

interface iFoo
{
    public function bar (string $baz);
}

class Foo implements iFoo
{
    public function bar (string $baz) : Foo  {...}
}

Ответ 3

Это выглядит как ожидаемое поведение для меня.

Просто измените метод Foo::bar, чтобы вернуть iFoo вместо self и сделать с ним.

Пояснение:

self, как используется в интерфейсе, означает "объект типа iFoo".
self, как используется в реализации, означает "объект типа Foo".

Следовательно, типы возврата в интерфейсе и реализации явно не совпадают.

В одном из комментариев упоминается Java и будет ли у вас эта проблема. Ответ: да, у вас была бы такая же проблема, если бы Java разрешала вам писать такой код, чего нет. Поскольку Java требует, чтобы вы использовали имя типа вместо PHP self shortcut, вы никогда не увидите этого. (См. здесь для обсуждения аналогичной проблемы на Java.)

Ответ 4

В случае, если вы хотите принудительно вызвать интерфейс, этот метод вернет объект, но тип объекта будет не типом интерфейса, а самим классом, тогда вы можете написать это так:

interface iFoo {
    public function bar (string $baz) : object;
}

class Foo implements iFoo {
    public function bar (string $baz) : self  {...}
}

Работает с PHP 7.4.

Ответ 5

Запуск некоторых тестов с PHP 7.3, я не могу жаловаться (даже со строгим), когда я делаю это...

interface A {
 function f() {}
}

interface B {
 function f():self {}
}

Либо мой тест не пройден, либо PHP изменил некоторые вещи. Вообще говоря, если вы уменьшите возможные типы возвращаемых данных, это не нарушит ООП. Как и в любом классе, использующем этот метод, он может обрабатывать любые возвращаемые значения, включая подмножество возвращаемых типов. Противоположное - примерно случай для параметров.

Они реализовали это в 7.2.