Home » Php » php – Passing an interface to another interface method

php – Passing an interface to another interface method

Posted by: admin February 25, 2020 Leave a comment

Questions:

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

How to&Answers:

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)