2016-06-17 2 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에 전달 될 수 없습니다.

첫 번째 단위 테스트에서는 Child의 인스턴스를 제공했으며 Base으로 확장되어 작동했습니다.

두 번째 단위 테스트에서 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을 기대하고 있다고 생각 있지만, 여기서는 그렇지 않다 : ChildServiceBaseService에서 파생되지 않습니다.

아시다시피 파생 클래스는 기본 클래스 형식 힌트를 충족하지만 파생 클래스 메서드는 기본 클래스 메서드의 API를 강화할 수 없습니다. 따라서 ChildBase을 허용하는 메소드에 전달할 수 있지만 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) {} } 

당신은 ChildService하지 않는 것이 중요한 차이, 코드에서 매우 비슷한 할 추상 BaseService을 상속받습니다. 따라서 ChildService은 원하는 모든 인수를 받아 들일 수 있으며 PHP로 인한 치명적인 오류는 없습니다. 기본 서비스를 추상으로 변경하고 하위 서비스를 파생 시키면 LSP 위반이 발생합니다. (또한 this question를 참조하십시오.)

지금, BaseService::process()ChildImplChildImpl 때문이다-ABase 받아들입니다. 또한 ChildBase을 구현하는 모든 것을 허용합니다.

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

을하지만 Child 소요 ChildService::process, 오프 BaseService::process 손 때문에 이것은 날려 버리겠다 - 그리고 BaseImplChild되지 않습니다 : 그것은 다음 코드가 유효을 의미합니다. PHPStorm은이 정적 분석을 수행했으며 설계시 발생할 수있는 런타임 결과를 경고합니다.

관련 문제