Интерфейсы 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  {...}
}

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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.