简体   繁体   中英

PHP type hint interface in abstract methods and type hint interface's child class in method implementation

Imagine I have a simple interface:

interface A {}

Then, I have some classes implementing that interface:

class B implements A {}
class C implements A {}

Then, I have a class which have a method which expects object of class B as an argument (I can not use interface type hint here because only class B have some unique features class D requires):

class D
{
    public function foo(B $bar) { /*...*/ }
}

But, I also have another class with the same method which now expectes object of class C as an argument (I can not use interface type hint here because only class C have some unique features class E requires):

class E
{
    public function foo(C $bar); { /*...*/ }
}

And objects of class D and E have to be used somewhere else and I have to make sure that these objects will have method foo , so I ended up with the following (abstract class is not a mistake here, should not use interface instead, because in real life this class should contain some non-abstract methods too) :

abstract class DE
{
    abstract public function foo(A $bar); // type-hint interface
}

Then I edited my classes so they extend class DE:

class D extends DE 
{
    public function foo(B $bar); // class B implements A
}

class E extends DE 
{
    public function foo(C $bar); // class C implements A
}

To my mind this code is logically valid, because we may have situations I tried to describe in this post, but I was surprised to see Fatal error with the message : Declaration of .. must be compatible with ..

So my question is the following: How to make sure that several objects will have a specific method and that method in each object will accept specific objects only as an argument?

As of now, the only workaround I see is something like the following ( but I believe there is a better solution for this ):

interface A {}
class B implements A {}
class C implements A {}

interface DE 
{
    public function foo(A $bar);
}

class D implements DE 
{
    public function foo(A $bar) 
    {
        assert($bar instanceof B);
        /*...*/
    }
}

class E implements DE 
{
    public function foo(A $bar)
    {
        assert($bar instanceof C);
        /*...*/
    }
}

One problem is classes D and E requiring concrete classes, B and C respectively. Now since you should be discouraging class inheritance I would avoid putting concrete classes in method signatures as much as I can. If your class needs to use a concrete class don't leak that information, keep it private.

So it is better to have:

public function foo(SomethingAbstract $noIdeaWhatIAmButIDoStuff) {
    $concrete = SomethingConcreteThatMakesUseOfAbstract($noIdeaWhatIAmButIDoStuff);
}

than:

public function foo(SomethingConcrete $tmi) {

}

I would recommend to think along these lines:

abstract class DE {

    public abstract function foo(A $a);

}

class D extends DE {

    public function foo(A $a) {
        $b = new B($a);
        //do with $b what you please
    }

}

class E extends DE {

    public function foo(A $a) {
        $c = new C($a);
        //now you can use $c which will leverage $a
    }

}

If B and C objects need also to implement A (like in your example) then you have a decorator pattern.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM