web-dev-qa-db-ja.com

PHP厳格な基準:の宣言は互換性がある必要があります

次のクラスの階層があります。

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エラーレベルについては知っていますが、その動作の理由を見つける(そして理解する)ことができません。誰か助けてもらえますか?

13
Alex

あなたのコードは明らかに リスコフの置換原則 に違反しています。抽象クラスでは、O_Baseのインスタンスをsaveメソッドに渡す必要があるため、A_Abstractの子のallO_Baseallインスタンスを受け入れることができるように定義されます。子クラスは、APIをさらに制限するバージョンのsaveを実装します。 ここで別の例を参照

あなたのコードでは、AAbstract_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を使用してコードを文書化します

16

関数シグネチャの違いがわかりますか?

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

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

それらは互換性がありません。

0
ComFreek

A_Abstractをに変更します

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

OO_Baseから拡張されたとしても、それらは2つの異なるオブジェクトであり、AA_Abstractを拡張するとき、保存関数は互いに互換性がある必要があります。 同じオブジェクトをOに渡す必要があります。

0
Styphon

私も同様の問題を抱えていました。

私にとって、説明ははるかに簡単で、クールな理論はありません:in PHP上記のコードはメソッドを作成していますオーバーライド。しかし、私の場合のそのようなコードのアイデアはメソッドでした- overloadingこれはPHPこの方法では不可能です。

したがって、必要なロジック(アプリケーションで必要)を作成するには、(パラメータータイプを継承するか、一部のパラメーターをオプションにすることで)互換性のあるメソッドシグネチャを作成するための回避策を使用する必要があります。

0
o.v