【问题标题】:PHP - Type Hint A More Specific Type When Implementing An InterfacePHP - 实现接口时的类型提示更具体的类型
【发布时间】:2021-05-16 05:08:09
【问题描述】:

给定如下的对象/接口层次结构

<?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...
    }
}

在从EventHandler 接口实现handle 方法的方法中,是否可以键入更具体的MyEvent 提示?

我的 ide 将此标记为错误

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

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

实现方法的签名与接口中方法的签名不同,但是在运行时MyEvent不会被视为与Event相同吗?

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

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

【问题讨论】:

    标签: interface php-7


    【解决方案1】:

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


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

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

    事实上,维基百科对这个问题有很好的描述here。在下面包括一个简短的 sn-p(代码不是 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() 方法移动到处理程序类的构造函数,这对您的特定情况会有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-01-19
      • 1970-01-01
      • 2021-11-18
      • 2012-07-06
      • 1970-01-01
      • 2017-01-29
      • 2017-08-09
      相关资源
      最近更新 更多