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允许它?
答案 0 :(得分:5)
PhpStorm警告您,您的代码可能允许Base
到BaseService::process
的实例不是有效的Child
实例,因此无法传递给{ {1}}。
在您的第一次单元测试中,您提供了ChildService::process
的实例,该实例扩展了Child
,因此它可以正常运行。
在您的第二次单元测试中,您事实证明它可能导致PHP错误。 PhpStorm只是提前警告你,你的类型提示允许这个问题。
如果Base
始终总是调用BaseService::process
,那么ChildService::process
应该键入其参数以与BaseService::process
兼容好。
我稍微修改了你的代码,重写了一些类名更简单,并删除了ChildService::process
方法。我只是希望尽可能简单地展示这一点,以帮助您了解正在发生的事情。
getId
答案 1 :(得分:1)
我认为你期待Liskov Substitution Principle violation,但情况并非如此: ChildService
并非来自BaseService
。
如您所知,派生类满足基类类型提示,但派生类方法无法加强基类方法的API。因此,您可以将Child
传递给接受Base
的方法,但无法加强Child
和Base
之间共享的方法的签名。
以下经典代码演示了违反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()
接受ChildImpl
,因为ChildImpl
是-a Base
。它还接受任何实现Child
的内容以及实现Base
的任何内容。这意味着以下代码有效:
class BaseImpl implements Base {
public function getId() { return 0; }
}
(new BaseService)->process(new BaseImpl);
但这会爆炸,因为BaseService::process
移交ChildService::process
,其中Child
- 而 BaseImpl
不是Child
} 的。 PHPStorm已执行此静态分析,并警告您设计可能的运行时结果。