Implementing Interfaces and Errors with Incompatible Functions

I have this test script:

<?php interface A { function myfunction(); } class B implements A { function myfunction($var = "default") { echo $var; } } class C extends B { function myfunction() { echo "myfunction"; } } $c = new C(); $c->myfunction(); $b = new B(); $b->myfunction(); 

This works fine and throws myfunctiondefault .

Now, when I remove the interface A and let B not implement A, like this:

 <?php class B { function myfunction($var = "default") { echo $var; } } class C extends B { function myfunction() { echo "myfunction"; } } $c = new C(); $c->myfunction(); $b = new B(); $b->myfunction(); 

I get this error:

 PHP Strict standards: Declaration of C::myfunction() should be compatible with that of B::myfunction() in /var/www/test/index.php on line 16 Strict standards: Declaration of C::myfunction() should be compatible with that of B::myfunction() in /var/www/test/index.php on line 16 myfunctiondefault 

Why did it even work for the first time? I would expect an error in both situations ...

+6
source share
1 answer

When using an interface, B and C independently validated with an interface declaration. It doesn't matter that C extends B C indirectly implements A , and C::myfunction compatible with A::myfunction , so there is no problem. B::myfunction also compatible with A::myfunction , so there is no problem.

Without an interface, B::myfunction is the canonical declaration for this method, and since it takes a parameter but does not override C::myfunction , a STRICT warning appears.

Basically, you want to make sure this code works:

 if ($obj instanceof <classWhoseInterfaceIExpect>) { $obj-><interfaceIExpect>(); } 

More specific:

 if ($obj instanceof A) { $obj->myfunction(); } 

Since A::myfunction canonically declared as taking no arguments, the above code will always work. However, without an interface:

 if ($obj instanceof B) { $obj->myfunction('foo'); } 

If B::myfunction is a canonical declaration that takes an argument, but C provides an implementation that does not, you have created a conflict. Hence the warning. This should answer the question of why it works the way it does, PHP explains.


Caution: Yes, even if you use the interface, it will still cause the same conflict:

 interface A { ... } class B implements A { ... } class C extends B { ... } // conflicting implementation to B $obj = new C; if ($obj instanceof B) { $obj->myfunction('foo'); } 

C is instanceof A and B , but it is not compatible with both myfunction implementations at the same time. It is your problem to create two conflicting implementations. Whether PHP should warn here or not is controversial. PHP cannot have your cake and eat it too. Its type system allows you to catch specific errors as early as possible; this, of course, is not perfect and cannot protect you from firing on your own leg from all possible angles.

Basically, you should not change method signatures if you want to avoid such problems. This is either a cascade change down; that each extensible class is compatible with the method signature of its direct parent class. Therefore, C::myfunction must accept at least one optional argument. Perhaps PHP should catch this thing, perhaps it can be considered a mistake that in all cases you do not receive a warning. Again, this is debatable, and you should avoid getting into such a situation to start with.

+3
source

All Articles