2016-04-11 72 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()來檢查它。