__getと__setは便利だが諸刃、補完が効かず事故も呼ぶ
__getと__setとは、存在しないプロパティにアクセスしたとき呼ばれるマジックメソッドです。動的なプロパティ操作ができる反面、補完や静的解析が効かなくなります。
存在しないプロパティを拾う仕組み
__get と __set は、アクセスしたプロパティが存在しない(またはスコープ外)のときに呼ばれるフックです。これを使うと内部配列を透過的にプロパティ風に見せられて、一見エレガントなんですが、便利さの裏でいくつか失うものがあると感じています。
class Config
{
private array $data = [];
public function __get(string $name): mixed
{
return $this->data[$name] ?? null;
}
public function __set(string $name, mixed $value): void
{
$this->data[$name] = $value;
}
}
$c = new Config();
$c->timeout = 30; // __set が呼ばれる
echo $c->timeout; // __get が呼ばれる
IDEの補完と静的解析が効かなくなる
一番の痛点はこれだと思います。どんな名前でも受け付けてしまうので、IDEはプロパティ名を補完できず、タイプミスも検出してくれません。$c->timeuot と書いても __set が黙って新しいキーを作るだけで、実行時まで気づけないわけですね。静的解析ツールも同じ理由で手を出せなくなります。
$c->retryCount = 3;
echo $c->retrycount; // 大文字小文字違い → null、しかもエラーにならない
補うなら @property のPHPDocを書いてIDEに教える手はありますが、実体と二重管理になるので、それなら最初から普通のプロパティでよかったのでは、という気持ちにもなります。
使うなら範囲を絞って明示的に
全面採用ではなく、たとえば外部APIのレスポンスのように事前にキーが確定しないデータのラッパーに限定する、といった割り切りが現実的だと思います。せめて未知のキーは黙って null を返さず例外にしておくと、タイプミスが早期に表面化します。
public function __get(string $name): mixed
{
if (!array_key_exists($name, $this->data)) {
throw new OutOfBoundsException("未定義のキー: {$name}");
}
return $this->data[$name];
}
まとめ
__get と __set は書き味こそ良いものの、補完も型チェックもすり抜けるので、事故が起きたときに原因が見えにくくなります。プロパティが確定しているなら素直に宣言する、どうしても動的にしたいなら範囲を絞って未知キーは例外に、というあたりが安全な落としどころだと思っています。
よくある質問
Q. __getと__setはいつ呼ばれますか?
A. アクセスしたプロパティが存在しない、またはスコープ外のときに呼ばれるフックです。
Q. 一番のデメリットは何ですか?
A. IDEの補完も静的解析も効かなくなる点です。タイプミスがあっても実行時まで気づけません。
Q. どうしても使いたいときは?
A. 事前にキーが確定しないデータのラッパーに限定し、未知のキーはnullではなく例外にするのが安全です。