Hello, internals!
I have a question about changed behavior in 7.0 for matching signature of
variadic method.
Here is an example of code:
class Foo {
public static function test($bar = null) {
var_dump(func_get_args());
}
}
class Baz extends Foo {
public static function test(...$args) {
parent::test(...$args);
}
}
Baz::test();
Baz::test('hello');
For 5.6 this code works normally, but in 7.0 it produces a warning about
signature: "Warning: Declaration of Baz::test(...$args) should be
compatible with Foo::test($bar = NULL)"
From userland point of view, these signatures should be compatible, so I
decided to check this behavior, was it intended or not.
Thanks!
Alexander Lisachenko wrote on 01/11/2015 21:49:
class Foo {
public static function test($bar = null) {
var_dump(func_get_args());
}
}class Baz extends Foo {
public static function test(...$args) {
parent::test(...$args);
}
}
[...]
From userland point of view, these signatures should be compatible, so I
decided to check this behavior, was it intended or not.
Should they? func_get_args()
can be used to simulate any function
signature, so you could equally say that the following are "compatible":
class Foo {
public static function test($foo, $bar, $baz) {
}
}
class Baz extends Foo {
public static function test() {
list($foo, $bar, $baz) = var_dump(func_get_args());
parent::test($foo, $bar, $baz);
}
}
Clearly, from the language's point of view, class Baz is missing the
parameters in its function signature that Foo specifies, so the warning
seems perfectly correct to me. In your example, you've replaced a single
optional parameter ($bar = null) with a specification of variadic
parameters (...$args); these are clearly not interchangeable, so again,
the signature is different.
Regards,
Rowan Collins
[IMSoP]
Hi Rowan,
Rowan Collins wrote:
Clearly, from the language's point of view, class Baz is missing the
parameters in its function signature that Foo specifies, so the warning
seems perfectly correct to me. In your example, you've replaced a single
optional parameter ($bar = null) with a specification of variadic
parameters (...$args); these are clearly not interchangeable, so again,
the signature is different.
An extending method merely needs to be "compatible", not have an
identical signature. The parent class required one optional parameter,
the subclass requires zero or more parameters. Those sounds compatible
to me. If you can call the parent class's method here, it would also
work on the subclass's method.
Thanks.
--
Andrea Faulds
http://ajf.me/
2015-11-02 12:22 GMT+03:00 Rowan Collins rowan.collins@gmail.com:
Should they?
func_get_args()
can be used to simulate any function
signature, so you could equally say that the following are "compatible":
Hello! Thank you for the answer.
However, question is not about func_get_args()
at all, it can be removed to
simplify an example. However, I still have a doubts that these two
signatures are not compatible.
First definition declares exactly one single parameter, which can be absent
during the method call, so I can even write
public static function test() {}
Second definition defines zero or more arguments, so it can be also
described by the same signature:
public static function test() {}
Signature with variadic args will be incompatible if the first definition
requires additional arguments before optional and second signature doesn't
respect this, see https://3v4l.org/5Fsou (this behaviour is correct and
warnings are expected).
But if the number of required parameters are matching, then whole signature
should be matching. PHP5.6 with HHVM prove this fact, see:
https://3v4l.org/YCmPS, and PHP7.0 probably doesn't play well.
If you think, that the new logic in PHP7.0 is correct, then it should be
nice to have a small notice in BC change with PHP5.6 logic, somewhere in
http://php.net/manual/en/migration70.incompatible.php#migration70.incompatible.other.func-parameters
.
Alexander Lisachenko wrote on 02/11/2015 11:12:
First definition declares exactly one single parameter, which can be
absent during the method call, so I can even writepublic static function test() {}
Second definition defines zero or more arguments, so it can be also
described by the same signature:public static function test() {}
OK, I got a bit confused with the different optional parameters, and
neither your explanation nor Andrea's quite did it for me.
But, I think you're right that this should be compatible, if the rule is
"any legal call on the parent should also be legal on the child".
Currently, all versions of PHP enforce the following:
- if parent accepts no parameters, child must accept zero, but could
accept one or more optional parameters - if parent accepts zero or one (an optional parameter), child must
accept zero, must accept one, but could accept more (so, two optional
parameters is fine; no parameters at all is incompatible)
Thus this is acceptable:
class Foo {
public function test() {}
}
class Bar extends Foo {
public function test($foo = null) {}
}
But these are all not:
class Foo2 {
public function test($foo = null) {}
}
class Bar2 extends Foo2 {
// Doesn't accept one parameter
public function test() {}
}
class Baz2 extends Foo2 {
// Doesn't accept zero parameters
public function test($foo) {}
}
class Foo3 {
public function test() {}
}
class Bar3 extends Foo3 {
// Doesn't accept zero parameters
public function test($foo) {}
}
Variadic signatures count as "zero or more" when the parent accepts only
zero:
class Foo4 {
public function test() {}
}
class Bar4 extends Foo4 {
public function test(...$foo) {}
}
But where the parent accepts "zero or one", the following ought to be
compatible because calls with no parameters, and calls with one
parameter, would both be valid:
class Foo {
public function test($foo = null) {}
}
class Bar extends Foo {
public function test(...$foo) {}
}
In short, I agree with you that this warning is incorrect, but will
leave this here in case anybody else is equally confused (and becomes
any less so by reading this...)
Regards,
Rowan Collins
[IMSoP]
Hi Rowan,
Alexander Lisachenko wrote on 02/11/2015 11:12:
First definition declares exactly one single parameter, which can be
absent during the method call, so I can even writepublic static function test() {}
Second definition defines zero or more arguments, so it can be also
described by the same signature:public static function test() {}
OK, I got a bit confused with the different optional parameters, and
neither your explanation nor Andrea's quite did it for me.But, I think you're right that this should be compatible, if the rule
is "any legal call on the parent should also be legal on the child".
Currently, all versions of PHP enforce the following:
- if parent accepts no parameters, child must accept zero, but could
accept one or more optional parameters- if parent accepts zero or one (an optional parameter), child must
accept zero, must accept one, but could accept more (so, two optional
parameters is fine; no parameters at all is incompatible)Thus this is acceptable:
class Foo {
public function test() {}
}
class Bar extends Foo {
public function test($foo = null) {}
}But these are all not:
class Foo2 {
public function test($foo = null) {}
}
class Bar2 extends Foo2 {
// Doesn't accept one parameter
public function test() {}
}
class Baz2 extends Foo2 {
// Doesn't accept zero parameters
public function test($foo) {}
}class Foo3 {
public function test() {}
}
class Bar3 extends Foo3 {
// Doesn't accept zero parameters
public function test($foo) {}
}Variadic signatures count as "zero or more" when the parent accepts
only zero:class Foo4 {
public function test() {}
}
class Bar4 extends Foo4 {
public function test(...$foo) {}
}But where the parent accepts "zero or one", the following ought to be
compatible because calls with no parameters, and calls with one
parameter, would both be valid:class Foo {
public function test($foo = null) {}
}
class Bar extends Foo {
public function test(...$foo) {}
}In short, I agree with you that this warning is incorrect, but will
leave this here in case anybody else is equally confused (and becomes
any less so by reading this...)
In my opinion the warning is valid as the one method defines one
optional argument and the overwritten method would accept no or more
arguments where the no arguments is wrong:
https://3v4l.org/6eMiu
class Foo {
public function test($foo = null) {}
}
class Bar extends Foo {
public function test() {}
}
The first defined argument gets more interesting if it defines a
type-hint what is not possible to define as variadic:
https://3v4l.org/HQdvI
class Foo {
public function test(Foo $foo = null) {}
}
class Bar extends Foo {
public function test(...$bar) {}
}
Additionally this issue can simply be addressed by:
https://3v4l.org/74r3K
class Foo {
public function test(Foo $foo = null) {}
}
class Bar extends Foo {
public function test(Foo $foo = null, ...$bar) {}
}
Regards,
Marc
Marc Bennewitz wrote on 02/11/2015 19:44:
Hi Rowan,
Alexander Lisachenko wrote on 02/11/2015 11:12:
First definition declares exactly one single parameter, which can be
absent during the method call, so I can even writepublic static function test() {}
Second definition defines zero or more arguments, so it can be also
described by the same signature:public static function test() {}
OK, I got a bit confused with the different optional parameters, and
neither your explanation nor Andrea's quite did it for me.But, I think you're right that this should be compatible, if the rule
is "any legal call on the parent should also be legal on the child".
Currently, all versions of PHP enforce the following:
- if parent accepts no parameters, child must accept zero, but could
accept one or more optional parameters- if parent accepts zero or one (an optional parameter), child must
accept zero, must accept one, but could accept more (so, two optional
parameters is fine; no parameters at all is incompatible)In my opinion the warning is valid as the one method defines one
optional argument and the overwritten method would accept no or more
arguments where the no arguments is wrong:
https://3v4l.org/6eMiu
class Foo {
public function test($foo = null) {}
}
class Bar extends Foo {
public function test() {}
}
That's not equivalent, because the version with no arguments explicitly
prohibits zero arguments, which the variadic signature doesn't.
You have to compare the possible calls, not convert them into signatures:
class Foo {
public function test($foo = null) {}
}
class Bar extends Foo {
public function test(...$foo) {}
}
$a = new Foo;
$a->test(); // valid
$a->test(42); // valid
$a->test(42, 6, 9); // NOT VALID
$b = new Bar;
$b->test(); // valid
$b->test(42); // valid
$b->test(42, 6, 9); // valid
So any call which is valid on Foo is also valid on Bar. There are calls
on Bar which are not valid on Foo, but that is not prohibited elsewhere:
class Baz extends Foo {
public function test($foo = null, $bar = null, $baz = null) {}
}
// no compilation warnings
$c = new Baz;
$c->test(); // valid
$c->test(42); // valid
$c->test(42, 6, 9); // valid
Regards,
Rowan Collins
[IMSoP]
Rowan Collins wrote on 03/11/2015 09:20:
That's not equivalent, because the version with no arguments
explicitly prohibits zero arguments, which the variadic signature
doesn't.
Sorry, this should read "the version with no arguments explicitly
prohibits passing one argument".