处理PHP 7返回类型的正确方法

28

我正忙于创建一个应用程序,并想使用PHP 7返回类型。现在我在php.net上读到,这是一个设计决定,当定义了返回类型时,不允许返回null

如何正确处理这个问题?

一种选择是使用try...catch块:

public function getMyObject() : MyObject
{ 
     return null;
}

try
{
    getMyObject();
}
catch(Exception $e)
{
    //Catch the exception
}

我对此感觉不太好,因为我的代码将成为一个巨大的try...catch块,我需要为每个返回对象的方法编写一个try...catch块。

空对象模式是一个好的解决方案,但是我不喜欢为我的应用程序中的每个对象创建一个NullObject的想法。有没有正确的方法来解决这个问题?


3
返回false?简单的数据类型,清楚地表明初始化对象失败。 - online Thomas
2
"void" 返回类型已被 PHP 7.1 接受,所以它将来会加入其中。 - Daan
明智的解决方案是确保 getMyObject 返回实际对象。看起来你的对象可能处于无效状态,这是次优的。但是如果不了解你的代码,很难猜测。 - PeeHaa
2
void返回类型与此情况无关。 - Andrea
我不是很理解这个问题,但不管它是什么:你永远都不应该随意捕获 Error 异常。除非你有一些非常特殊的情况,否则你的应用程序应该只在一个地方捕获这些异常: 顶级异常处理程序。我认为你在这里寻找的是简单地省略类型注释。这仍然被允许。如果你的返回值是“对象或 null”,那么这就是你应该做的(直到我们引入可空类型)。 - NikiC
你的代码需要像这样才能捕获TypeError异常:https://gist.github.com/ggirtsou/ee09a5a053e0d17e8caa。```catch Exception```只在PHP 5.x中执行,在PHP 7中将不会被执行 - ggirtsou
6个回答

29

如果您的代码期望返回一个类的实例,比如说MyObject,但实际上却没有返回,这就是一个异常情况,应该按照异常情况来处理。

不过,您需要自己抛出这个异常。

public function getMyObject() : MyObject
{
    throw new MyCustomException("Cannot get my object");
}

然后你可以捕获该特定异常并相应地进行处理。


有没有办法拥有特定对象的数组:类似 public function getMyObject() : Array[MyObject] 这样的? - Panagiotis Koursaris

7
我对此并不感到乐观,因为我的代码将成为一个巨大的try/catch块,由于我需要为每个返回对象的方法编写try/catch块。

通过你的代码来看,另一种选择是始终检查返回值是否为null,以避免使用null作为对象而出现错误。

使用异常更表达了意图,并使错误早期可见,这是一件好事。因此,请确保您的方法始终返回一个对象,或者如果无法履行合同则引发异常。否则,返回类型没有实际价值。


3
你说的很有道理。也许这是一种避免使用try/catch代码块的习惯,但我认为现在是接受这种行为并利用其优点的好时机。 - S.Pols
如果一个空对象没有意义(这通常是情况),那么添加第二个方法hasMyObject()是有意义的,您可以在实际获取对象之前进行检查。这是最明确的方法。 - DASPRiD

6
您可以使用“Option”数据类型。
它是一种将SomethingNothing作为包装值的类型。
您可以在这里找到更详细的描述,并在这里找到PHP中的一个开放实现。
PS请不要使用异常、输出参数或其他方法来处理这种逻辑,因为这是错误的工具。

这看起来像是 Haskell 的 Maybe 数据类型,并且必须是 SPL 的一部分。 - Damien Flament
在Rust中有Option选项(设置为Some(value)或None),但PHP的SPL中没有类似的东西。此外,在PHP中创建新对象的性能相对较低,因此在PHP中使用运行时对象来模拟这种情况有点浪费。 - Mikko Rantalainen

3

目前这是不可能的,虽然提到了RFC可能会在未来版本中解决这个问题。

现在,你的选择有:

1.从方法签名中删除返回类型:

public function getMyObject()
{ 
     return null;
}

2. 抛出和捕获异常(参考@NiettheDarkAbsol的答案):

public function getMyObject() : MyObject
{
    throw new MyCustomException("Cannot get my object");
}

3.重构为两个方法:

private $myObject;
//TODO think of a better method name
public function canGetMyObject() : bool
{
    $this->myObject = new myObject();
    return true;
}

public function getMyObject() : MyObject
{
    if(!$this->myObject){
        throw new MyCustomException("Cannot get my object");
    }
    return $this->myObject;
}

呼叫代码:

if($cls->canGetMyObject()){
    $myObject  = $cls->getMyObject();
}

4. 使用bool返回类型和out参数:

public function tryGetMyObject(&$out) : bool
{
    $out = new myObject();
    return true;
}

调用代码:

$myObject = null;

if($cls->tryGetMyObject($myObject)){      
    $myObject->someMethod(); //$myObject is an instance of MyObject class
}

第三个和第四个选项只有在预期返回null并且频繁出现的情况下才值得考虑,因为异常的开销是一个因素。 可能你会发现这种情况并不经常出现,使用异常是更好的选择。

从不同的答案中,我感觉最好的方法是抛出自定义异常。我会尝试一下,希望这种方式的开销能够值得它所带来的优势! - S.Pols
1
是的,这可能是正确的,上面的其他选项只适用于特定情况,有人会认为例外是最好的解决方案。请注意,我添加了第四种方法,您可能会喜欢它胜过第三种方法。 - Steve

2

1
而且在 PHP 8 中,如果您不想返回 null 而是想返回 false,您可以编写 : Myobject|false - Mikko Rantalainen

0

我本来也要问同样的问题。我觉得这没什么意义。

如果我有以下示例代码用于根据姓名查找用户:

<?php

class User
{
    protected $name;

    /**
     * User constructor.
     *
     * @param $name
     */
    public function __construct(string $name)
    {
        $this->name = $name;
    }

    /**
     * @return mixed
     */
    public function getName() : string
    {
        return $this->name;
    }
}


function findUserByName(string $name, array $users) : User {
    foreach ($users as $user) {
        if ($user->getName() == $name) {
            return $user;
        }
    }

    return null;
}


$users = [
    new User('John'), new User('Daniel')
];

$user = findUserByName('John', $users);

echo $user->getName()."\n";

$user2 = findUserByName('Micheal', $users);

我想告诉Php我可以获取User或null。现在我需要将其始终包装在try catch块中,而且我不认为在这种情况下声明返回类型有多大意义。另一方面,如果我的findUserByName函数复杂,我可能会意外返回例如用户名而不是完整的用户对象,因此我希望定义返回类型。

我认为更合理的做法是定义函数的多个返回类型。有时您创建一个返回一个对象或对象数组的函数,定义该函数应该返回例如User或用户数组会很好:User[]

在OP的情况(也是我的情况)下,我们可以将返回类型声明为User,null,这将解决问题。


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