モデルクラスに条件判定メソッドを定義して、仕様をコードにまとめる。
※ コンストラクタは省略しています。
<?php declare(strict_types=1); final class Post { const STATUS_PUBLIC = 1; const STATUS_CLOSE = 0; /** @var int */ private $status; public function status(): int { return $this->status; } }
ブログ記事を表すPostモデルを例に使います。$post = Post::find(1);
とDBから取得できるとします。
statusカラムに記事の公開状態を数値で管理します。
<?php if ($post->status === Post::STATUS_PUBLIC) { // 記事が公開されている時の処理 }
記事が公開されているかを確認する時に、クラスの外で定数を比較していませんか? 条件はメソッドにして分かりやすくしましょう。Postクラスに下記を定義します。 クラス定数をprivateにすることも重要です。クラス外ではisPublicでしか定数が比較されないことを保証します。
<?php // 一部省略 final class Post { private const STATUS_PUBLIC = 1; private const STATUS_CLOSE = 0; public function isPublic(): bool { return $this->status === self::STATUS_PUBLIC; } }
If文はこう変わります。
<?php if ($post->isPublic()) { // ... }
ここから理由について掘り下げていきます。
メソッドで条件を1行だけに留める
記事はどのような判断で、「公開」と見なすでしょう。アップデートに伴い、postのstatusが増える可能性があります。
- 下書き
- フォロワーだけに公開
- 特定の選択した人にだけ公開
このように増えたとしても、クラス外ではisPublicを使っていれば、変更はありません。もし、直接書いていた場合は、&&
や||
を使いビシネスロジックが読みづらくなってしまうでしょう。
英文のように読みやすいかが良いメソッドの判断基準です。主語と動詞を意識します。上記の追加したステータスによっては、isPublicをisAllPublicなどにリネームする必要があるかもしれません。
考える範囲が狭まり、PRが見やすい
isPublicなど条件判定が複雑なっていたとしても、このメソッドの定義を見る時は「記事が公開の時のみtrueを返すか?」だけを考えればよく、isPublicを呼び出す側のビジネスロジックは考える必要はありません。もちろんグローバル変数など使っておらず、きちんと互いに影響のない独立したisPublicを作ることが条件です。その条件を満たしているかを見れば良いことになります。
そして、一度独立したisPublic作り運用すれば、今後はこのメソッドを使ってもらえれば、判定条件までレビューする必要がなくなります。
※ 「公開する判断」という仕様が変わる時は別です。
もしisPublicがなければ、毎回クラス定数との比較などを見る必要が出てきます。複雑になるほど、! / && / || / ()
が多用され、毎回全てが正しいかを確認する必要があります。それなら、誰かが時間をかけて実装したisPublicを使い回しましょう。
データの仕様が分かる・まとまる
Postクラスを見るとステータスがどのように使われているか分かります。もしかしたら、ステータスは公開範囲外でも使われているかもしれません。しかし、データがある場所、つまりPostクラスを見れば分かります。
クラスのデータ、さらに細かく言うと各値の仕様をまとめるためにも、クラスにメソッドをまとめます。別でドキュメントを用意するより、PHPDocなどで書いた方が確実で、小コストで保守できます。
一番良いのは、間違えて使うのが難しいコードです。そのために、メソッドを用意し、引数もできるだけ少なくすると良いでしょう。
プロパティ、1つの値をクラス化してしまう
StatusをPostStatusというクラスにして、PostクラスはPostStatusのインスタンスを保持するようにすると、さらに見やすくなります。
<?php final class Post { /** @var PostStatus */ private $status; public function status(): PostStatus { return $this->status; } } final class PostStatus { const PUBLIC = 1; const CLOSE = 0; /** @var int */ private $value; public function __construct(int) public function value(): int { return $this->value; } public function isPublic(): bool { return $this->value === self::PUBLIC; } } if ($post->status()->isPublic()) { }
以前のisPublicを残して、ショートカットとして使うの良いと思います。私はプロキシメソッドと呼んでいます。PostStatusのメソッドが増えるたびに、用意する必要が出るかもしれないため、面倒かもしれません。
<?php final class Post { /** @var PostStatus */ private $status; public function isPublic(): bool { return $this->status->isPublic(); } }
このようにクラスを分けると、記事の公開状態だけの仕様を考える事ができます。Postには他にも色々なメソッドが増えて、どんなメソッドがあるか分かりづらくなってきます。
インスタンスプロパティが増えるほど、互いのメソッドに影響が出る可能性もあります。「他のメソッドで$status
が変更されていないか?」を考える必要も減らすためにも、PostStatusがあると便利です。少なくともPostStatus内では、Postクラス内のことを考える必要はありません。