Skip to content
Advertisement

Is there a way to redefine a type hint to a descendant class when extending an abstract class?

I will be using the following example to illustrate my question:

JavaScript

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:

JavaScript

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?

Advertisement

Answer

I wouldn’t expect so, as it can break type hinting contracts. Suppose a function foo took an AbstractFactory and was passed a SimpleFactory.

JavaScript

foo calls update and passes an Attribute to the factory, which it should take because the AbstractFactory::update method signature promises it can take an Attribute. Bam! The SimpleFactory has an object of type it can’t handle properly.

JavaScript

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. SimpleFactory is a subtype of AbstractFactory, and foo takes an AbstractFactory. Thus, according to LSP, foo should take a SimpleFactory. Doing so causes a “Call to undefined method” fatal error, which means LSP has been violated.

User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement