Інтерфейси PHP 7, натяк на повернення та самості


89

ОНОВЛЕННЯ : PHP 7.4 тепер підтримує коваріацію та контраваріацію, які вирішують основну проблему, порушену в цьому питанні.


Я зіткнувся з якоюсь проблемою із використанням підказки типу return у 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 return, можливо, вам слід підняти це як помилку ; але будь-яке виправлення навряд чи потрапить у PHP 7.1 на цьому пізньому етапі
Марк Бейкер

Оскільки остання бета-версія 7.1 вийшла в мережу кілька днів тому, дуже малоймовірно, щоб якесь виправлення потрапило в 7.1.
Шарлотта Дюнуа

З цікавості, де ви читаєте свою інтерпретацію того, як selfповинен працювати тип повернення?
Адам Кемерон,

@Adam: Просто здається, що логічно selfозначати "Повернути екземпляр, до якого ви викликали це, а не якийсь інший екземпляр, який реалізує той самий інтерфейс". Здається, я пам’ятаю, що у Java був подібний тип повернення (хоча
минув

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.