__invokeでオブジェクトを関数のように呼ぶ
__invokeは、オブジェクトに()を付けて関数のように呼び出せるようにするマジックメソッドです。状態を持ったコールバックをcallableとして扱えるのが持ち味です。
オブジェクトに()を付けて呼べる
PHPのオブジェクトは、__invoke を実装すると変数の後ろに () を付けて関数のように呼び出せるようになります。最初は何が嬉しいのか分かりにくいのですが、状態を持った処理を関数のように扱いたいときに効いてくる仕組みだと思っています。
final class TaxCalculator
{
public function __construct(private float $rate = 0.10) {}
public function __invoke(int $price): int
{
return (int) round($price * (1 + $this->rate));
}
}
$withTax = new TaxCalculator(0.10);
echo $withTax(1000); // 1100
ただの関数と違うのは、税率のような設定をコンストラクタで抱えられる点ですね。呼び出し側は $withTax(1000) と書くだけで、内部にどんな状態があるかを気にしなくて済みます。
callableとして渡せるのが本題
__invoke を持つオブジェクトは callable として扱われるので、array_map や usort、あるいは callable を要求するあらゆる関数にそのまま渡せます。ここが一番の使いどころだと思います。無名関数と違って名前とテストが付けやすいので、少し複雑なコールバックはオブジェクトにしておくと後で楽ですね。
$prices = [1000, 2000, 3000];
$result = array_map(new TaxCalculator(0.10), $prices);
// [1100, 2200, 3300]
// is_callable でも true になる
var_dump(is_callable(new TaxCalculator())); // bool(true)
単一責務のクラスとして書く
実務では「一つの処理だけを行うクラス」を invoke で表現するスタイルをよく見ます。いわゆる Action クラスや Handler ですね。メソッド名を何にするか悩まずに済むうえ、依存はコンストラクタで注入できるので、テスタビリティと表現力のバランスが良いという印象です。
final class SendWelcomeMail
{
public function __construct(private Mailer $mailer) {}
public function __invoke(string $email): void
{
$this->mailer->send($email, 'ようこそ');
}
}
$action = new SendWelcomeMail($mailer);
$action('user@example.com');
まとめ
__invoke はオブジェクトと関数の間をつなぐ橋のようなもので、状態を持ったコールバックを綺麗に書けるのが持ち味だと思います。乱用すると呼び出し箇所で何が起きているか読みにくくなるので、単一の処理に絞った小さなクラスに限って使うくらいがちょうどいい気がします。
よくある質問
Q. __invokeを実装すると何ができますか。
A. オブジェクトに()を付けて関数のように呼び出せます。$obj($arg) という書き方ができるようになります。
Q. 無名関数と比べた利点は。
A. コンストラクタで状態を注入でき、名前とテストが付けやすい点です。callableとしてarray_mapなどにそのまま渡せます。
Q. どんな場面で使うのが向いていますか。
A. 単一の処理だけを行うActionクラスやHandlerに向いています。乱用は読みにくくなるので処理を絞るのがコツです。