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)