Edit report at https://bugs.php.net/bug.php?id=73987&edit=1
ID: 73987
Updated by: ab@php.net
Reported by: requinix@php.net
Summary: Method compatibility check looks to original
definition and not parent
Status: Closed
Type: Bug
Package: Class/Object related
PHP Version: 7.1.1
-Assigned To:
+Assigned To: ab
Block user comment: N
Private report: N
New Comment:
Yeah, I've understood your idea exactly as you've explained lately, to enforce strictness
on the language level. As for usages in other languages, what i had in mind, here is a sample code
in Java, but could be good in any other of C# or C++, etc.
I.java
interface I
{
public int method(int i);
}
A.java
class A implements I
{
public int method(int i)
{
return 0;
}
}
B.java
class B extends A
{
public String method(String i)
{
return "";
}
}
A implements I, B doesn't explicitly implement it, but derives from A and additionally
overloads the method. One can rephrase it in other way - "class B extends A implements I",
as B already contains an instance of "method", so maybe it's even right to say it
indirectly implements I, at least it ensures LSP. Clear, tihs is not possible in PHP, that's
why i mentioned it right in my first sentence :) I'm not sure, what else would be required to
enforce LSP on the language level in PHP, as the weak interface declaration is still a factor, for
what this ticket cares. For example in PHP, one can can still do
class B implements I {
public function example(): string { }
}
so then both A und B are perfectly an instance of I, but are incompatible indeed. I'd see an
explicit interface declaration as a more robust and universal way, but a mitigation might be not
that easy. There are possibly other cases. Anyway, if some particular restriction helps to fix a
widely possible anti pattern sub case, it still could be considered as a sensible thing to do.
Probably it's a bit wider topic than just one bug ticket.
Thanks.
Previous Comments:
------------------------------------------------------------------------
[2017-01-28 08:29:01] krakjoe@php.net
Automatic comment on behalf of krakjoe
Revision: http://git.php.net/?p=php-src.git;a=commit;h=19fff2ece61c143278d01a9ed136969b592f6280
Log: [ci skip] news entry for Fixed bug #73987
------------------------------------------------------------------------
[2017-01-28 06:48:16] krakjoe@php.net
Automatic comment on behalf of krakjoe
Revision: http://git.php.net/?p=php-src.git;a=commit;h=47c2da96467c56dd800bb6873db7875c78d13569
Log: [ci skip] news entry for Fixed bug #73987
------------------------------------------------------------------------
[2017-01-28 06:48:12] krakjoe@php.net
Automatic comment on behalf of krakjoe
Revision: http://git.php.net/?p=php-src.git;a=commit;h=19fff2ece61c143278d01a9ed136969b592f6280
Log: [ci skip] news entry for Fixed bug #73987
------------------------------------------------------------------------
[2017-01-27 16:27:32] requinix@php.net
> As "B extends A" doesn't imply "B implements I" indeed.
PHP says it does: https://3v4l.org/ZQgJG (that PHP checks
B<->I compatibility proves it)
C# says it does: http://ideone.com/AZRdRy
Java says it does: http://ideone.com/CZICMN
Longer explanation - I know you are familiar with this, but I'm writing it all out for anyone
else reading.
1. method($param) vs. method($param=null)
LSP says parameters must be contravariant (parent->child) meaning one used in a child class must
be the same or more permissive. $param=null is more permissive than $param as it allows the
parameter to be omitted. Therefore
(a) is_a(A,I) and I::example($a, $b) therefore A::example($a, $b=null) is allowed
(b) is_a(B,I) and I::example($a, $b) therefore B::example($a, $b) is allowed
(c) is_a(B,A) and A::example($a, $b=null) therefore B::example($a, $b) is NOT allowed
According to examples #1 and #2, PHP allows (c) to happen because it only checks I->A, I->B
inheritance and not A->B. It is also clear that I->A and I->B does not imply A->B.
If PHP instead checks I->A and A->B (the parent class/interface) then example #1 fails in the
same way that example #2 fails. Yes this is a change, and some code that "works" today
will not "work" tomorrow, however as the accepts_a() demo shows such code does not
actually "work" today - the fact that nobody has noticed means they haven't written
an accepts_a() function yet.
If PHP does check I->A and A->B then it implies I->B as well.
2. method() vs. method():int
LSP says return types are covariant (parent<-child); PHP requires invariance when the parent uses
a return type and covariance when the parent does not. Covariance is being the same or less
permissive. No type is like saying ":mixed", and int is less permissive than mixed.
Therefore
(a) is_a(A,I) and I::example() therefore A::example():int is allowed
(b) is_a(B,I) and I::example() therefore B::example():string is allowed
(c) is_a(B,A) and A::example():int therefore B::example():string is NOT allowed
Again, according to examples #3 and #4, PHP allows (c) because it only checks I<-A, I<-B
inheritance and not A<-B. I<-A and I<-B also does not imply A<-B.
Again, if PHP checked I<-A and A<-B then example #3 fails; existing working code does not
actually work as accepts_a_expects_int() demonstrates.
And again, if PHP checks I<-A and A<-B then it implies I<-B.
The goal of changing validation to be against the parent rather than the interface is not to allow
more code. It is to be more strict and to disallow some code which seems to, but does not truly,
work correctly.
> the suggested way will in fact disallow even what works today
Do you have an example that does not already violate LSP?
> even more advanced examples are possible
Such as?
------------------------------------------------------------------------
[2017-01-27 13:51:51] ab@php.net
It's clear, that there's no method overloading of the methods with the same name in PHP,
but the suggested way will in fact disallow even what works today. Shouldn't the interface be
defined explicitly in this case? As semantically the examples look not controversial, in other
programming languages with stronger OOP, even more advanced examples are possible. As "B
extends A" doesn't imply "B implements I" indeed.
Thanks.
------------------------------------------------------------------------
The remainder of the comments for this report are too long. To view
the rest of the comments, please view the bug report online at
https://bugs.php.net/bug.php?id=73987
--
Edit this bug report at https://bugs.php.net/bug.php?id=73987&edit=1