PHP - 实现接口时类型提示更具体的类型
PHP - Type Hint A More Specific Type When Implementing An Interface
给定如下 object/interface 层次结构
<?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 将其标记为错误
Method 'MyEventHandler::handle()' is not compatible with method 'EventHandler::handle()'.
虽然我觉得以可扩展性的名义,这应该是可能的...
实现方法的签名与接口中方法的签名不同,但在运行时不会将 MyEvent
视为与 Event
相同吗?
显然,如果具体实现类型提示 Event
接口,则没有问题,并且在运行时该方法将接受实现 MyEvent
但随后我们失去了具体 MyEventHandler
应该处理 MyEvent
类型的事件。 Event
和 EventHandler
接口的实现可能有很多,因此能够尽可能明确是我们在这里寻找的,而不仅仅是在具体句柄中键入提示接口方法。
如果接口和类型提示无法做到这一点,是否有另一种方法可以使用抽象 class 实现这一点,同时保留强(ish)类型和传达意图?或者我们是否需要 MyEventHandler
中的显式方法来允许具体事件的类型提示 class?
你问的是“协变方法参数类型”。
问题是 MyEvent
class 可以定义一些额外的行为 to/by Event
接口。由于 handler()
方法需要 MyEvent
类型(而不是 Event
类型),因此可以得出结论,handler()
方法实际上使用了该额外行为;否则为什么要改变参数的类型?
如果 PHP 允许这样做,MyEventHandler
class 实例的 handle()
方法可能会收到非 MyEvent
事件对象。然后它会导致运行时错误。
事实上,维基百科对这个问题有很好的描述here。包括下面的一小段代码(代码不是 PHP,但你会得到它):
class AnimalShelter {
void putAnimal(Animal animal) {
//...
}
}
class CatShelter extends AnimalShelter {
void putAnimal(covariant Cat animal) {
// ...
}
}
This is not type safe. By up-casting a CatShelter to an AnimalShelter, one can try to place a dog in a cat shelter. That does not meet CatShelter parameter restrictions, and will result in a runtime error. The lack of type safety (known as the "catcall problem" in the Eiffel community, where "cat" or "CAT" is a Changed Availability or Type) has been a long-standing issue. Over the years, various combinations of global static analysis, local static analysis, and new language features have been proposed to remedy it,[7] [8] and these have been implemented in some Eiffel compilers.
有趣的是,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() 方法移动到处理程序 classes 的构造函数,这对您的特定情况会有所帮助。
给定如下 object/interface 层次结构
<?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 将其标记为错误
Method 'MyEventHandler::handle()' is not compatible with method 'EventHandler::handle()'.
虽然我觉得以可扩展性的名义,这应该是可能的...
实现方法的签名与接口中方法的签名不同,但在运行时不会将 MyEvent
视为与 Event
相同吗?
显然,如果具体实现类型提示 Event
接口,则没有问题,并且在运行时该方法将接受实现 MyEvent
但随后我们失去了具体 MyEventHandler
应该处理 MyEvent
类型的事件。 Event
和 EventHandler
接口的实现可能有很多,因此能够尽可能明确是我们在这里寻找的,而不仅仅是在具体句柄中键入提示接口方法。
如果接口和类型提示无法做到这一点,是否有另一种方法可以使用抽象 class 实现这一点,同时保留强(ish)类型和传达意图?或者我们是否需要 MyEventHandler
中的显式方法来允许具体事件的类型提示 class?
你问的是“协变方法参数类型”。
问题是 MyEvent
class 可以定义一些额外的行为 to/by Event
接口。由于 handler()
方法需要 MyEvent
类型(而不是 Event
类型),因此可以得出结论,handler()
方法实际上使用了该额外行为;否则为什么要改变参数的类型?
如果 PHP 允许这样做,MyEventHandler
class 实例的 handle()
方法可能会收到非 MyEvent
事件对象。然后它会导致运行时错误。
事实上,维基百科对这个问题有很好的描述here。包括下面的一小段代码(代码不是 PHP,但你会得到它):
class AnimalShelter {
void putAnimal(Animal animal) {
//...
}
}
class CatShelter extends AnimalShelter {
void putAnimal(covariant Cat animal) {
// ...
}
}
This is not type safe. By up-casting a CatShelter to an AnimalShelter, one can try to place a dog in a cat shelter. That does not meet CatShelter parameter restrictions, and will result in a runtime error. The lack of type safety (known as the "catcall problem" in the Eiffel community, where "cat" or "CAT" is a Changed Availability or Type) has been a long-standing issue. Over the years, various combinations of global static analysis, local static analysis, and new language features have been proposed to remedy it,[7] [8] and these have been implemented in some Eiffel compilers.
有趣的是,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() 方法移动到处理程序 classes 的构造函数,这对您的特定情况会有所帮助。