I have some code like follows. I tried to simplify the code to be easy to understand as much as possible.
class BaseFooClass {
protected $keys = [];
private $map = [];
public function __construct($keyValuePairs) {
foreach($this->keys as $key => $value) {
$this->map[$key] = $keyValuePairs[$key] ?? null;
}
}
}
class ChildFooClass1 extends BaseFooClass {
protected $keys = ['foo1_a', 'foo1_b'];
}
class ChildFooClass2 extends BaseFooClass {
protected $keys = ['foo2_a', 'foo2_b', 'foo2_c'];
}
//... (there are like a hundred child foo classes)
abstract class BaseBarClass {
protected $classIndex;
protected function getFooBase(int $dataIndex) : ?BaseFooClass
{
// GetRemoteData is assumed to be a global function, the important thing here is the retrieved data depends on classIndex and dataIndex
// If $classIndex is 1, the $keyValuePairs will look like ['foo1_a' => value1, 'foo1_b' => value2] where value1 and value2 depend on $dataIndex
$keyValuePairs = GetRemoteData($this->classIndex, $dataIndex);
if (checkDataIntegrity($keyValuePairs)) {
$class = "ChildFooClass" . $this->classIndex;
return new $class($keyValuePairs);
}
return null;
}
}
class ChildBarClass1 extends BaseBarClass {
protected $classIndex=1;
public function getFoo(int $dataIndex) : ?ChildFooClass1
{
// this line violates covariance/contravariance rule
return $this->getFooBase($dataIndex);
}
}
class ChildBarClass2 extends BaseBarClass {
protected $classIndex=2;
// input to getFoo in each BarClass can be different
public function getFoo($someInput) : ?ChildFooClass2
{
$dataIndex = $this->calculateDataIndex($someInput);
// this line also violates covariance/contravariance rule
return $this->getFooBase($dataIndex);
}
}
The three critertia I want to meet are:
(1) I want to make sure ChildBarClass1::getFoo only returns ChildFooClass1, but also making sure BaseBarClass::getFooBase only returns a class that inherits BaseFooClass. Rule of thumb: make type declaration as strict as possible.
(2) I want to make sure there is no repeated code. I don't want to call GetRemoteData and checkDataIntegrity in every single getFoo function. In fact there are much more stuff than these two in the real code as well.
(3) I want to adhere to the covariance/contravariance rule. Currently the third last line shown in the code violates this rule as it uses a function that returns the parent foo class.
I cannot seem to come up with a good solution. Anything I came up with will either break either rules or make the code look like a "hack" and really ugly. If anyone can provide a good way to solve this problem, without breaking any of the three criteria or changing the general structure (i.e. every Foo class has a corresponding Bar class handling creating the Foo object), I will really appreciate.
Given your code, you could simply remove the return type from getFooBase()
(or with php v8, make it : mixed
). The method is not part of the public api. And as such there's no real loss here.
As your getFoo()
s return values are typed individually, you'd get a type error anyways if whatever getFooBase()
returned wasn't suitable.