7
votes

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

2
I added some information, good question!Daniel W.
Your interface is saying "I'll use one of these thanks" and you implementation says "Yea well I'm using one of these instead"... I don't see where the question isDale

2 Answers

8
votes

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!

1
votes

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()
    {

    }
}