12

consider this:

class A{}

class B extends A{}

interface I{
 // expects object instanceof A
 function doSomething(A $a);
}

class C implements I
{
 // fails ????
 function doSomething(B $b){}
}

In my conception the above should work but it doesn't as php rejects that implementation requiring the first parameter to be exactly the same type (A) as defined in the interface (I). Since B is a subclass of A, I don't see whats the problem. Am I missing something here?

fabio
  • 2,269
  • 5
  • 22
  • 34
  • 3
    Well `B` is descendent from, but not equal to, `A`, and PHP's view on this is strict. It's the way it is, I don't think it's possible to work around it - you will probably just have to work without the type hint – Pekka Jan 24 '11 at 14:03
  • It won't work. See example 2. http://php.net/manual/en/language.oop5.interfaces.php – Wiseguy Jan 24 '11 at 14:15
  • 5
    Liskov Substitution Principle: if `B` extends `A`, there's no reason `C::doSometing()` should only accept `B`, and not all objects of `A` type. – Mchl Jan 24 '11 at 14:21
  • @Mchl I agree that logically you would think it _should_ work, but unfortunately it just doesn't in PHP. – Wiseguy Jan 24 '11 at 14:25
  • 2
    @Wiseguy: My point is PHP is absolutely right in rejecting this definition. – Mchl Jan 24 '11 at 14:28
  • @Mchl Ah, well, ultimately the problem here is the definition mismatch and not subtype substitution. If `class C` defined `function doSomething(A $a){}` instead, it should accept an argument of type B. – Wiseguy Jan 24 '11 at 14:39
  • Yes it should. Since `B` extends `A`, one should be able to use `B` in each place where `A` can be used, so there's no point in limiting the type hint. – Mchl Jan 24 '11 at 14:50
  • By **restricting the scope** you're breaking the contract of interface `I`. If the interface says `doSomething` can be called with A, and your implementation can not called with A (any A, not only a *subset* of As) then your code certainly does not implement the required behavior. – vbence Jul 18 '13 at 13:08
  • possible duplicate of [Is there a way to redefine a type hint to a descendant class when extending an abstract class?](http://stackoverflow.com/questions/1897795/is-there-a-way-to-redefine-a-type-hint-to-a-descendant-class-when-extending-an-a) – Cypher Jan 17 '14 at 23:43

2 Answers2

10

class C implements I means that there must be subtype relation between C and I. It means object of type C should be usable wherever an object of type I is required.

In your case C is more restrictive than I because it has more precise requirements on its doSomething argument -- I.doSomething is fine with any A but C.doSomething requires a specific subtype of A

Note that if you change C.doSomething to accept any A then nothing prevents you to pass it an object of type B. You just can't require only B, because then you would break subtyping contract.

In theory, subtypes can be more liberal about their function arguments and more specific about their return types (but never vice versa, as it was in your case). In practice, a programming language may require that argument types in overridden methods must be same everywhere.

Aivar
  • 6,814
  • 5
  • 46
  • 78
  • 1
    The second paragraph of your answer explains everything I needed to know. Thanks. – fabio Jan 24 '11 at 14:47
  • How is `C` more restrictive than `I`, if `B` implements all of `A`'s functionality and then some? – Cypher Jan 17 '14 at 23:38
  • 1
    @Cypher -- if `B` implements all of `A`'s functionality and then some, it's more powerful than `A`, hence `C`'s `doSomething` requires more powerful argument than `I`'s `doSomething` (which is content with a "mere" `A`. In other words, `C`'s `doSomething` is more picky than `I`'s `doSomething`. – Aivar Jan 19 '14 at 21:06
4

In theory, subtypes can be more liberal about their function arguments and more specific about their return types (but never vice versa, as it was in your case). In practice, a programming language may require that argument types in overridden methods must be same everywhere.

little work around- instanceof to solve that problem:

class A{}

class B extends A{}

interface I{
 // expects object instanceof A
 function doSomething(A $a);
}

class C implements I
{
 
 function doSomething(A $b){
   if($b instanceof of B){
   //do something
   }else{throw new InvalidArgumentException("arg must be instance of B") };
 }
}
luckydonald
  • 5,976
  • 4
  • 38
  • 58
YAMM
  • 572
  • 4
  • 9