有没有可能对多个类型进行类型提示?

76

我可以在类型提示中允许两种不同的类型吗?

例如,参数$requester可以是UserFile中的任意一种:

function log (User|File $requester) {

}

3
如果两个类有一个共同的继承或接口(例如“Loggable”),那么您可以将其用于类型提示。 - Mark Baker
1
可能是重复的问题:是否可以为参数指定多个类型提示? - faintsignal
6个回答

84

在PHP 8.0及以上版本中,通过添加联合类型(Union Types),这将会成为可能

提案以61票赞成、5票反对的结果获得通过,并且已经准备好实施。

有一个之前的RFC也曾提出过类似的想法,正如另一个答案中提到的那样,但是那个提案最终被否决了。

它将与您问题中的示例完全相同:

class F
{
   public function foo (File|Resource $f) : int|float { /** implement this**// }
}
这意味着F :: foo()的期望值是File或资源,并将返回intfloat
还有一些额外的要点:

可空性

另外,您可以用null声明一个union。 A|null等价于?A,但更复杂的声明A | B | null也是可能的。

"False" 伪类型

在联合类型声明中使用false类型也是可能的。例如:int | false。这主要源于历史原因,因为某些内部函数在某些类型的错误条件下返回false。请参见strpos()
更现代的函数应该在这些情况下返回null或引发异常,但是包括此选项以考虑遗留代码。

在继承时添加和删除联合类型的一部分

对于参数类型提示,合法的操作是添加union类型(从而使函数不那么严格),并且对于返回类型提示,合法的操作是删除union类型(使返回类型更具体)。
给定上面的F类,这是合法的:
class G extends F
{
    public function foo(File|Resource|string $f) : int { /** **/ }
}

但是这并不是:

class H extends F
{
    public function foo(File $f) : int|float|bool { /** **/ }
}

你能为 PHP 8 提供异常类型提示吗? - Adam
为什么不行呢?异常是类。 - yivi
我的意思是类型提示一个方法可能会抛出异常 - 而不是返回它。myMethod(): void|MyException 不起作用。 - Adam
这是一个返回类型标注。使用这种语法表示“该方法返回void或者MyException的实例”。没有“可能抛出异常”的语法,您需要使用注释或属性来标注。 - yivi

62

学术上,这被称为联合类型

PHP中的联合类型

你可以通过创建接口、父类型等方式来欺骗,正如其他答案中提到的那样,但这有什么意义呢,除了增加复杂性和项目行数?此外,对于标量类型,这是行不通的,因为你无法扩展/实现标量类型。

与其使代码更易读,倒不如相反。除非那些类/接口已经存在并且它们在这里是因为面向对象编程而不是为了解决类型提示问题。

解决方法

在 PHP 中,规范的做法就是... 不要放置类型提示。该语言并没有考虑具有复杂且强大的类型系统,试图绕过语言的缺陷并不是一个好答案。

相反,请正确记录您的函数:

/**
 * Description of what the function does.
 *
 * @param User|File $multiTypeArgument Description of the argument.
 *
 * @return string[] Description of the function's return value.
 */
function myFunction($multiTypeArgument)
{

这至少可以为自动完成和静态代码分析带来IDE支持,在处理私人项目、网站等方面非常有用。

当设计公共API(例如PHP库等)时,有时您可能希望更加谨慎地处理API消费者的输入。

那么,@tilz0R的答案就是正确的选择:

function log($message) {
    if (!is_string($message) && !$message instanceof Message) {
        throw new \InvalidArgumentException('$message must be a string or a Message object.');
    }

    // code ...
}

PHP(几乎)具备联合类型的那一天

2015年2月14日,Union Types PHP RFC 被提议用于 PHP 7.1。经过讨论和投票,它被否决了,18票反对,11票赞成。

如果这个RFC被接受了,PHP将会有像你展示的一样的联合类型(User|File)。

这个RFC有一些缺陷,但主要原因是 维护者 投票人对于变化特别是涉及到类型严格性和其他编程范例(例如:"为什么我们需要类型联合,当默认可以接受所有类型的值""那会影响性能")持有相当大的抵抗心理。


9
我有点不相信这个消息,但我刚刚核实了一下。不幸的是,我去看了RFC,对此感到沉思,然后决定去查看RFC索引,结果发现联合类型V2位于RFC列表的顶部。哇,它只是在6天前开始了讨论。https://github.com/php/php-rfcs/pull/1 - 这也是GH上第一个被讨论的RFC。时间安排真是有趣哈哈。 - i336_
@i336_ 是的,我也认为它将与这个进行对抗- https://wiki.php.net/rfc/mixed-typehint - jave.web
我反复遇到的联合类型的主要需求之一是ArrayAccess不允许传递数组。我从未找到一个好的解决方案来解决这个问题,在这些情况下默认不具有任何类型提示。 - Katai
3
联合类型的RFC已被接受并实现于PHP 8中。 - Umair Khan

19

目前在 PHP 中无法实现。但是你可以拥有一个interface,并为UserFile实现它,然后在log()中使用该接口作为类型提示:

<?php
interface UserFile {
}

class User implements UserFile {
}
class File implements UserFile {
}

// snip

public function log (UserFile $requester) {
}

6

您可以在函数内部检查类型。

function log ($requester) {
    if ($requester instanceof User || $requester instanceof File) {
        //Do your job
    }
}

4

或者您可以为每个方法都设置2个,以及一个动态方法:

function logUser($requester)
{
  //
}

function logFile($requester)
{
  //
}

还有动态的

function log($requester)
{
  if ($requester instanceof File) {
    return $this->logFile($requester);
  }

  if ($requester instanceof User) {
    return $this->logUser($requester);
  }

  throw new LogMethodException();
}

1
你可以为这两个创建一个父类:
abstract class Parent_class {}
class User extends Parent_class {
    // ...
}
class File extends Parent_class {
    // ...
}

并在函数中使用它

function log (Parent_class $requester) {
    // ... code
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接