次のクラスの階層があります。
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エラーレベルについては知っていますが、その動作の理由を見つける(そして理解する)ことができません。誰か助けてもらえますか?
あなたのコードは明らかに リスコフの置換原則 に違反しています。抽象クラスでは、O_Base
のインスタンスをsave
メソッドに渡す必要があるため、A_Abstract
の子のallはO_Base
のallインスタンスを受け入れることができるように定義されます。子クラスは、APIをさらに制限するバージョンのsave
を実装します。 ここで別の例を参照
あなたのコードでは、A
はAbstract_A
が強制/記述している契約に違反しています。実生活と同じように、契約を結ぶ場合は、特定の条件に同意する必要があり、使用する用語についてはすべて同意する必要があります。そのため、ほとんどの契約では、当事者に名前を付けてから、「以降、X氏を従業員と呼びます」のように言います。
これらの用語は、抽象型のヒントと同様に、negociableではないため、さらに下の行では次のように言うことはできません: 「ああ、まあ……あなたが経費と呼んでいるもの、私は標準賃金と呼んでいる」
わかりました、これらの半端なアナロジーの使用をやめ、簡単な例を使用して、なぜあなたがしていることが許可されていないのかを説明します。
このことを考慮:
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
メソッドを簡単に破ることができます。この種の自傷行為から身を守るために、子クラスが親クラスから継承するメソッドに厳密な型を適用することを許可しないでください。
また、いくつかのインスタンスをループし、これらのインスタンスがinstanceof Foo
であることに基づいて、このsave
メソッドがあるかどうかを確認するシナリオを検討してください。
$arg = new OtherChildOfGuaranteed;
$array = array(
'fb' => new FBar,
'fb2' => new FBar2
);
foreach($array as $k => $class)
{
if ($class instanceof Foo) $class->save($arg);
}
これで、メソッドシグネチャでGuaranteed
をほのめかすだけで問題なく動作します。しかし、2番目のケースでは、型ヒントを少し厳しくしすぎたため、このコードは致命的なエラーになります。より複雑なプロジェクトでこれをデバッグして楽しんでください...
PHPは非常に寛容ですが、ほとんどの場合、それほど寛容ではありませんが、ここではそうではありません。 PHPは、メソッドの実装が契約に違反していると言って、非常に賢明に頭をかきむしります。そのため、mustこの問題を修正する必要があります。
これで、簡単な回避策(およびこれも一般的に使用されているもの)は次のようになります。
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...
}
}
したがって、抽象タイプヒントをそのままのままにしますが、docblockを使用してコードを文書化します
関数シグネチャの違いがわかりますか?
// A_Abstract
public function save(O_Base $obj) {...}
// A
public function save(O $obj) {
それらは互換性がありません。
A_Abstractをに変更します
abstract class A_Abstract {
public function save(O $obj) {...}
}
O
がO_Base
から拡張されたとしても、それらは2つの異なるオブジェクトであり、A
がA_Abstract
を拡張するとき、保存関数は互いに互換性がある必要があります。 同じオブジェクトをO
に渡す必要があります。
私も同様の問題を抱えていました。
私にとって、説明ははるかに簡単で、クールな理論はありません:in PHP上記のコードはメソッドを作成していますオーバーライド。しかし、私の場合のそのようなコードのアイデアはメソッドでした- overloadingこれはPHPこの方法では不可能です。
したがって、必要なロジック(アプリケーションで必要)を作成するには、(パラメータータイプを継承するか、一部のパラメーターをオプションにすることで)互換性のあるメソッドシグネチャを作成するための回避策を使用する必要があります。