書いてたら、見覚えないけど、私の中では定着しつつある実装パターンを書き殴る感じの記事になってたので、整理する。
概要
- 『マジックメソッド(特に__construct())は、Traitに「メソッドの引き上げ」をしてはいけない』のツッコミを依頼したら、無事にツッコミを頂戴できました。
- この記事中で、私の師匠氏に「Getter/Sestterの乱用はアンチパターン」については、実はおいちゃんは些か懐疑的。」と言われたので。多分、「乱用」のボーダの認識を共有できていないので、私の認識を書いとこうと思いました。
この記事での結論は、以下の通りです。
- 不要なGetterを作らない。
- もし、Unitテストでのみ使用するようなものなら、PHPUnitならprivate/protectedなパラメータでもAssertできるので、不要。
- PHPUnitのAssertionで機能不足なら、リフレクションを使えばなんとかなる。
- 不要なSetterを作らない。
- インスタンスのアイデンティティを示すような、primary なプロパティにSetterを作るな。
乱用に当たる例
インスタンスの primary なプロパティがSetterで変更される
この記事でも言っているように"Violated Encapsulation Principle(カプセル化原則に違反)"してると思います。SOLIDで言えば、OCP(Open-Closed Principle)違反でしょうか。
class User
{
private $id; // 主キー的なやつ
private $name;
private $age;
public function setId(int $id)): User
{
$this->id = $id;
return $this;
}
public function setName(string $name)): User
{
$this->name = $name;
return $this;
}
public function setAge(int $age): User
{
$this->age = $age;
return $this;
}
}
$userA = new User();
$userB = new User();
$userA->setId(1)->setName('a')->setAge(14);
$userB->setId(2)->setName('b')->setAge(196);
$userA->setId(2); // ユーザBを示すようになった!!
// 普通に提供されているメソッドを使っただけなのに、
// なんでデータに矛盾が生じてるんだよ!!ふざけんな!!!
以下の操作と同じだから、ダメです。
CREATE TABLE user(
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARBYNARY(127),
age INT
);
INSERT INTO user(name, age) VALUES('a', 14);
INSERT INTO user(name, age) VALUES('b', 196);
UPDATE uesr SET id = 2 WHERE id = 1; // ここで本来エラーになる
常識的に考えて、Priamry Key が重複しようものなら Duplicated Errorが発生するべきですけど。それはUserクラス自身ではなく、UserManagerの責任です。
上記の場合だと「主キーをUPDATEする」事自体がよくなさそうに感じますが、そこは別に問題ありません。問題点は、本来エラーになる操作が、普通に使えるAPIのように実装されていることです。ふつーに使ったらバグるってヤバい。
管理者は主キーを更新することがあるかもしれませんが。管理者はUserクラスではなく、UserManagerという、Userとは全く別のインターフェースを持つクラスなので、無関係です。UserManagerを実装した上で、更にUserクラスの実装もComposite(or 継承)するかは、また別の話。
場合によっては乱用じゃない
設定とかフラグ建設
フィルタリングとかをするときのフラグメントは、普通にsetFlags
でいいと思います。SplFileObject::setFlagsとか、普通に使いやすいですし。ただし、当然、ビットマスクにはそのクラスのオブジェクト定数を使うのが必須になります。
振る舞いに直に影響しますが、「デフォルトの引数の再設定」をしているようなものだし、抽象クラスとかならセーフかなぁと思います。