我今天在PHP(至少为7.1和7.2)中使用以下代码遇到了这个问题:
namespace PlaceHolderX\Tests\PHPUnit\Unit;
use PHPUnit\Framework\TestCase;
final class BreakingClassesTest extends TestCase
{
public function testBreak(): void
{
$tester = new SomeClassA();
$tester->test();
$this->assertNull($tester->get());
}
}
interface InterfaceA {
public function test(string $testString): void;
}
class SomeClassA implements InterfaceA
{
/** @var null|string */
private $testString;
public function test(string $testString = null): void
{
$this->testString = $testString;
}
public function get(): ?string
{
return $this->testString;
}
}
所以我有一个接口(InterfaceA),该接口的方法需要一个字符串。此参数不可为空,因为如果我希望将其指定为:
public function test(?string $testString): void;
但是在实现类(SomeClassA)中,我可以使用默认值null覆盖参数定义,这会导致我的界面没有预期的行为。
所以我的主要问题是:为什么会这样?当然,我们需要在代码审查中进行检查,但这很容易错过。
我尝试搜索是什么原因导致此行为,但找不到解释。也许我的搜索条件已关闭。
答案 0 :(得分:3)
在PHP7.2中,实现了参数类型扩展。这是某种相反方差。可悲的是,PHP目前不支持参数的对数方差,但草稿中也有和rfc来支持这一点。
主要思想是:如果您有子类,则可以在子类中使用“更广泛”的参数类型。对于返回类型,oppsite是有效的(协方差)。
这是好还是坏的做法,取决于您的需求。据我所知,其他语言的行为方式相同。
为进一步阅读,有两个rfc:
答案 1 :(得分:1)
超类应该可以被其子类替代。因此,子类必须能够执行超类所做的所有事情,但它还可以做更多的事情。
在您的示例中,超类/接口不知道如何处理null。但是子类确实可以,这很好,因为超类的用户会认为超类契约有效,因此只会传递非null。
答案 2 :(得分:0)
PHP允许您在实现中设置或修改默认值,只要类型匹配即可。需要注意的是,所有提示类型均允许将null作为默认值。
如果有人找到对此的特定解释,那么我可以更新此答案,但是在7.1之前,声明可选参数的唯一方法是分配默认值null。 ?string
语法不存在,因此此行为可能源于此,并且为了向后兼容仍然存在。
如果您尝试将默认值设置为“整数”,则会看到一条错误消息,显示:
Fatal error: Default value for parameters with a string type can only be string or NULL
截至目前,确保实现的默认值与可为空或不可为空的接口声明相匹配似乎是开发人员的责任。