Передача интерфейса другому методу интерфейса

Мне нужно реализовать модель прослушивателя событий в ООП. Теперь у меня есть EventInterface и ListenerInterface. Но я хочу передать событие слушателю, как:

interface ListenerInterface {
  public function listen(EventInterface $event);
}

Но я не могу этого сделать, даже если Event, который я передал слушателю, реализует EventInterface. Что мне делать тогда? Я хочу сделать его абстрактным для простого использования.

Скриншот из PhpStorm: введите здесь описание изображения


person Alexxosipov    schedule 13.02.2020    source источник
comment
Вы должны быть в состоянии сделать это. Почему вы думаете, что не можете?   -  person Greg Schmidt    schedule 13.02.2020
comment
Дайте ему имя интерфейса там же. Пока объект, который вы передаете, реализует этот интерфейс, все будет в порядке.   -  person Greg Schmidt    schedule 13.02.2020
comment
userLoggedInEvent должен реализовать ListenerInterface, это то, что говорится в сообщении, если это не логика, поэтому вы должны проверить свою логику снова   -  person Mohammed Omer    schedule 13.02.2020
comment
Код на вашем скриншоте не соответствует коду в интерфейсе шоу. В коде, который вы показываете, интерфейс объявляет только метод listen(), но на вашем снимке экрана он жалуется на сигнатуру метода __construct(). Пожалуйста, добавьте соответствующий код для этого примера (фактический интерфейс и реализующий класс) в виде кода, а не скриншота, чтобы я мог дать вам правильный ответ.   -  person yivi    schedule 13.02.2020
comment
@Alexxosipov, вопрос был закрыт, но было бы здорово, если бы вы все равно обновили его, указав правильный код. Кроме того, отзывы об ответе ниже будут высоко оценены.   -  person yivi    schedule 14.02.2020


Ответы (1)


Ваш SendUserNotificationListener не соответствует договору, заключенному ListenerInterface.

Ошибка услужливо говорит вам об этом. Если вы попытаетесь запустить этот код, вы получите фатальную ошибку, и ваш скрипт рухнет.

Если у вас есть пара таких интерфейсов:

interface ParentInterface {
    public function foo();
}

interface ChildInterface extends ParentInterface {
    public function bar();
}

interface AnotherChildInterface extends ParentInterface {
    public function baz();
}

interface OriginalHandlerInterface
{
    public function handle(ParentInterface $a): string;
}

Любой класс, реализующий OriginalHandlerInterfce, должен точно следовать ему.

E.g.

class OriginalImplementor implements OriginalHandlerInterface
{
    public function handle(ParentInterface $a) : string
    {
        return class_name($a);
    }
}

Если вы попытаетесь заставить реализацию использовать ковариацию для параметра, это не удастся, так как реализация не будет следовать интерфейсу. Например.

class WrongOriginalImplementor implements OriginalHandlerInterface
{
    public function handle(ChildInterface $a) : string
    {
        return class_name($a);
    }
}

Логика этого проста и здрава. Если кто-то использует созданный вами класс, который объявляет, что реализует OriginalHandlerInterface, этому пользователю вообще не нужно смотреть на вашу реализацию, ему нужно только смотреть на интерфейсы.

Если исходный интерфейс объявляет, что handle() ожидает объект ParentInterface, это означает, что я могу передать ему объекты из классов, реализующих ParentInterface, но объекты из классов, реализующих ChildInterface или AnotherChildInterface, также будут действительными.

С другой стороны, если бы ваша реализация могла сказать handle(ChildInterface $a), то ожидания пользователя были бы обмануты, поскольку он не смог бы передать handle() объект из класса, реализующего AnotherChildInterface, несмотря на то, что он действителен в соответствии с OriginalHandlerInterface.

С другой стороны, если бы у вас был этот интерфейс:

interface AnotherHandlerInterface
{ 
    public function handle(ChildInterface $a): string;
}

В ваших реализациях может использоваться контравариантность для параметра handle(). Они могут указать менее строгий тип параметра. Например.:

class ContravariantImplementor implements AnotherHandlerInterface
{
    public function handle(ParentInterface $a): string {
        return class_name($a);
    }
}

Эта реализация, несмотря на то, что она не соответствует действительности, действительна. Опять же, если подумать, применима та же логика: потребитель вашего класса может посмотреть на AnotherHandlerInterface и увидеть, что handle() ожидает ChildInterface. Таким образом, они никогда не будут сбиты с толку вашей реализацией, если они будут следовать исходному контракту.

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

Обратите внимание, что поддержка ковариантности и контравариантности появилась в PHP впервые, и была добавлена только в PHP 7.4.

Вы можете увидеть работающий приведенный выше код здесь (включая ошибку)

person yivi    schedule 13.02.2020