2016-04-11 16 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に設定することもできます。私のプロフィールページでは、アドレスがnull(空)の場合、ヒントを表示したいと思います。

しかし、これは1つの例にすぎません。私たちのアプリケーションには100以上のデータベーステーブルと対応するPHPクラスがあり、ほとんどすべてにオプションフィールドがあります。オプションのフィールドを処理するためにnullをチェックするのではなく、常にtry catchブロックを書くことは、私にとっては最適ではないようです。

上記の例の良い解決策は何ですか?

+0

変数を空のデフォルト値に初期化するだけで済みます(空白/無効/デフォルト値を持つアドレスオブジェクトの文字列やアドレスなど)。例外をキャッチするラッパーから呼び出すクラスを使用することもできます – Nadir

+0

getAddress()。getCity === ''は単純にgetAddress()== nullをチェックするよりも優れています。この他にもチェックが複雑になることがあります。例えば、郵便番号がオプションの場合新しい開発者は、getAddress()。getZip()=== ''というコードを書くことができます。アドレスが不足していると思っていますが、郵便番号だけが見つかりません。 – Vmxes

+0

あなたの例では、 '$ this-> realName'がnullのときに' getRealName() 'を呼び出すときの動作ですか?それがnullの場合は値をテストして処理する必要がありますか?そうなら、あなたのクラスはそうしているはずです。ヌル文字列は、多くの文字列操作のために空文字列のように扱われますが、実際にはヌル文字列ではなく空文字列にデフォルト設定しなければなりません。ほとんどの状況はこのように解決できます。それは、私はPHPがこれを強制することで不適切に説得されていると思います。 –

答えて

2

問題は、オブジェクトがOOPルールに従わないことです。

まず、getters and setters are evilはオブジェクトの内部構造を公開しているためです。残りのシステムの内部ユーザーオブジェクト構造に依存する無限の可能性を導入するだけです。オブジェクトの目的は、実装の詳細を隠し、この隠しデータを操作するためのインタフェースを提供することです。

およびnull is evil(またはa billion dollar mistake)は、コードの信頼性を大幅に低下させるためです。 特殊なケース(戻り値はnull)を導入しています。この特殊ケースは、オブジェクトを使用してコード内で処理する必要があります。 悪いことに、この "null"特殊ケース(または特別なケースがあることがわからない場合)を処理するのを忘れた場合、すぐにエラーが発生することはありません。 これで、見つけて修正するのに非常に時間がかかる隠されたエラーが発生します。

実際に、私は次のオプション参照(すべてのケースで - ゲッターを削除):

オプション1)

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

何らかの統一形式のプロファイルデータのスナップショットを取得します。これは、まだ一種であり、すべてのデータを統一された形式(文字列)に変換します。データベースに浮動小数点数(年齢や高さなど)の整数があっても、残りのシステムはオブジェクトの実際の内部構造に依存しないため、ここでも文字列を返します。

オプションのフィールドの空の文字列(または「指定されていない」などの特殊な値)も、「nullオブジェクト」の一種として機能します。返された値を小さなフィールド(オブジェクト)にラップし、実際にはオプションのフィールドにnullオブジェクトパターンを使用することもできます。

クラスを使用するコードでは、特別な "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とUserプロパティのサブセット用の新しいクラスを作成したくありません。もちろん、私は自分のエンティティクラスにプレゼンテーションロジックを必要としません。 – Vmxes

+0

@Vmxes現在の実装の問題は、カプセル化という基本的なOOPの原則の1つに違反していることです。あなたのフィールドを 'private'として定義していますが、ゲッターとセッターでこれらのフィールドを世界に公開します。そしてnullを返すので、コードのすべての特殊なケースをチェックする必要があります。これらの欠陥を取り除き、他のアプローチを使用する方法を考え始めると、残りのコードもよりシンプルで統一されたものになることがわかります。私の例は、念頭に置いたほんのいくつかのオプションです。私がリンクしている記事もチェックしてください。 –

0

私は最近、私はそれを見たとき、それは何かが間違っていた意味を知っているように、ヌルは、問題があります意味という考えを受け入れています。

このような状況で私はNull Object design patternを使用する傾向があります。

class NullAddress implements AddressInterface { } 

AddressクラスもAddressInterfaceを実装する必要があります:
ので、アドレスを処理するために、次のようなものを作成します。
その後、getAddress()は次のようになります。getAddress()を呼び出す関数は、その後でNULL値をチェックすることができ

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

:文字列を処理するために

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

、戻って空の文字列は、これまでも私を務めています。私はただそれを確認するためにempty()を使用します。