问题描述
我具有以下类层次结构:
I have the following hierarchy of classes:
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);
运行此代码时,我收到消息:
When I run this code I get the message:
我知道 E_STRICT 错误级别,但是我找不到(也不了解)该行为的原因.有人可以帮我吗?
I know about E_STRICT error level but I can't find (and understand) the reason of that behavior. Can anybody help me?
推荐答案
您的代码明显违反了 Liskov替代原则.抽象类要求将O_Base的实例传递给save方法,因此,的所有 all 子代都应以可以接受 O_Base的所有实例.您的子类实现了save的版本,该版本进一步限制了API. 在此处查看另一个示例
Your code is a clear violation of the Liskov Substitution principle. The abstract class requires an instance of O_Base to be passed to the save method, therefore all of the children of the A_Abstract should be defined in such a way that they can accept all instances of O_Base. Your child class implements a version of save that further restricts the API. see another example here
在您的代码中,A违反了Abstract_A强制执行/描述的合同.就像在现实生活中一样,如果您签订合同,则必须在某些条款上达成一致,并且在要使用的术语上都必须达成一致.这就是为什么大多数合同都首先命名双方,然后说出类似"
这些术语(例如您的抽象类型提示是不可转让的),这样一来,您就不能说出以下内容:哦,好吧……您所说的费用,我称之为标准工资"
好的,我将不再使用这些烦恼的类比,而仅使用一个简单的例子来说明为什么正在做的事,理所当然地是不允许的.
In your code, A breaches the contract which Abstract_A enforces/describes. Just like in real life, if you sign a contract, you have to agree on certain terms, and all be in agreement over the terminology you're going to use. That's why most contracts start by naming the parties, and then saying something along the lines of "Henceforth Mr X will be referred to as the employee".
These terms, like your abstract type hints are non-negociable, so that, further down the line you can't say: "Oh, well... what you call expenses, I call standard wages"
Ok, I'll stop using these half-arsed analogies, and just use a simple example to illustrate why what you're doing is, rightfully, not allowed.
考虑一下:
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 } }
请参见此处,在这种情况下,完全有效的抽象类正在使用Guaranteed的实例调用save方法.如果允许您在此类的子级中强制执行更严格的类型提示,则可以轻松中断此doStuff方法.为了帮助您保护自己免受此类自我伤害,不应允许子类对从其父类继承的方法执行更严格的类型.
还请考虑以下情形:我们遍历某些实例,并根据这些实例为instanceof Foo:
See here, in this case the perfectly valid abstract class is calling the save method with an instance of Guaranteed. If you were allowed to enforce a stricter type-hint in the children of this class, you could easily break this doStuff method. To help you protect yourself against these kinds of self-inflicted wounds, child classes should not be allowed to enforce stricter types on methods they inherit from their parent classes.
Also consider the scenario where we're looping over some instances and checking if they have this save method, based on these instances being an 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,这将很好地工作.但是在第二种情况下,我们对类型提示的要求太严格了,此代码将导致致命错误.在一个更复杂的项目中调试它很有趣...
Now this will work fine if you just hint at Guaranteed in the method signature. But in the second case, we've made the type-hint a little too strict, and this code will result in a fatal error. Have fun debugging this in a more complex project...
PHP非常宽容,即使在大多数情况下也不太宽容,但此处并非如此. PHP不会让您挠头直到听到声音消失,而是非常明智地提醒您,说方法的实现违反了合同,因此您 必须 解决此问题.
PHP is very forgiving, if not too forgiving in most cases, but not here. Instead of having you scratching your head 'till your hear falls out, PHP very sensibly gives you a heads up, saying the implementation of your method breaches the contract, so you must fix this issue.
现在,快速的解决方法(也是一种常用的解决方法)将是:
Now the quick workaround (and it is one in common use, too) would be this:
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... } }
因此,您只需保留抽象的类型提示 ,但是 使用文档块记录代码
So, you just leave the abstract type-hint as-is, but use the docblocks to document your code
这篇关于PHP严格标准:的声明应兼容的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!