[PHP]__invoke()とは?オブジェクトを関数のように呼び出す方法を徹底解説

PHP

はじめに

前回の記事では、オブジェクトを文字列として扱う __toString() を解説しました。今回取り上げる __invoke() は、それとよく似た発想で、オブジェクトを関数のように「呼び出し可能(callable)」にするマジックメソッドです。

$obj() のように、変数名の直後に括弧を付けてオブジェクトを呼び出すと、通常は「Object of class X is not callable」というエラーになります。しかし、クラスに __invoke() が定義されていると、この呼び出しが自動的に __invoke() に転送されます。この仕組みは、array_map() などのコールバックを受け取る関数に、状態を持った「関数オブジェクト」を渡したい場合や、ミドルウェアパターンの実装などで頻繁に活用されます。本記事では基本的な使い方から、実践的な活用例まで詳しく解説します。


メソッド概要

項目内容
メソッド名__invoke()
種別マジックメソッド
シグネチャpublic function __invoke(...$args): mixed
引数呼び出し時に渡された任意の引数(可変長)
呼び出しタイミングオブジェクトが $obj(...) の形式で呼び出された時
戻り値任意(呼び出し元に返したい値)
対応バージョンPHP 5.3.0以降
判定関数is_callable() が __invoke() を持つオブジェクトに対して true を返す
用途の分類「関数オブジェクト(Functor)」や「呼び出し可能なオブジェクト」の実現

呼び出しの仕組み(イメージ図)

  $multiplier = new Multiplier(3);

  $multiplier(5);
              │
              ▼
  ┌───────────────────────────┐
  │ PHPが$multiplierの直後の()を検知し、   │
  │ オブジェクトの呼び出しとして解釈する     │
  └───────────┬───────────────┘
              ▼
      __invoke(5) が自動的に呼ばれる
      ★この記事の対象
              │
              ▼
  戻り値がそのまま呼び出し結果となる
  (この例では 5 * 3 = 15 のような計算結果)

ポイントは、__invoke() を持つオブジェクトが通常の関数やクロージャと同じように callable 型として扱えるという点です。これにより、オブジェクトの持つ状態(プロパティ)を活かしながら、関数のように振る舞わせることができます。


実践サンプル7選

例1:基本的な使い方

<?php

class Multiplier
{
    public function __construct(private int $factor)
    {
    }

    public function __invoke(int $value): int
    {
        return $value * $this->factor;
    }
}

$triple = new Multiplier(3);
echo $triple(5) . PHP_EOL; // 15(関数のように呼び出せる)
echo $triple(10) . PHP_EOL; // 30

例2:array_map()などのコールバックとして使う

<?php

class CurrencyFormatter
{
    public function __construct(private string $currency)
    {
    }

    /**
     * __invoke()を持つオブジェクトは、
     * array_map()などのcallable引数にそのまま渡せる
     */
    public function __invoke(int $amount): string
    {
        return number_format($amount) . ' ' . $this->currency;
    }
}

$formatter = new CurrencyFormatter('JPY');
$prices = [1000, 25000, 300000];
$formatted = array_map($formatter, $prices);
print_r($formatted);

例3:ミドルウェアパターンの実装

<?php

class LoggingMiddleware
{
    /**
     * __invoke()を使うことで、ミドルウェアを
     * シンプルな関数のように連鎖させて呼び出せる
     */
    public function __invoke(string $request, callable $next): string
    {
        echo "[LOG] リクエスト受信: {$request}" . PHP_EOL;
        $response = $next($request);
        echo "[LOG] レスポンス送信: {$response}" . PHP_EOL;

        return $response;
    }
}

$middleware = new LoggingMiddleware();
$result = $middleware('GET /users', function (string $req) {
    return "200 OK ({$req})";
});
echo $result . PHP_EOL;

例4:バリデーションルールを関数オブジェクトとして表現する

<?php

class RangeValidator
{
    public function __construct(
        private int $min,
        private int $max
    ) {
    }

    public function __invoke(mixed $value): bool
    {
        return is_numeric($value) && $value >= $this->min && $value <= $this->max;
    }
}

$ageValidator = new RangeValidator(0, 120);
var_dump($ageValidator(30));   // true
var_dump($ageValidator(150));  // false

// array_filter()と組み合わせて、条件に合う値だけを抽出する例
$values = [10, 50, 150, -5, 80];
$validValues = array_filter($values, $ageValidator);
print_r($validValues);

例5:キャッシュ付きの計算をカプセル化する関数オブジェクト

<?php

class MemoizedFibonacci
{
    private array $cache = [];

    /**
     * 内部に状態(キャッシュ)を持ちながら、
     * 関数のように呼び出せるオブジェクトを作る
     * (通常の関数ではこうした状態の保持は難しい)
     */
    public function __invoke(int $n): int
    {
        if (isset($this->cache[$n])) {
            return $this->cache[$n];
        }

        $result = $n <= 1 ? $n : $this($n - 1) + $this($n - 2);
        $this->cache[$n] = $result;

        return $result;
    }
}

