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

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


person GordonM    schedule 21.08.2016    source источник
comment
Я думаю, что это ошибка при указании типа возвращаемого значения PHP, возможно, вам следует указать ее как ошибку; но вряд ли какое-либо исправление попадет в PHP 7.1 на этой поздней стадии   -  person Mark Baker    schedule 22.08.2016
comment
Поскольку последняя бета-версия 7.1 была запущена несколько дней назад, маловероятно, что какое-либо исправление попадет в 7.1.   -  person Charlotte Dunois    schedule 22.08.2016
comment
Ради интереса, где вы читаете свою интерпретацию того, как должен работать возвращаемый тип self?   -  person Adam Cameron    schedule 22.08.2016
comment
@ Адам: Кажется, что для self логично означать «Вернуть экземпляр, в котором вы его вызывали, а не какой-то другой экземпляр, реализующий тот же интерфейс». Кажется, я помню, что у Java был аналогичный тип возвращаемого значения (хотя я давно не занимался программированием на Java)   -  person GordonM    schedule 22.08.2016
comment
Привет, Гордон. Что ж, если это где-то не задокументировано, я бы не стал рассчитывать на то, что может быть логичным. TBH с ситуацией, которую я описал, я был бы настолько декларативным, насколько это возможно, и использовал бы iFoo в качестве возвращаемого типа. Есть ли ситуация, в которой это на самом деле не сработает? (Я понимаю, что сказанное - это скорее совет / мнение, чем ответ.   -  person Adam Cameron    schedule 22.08.2016


Ответы (5)


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  {...}
}
person user3942918    schedule 22.08.2016
comment
Тем не менее, я ожидал, что сработает указание типа возвращаемого значения static, но оно даже не распознается - person Mark Baker; 22.08.2016
comment
Я подумал, что так и будет, и вот как я собираюсь решить проблему. Однако я бы предпочел просто использовать: self, если это возможно, потому что, если класс реализует интерфейс, то возвращаемое значение self неявно также является возвращаемым значением экземпляра интерфейса. - person GordonM; 22.08.2016
comment
Пол, удаление комментариев, которые вы здесь удалили, на самом деле вредно, потому что (A) теряется важная информация и (B) нарушается ход обсуждения по сравнению с другими комментариями. Я не вижу причин, по которым нужно было удалить ваши комментарии, относящиеся к Марку и Гордону. Фактически, вы делаете это повсюду, и это нужно прекратить. Нет абсолютно никакой веской причины возвращаться к вопросу годичной давности и удалять все свои комментарии, полностью разрушая поток обсуждения. На самом деле это вредно и разрушительно. - person Cody Gray; 22.08.2017
comment
К части вашей цитаты, приведенной здесь, есть важное предисловие: Ковариантные возвращаемые типы считаются правильным типом и используются во многих других языках (C ++ и Java, но не C #, как мне кажется). Этот RFC изначально предлагал ковариантные возвращаемые типы, но был изменен на инвариантный из-за нескольких проблем ... Мне любопытно, какие проблемы были у PHP. Их выбор дизайна вызывает несколько ограничений, которые также вызывают странные проблемы с характеристиками, делая их несколько бесполезными во многих случаях, если вы не ослабите ограничения типа возвращаемого значения (как показано в некоторых ответах ниже). Очень неприятно. - person John Pancoast; 03.01.2020
comment
@MarkBaker тип возвращаемого значения static добавляется в PHP 8. - person Ricardo Boss; 05.08.2020
comment
Возможно, это лучший вариант, который заслуживает /** @return Foo */ (если вы не можете обновить свою версию PHP). По крайней мере, таким образом IDE предлагала бы правильный тип. Протестировал с Netbeans. - person volvpavl; 12.01.2021

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

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

class Foo implements iFoo
{
    public function bar (string $baz) : Foo  {...}
}
person Gabor    schedule 20.12.2016
comment
Или вместо Foo просто используйте self. - person instead; 25.12.2018

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

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

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

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

person instead    schedule 25.12.2018

PHP 8 добавит статический тип возврата, который решит вашу проблему.

Ознакомьтесь с этим RFC: https://wiki.php.net/rfc/static_return_type

person Alexandru    schedule 27.08.2020

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

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

Объяснение:

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

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

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

person Moshe Katz    schedule 22.08.2016
comment
Итак, объявление self похоже на объявление MyClass::class? - person peterchaula; 22.08.2016
comment
@Laser Да, это так. - person Moshe Katz; 22.08.2016
comment
Но если Foo реализует iFoo, то Foo по определению имеет тип iFoo - person GordonM; 20.12.2016