Интерфейсы PHP 7, хинтинг типа возвращаемого значения и self

89

ОБНОВЛЕНИЕ : PHP 7.4 теперь поддерживает ковариацию и контравариантность, что решает основную проблему, поднятую в этом вопросе.


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

Гордон М.
источник
Я думаю, что это ошибка при указании типа возвращаемого значения PHP, возможно, вам стоит поднять это как ошибку ; но вряд ли какое-либо исправление попадет в PHP 7.1 на этой поздней стадии
Марк Бейкер
Поскольку последняя бета-версия 7.1 была запущена в сеть несколько дней назад, маловероятно, что какое-либо исправление попадет в 7.1.
Шарлотта Дюнуа,
Ради интереса, где вы читаете свою интерпретацию того, как selfдолжен работать возвращаемый тип?
Адам Кэмерон
@Adam: Кажется логичным для selfобозначения «Вернуть экземпляр, на котором вы его вызывали, а не какой-то другой экземпляр, реализующий тот же интерфейс». Кажется, я помню, что у Java был аналогичный тип возвращаемого значения (хотя я давно не занимался программированием на Java)
GordonM
1
Привет, Гордон. Ну, если это где-то не задокументировано, я бы не стал рассчитывать на то, что может быть логичным. TBH с ситуацией, которую я описал, я был бы настолько декларативен, насколько это возможно, и использовал бы iFoo в качестве возвращаемого типа. Есть ситуация, в которой это на самом деле не сработает? (Я понимаю, что это был «совет» / мнение, а не «ответ».
Адам Кэмерон,

Ответы:

94

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  {...}
}
user3942918
источник
18
Тем не менее, я ожидал, что возвратный тип-намек staticсработает, но он даже не распознается
Марк Бейкер,
Я подумал, что это так, и вот как я собираюсь решить проблему. Однако я бы предпочел просто использовать: self, если это возможно, потому что если класс реализует интерфейс, то возвращаемое значение self неявно также является возвращаемым значением экземпляра интерфейса.
GordonM
19
Пол, удаление комментариев, которые вы здесь удалили, на самом деле вредно, потому что (A) теряется важная информация и (B) нарушается поток обсуждения по сравнению с другими комментариями. Я не вижу причин, по которым нужно было удалить ваши комментарии, касающиеся Марка и Гордона. Фактически, вы делаете это повсюду, и это нужно прекратить. Нет абсолютно никаких причин возвращаться к вопросу годичной давности и удалять все свои комментарии, полностью разрушая поток обсуждения. На самом деле это вредно и разрушительно.
Коди Грей
К части вашей цитаты, приведенной здесь, есть важное предисловие: « Ковариантные возвращаемые типы считаются правильным типом и используются во многих других языках (C ++ и Java, но не в C #, как мне кажется). Этот RFC первоначально предлагал ковариантные возвращаемые типы, но был изменен на инвариантный из-за нескольких проблем. ". Мне любопытно, какие проблемы были с PHP. Их выбор дизайна вызывает несколько ограничений, которые также вызывают странные проблемы с характеристиками, делая их несколько бесполезными во многих случаях, если вы не ослабите ограничения типа возвращаемого значения (как показано в некоторых ответах ниже). Очень неприятно.
Джон Панкост,
1
@MarkBaker, возвращаемый тип staticдобавляется в PHP 8.
Рикардо Босс,
16

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

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

class Foo implements iFoo
{
    public function bar (string $baz) : Foo  {...}
}
Габор
источник
2
Или вместо того, чтобы Fooпросто использовать self.
вместо
2

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

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

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

Он работает с PHP 7.4.

вместо
источник
0

Мне это кажется ожидаемым поведением.

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

Пояснение:

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

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

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

Моше Кац
источник
Итак, декларирование selfпохоже на декларирование MyClass::class?
peterchaula
1
@Laser Да, это так.
Моше Кац
4
Но если Foo реализует iFoo, то Foo по определению относится к типу iFoo
GordonM