我有以下类层次结构:

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);

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



我知道 E_STRICT 错误级别,但我找不到(和理解)这种行为的原因。有谁能够帮我?

最佳答案

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

在您的代码中,save 违反了 A 执行/描述的契约(Contract)。就像在现实生活中一样,如果你签了一份契约(Contract),你必须就某些条款达成一致,并且所有人都对你将要使用的术语达成一致。这就是为什么大多数契约(Contract)都是先命名当事人,然后说“从今往后 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
    }
}

请参阅此处,在这种情况下,完全有效的抽象类正在使用 Abstract_A 的实例调用 save 方法。如果你被允许在这个类的子类中强制执行更严格的类型提示,你可以很容易地破坏这个 Guaranteed 方法。为了帮助您保护自己免受这些类型的自我伤害,不应允许子类对从父类继承的方法强制执行更严格的类型。
还要考虑我们循环遍历某些实例并检查它们是否具有这个 doStuff 方法的场景,基于这些实例是 save :
$arg = new OtherChildOfGuaranteed;
$array = array(
    'fb'  => new FBar,
    'fb2' => new FBar2
);
foreach($array as $k => $class)
{
    if ($class instanceof Foo) $class->save($arg);
}

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

PHP 是非常宽容的,即使在大多数情况下不是太宽容,但在这里不是。 PHP 不会让你挠头直到你的耳朵听不见,而是非常明智地提醒你,说你的方法的实现违反了契约(Contract),所以你 必须 解决这个问题。

现在快速的解决方法(它也是一种常用的方法)是这样的:
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...
    }
}

因此,您只需保留抽象类型提示,但 使用文档块来记录您的代码

关于PHP 严格标准 : Declaration of should be compatible,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/21092605/

10-16 13:46