Is there any decent work around to PHP's lack of Generics that allow static code inspection to detect type consistency?
I have an abstract class, that I want to sub-class and also enforce that one of the methods changes from taking a parameter of one type, to taking a parameter which is a sub-class of that parameter.
abstract class AbstractProcessor {
abstract function processItem(Item $item);
}
class WoodProcessor extends AbstractProcessor {
function processItem(WoodItem $item){}
}
This is not allowed in PHP because it's changing the methods signature which is not allowed. With Java style generics you could do something like:
abstract class AbstractProcessor<T> {
abstract function processItem(T $item);
}
class WoodProcessor extends AbstractProcessor<WoodItem> {
function processItem(WoodItem $item);
}
But obviously PHP doesn't support those.
Google for this problem, people suggest using instanceof
to check errors at run-time e.g.
class WoodProcessor extends AbstractProcessor {
function processItem(Item $item){
if (!($item instanceof WoodItem)) {
throw new \InvalidArgumentException(
"item of class ".get_class($item)." is not a WoodItem");
}
}
}
But that only works at runtime, it doesn't allow you to inspect your code for errors using static analysis – so is there any sensible way of handling this in PHP?
A more complete example of the problem is:
class StoneItem extends Item{}
class WoodItem extends Item{}
class WoodProcessedItem extends ProcessedItem {
function __construct(WoodItem $woodItem){}
}
class StoneProcessedItem extends ProcessedItem{
function __construct(StoneItem $stoneItem){}
}
abstract class AbstractProcessor {
abstract function processItem(Item $item);
function processAndBoxItem(Box $box, Item $item) {
$processedItem = $this->processItem($item);
$box->insertItem($item);
}
//Lots of other functions that can call processItem
}
class WoodProcessor extends AbstractProcessor {
function processItem(Item $item) {
return new ProcessedWoodItem($item); //This has an inspection error
}
}
class StoneProcessor extends AbstractProcessor {
function processItem(Item $item) {
return new ProcessedStoneItem($item);//This has an inspection error
}
}
Because I'm passing in just an Item
to new ProcessedWoodItem($item)
and it expects a WoodItem as the parameter, the code inspection suggests there is an error.
Best Answer
You can use methods with no arguments, documenting the parameters with doc-blocks instead:
I'm not going to say I think this is a good idea though.
Your problem is, you have a context with a variable number of members - rather than trying to force those through as arguments, a better and more future-proof idea is to introduce a context-type to carry all possible arguments, so that the argument list never needs to change.
Like so:
Static factory methods are optional of course - but could be useful, if only certain specific combinations of members produce a meaningful context. If so, you may wish to declare
__construct()
as protected/private as well.