tags:

views:

89

answers:

1

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?

+3  A: 

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

function foo(AbstractFactory $maker) {
    $attr = new Attribute();
    $maker->update($attr, 42);
}
...
$packager=new SimpleFactory();
foo($packager);

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.

class Attribute {}
class SimpleAttribute extends Attribute {
    public function spin() {...}
}
class SimpleFactory extends AbstractFactory {
    public function update(SimpleAttribute $attr, $data) {
        $attr->spin(); // This will fail when called from foo()
    }
}

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"

outis
But, your first case would never happen because `foo` will not accept an instance of `SimpleFactory`.My question was leaning more towards why I cannot make the contract of the `update` method _more strict_ in the concrete factory.
jason
The use case being that there are multiple concrete factories, say `SimpleFactory`, `EnumberableFactory` and so on, each of which is meant to just operate on its own type of attribute, `SimpleAttribute` and `EnumerableAttribute`, respectively. But, as it stands, since I'm not able to tighten the hint restriction in the factory subclass, all the concrete factories will have to accept all attribute types... barring a simple check in the top of the method body, which is fine, but I was just wondering if it was possible at the signature level.
jason
Still, I really appreciate your answer.
jason
`foo` will take an instance of `SimpleFactory`; just change the type hint for `SimpleFactory::update` and try it. Even though your code may not break the type hint contract, the language feature you need allows for contract breaking. Thus the feature will not be a part of PHP.
outis
"My question was leaning more towards why I cannot make the contract of the update method more strict in the concrete factory." Take a closer look at my answer, which addresses this exactly. In short: it allows for the possibility of contracts to be broken.
outis
OH, yes of course. I see. Thanks so much for your time and patience.
jason
You're welcome. It just goes to show the limitations of PHP's OOP support. In other languages, you could make use of overloading (real overloading, not what PHP calls overloading).
outis
Great question, great answer.
JW