PHP严格标准:声明应该兼容

时间:2014-01-13 13:34:41

标签: php extend

我有以下类的层次结构:

class O_Base {...}

class O extends O_Base {...}

abstract class A_Abstract {
    public function save(O_Base $obj) {...}
}

class A extends A_Abstract {
    public function save(O $obj) {
        echo 'save!';
    }
}

$o = new O;
$a = new A;

$a->save($o);

当我运行此代码时,我收到消息:

  

严格标准:A :: save()声明应与之兼容   第21行的.php中的A_Abstract :: save(O_Base $ obj)

我知道 E_STRICT 错误级别,但我无法找到(并理解)该行为的原因。有人能帮助我吗?

4 个答案:

答案 0 :(得分:16)

您的代码明显违反了the Liskov Substitution principle。抽象类需要将O_Base的实例传递给save方法,因此{em>所有的A_Abstract子项应该以这种方式定义他们可以接受 O_Base的所有个实例。您的子类实现了save的版本,进一步限制了API。 see another example here

在您的代码中,A违反了Abstract_A强制执行/描述的合同。就像在现实生活中一样,如果您签订合同,则必须就某些条款达成一致,并且所有条款都与您将要使用的术语达成一致。这就是为什么大多数合同都是从命名各方开始,然后按照“此后X先生将被称为雇员”的方式说出一些内容。 这些术语,如你的抽象类型提示是不可讨论的,所以,更进一步说你不能说:“哦,好吧......你称之为费用,我打电话标准工资“
好的,我将停止使用这些半生不熟的类比,只是用一个简单的例子来说明为什么你正在做的事情,理所当然,不允许。

考虑一下:

abstract class Foo
{
    abstract public function save(Guaranteed $obj);
    //or worse still:
    final public function doStuff(Guaranteed $obj)
    {
        $obj->setSomething('to string');
        return $this->save($obj);//<====!!!!
    }
}

class FBar extends Foo
{
    public function save(Guaranteed $obj)
    {
        return $obj->setFine(true);
    }
}

class FBar2 extends Foo
{
    public function save(ChildOfGuaranteed $obj)
    {//FAIL: This method is required by Foo::doStuff to accept ALL instances of Guaranteed
    }
}

在这里看到,在这种情况下,完全有效的抽象类使用save实例调用Guaranteed方法。如果允许您在此类的子级中强制执行更严格的类型提示,则可以轻松中断此doStuff方法。为了帮助您保护自己免受这些类型的自我伤害,不应允许子类对从父类继承的方法强制执行更严格的类型。
还要考虑我们循环某些实例并检查它们是否具有此save方法的方案,基于这些实例为instanceof Foo

$arg = new OtherChildOfGuaranteed;
$array = array(
    'fb'  => new FBar,
    'fb2' => new FBar2
);
foreach($array as $k => $class)
{
    if ($class instanceof Foo) $class->save($arg);
}

现在,如果您只是在方法签名中提示Guaranteed,这将正常工作。但在第二种情况下,我们已经使类型提示有点过于严格,这段代码将导致致命错误。在一个更复杂的项目中调试这个很有趣......

PHP非常宽容,如果在大多数情况下不太宽容,但不是在这里。 PHP没有让你摸不着头,直到你的听力下降,PHP非常明智地提醒你,说你的方法的实施违反了合同,所以你 必须 解决了这个问题。

现在快速解决方法(也是常用的方法)就是:

class FBar2 extends Foo
{
    /**
     * FBar2 save implementation requires instance of ChildOfGuaranteed
     *  Signature enforced by Foo
     * @return Fbar2
     * @throw InvalidArgumentException
     **/
    public function save(Guaranteed $obj)
    {
        if (!$obj instanceof ChildOfGuaranteed)
            throw new InvalidArgumentException(__METHOD__.' Expects instance of ChildOfGuaranteed, you passed '.get_class($obj));
        //do save here...
    }
}

因此,您只需将抽象类型提示保留为,但 使用docblocks来记录您的代码

答案 1 :(得分:0)

您是否看到了功能签名的区别?

// A_Abstract
public function save(O_Base $obj) {...}

// A
public function save(O $obj) {

它们不兼容。

答案 2 :(得分:0)

将A_Abstract更改为

abstract class A_Abstract {
    public function save(O $obj) {...}
}

无论O是从O_Base扩展而来的,它们是两个不同的对象,而A扩展A_Abstract,保存功能需要相互兼容,意思是你需要传递{strong>相同对象,O

答案 3 :(得分:0)

我有类似的问题。

对我来说,解释更简单,没有很酷的理论:在PHP中,上面的代码正在使方法覆盖。 但在我的例子中,这种代码的想法是方法重载,这在PHP中是不可能的。

因此,要创建所需的逻辑(您的应用程序所需),您需要使用变通方法来制作兼容的方法签名(通过继承参数类型或使某些参数可选)。