问题
This code of dependency inversion should work fine but instead it gives error.
What am I doing wrong in here?
interface A { }
abstract class B implements A { }
class C extends B { }
abstract class D {
public function foo(A $a) { }
}
class E extends D {
public function foo(C $c) { }
}
The error is:
Warning: Declaration of E::foo(C $c) should be compatible with D::foo(A $a) in [...][...] on line 24
Surprisingly, doing the same for the constructor method works just fine:
interface A { }
abstract class B implements A { }
class C extends B { }
abstract class D {
public function __construct(A $a) { }
public function foo(A $a) { }
}
class E extends D {
public function __construct(C $c) { }
public function foo(A $c) { }
}
回答1:
There are two questions here.
Why the first part raises a WARNING
: the error is pretty explicit. Any time you override a method you need to respect the parent's signature, so a client application's expectations are met.
class Foo {}
class Bar extends Foo {}
class A {
public function test(Foo $foo) {}
}
class B extends A {
public function test(Bar $bar) {}
}
Now, since B
extends A
, and A::test()
expects a Foo
, a user might try to do this:
$a = new A();
$b = new B();
$foo = new Foo();
$bar = new Bar();
$b->test($bar);
$b->test($foo);
The first call to test()
would work, but the second would fail (and fatally): a Foo
is not a Bar
.
The parameter type is more restrictive than the original, and thus breaks expectations from client applications. Note that if we had a legal declaration (B::test(Foo)
), we could still call $b->test($bar)
and would work as expected, so changing the signature is not really necessary.
In November 2019, When PHP 7.4 is released, contravariance will be supported for parameter types, and covariance will be supported for return types; so your example would still be invalid.
Finally, notice that the error raised is a WARNING
, it won't crash the program immediately. But it is warning you that you are opening your application to behaviour that might crash it, and that's it's generally poor design (for the reasons explained above).
The second question: Why you do not get the same warning for the constructor? They do not raise this warning by design. If you check the docs, you'll find this:
Unlike with other methods, PHP will not generate an E_STRICT level error message when __construct() is overridden with different parameters than the parent __construct() method has.
Why constructors are treated differently? Because it is generally understood that constructors are not part of the "public API" of an object, and do not need to be subjected to the same constraints. The Liskov Substitution Principle deals with objects, not with classes; and before an class is instantiated there is yet no object. Thus, the constructor is out of scope.
You can read more about this here, for example.
回答2:
TL;DR C
is implementing A
, but it can also inherit other methods from B
which implementing A
doesn't guarantee. This creates situation where two classes of same inherited type can have two methods with different signatures. A sub-class must accept all inputs that the parent class would accept, but it can accept additional inputs if it wants,
Let's extend your example.
Nothing crazy, just some example methods.
interface A
{
public function gotA();
}
abstract class B implements A
{
abstract public function gotB();
}
class C extends B
{
public function gotA()
{
// ...
}
public function gotB()
{
// ...
}
}
I can agree that C
is indirectly implementing A
, but it also contains methods from B
, so the call in E::foo()
would be perfectly fine.
abstract class D
{
public function foo(A $a)
{
$a->gotA();
}
}
class E extends D
{
public function foo(C $c)
{
$c->gotB();
}
}
Another example class implementing A
.
class ExampleA implements A
{
public function gotA()
{
// ...
}
}
Now the fun part. Assume this function anywhere else in your code. It assumes D
(expecting A
in foo
) as parameter.
But E
is also instanceof D
and can be passed to x
, but it expects C
in foo
which ExampleA
has nothing to do with.
function x(D $d)
{
$exampleA = new ExampleA();
$d->foo($exampleA);
}
$e = new E();
x($e);
To sum up, the contract for function x
has been met and yet the function broke.
You ended up with two 'different' D
types. The one expecting A
and the one expecting C
.
As for why it doesn't throw warning with __constructor
I don't know. Need further investigation.
来源:https://stackoverflow.com/questions/58107389/what-is-wrong-with-this-example-of-class-inheritance