2016-06-17 6 views
4

Phpstormの検査結果はInvocation parameter types are not compatible with declaredです。呼び出しパラメータタイプは宣言と互換性がありません

私はphpがサブタイプとして使用ベースタイプを許可していることを驚いていました。

interface Base 
{ 
    public function getId(); 
} 

interface Child extends Base 
{ 

} 

interface SecondChildType extends Base 
{ 

} 

class ChildImpl implements Child 
{ 
    public function getId() 
    { 
     return 1; 
    } 
} 

class SecondChildTypeImpl implements SecondChildType 
{ 
    public function getId() 
    { 
     return 2; 
    } 
} 

class BaseService 
{ 
    public function process(Base $base) 
    { 
     $childService = new ChildService($base); 

     return $childService->process($base); //Invocation parameter types are not compatible with declared 
    } 
} 

class ChildService 
{ 
    public function process(Child $child) 
    { 
     return $child->getId(); 
    } 
} 

class InheritanceTest extends \PHPUnit_Framework_TestCase 
{ 
    public function testInterfacesCanUsesAsSubstitute() 
    { 
     $baseService = new BaseService(); 
     $this->assertEquals(1, $baseService->process(new ChildImpl())); 
    } 

    /** 
    * @expectedException \TypeError 
    */ 
    public function testInterfacesCanUsesAsSubstitute_Exception() 
    { 
     $baseService = new BaseService(); 
     $baseService->process(new SecondChildTypeImpl()); 
    } 
} 

なぜ最初のテストに合格するのですか?なぜPHPはそれを許可したのですか?

答えて

4

PhpStormはあなたのコードが潜在的に有効なChildインスタンスではありませんBaseService::processからBaseのインスタンスを可能にすることを警告されているので、ChildService::processに渡すことはできません。

最初の単体テストでのインスタンスを提供しました。これはBaseに拡張されています。

2回目のユニットテストでは、実際にはPHPエラーが発生する可能性があることを証明しています。 PhpStormはあなたのタイプヒントがこの問題を許容することを単に警告しています。あなたが今持っているように

BaseService::processの場合は常にコールChildService::processは、その後、BaseService::processは同様ChildService::processと互換性があるように、その引数をtypehintする必要があります。


私は、少しあなたのコードを変更したクラス名の一部を単純に書き換え、およびgetId方法を削除しました。私はこれをできるだけシンプルに表示して、何が起こっているのかを理解するのを助けたいと思います。

interface Base {} 
interface Child extends Base {} 
interface Base2 extends Base {} 

// This class implements Child, which extends Base. So this will meet either requirement. 
class Class1 implements Child {} 

// This class implements Base2, which extends Base. 
// So this will meet any Base requirement, but NOT a Child requirement 
class Class2 implements Base2 {} 


class BaseService 
{ 
    /** 
    * Problem! We are requiring Base here, but then we pass the same argument to 
    * ChildService->process, which requires Child. 
    * 
    * 1) Class1 WILL work, since it implements Child which extends Base. 
    * 
    * 2) Class2 WILL NOT work. Or at least, we can't pass it to ChildService->process 
    * since it only implements Base2 which extends Base. It doesn't implement Child, 
    * therefore ChildService->process won't accept it. 
    */ 
    public function process(Base $base) 
    { 
     $childService = new ChildService($base); 

     return $childService->process($base); 
    } 
} 

class ChildService 
{ 
    /** 
    * I will ONLY receive an instance that implements Child. 
    * Class1 will work, but not Class2. 
    */ 
    public function process(Child $child) 
    { 
     return $child->getId(); 
    } 
} 

$service = new BaseService(); 

// I can do this! I'm passing in Child1, which implements Child, which extends Base. 
// So it fulfills the initial Base requirement, and the secondary Child requirement. 
$service->process(new Child1()); 

// I can't do this. While BaseService will initially accept it, ChildService will refuse 
// it because this doesn't implement the Child interface as required. 
$service->process(new Child2()); 
+0

なぜですか?実装が重要な理由 –

+2

@ Onedev.Linkあなたが何を求めているのかわかりません – jszobody

+0

'BaseService :: process'では' Base'オブジェクトだけを渡すことができます。そして 'ChildService :: process'に' Base'オブジェクトを 'Child'として渡します。 'Child'または' Child'サブタイプを 'ChildService :: process'で渡したいだけです。しかし、なぜ私は 'Base'を' Child'として使うことができるのか分かりません。 –

1

私はあなたがLiskov Substitution Principle violationを期待していると思いますが、それはここではケースではありません:​​はBaseServiceから派生していません。

ご存じのように、派生クラスは基本クラス型ヒントを満たしますが、派生クラスメソッドは基本クラスメソッドのAPIを強化できません。したがってをBaseを受け入れるメソッドに渡すことはできますが、ChildBaseの間で共有されるメソッドのシグネチャを強化することはできません。

次の古典的なコードでは、LSPに違反する試みを示し、そして致命的な「宣言は互換性がなければならない」スロー:

abstract class AbstractService { } 
abstract class AbstractFactory { abstract function make(AbstractService $s); } 

class ConcreteService extends AbstractService { } 
class ConcreteFactory extends AbstractFactory { function make(ConcreteService $s) {} } 

あなたは​​にはない決定的な違いは、あなたのコードでは非常に似た何かを抽象的BaseServiceから継承します。したがって、​​は任意の引数を自由に受け入れることができ、PHPからの致命的なエラーはありません。ベースサービスを抽象的に変更してそれから子サービスを派生させると、LSP違反が発生します。 (またthis questionを参照してください。)

は今、BaseService::process()ChildImplChildImplためである-Base受け付けます。 Childを実装するもの、およびBaseを実装するものも受け入れます。

class BaseImpl implements Base { 
    public function getId() { return 0; } 
} 
(new BaseService)->process(new BaseImpl); 

しかし、これはChildService::processにオフBaseService::process手のでChildをとる、爆破されます - とBaseImplChildではありません:それは、次のコードが有効であることを意味します。 PHPStormはこの静的解析を実行しており、実行時の設計結果の可能性を警告しています。

関連する問題