开发者

Can parameter types be specialized in PHP

Say we've got the following two classes:

abstract class Foo {
    public abstract function run(TypeA $object);
}

class Bar extends Foo {
    public function run(TypeB $object) {
        // S开发者_开发百科ome code here
    }
}

The class TypeB extends the class TypeA.

Trying to use this yields the following error message:

Declaration of Bar::run() must be compatible with that of Foo::run()

Is PHP really this broken when it comes to parameter types, or am I just missing the point here?



This answer is outdated since PHP 7.4 (partially since 7.2).


The behavior you describe is called covariance and is simply not supported in PHP. I don't know the internals but I might suspect that PHP's core does not evaluate the inheritance tree at all when applying the so called "type hint" checks.

By the way, PHP also doesn't support contravariance on those type-hints (a feature commonly support in other OOP languages) - most likely to the reason is suspected above. So this doesn't work either:

abstract class Foo {
    public abstract function run(TypeB $object);
}

class Bar extends Foo {
    public function run(TypeA $object) {
        // Some code here
    }
}

And finally some more info: http://www.php.net/~derick/meeting-notes.html#implement-inheritance-rules-for-type-hints


This seems pretty consistent with most OO principals. PHP isn't like .Net - it doesn't allow you to override class members. Any extension of Foo should slide into where Foo was previously being used, which means you can't loosen constraints.

The simple solution is obviously to remove the type constraint, but if Bar::run() needs a different argument type, then it's really a different function and should ideally have a different name.

If TypeA and TypeB have anything in common, move the common elements to a base class and use that as your argument constraint.


I think this is by design: It is the point of abstract definitions to define the underlying behaviour of its methods.

When inheriting from an abstract class, all methods marked abstract in the parent's class declaration must be defined by the child; additionally, these methods must be defined with the same (or a less restricted) visibility.


One could always add the constraint in code:

public function run(TypeA $object) {
    assert( is_a($object, "TypeB") );

You'll have to remember or document the specific type limitation then. The advantage is that it becomes purely a development tool, as asserts are typically turned off on production servers. (And really this is among the class of bugs to be found while developing, not randomly disrupt production.)


The code shown in the question is not going to compile in PHP. If it did class Bar would be failing to honour the gurantee made by it's parent Foo of being able to accept any instance of TypeA, and breaching the Liskov Substitution Principle.

Currently the opposite code won't compile either, but in the PHP 7.4 release, expected on November 28 2019, the similar code below will be valid, using the new contravariant arguments feature:

abstract class Foo {
    public abstract function run(TypeB $object); // TypeB extends TypeA
}

class Bar extends Foo {
    public function run(TypeA $object) {
        // Some code here
    }
}

All Bars are Foos, but not all Foos are Bars. All TypeBs are TypeAs but not all TypeAs are TypeBs. Any Foo will be able to accept any TypeB. Those Foos that are also Bars will also be able to accept the non-TypeB TypeAs.

PHP will also support covariant return types, which work in the opposite way.


Although you cannot use whole class-hierarchies as type-hinting. You can use the self and parent keywords to enforce something similar in certain situations.

quoting r dot wilczek at web-appz dot de from the PHP-manual comments:

<?php
interface Foo 
{
    public function baz(self $object);
}

class Bar implements Foo
{
    public function baz(self $object)
    {
        // 
    }
}
?>

What has not been mentioned by now is that you can use 'parent' as a typehint too. Example with an interface:

<?php
interface Foo 
{
    public function baz(parent $object); 
}

class Baz {}
class Bar extends Baz implements Foo
{
    public function baz(parent $object)
    {
        // 
    }
}
?>

Bar::baz() will now accept any instance of Baz. If Bar is not a heir of any class (no 'extends') PHP will raise a fatal error: 'Cannot access parent:: when current class scope has no parent'.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