我有以下类的层次结构:
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 错误级别,但我无法找到(并理解)该行为的原因。有人能帮助我吗?
答案 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中是不可能的。
因此,要创建所需的逻辑(您的应用程序所需),您需要使用变通方法来制作兼容的方法签名(通过继承参数类型或使某些参数可选)。