Reputation: 2015
Why should the class type-hint be 100% same with the interface?
I mean, why can't it accept implementing class as type hint?
To make it clear,
<?php
interface MyInterface
{
public function doMethod(SomeInterface $a)
{
}
}
class MyClass implements MyInterface
{
public function doMethod(ClassThatImplementsSomeInterface $a)
{
}
}
Error: Fatal error: Declaration of MyClass::doMethod() must be compatible with that of MyInterface::doMethod()
Since the class type hinting implements SomeInterface
, I expect it doesn't break the contract.
Why do I want it? Because of the advantages of interface flexibility.
The same goes for abstract class.
If I rewrite the code so that method 'do' has no type-hint, I know that it will 'fix' it.
But, somehow I think I should define the contract that type-hinting of $a
must implement SomeInterface
.
And, Why don't I just use the same type-hint which is SomeInterface
?
That's because there are methods that don't exist in SomeInterface
that I need.
So, what's the point of this limitation?
Reproducable codepad: http://codepad.org/2PLd8AmV
Upvotes: 8
Views: 10710
Reputation: 43690
Type hinting a different class changes the contract. Your interface says "anything that implements me must use a certain type in this method". Your actual implementation says that the method actually needs to use a different type.
As long as I have an object that implements "MyInterface", I know that there exists a do
method. I also know that this method takes one argument that has to be SomeInterface
. Because of the interface, I don't actually need to know or care about any specifics of the object (which specific implementation of the interface that it is).
However, if the object that I have is of type MyObject
that isn't the case! I actually need to have a ClassThatImplementsSomeInterface
object! If my object isn't this type, then my program crashes. Possibly randomly if I have factory methods creating objects. I can no longer trust that MyInterface
objects have a consistent implementation and have to check to make sure that the argument that I want to pass it is the correct one.
The problem that you are having is a good smell that your classes and interfaces aren't properly defined. MyObject
may not actually be an instance of SomeInterface
. Or SomeInterface
needs to actually take a ClassThatImplementsSomeInterface
instead.
Addendum
Based on your code example, remove the argument from the handle function. Have the __construct
arguments for the specific types of Listener
objects take a particular implementation of Event
.
interface Event
{
public function getName();
}
interface Listener
{
public function handle();
}
class UserHasLoggedIn implements Event
{
public function __construct($name, $id)
{
$this->name = $name;
$this->id = $id;
}
public function getName()
{
return 'UserHasLoggedIn';
}
}
class ChangeUserName implements Listener
{
private $event;
public function __construct(UserHasLoggedIn $event)
{
$this->event = $event;
}
public function handle()
{
}
}
Upvotes: 1
Reputation: 32260
The answer is simply, that you need to be able to rely on the object that the interface requires.
Real world example:
Interface requires APPLE
.
You now try to implement a class that says I require a GREEN APPLE
(it's still an apple!).
Someone now tries to implement your INTERFACE and put it into the class for the green apple. He tries to put in a RED APPLE
which is compatible with APPLE
but not with GREEN APPLE
.
=> bang, contract broken!
Coding example:
interface MyInterface
{
public function doMethod(SomeInterface $a);
}
class MyClass implements MyInterface
{
public function doMethod(ClassThatImplementsSomeInterface $a) { }
}
class DifferentClass implements SomeInterface { }
$ding = new MyClass();
$ding->doMethod(new DifferentClass);
This wouldn't work because DifferentClass
is not ClassThatImplementsSomeInterface
!
Upvotes: 8