it-swarm.com.ru

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? Если нет, то каков правильный способ возврата, указывающий на то, что ваш интерфейс ожидает, что вы вернете экземпляр, для которого вы только что вызвали метод, для создания цепочки? 

55
GordonM

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

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

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

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

class Foo implements iFoo
{

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

что не разрешено.


Возвращаемые объявления типов RFC has это сказать :

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

...

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


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

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

class Foo implements iFoo
{

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

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

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

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

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

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

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

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

0
instead

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

Просто измените свой метод Foo::bar, чтобы он возвращал iFoo вместо self, и покончите с этим.

Объяснение:

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

Поэтому типы возвращаемых данных в интерфейсе и реализации явно не совпадают.

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

0
Moshe Katz

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

interface A {
 function f() {}
}

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

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

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

0
jgmjgm