出品者のデータにフィルタをかけて該当する出品者のSellerオブジェクトを返すメソッドがあるとします。
サンプルコード
<?php class Seller { private $data; /** * Seller constructor. * @param $data */ public function __construct(array $data) { // 出品者の情報が色々入ったデータ $this->data = $data; } /** * 出品者の価格 * @return int */ public function price(): int { return $this->data['price']; } /** * 出品者の評価率 * @return int */ public function rate(): int { return $this->data['rate']; } }
<?php class Filter { /** * @var Seller[] */ private $sellers; /** * Filter constructor. * @param Seller[] */ public function __construct(array $sellers) { // 販売者のクラスが入った配列 $this->sellers = $sellers; } /** * 評価率が80%以上で最安値の出品者情報を取得する * @return Seller */ public function featuredSeller(): Seller { $lowestPrice = null; $featuredSellerObject = null; foreach ($this->sellers as $seller) { // 評価率80%以上かつ最安値の出品者に絞る if (80 <= $seller->rate() && $this->isLowestPrice($seller, $lowestPrice)) { $lowestPrice = $seller->price(); $featuredSellerObject = $seller; } } return $featuredSellerObject; } /** * @param Seller $seller * @param $lowestPrice * @return bool */ private function isLowestPrice(Seller $seller, $lowestPrice): bool { return ($seller->price() < $lowestPrice || !$lowestPrice); } }
<?php // 出品者データ $sellers[] = new Seller(['rate' => 79, 'price' => 500]); $sellers[] = new Seller(['rate' => 60, 'price' => 700]); // 出品者にフィルターをかけて結果を取り出す $filter = new Filter($sellers); // 評価率80%以上で最安値の出品者を抽出する print_r($filter->featuredSeller());
featuredSellerメソッド
は評価率が80%以上で最安値の出品者を返しますが、出品者のデータの中に評価率80%以上の出品者がいない場合、nullが返りエラーになります。
<?php PHP Fatal error: Uncaught TypeError: Return value of Filter::featuredSeller() must be an instance of Seller, null returned in /PhpstormProjects/nullobject/filter.php:36 Stack trace: #0 /PhpstormProjects/nullobject/main.php(10): Filter->featuredSeller()
エラーが出ないようにするためには返り値の型をfeaturedSeller(): ?Seller
に書き換えてnullも許容できるようにしてあげないといけません。
<?php /** * 評価率が80%以上で最安値の出品者情報を取得する * @return Seller */ public function featuredSeller(): ?Seller // 返り値の型をnull or Sellerにする { $lowestPrice = null; $featuredSellerObject = null; foreach ($this->sellers as $seller) { // 評価率80%以上かつ最安値の出品者に絞る if (80 <= $seller->rate() && $this->isLowestPrice($seller, $lowestPrice)) { $lowestPrice = $seller->price(); $featuredSellerObject = $seller; } } return $featuredSellerObject; }
しかし、?Seller
としてしまうと利用する側でnullが返ってきているのか、Sellerオブジェクトが返ってきているのかいちいち確認しないといけません。
もしnullチェックを忘れてSellerクラスのメソッドを使用するとエラーになります。
<?php $filter = new Filter($sellers); // $filter->featuredSeller()の返り値がnullかどうか確認する必要がある if ($seller = $filter->featuredSeller()) { echo $seller->price(); } // nullチェックを忘れてSellerクラスのメソッドを使用するとエラーになる echo $filter->featuredSeller()->price();
NullObjectパターンを導入する
<?php interface SellerInterface { public function rate(); public function price(); }
<?php // SellerInterfaceを実装したメソッドは使えるが何も返さないクラス class NullSeller implements SellerInterface { public function price() { return null; } public function rate() { return null; } }
<?php class Seller implements SellerInterface //SellerInterfaceを実装 { private $data; /** * Seller constructor. * @param $data */ public function __construct(array $data) { // 出品者の情報が色々入ったデータ $this->data = $data; } /** * 出品者の価格 * @return int */ public function price(): int { return $this->data['price']; } /** * 出品者の評価率 * @return int */ public function rate(): int { return $this->data['rate']; } }
<?php /** * 評価率が80%以上で最安値の出品者情報を取得する * @return Seller */ public function featuredSeller(): SellerInterface // SellerInterfaceを返り値にする { $lowestPrice = null; $featuredSellerObject = new NullSeller(); // NullSellerを初期値として入れておく foreach ($this->sellers as $seller) { // 評価率80%以上かつ最安値の出品者に絞る if (80 <= $seller->rate() && $this->isLowestPrice($seller, $lowestPrice)) { $lowestPrice = $seller->price(); $featuredSellerObject = $seller; } } return $featuredSellerObject; }
条件に当てはまる出品者がいない場合、nullを返すのではなくNullSellerオブジェクトを返すようにし、またSellerInterface
をfeaturedSellerメソッド
の返り値とすることで、呼び出した側でnullが返ってくるのか確認する必要がなくなります。
<?php // 出品者 $sellers[] = new Seller(['rate' => 79, 'price' => 500]); $sellers[] = new Seller(['rate' => 60, 'price' => 700]); $filter = new Filter($sellers); // エラーにならない echo $filter->featuredSeller()->price();
NullObjectパターンを導入することで、
returnの型を明確にできる
返り値の型を?Seller
とする必要がなく、nullもしくはSellerが返るという曖昧な書き方をしなくて済む。返ってきた値に対してif文でnullかどうかいちいち確認しなくても良い
というメリットが得られます。
ただ昨日初めて使ったのでそもそも本来のNullObjectパターンとずれてるかもしれないし、後々にやっぱこれアンチパターンだわって気づくかもしれない。