Ковариация типов с абстрактным классом и признаками в PHP

Я разрабатываю библиотеку PHP (7.4), и мне нужно использовать черты для новой функции, но я столкнулся с проблемой с ковариацией типа параметра.

У меня есть абстрактный родительский класс:

<?php

abstract class ParentClass {
    abstract public function parentMethod($param): bool;
}

?>

У меня тоже есть черта:

<?php

trait MyTrait {
    abstract public function traitMethod($param): bool;
}

?>

Я использую как класс, так и черту в дочернем классе:

<?php

class ChildClass extends ParentClass {

    use MyTrait;

    // implementation of the abstract methods

    public function parentMethod(int $param): bool { // change parent method parameter type
        // implementation
    }

    public function traitMethod(int $param): bool { // change trait method parameter type
        // implementation
    }
}

?>

Проблема в том, что я получаю эту ошибку:

Неустранимая ошибка: объявление ChildClass::parentMethod(int $param): bool должно быть совместимо с ParentClass::parentMethod($param): bool

Кажется, я не могу изменить тип параметра parentMethod(). Если я удалю тип int в определении parentMethod(), я не получу ошибку! Даже с определенным параметром типа в методе типажа.

Почему я могу использовать ковариантный тип параметра с абстрактным методом признаков, но не с методом абстрактного класса?


person hunomina    schedule 02.01.2020    source источник
comment
Привет, Хуномина, возможно, ты захочешь взглянуть на новые обновления в моем ответе.   -  person Rain    schedule 08.10.2020


Ответы (1)


Ковариация и контравариантность — это концепции, связанные с наследованием, использование признака не является наследованием.

Примечание. Приведенное выше утверждение не совсем верно, я объясню почему в конце этого ответа.

Из документации по PHP.

Черта аналогична классу, но предназначена только для группировки функций в мелкозернистой и согласованной форме. Невозможно создать экземпляр черты сам по себе. Это дополнение к традиционному наследованию, которое обеспечивает горизонтальную композицию поведения; то есть применение членов класса без необходимости наследования.

Почему вы видите эту ошибку?

Потому что int не является супертипом всего и не является псевдотипом, представляющим любой тип (замените int на mixed в PHP 8 и посмотрите, что произойдет). Также расширение типа не позволяет использовать произвольный супертип (вы можно только опустить тип)

Например, допустим, вы определяете свой родительский метод следующим образом:

abstract public function parentMethod(int $param): bool;

Расширение типа позволяет вам опустить только тип данных $param в файле ChildClass.

Контравариантность позволяет тип параметра должен быть менее конкретным в дочернем методе, чем у его родителя

Допустим, у нас есть еще один класс с именем C, который расширяет stdClass, и мы определяем parentMethod для приема только объектов типа C.

class C extends stdClass {}

abstract class ParentClass
{
    abstract public function parentMethod(C $param): bool;
}

Теперь в ChildClass, если мы реализуем parentMethod для приема объектов типа stdClass

public function parentMethod(stdClass $param): bool
{ 
    
}

Это будет работать, и никаких ошибок не будет.

Это контравариантность.

#Изменить Что касается вашего вопроса в комментариях

Почему можно ввести параметр реализованного типаж-метода в дочернем классе?

Поскольку черты копируются в класс, вы не можете применять к ним правила ООП. Вот почему вы можете переопределить метод final в трейте.

trait Foo
{
    final public function method($var)
    {
        return $var;
    }
}
class Bar
{
    use Foo;
    // "Override" with no error
    final public function method($var)
    {
        return $var;
    }
}

Цель абстрактных методов в трейте – заставить демонстрирующий класс реализовать их (типы, а также модификаторы доступа могут отличаться)

Документация PHP гласит

Внимание Конкретный класс выполняет это требование, определяя конкретный метод с тем же именем; его подпись может отличаться.

Обновление, октябрь 2020 г.

Начиная с PHP 8 изменилось поведение абстрактных методов в типажах, и теперь абстрактные методы с несовпадающими сигнатурами завершатся с фатальной ошибкой и к ним будут применяться правила LSP.

Хорошо, почему?

Все это изменение началось с отчета об ошибке, и было некоторое обсуждение здесь

Кажется, что до PHP 8 был какой-то конфликт в поведении:

А также потому, что abstract само по себе указывает на контракт, поэтому то, о чем вы спрашивали, является законным, и теперь с PHP 8 вы будете счастливы https://3v4l.org/7sid7 :).

person Rain    schedule 02.01.2020
comment
Я знаю, что супертипа нет, но почему тогда можно ввести параметр реализованного метода типажа в дочернем классе (например, ковариация)? - person hunomina; 03.01.2020
comment
Хорошо :/ На данный момент черты выглядят как сломанная языковая структура :/ Спасибо за ваш ответ :) - person hunomina; 04.01.2020