0

I need to implement an event-listener model in OOP. Now I have EventInterface and ListenerInterface. But I want to pass the event to a listener, as:

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

But I can't do that even if the Event I passed to the Listener implements EventInterface. What should I do then? I want to make it abstract for simple usage.

Screenshot from PhpStorm: enter image description here

yivi
  • 42,438
  • 18
  • 116
  • 138
Alexxosipov
  • 1,215
  • 4
  • 19
  • 45
  • You should be able to do that. Why do you think you can't? – Greg Schmidt Feb 13 '20 at 03:46
  • Give it the interface name there too. As long as the object you pass in implements that interface, you'll be fine. – Greg Schmidt Feb 13 '20 at 04:34
  • userLoggedInEvent should implement ListenerInterface, that is what the message says, as long as it not logic , so you should check yout logic agian – Mohammed Omer Feb 13 '20 at 05:01
  • The code in your screenshot does not match the code in the interface show. In the code you show, the interface only declares the method `listen()`, but in your screenshot it's complaining about the signature of the method `__construct()`. Please, add the appropriate code for this example (the actual interface and implementing class) **as code**, not screenshot, so I can give you a proper answer. – yivi Feb 13 '20 at 07:23
  • @Alexxosipov, the question was closed, but it would be great if you could you update it with the correct code anyway. Additionally, feedback on the answer below would be much appreciated. – yivi Feb 14 '20 at 08:32

1 Answers1

2

Your SendUserNotificationListener does not comply with the contract set forth by ListenerInterface.

The error is helpfully telling you this. If you try to run this code, you'll get a fatal error and your script will crash.

If you have a couple of interfaces like this:

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;
}

Any class that implements OriginalHandlerInterfce needs to follow it exactly

E.g.

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

If you try to make the implementation use covariance on the parameter, it will fail, since the implementation will not follow the interface. E.g.

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

The logic for this is simple and sound. If someone is using a class you made that declares that implements OriginalHandlerInterface, that user should not need to look at your implementation at all, they need only to look at the interfaces.

If the original interface declares that handle() expects a ParentInterface object, it means I can pass it objects from classes that implement ParentInterface, but objects from classes that implement ChildInterface or AnotherChildInterface would also be valid.

On the other hand, if your implementation were able to say handle(ChildInterface $a), then the user would have their expectations betrayed, since they would not be able to pass handle() an object from a class that implements AnotherChildInterface, despite being valid according to the OriginalHandlerInterface.

On the other hand, if you had this interface:

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

Your implementations could use contravariance on the handle() parameter. They could specify a less restrictive parameter type. E.g.:

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

This implementation, despite not following to the dot, is valid. Again, if you think about it the same logic applies: a consumer for your class could look at AnotherHandlerInterface and see that handle() expects a ChildInterface. So they would never be tripped by your implementation if the follow the original contract.

Your implementation is less restrictive, and accepts objects that the original interface wouldn't, but that's fine because the rules for covariance and contravariance allow it.

Note that covariance and contravariance support is quite new in PHP, and has been added only in PHP 7.4.

You can see the above code working here (including the error)

yivi
  • 42,438
  • 18
  • 116
  • 138