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 runs fine and outputs myfunctiondefault
.
Now when I remove the interface A and let B not implement A anymore, like so:
<?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 this even work in the first time? I would expect the error in both situations...
When using the interface, B
and C
are independently verified against the interface declaration. It doesn't matter that C extends B
. C
indirectly implements A
, and C::myfunction
is compatible with A::myfunction
, so no problem there. B::myfunction
is also compatible with A::myfunction
, so no problem there either.
Without the interface, B::myfunction
is the canonical declaration for that method, and since it accepts a parameter but the overriding C::myfunction
does not, a STRICT
warning is raised.
Basically, you want to make sure this code works:
if ($obj instanceof <classWhoseInterfaceIExpect>) {
$obj-><interfaceIExpect>();
}
In more concrete terms:
if ($obj instanceof A) {
$obj->myfunction();
}
Since A::myfunction
is canonically declared as accepting no arguments, the above code will always work. However, without the interface:
if ($obj instanceof B) {
$obj->myfunction('foo');
}
If B::myfunction
is the canonical declaration which accepts an argument, but C
delivers an implementation which does not, you've created a conflict. Hence the warning. This is to answer the question why it works the way it does, it's PHP's thinking explained.
Caveat: Yes, even if you use the interface, this will still create 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 both an instanceof
A
and B
, yet it isn't compatible with both implementations of myfunction
at the same time. This is your problem for creating two conflicting implementations. Whether PHP should warn here as well or not is debatable. PHP can't have your cake and eat it too. Its type system is there to help you catch certain errors as early as possible; it's certainly not perfect and can't protect you from shooting your own foot from all possible angles.
You basically shouldn't change method signatures ever if you want to avoid such issues. That, or have the change be cascading downwards; meaning that each extending class is compatible with the method signature of its direct parent class. So C::myfunction
should accept at least one optional argument. Maybe PHP should catch this case, maybe it could be considered a bug that you're not getting a warning in all cases. Again, it's debatable and you should avoid getting into this situation to begin with.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With