2

I have the following OOP structure:

<?php


interface AnimalInterface
{
    public function getName();
}

class Dog implements AnimalInterface
{
    public function getName() {
        return 'dog';
    }

    public function makeFriends()
    {
        echo 'I have friends now :)';
    }
}

class Cat implements AnimalInterface
{
    public function getName() {
        return 'cat';
    }

    public function hateFriends()
    {
        echo 'I cant make friends :(';
    }
}

interface AnimalDoInterface
{
    public function makeFriend(AnimalInterface $animal);
}

class DogFriend implements AnimalDoInterface
{
    public function makeFriend(Dog $dog)
    {
        $dog->makeFriends();
    }
}

class CatFriend implements AnimalDoInterface
{
    public function makeFriend(Cat $cat)
    {
        $cat->hateFriends();
    }
}

Now PHP's manual on Object Interfaces says:

The class implementing the interface must use the exact same method signatures as are defined in the interface. Not doing so will result in a fatal error.

Why is this the case? Am I misunderstanding interfaces completely? Surely I should be able to declare AnimalDoInterface::makeFriend with anything that is the interface or an implementation of that interface? In this case it should technically be compatible as Cat implements AnimalInterface, which is what it's expecting.

Regardless of whether I am getting my OOP wrong, is there a way to implement this in PHP?

So it seems I wasn't clear enough, my bad for that. However, basically what I'm trying to achieve is to have the implementations of AnimalDoInterface to be more restrictive than it's interface says. So in this case, I'd like DogFriend::makeFriend to only allow the Dog class as it's argument, which in my mind should be acceptable as it implements the AnimalInterface, and the CatFriend to allow a Cat class, which again, same thing.

EDIT: fixed the classes, also added what I'm trying to achieve.

EDIT 2:

So at the moment, the way I'd have to implement it is as following:

class DogFriend implements AnimalDoInterface
{
    public function makeFriend(AnimalInterface $dog)
    {
        if(!($dog instanceof Dog)) {
            throw new \Exception('$dog must be of Dog type');
        }
        $dog->makeFriends();
    }
}

class CatFriend implements AnimalDoInterface
{
    public function makeFriend(AnimalInterface $cat)
    {
        if(!($dog instanceof Cat)) {
            throw new \Exception('$dog must be of Cat type');
        }
        $cat->hateFriends();
    }
}

I'd like to have to avoid this extra check for the class type.

5
  • I don't think I'm following anything you're saying. What's the actual issue? "method signatures" means method name, parameters (with type hints), visibility (public/private) and whether it's static or not. Commented Feb 9, 2014 at 22:19
  • Not entirely clear on what you are asking. Did you try to run the code you have above? It will error with Fatal error: Class Dog contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (AnimalInterface::getAnimalName) Commented Feb 9, 2014 at 22:20
  • 1
    Implemented AnimalInterface classes doesn't have the method getAnimalName() which is abstract. And must be used. Remember all interfaces methods itself are abstract and must be implemented within a class Commented Feb 9, 2014 at 22:23
  • An interface is a contract guaranteeing that whoever will be using an instance of a class implementing the interface implements a set of methods with certain signatures. If you need two sets of functionality, not shared between your classes then separate the signatures in two separate interfaces. Commented Feb 9, 2014 at 22:35
  • My bad, fixed the PHP to go to the correct error rather than what @MichaelBerkowski said. Commented Feb 9, 2014 at 22:59

3 Answers 3

4

An interface's only job is to enforce the fact that two objects behave in an identical way, regardless of how they implement that behaviour. It is a contract stating that two objects are interchangeable for certain specific purposes.

(Edit: This part of the code has been corrected, but serves as a good introduction.) The interface AnimalInterface defines the behaviour (function) getAnimalName(), and any class claiming to implement that interface must implement that behaviour. class Dog is declared with implements AnimalInterface, but doesn't implement the required behaviour - you can't call getAnimalName() on instances of Dog. So we already have a fatal error, as we have not met the "contract" defined by the interface.

