Trying to use a property in a child always fail in the following error :
Fatal error: Type of D::$prop must be A (as in class C)
I've read about https://www.php.net/manual/en/language.oop5.variance.php but this still a lot confuse and I can't really find a logical reason to prevent doing such a thing.
It feels so unnatural and frustrating not to be able to use a class that implement an interface and so respect the contract of that interface to just not be able to type hint it.
Such as in the following exemple :
<?php
interface A {
public function foo();
}
class B implements A {
public function foo() {
echo "foo";
}
public function bar() {
echo "bar";
}
}
class C {
public function __construct(protected A $prop) {
$this->prop->foo();
}
}
class D extends C {
public function __construct(protected B $prop) { // This can't be done even though B implements A interface !!!
$this->prop->bar();
parent::__construct($prop);
}
}
$b = new B();
$d = new D($b);
I need to understand why it's not possible and have a valid exemple of why it could'nt run this way though this code seems perfectly logical.
Assuming your code is possible, consider the following scenario:
class C {
public function setProp(A $prop) {
$this->prop = $prop;
}
}
class D extends C {
public function getProp() : B {
return $this->prop;
}
}
class E implements A {}
$e = new E();
$d->setProp($e);
$e = $d->getProp(); # Should return an instance of B
# but it will return an instance of E.
Obviously, this is unexpected. So PHP doesn't allow you to redeclare a property of a different type. (Not apply to private members, as private members will not be inherited.)
What you can do is to define the parameter type in the constructor. If you need member hints from B, you can declare another method.
class D extends C {
public function __construct(B $prop) { # No protected
parent::__construct($prop);
$this->prop->bar(); # This is legal, but your IDE may complain
$this->getProp()->bar();
}
private function getProp() : B {
# Although you still cannot avoid the above problem,
# you may need to perform a type check because the type is still `A`.
return $this->prop;
}
}