I will be using the following example to illustrate my question:
class Attribute {}
class SimpleAttribute extends Attribute {}
abstract class AbstractFactory {
abstract public function update(Attribute $attr, $data);
}
class SimpleFactory extends AbstractFactory {
public function update(SimpleAttribute $attr, $data);
}
If you try to run this, PHP will throw a fatal error, saying that the Declaration of SimpleFactory::update() must be compatible with that of AbstractFactory::update()
I understand exactly what this means: That SimpleFactory::update()s method signature must exactly match that of its parent abstract class.
However, my question: Is there any way to allow the concrete method (in this case, SimpleFactory::update()) to redefine the type hint to a valid descendant of the original hint?
An example would be the instanceof operator, which would return true in the following case:
SimpleAttribute instanceof Attribute // => true
I do realize that as a work around, I could make the type hint the same in the concrete method, and do an instanceof check in the method body itself, but is there a way to simply enforce this at the signature level?
I wouldn’t expect so, as it can break type hinting contracts. Suppose a function
footook an AbstractFactory and was passed a SimpleFactory.foocallsupdateand passes an Attribute to the factory, which it should take because theAbstractFactory::updatemethod signature promises it can take an Attribute. Bam! The SimpleFactory has an object of type it can’t handle properly.In contract terminology, descendent classes must honor the contracts of their ancestors, which means function parameters can get more basal/less specified/offer a weaker contract and return values can be more derived/more specified/offer a stronger contract. The principle is described for Eiffel (arguably the most popular design-by-contract language) in “An Eiffel Tutorial: Inheritance and Contracts“. Weakening and strengthening of types are examples of contravariance and covariance, respectively.
In more theoretical terms, this is an example of LSP violation. No, not that LSP; the Liskov Substitution Principle, which states that objects of a subtype can be substituted for objects of a supertype.
SimpleFactoryis a subtype ofAbstractFactory, andfootakes anAbstractFactory. Thus, according to LSP,fooshould take aSimpleFactory. Doing so causes a “Call to undefined method” fatal error, which means LSP has been violated.