2016-04-11 139 views
4

根据PHP 7返回类型提示,以及为什么如果将类定义为返回类型时不可能返回null,已经有很多问题了。像这样:Correct way to handle PHP 7 return types。 答案通常说,它不是一个问题,就好像一个函数应该返回一个类,然后返回null可能是一个例外。 也许我错过了某些东西,但我不明白为什么。例如,让我们看到一个简单的用户类:PHP 7返回类型提示

class User 
{ 
    private $username; // mandatory 
    private $password; // mandatory 
    private $realName; // optional 
    private $address; // optional 

    public function getUsername() : string { 
     return $this->username; 
    } 

    public function setUsername(string $username) { 
     $this->username = $username; 
    } 

    public function getPassword() : string { 
     return $this->password; 
    } 

    public function setPassword(string $password) { 
     $this->password = $password; 
    } 

    public function getRealName() : string { 
     return $this->realName; 
    } 

    public function setRealName(string $realName = null) { 
     $this->realName = $realName; 
    } 

    public function getAddress() : Address { 
     return $this->address; 
    } 

    public function setAddress(Address $address = null) { 
     $this->address = $address; 
    } 

} 

在我们的应用程序是完全合法的,有一个用户没有实名和/或不带地址。我甚至可以将realName和address字段设置为null - 即使上述解决方案不是最优的。而在我们的个人资料页面中,如果地址为空(空),我想显示提示。

但这只是一个例子。我们的应用程序有超过100个数据库表和相应的PHP类,几乎所有的都有可选字段。总是写一个try catch块,而不是简单地检查null来处理可选字段,这对我来说似乎有点不理想。

那么对于上面的例子什么是一个好的解决方案?

+0

你可以初始化变量以清空默认值(像字符串为''和地址对象为空/无效/默认值。你也可以使用你的类从包装器调用它们来捕获异常if您不会返回null – Nadir

+0

我不认为getAddress()。getCity ===''比简单地检查getAddress()== null更好,除此之外,这个检查可能会很复杂,例如,如果邮政编码是可选的那么一个新的开发人员可以编写一个代码:getAddress()。getZip()==='',他/她认为地址丢失了,但是只有邮政编码丢失了 – Vmxes

+0

在你的例子中,你期望调用代码的行为在'$ this-> realName'为空时调用'getRealName()'时是否需要测试值并在它为空时处理它?如果是这样的话:你的类应该这样做。对于大量的字符串操作来说,空字符串应该像空字符串一样对待,您可能实际上应该将其默认为空字符串,而不是空字符串。大多数情况可以像这样解决。这就是说,我认为PHP在执行这个过程中被认为是不恰当的。 –

回答

2

问题是您的对象不遵循OOP规则。

首先,getters and setters are evil因为它们暴露了对象的内部结构。您只需介绍依赖于系统其余部分的内部用户对象结构的无限可能性。虽然该对象的目的是隐藏实现细节并提供操作此隐藏数据的接口。

null is evil(或a billion dollar mistake)因为它使代码更不可靠。 它引入了特殊情况(空返回值),并且有必要在使用您的对象的代码中处理这种特殊情况。 更糟的是,如果您忘记处理这个“空”特殊情况(或者如果您不知道存在特殊情况),您将不会立即得到错误。 所以你会得到隐藏的错误,这可能是非常耗时找到和修复。

实际上,我看到下面的选项(在所有情况下 - 删除干将):

选项1)获取配置文件数据的快照在一些统一的形式

$user->getProfileData() { 
    return [ 
     'username': $this->username, 
     'realName': $this->realName ? $this->realName : '', 
     'address': $this->address ? $this->address : 'Not specified' 
     ... 
    ]; 
} 

这仍然是一种的吸气剂,但你将所有的数据转换成统一的形式(字符串)。即使你的数据库中有一些float字段的整数(比如年龄或者高度),你仍然会在这里返回字符串,所以你系统的其余部分并不依赖于对象的实际内部。

可选字段的空字符串(或特殊值,如“未指定”)也充当一种“空对象”。也可以将返回的值包装到小字段对象中,并将空对象模式实际用于可选字段。

在使用类的代码中,您不应该处理特殊的“null”情况,您只需循环字段并显示字符串(包括那些空的字符串)。

选项2)使对象本身负责表示

$user->showProfile($profileView) { 
    $profileView->addLabel('First Name'); 
    $profileView->addString($this->username); 
    ... 
} 

在这里你保持物体内部的内部细节,但现在它确实太多了,所以有人可能会说,现在违反了SRP

选项3)创建负责表示

$userPresentation = $user->createPresentation() 
// internally it will return new UserPresentation($this->username, this->realName, $this->address, ...); 
// now display it - generate the template and insert it into the view 
<? echo $userPresentation->getHtml(); ?> 

在这里,您将演示文稿逻辑到单独的对象特殊对象。用户对象和它的表示是紧密耦合的,但系统的其余部分现在什么都不知道(并且没有机会知道)关于用户对象内部的信息。

+0

非常详细的答案,但我认为在一个简单的实体类的情况下,所有的3个选项都违反了SRP。我不想为用户属性的一个子集创建一个新的getter和一个新类,这取决于调用者类需要什么。当然,我不希望我的实体类中有任何表示逻辑。 – Vmxes

+0

@Vmxes当前实现的问题在于它违反了基本的OOP原则之一 - 封装。虽然你把你的领域定义为'private',然后你用getter和setter打开这些领域。如果你返回空值,那么你必须检查代码中的特殊情况。一旦开始思考如何摆脱这些缺陷并使用其他方法,您会发现其他代码也将变得更加简单和统一。我的例子只是一些选项,请检查我链接的文章。 –

0

我最近接受的想法,意味着有一个问题,让我知道,当我看到它,就意味着出事了。

在这些情况下,我倾向于使用Null Object design pattern
因此,要处理的地址,您将创建类似:

class NullAddress implements AddressInterface { } 

Address类还需要实现AddressInterface
然后,getAddress()会是什么样子:

public function getAddress(): AddressInterface { 
    return $this->address ?? new NullAddress(); 
} 

调用getAddress()则可以通过检查空值的函数:

if ($user->getAddress() instanceof NullAddress) { 
    // Implement result of empty address here. 
} 

对于处理字符串,一个返回空字符串一直担任我很好,到目前为止。我只是用empty()来检查它。