调用参数类型与声明不兼容

时间:2016-06-17 18:38:38

标签: php

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允许它?

2 个答案:

答案 0 :(得分:5)

PhpStorm警告您,您的代码可能允许BaseBaseService::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的方法,但无法加强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()接受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已执行此静态分析,并警告您设计可能的运行时结果。