首页 > 解决方案 > PHP - 实现接口时的类型提示更具体的类型

问题描述

给定一个对象/接口层次结构,如下所示

<?php

interface Event 
{
    public function getType(): string;
}

final class MyEvent implements Event
{
    public function getType(): string
    {
        return 'MyEvent';
    }
}

interface EventHandler
{
    public function handle(Event $event): void;
}

final class MyEventHandler implements EventHandler
{
    public function handle(MyEvent $event): void
    {
        // do something with the concrete MyEvent object...
    }
}

是否无法在从接口MyEvent实现方法的方法中输入更具体的提示?handleEventHandler

我的 ide 将此标记为错误

方法“MyEventHandler::handle()”与方法“EventHandler::handle()”不兼容。

虽然我觉得以可扩展性的名义,这应该是可能的......

实现方法的签名与接口中方法的签名不同,但在运行时不会将 aMyEventEvent?

显然,如果具体实现类型提示Event接口,则没有问题,并且在运行时该方法将接受实现MyEvent,但是我们失去了具体MyEventHandler应该处理类型事件的意图MyEventEvent和接口可能有非常多的实现EventHandler,所以我们在这里寻找的是尽可能明确,而不是仅仅在具体的句柄方法中输入提示接口。

如果接口和类型提示无法做到这一点,是否有另一种方法可以用抽象类实现它,同时保留强(ish)类型和传达意图?或者我们是否需要一个显式方法MyEventHandler来允许具体 Event 类的类型提示?

标签: interfacephp-7

解决方案


您要问的是“协变方法参数类型”。


问题在于,MyEvent根据定义,该类可以具有接口不知道/不知道的一些额外行为Event。而且由于该handler()方法需要一个MyEvent类型(而不是一个类型Event),因此可以得出结论,该handler()方法实际上使用了这种额外的行为;否则为什么要更改参数的类型?

如果 PHP 允许这样做,那么类handle()实例的方法可能MyEventHandler会收到一个非MyEvent事件对象。然后它将导致运行时错误。

事实上,维基百科在这里对这个问题有很好的描述。包括下面的一小段(代码不是 PHP,但你会明白的):

class AnimalShelter {
    void putAnimal(Animal animal) {
        //...
    }
}
class CatShelter extends AnimalShelter {

    void putAnimal(covariant Cat animal) {
        // ...
    }
}

这不是类型安全的。通过将 CatShelter 向上转换为 AnimalShelter,可以尝试将狗放在猫收容所中。这不符合 CatShelter 参数限制,并且会导致运行时错误。缺乏类型安全(在 Eiffel 社区中称为“catcall 问题”,其中“cat”或“CAT”是更改的可用性或类型)一直是一个长期存在的问题。多年来,已经提出了全局静态分析、局部静态分析和新语言特性的各种组合来解决它,[7] [8] 并且这些已经在一些 Eiffel 编译器中实现。

有趣的是,PHP 确实允许在构造函数参数的情况下使用协变。

interface AnimalInterface {}


interface DogInterface extends AnimalInterface {}


class Dog implements DogInterface {}


class Pet
{
    public function __construct(AnimalInterface $animal) {}
}


class PetDog extends Pet
{
    public function __construct(DogInterface $dog)
    {
        parent::__construct($dog);
    }
}

如果您将参数从 handle() 方法移动到处理程序类的构造函数,这在您的特定情况下会有所帮助。


推荐阅读