$fib = new MemoizedFibonacci();
echo $fib(10) . PHP_EOL; // 55
echo $fib(20) . PHP_EOL; // 6765(内部でキャッシュが効いている)

例6:is_callable()とcallable型宣言での扱いを確認するデモ

<?php

class SimpleCallable
{
    public function __invoke(): string
    {
        return '呼び出されました';
    }
}

function executeCallback(callable $callback): string
{
    // __invoke()を持つオブジェクトは、callable型の引数として受け取れる
    return $callback();
}

$obj = new SimpleCallable();
var_dump(is_callable($obj)); // true
echo executeCallback($obj) . PHP_EOL;
echo executeCallback(fn () => 'クロージャも同様に動作する') . PHP_EOL;

例7:イベントリスナーを表現する関数オブジェクト

<?php

class EventLogger
{
    private array $log = [];

    public function __invoke(string $eventName, array $payload): void
    {
        $this->log[] = [
            'event'   => $eventName,
            'payload' => $payload,
            'time'    => date('H:i:s'),
        ];
    }

    public function getLog(): array
    {
        return $this->log;
    }
}

class SimpleEventDispatcher
{
    private array $listeners = [];

    public function listen(string $eventName, callable $listener): void
    {
        $this->listeners[$eventName][] = $listener;
    }

    public function dispatch(string $eventName, array $payload = []): void
    {
        foreach ($this->listeners[$eventName] ?? [] as $listener) {
            $listener($eventName, $payload);
        }
    }
}

$logger = new EventLogger();
$dispatcher = new SimpleEventDispatcher();
$dispatcher->listen('user.registered', $logger); // __invoke()を持つオブジェクトをリスナーとして登録

$dispatcher->dispatch('user.registered', ['id' => 1, 'name' => '太郎']);
print_r($logger->getLog());

関連機能との比較

機能役割__invoke()との違い
__invoke()オブジェクトを関数のように呼び出し可能にする本記事の対象。$obj()形式の呼び出しに対応する
クロージャ(Closure)無名関数としての呼び出し可能な値状態(プロパティ)を明示的な形で保持しにくいが、構文がより簡潔
__call()未定義のメソッド呼び出しを処理するメソッド名を伴う呼び出し($obj->method())が対象で、$obj()とは異なる
Closure::fromCallable()callableをClosureオブジェクトに変換する__invoke()を持つオブジェクトをクロージャとして扱いたい場合に利用できる
通常の関数グローバルスコープで定義される関数状態を持てない点が、__invoke()を使うオブジェクトとの大きな違い

よくある落とし穴(注意点)

  1. callable型として扱われることを意識した設計 __invoke() を実装すると、そのクラスのインスタンスは is_callable() が true を返す「呼び出し可能な値」になります。この性質を活かして、コールバックを期待する関数やメソッドに自然に渡せる設計を意識しましょう(例2・例4を参照)。
  2. 引数の型や数を明確にドキュメント化する __invoke() は可変長引数としても定義できますが、呼び出し側から見て「このオブジェクトはどんな引数を受け取るのか」が分かりにくくなりがちです。型宣言やPHPDocコメントで明確にしておくことが推奨されます。
  3. 状態を持つことのメリットとデメリットを理解する __invoke() を使うオブジェクトは、クロージャの use 句よりも柔軟に内部状態(プロパティ)を保持・変更できます(例5を参照)。一方で、単純な処理であれば、通常のクロージャの方がコード量が少なく済む場合も多いため、状態管理が本当に必要かどうかを見極めましょう。
  4. 再帰呼び出し時は$thisを使う __invoke() 内から自分自身を再帰的に呼び出す場合、$this(...) という記法を使います(例5を参照)。これは一見見慣れない書き方に思えるかもしれませんが、__invoke() が定義されたオブジェクトとして正しく動作します。
  5. デバッグ時にスタックトレースが読みにくくなることがある __invoke() を多用した処理のチェーンは、エラー発生時のスタックトレースにおいて、通常のメソッド呼び出しよりも文脈が追いにくくなることがあります。複雑なロジックを組む場合は、適度にコメントや명확なクラス名を付けることを心がけましょう。

まとめ

観点まとめ
何をするメソッドかオブジェクトを$obj()という関数のような構文で呼び出せるようにする
主な用途コールバックとしての利用、ミドルウェアパターン、状態を持つ関数オブジェクト(Functor)
判定方法is_callable()がtrueを返し、callable型として扱える
クロージャとの違いプロパティとして状態を明示的に保持しやすい
注意点引数仕様の明確化、状態管理の必要性の見極め、再帰呼び出し時の$thisの使用

__invoke() は、オブジェクト指向の柔軟性と関数型的な「呼び出し可能な値」という性質を組み合わせられる、強力なマジックメソッドです。状態を持つコールバックやミドルウェアの実装など、クロージャだけでは表現しにくい場面で、その真価を発揮します。

タイトルとURLをコピーしました