Bug #73987 [NEW]: Method compatibility check looks to original definition and not parent

From: Date: Tue, 24 Jan 2017 14:24:57 +0000
Subject: Bug #73987 [NEW]: Method compatibility check looks to original definition and not parent
Groups: php.bugs 
Request: Send a blank email to php-bugs+get-206920@lists.php.net to get a copy of this message
From: requinix Operating system: PHP version: 7.1.1 Package: Scripting Engine problem Bug Type: Bug Bug description:Method compatibility check looks to original definition and not parent Description: ------------ Originally spotted as bug #73985. When PHP validates method signatures for compatibility, if a method is defined in an interface then compatibility is measured against the interface and any parent method's signature is ignored. This leads to inconsistencies... Example #1: method is defined in an interface (valid) - https://3v4l.org/pblqI ---------- interface I { public function example($a, $b, $c); } class A implements I { public function example($a, $b = null, $c = null) { } // compatible with I::example } class B extends A { public function example($a, $b, $c = null) { } // compatible with I::example } Example #2: method is not defined in an interface (invalid) - https://3v4l.org/bJJ1h ---------- //interface I { // public function example($a, $b, $c); //} class A { public function example($a, $b = null, $c = null) { } } class B extends A { public function example($a, $b, $c = null) { } // not compatible with A::example } The same signature is used in A and B, however only the second has a problem. The first is easy to explain on its own ("example" was defined in I so methods must be compatible with I::example) and the second is easy to explain on its own ("example" was defined in A so methods must be compatible with A::example) however the two together are inconsistent. The problem appears when calling a function using an I or A parameter type - https://3v4l.org/OneV3 --- function accepts_i(I $i) { $i->example(1, 2, 3); } accepts_i(new B); // no problem function accepts_a(A $a) { $a->example(1); } accepts_a(new B); // problem --- PHP <7.1: missing argument 2 for B::example PHP >=7.1: ArgumentCountError: Too few arguments to function B::example, 1 passed A::example() only has one required argument, therefore it should be safe for accepts_a to call ->example(1). But it isn't. Like with method parameters, this problem also exists for return types. Example #3: interface does not have return type (valid) - https://3v4l.org/Jik7I ---------- interface I { public function example(); } class A implements I { public function example(): int { } // compatible with I::example } class B extends A { public function example(): string { } // compatible with I::example } Example #4: class has a return type (invalid) - https://3v4l.org/n1q0G ---------- <?php //interface I { // public function example(); //} class A { public function example(): int { } } class B extends A { public function example(): string { } // not compatible with A::example } Like with method parameters, this can result in unexpected behavior. Unlike with method parameters, there's no warning about it - https://3v4l.org/TUbfJ --- function accepts_i_expects_any(I $i) { var_dump($i->example()); } accepts_i_expects_any(new B); // receives string, no problem function accepts_a_expects_int(A $a) { var_dump($a->example()); } accepts_a_expects_int(new B); // receives string, problem --- Proposed solution: methods in a subclass are validated against methods in the nearest ancestor who (re)defines the method - be that normally, as abstract, or using a trait. The result is that since A implemented example(), B::example gets validated against that rather than the original definition of I::example. BC: Yes, but code reliant on current behavior is susceptible to the "inconsistencies" noted earlier so it's already flawed. Test script: --------------- <?php // https://3v4l.org/gl1Gt interface I { public function example($a, $b, $c); } class A implements I { public function example($a, $b = null, $c = null): int { } } class B extends A { public function example($a, $b, $c = null): string { } } ?> Expected result: ---------------- Error that B::example is not compatible with A::example, due to the return type (a fatal error by itself) and the second required argument (a warning by itself). Actual result: -------------- No error(s). -- Edit bug report at https://bugs.php.net/bug.php?id=73987&edit=1 -- Try a snapshot (PHP 5.4): https://bugs.php.net/fix.php?id=73987&r=trysnapshot54 Try a snapshot (PHP 5.5): https://bugs.php.net/fix.php?id=73987&r=trysnapshot55 Try a snapshot (trunk): https://bugs.php.net/fix.php?id=73987&r=trysnapshottrunk Fixed in SVN: https://bugs.php.net/fix.php?id=73987&r=fixed Fixed in release: https://bugs.php.net/fix.php?id=73987&r=alreadyfixed Need backtrace: https://bugs.php.net/fix.php?id=73987&r=needtrace Need Reproduce Script: https://bugs.php.net/fix.php?id=73987&r=needscript Try newer version: https://bugs.php.net/fix.php?id=73987&r=oldversion Not developer issue: https://bugs.php.net/fix.php?id=73987&r=support Expected behavior: https://bugs.php.net/fix.php?id=73987&r=notwrong Not enough info: https://bugs.php.net/fix.php?id=73987&r=notenoughinfo Submitted twice: https://bugs.php.net/fix.php?id=73987&r=submittedtwice register_globals: https://bugs.php.net/fix.php?id=73987&r=globals PHP 4 support discontinued: https://bugs.php.net/fix.php?id=73987&r=php4 Daylight Savings: https://bugs.php.net/fix.php?id=73987&r=dst IIS Stability: https://bugs.php.net/fix.php?id=73987&r=isapi Install GNU Sed: https://bugs.php.net/fix.php?id=73987&r=gnused Floating point limitations: https://bugs.php.net/fix.php?id=73987&r=float No Zend Extensions: https://bugs.php.net/fix.php?id=73987&r=nozend MySQL Configuration Error: https://bugs.php.net/fix.php?id=73987&r=mysqlcfg

« previous php.bugs (#206920) next »