Fixing that and proceeding, you then have an interface AnimalDoInterface which has the defined behaviour (function) of makeFriend(AnimalInterface $animal) - meaning, you can pass any object which implements AnimalInterface to the makeFriend method of any object which implements AnimalDoInterface.

But you then define class DogFriend with a more restrictive behaviour - its version of makeFriend can only accept Dog objects; according to the interface it should also be able to accept Cat objects, which also implement AnimalInterface, so again, the "contract" of the interface is not met, and we will get a fatal error.

If we were to fix that, there is a different problem in your example: you have a call to $cat->hateFriends(); but if your argument was of type AnimalInterface or AnimalDoInterface, you would have no way to know that a hateFriends() function existed. PHP, being quite relaxed about such things, will let you try that and blow up at runtime if it turns out not to exist after all; stricter languages would only let you use functions that are guaranteed to exist, because they are declared in the interface.


To understand why you can't be more restrictive than the interface, imagine you don't know the class of a particular object, all you know is that it implements a particular interface.

If I know that object $a implements AnimalInterface, and object $b implements AnimalDoInterface, I can make the following assumptions, just by looking at the interfaces:

  1. I can call $a->getName(); (because AnimalInterface has that in its contract)
  2. I can call $b->makeFriend($a); (because AnimalDoInterface has in its contract that I can pass anything that implements AnimalInterface)

But with your code, if $a was a Cat, and $b was a DogFriend, my assumption #2 would fail. If the interface let this happen, it wouldn't be doing its job.

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks, so the first problem wasn't meant to be an issue, it's the second one that is my actual issue. I've updated my question to explains what I'm trying to achieve.
@HoshSadiq I've added an extra section which might make clearer why interfaces couldn't let you do what you were attempting.
I understand that, but surely it should be possible to make a certain argument more restrictive, that way the class that's making the implementation more restrictive, i.e. in this case DogFriend, then it still knows that it has it's 'contracted' methods, but also other methods specific to Dog? See my edited question, although it's not ideal, it's one workaround, albeit, in my opinion an ugly one. Based on what you're saying, it shouldn't be like this at all, or am I mistaken?
@HoshSadiq You are thinking of it from the point of view of DogFriend, not the code using a DogFriend. The point of the interface is to know in advance what can be done with a particular object; if an object can claim to implement AnimalDoInterface but doesn't allow Cats, there is no point in me reading that interface - I have no way of knowing what kind of object I can pass in without knowing the details of the particular class. If you want to vary the function signatures, don't use an interface to claim they are the same.
Fair enough I suppose. Thanks for the explanation :)
3

The reason all classes implementing an interface must have the same methods is so that you can call that method on the object regardless of which subtype is instantiated.

In this case, you have a logical inconsistency because two subtypes, Cat and Dog, have different methods. So you can't call makeFriends() on the object, because you don't know that the object has that method.

That's the point of using interfaces, so you can use different subtypes, but at the same time you can be sure of at least some common methods.

One way to handle this in your case is to make sure Cat implements the same method, but make the method throw an exception at runtime, indicating that it's not possible. This allows the interface to be satisfies at compile time (I know PHP doesn't have a compiler, but this is mimicking languages like Java that do the interface checking at compile time).

class Cat implements AnimalInterface
{
    public function makeFriends()
    {
        throw new RuntimeException('I cant make friends :(');
    }
}

2 Comments

PHP most certainly does have a compiler, and will indeed do checks like this at compile-time, as can be seen if you include a file with a bad class definition in it. (You just don't manually invoke the compiler, and it will normally re-process the file every time it is loaded, unless you use an "opcode cache".)
Sorry, please see updated question, I had accidentally posted an incomplete question :)
0

A class which implements AnimalDoInterface must have a makeFriend method which takes any object which implements AnimalInterface. In your case, trying to declare

class DogFriend implements AnimalDoInterface {
  public function makeFriend(Dog $foo) { }
}

will not accurately implement this, since it should always be safe to pass anything which implements AnimalInterface to the makeFriend method of anything which implements AnimalDoInterface.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.