基礎

PHPの__callStaticメソッド|未定義の静的メソッド呼び出しを制御する

PHPの__callStatic()は、アクセスできない静的メソッドが呼び出されたときに自動的に実行されるマジックメソッドです。__call() のstatic版で、クラス名::メソッド名() の形式で呼ばれた未定義の静的メソッドをキャッチします。

ファクトリメソッドの動的生成やFacadeパターン(Laravelで有名)など、静的な呼び出しに対する柔軟なAPIを設計する際に使われます。

基本的な使い方

PHP
<?php
class StaticMagic {
    public static function __callStatic(string $name, array $arguments): mixed {
        echo "静的メソッド '{$name}' が呼ばれました\n";
        echo "引数: " . implode(", ", $arguments) . "\n";
        return null;
    }
}

StaticMagic::hello("田中");
StaticMagic::calculate(10, 20);
実行結果
静的メソッド 'hello' が呼ばれました
引数: 田中
静的メソッド 'calculate' が呼ばれました
引数: 10, 20

__callStatic()static メソッドとして定義する必要があります。インスタンスの __call() とは独立して動作します。

動的なファクトリメソッド

PHP
<?php
class Color {
    public function __construct(
        private int $r,
        private int $g,
        private int $b
    ) {}

    public static function __callStatic(string $name, array $args): self {
        $colors = [
            "red" => [255, 0, 0],
            "green" => [0, 255, 0],
            "blue" => [0, 0, 255],
            "white" => [255, 255, 255],
            "black" => [0, 0, 0],
            "yellow" => [255, 255, 0],
        ];

        $key = strtolower($name);
        if (isset($colors[$key])) {
            return new self(...$colors[$key]);
        }
        throw new BadMethodCallException("未定義の色: {$name}");
    }

    public function __toString(): string {
        return sprintf("rgb(%d, %d, %d)", $this->r, $this->g, $this->b);
    }
}

echo Color::red() . "\n";
echo Color::blue() . "\n";
echo Color::yellow() . "\n";

try {
    Color::purple();
} catch (BadMethodCallException $e) {
    echo "エラー: {$e->getMessage()}\n";
}
実行結果
rgb(255, 0, 0)
rgb(0, 0, 255)
rgb(255, 255, 0)
エラー: 未定義の色: purple

Facadeパターン

Laravelで広く使われているFacadeパターンは __callStatic() を利用しています。

PHP
<?php
// 実際のサービスクラス
class CacheService {
    private array $store = [];

    public function get(string $key): mixed {
        return $this->store[$key] ?? null;
    }

    public function set(string $key, mixed $value): void {
        $this->store[$key] = $value;
    }

    public function has(string $key): bool {
        return isset($this->store[$key]);
    }
}

// Facadeクラス
class Cache {
    private static ?CacheService $instance = null;

    private static function getInstance(): CacheService {
        if (self::$instance === null) {
            self::$instance = new CacheService();
        }
        return self::$instance;
    }

    public static function __callStatic(string $name, array $args): mixed {
        return self::getInstance()->$name(...$args);
    }
}

// 静的メソッドのように使える
Cache::set("user", "田中");
Cache::set("score", 95);

echo "ユーザー: " . Cache::get("user") . "\n";
echo "スコア: " . Cache::get("score") . "\n";
echo "存在チェック: " . (Cache::has("user") ? "あり" : "なし") . "\n";
実行結果
ユーザー: 田中
スコア: 95
存在チェック: あり

__callとの組み合わせ

PHP
<?php
class DB {
    private string $table;

    private function __construct(string $table) {
        $this->table = $table;
    }

    // 静的呼び出し: DB::users() → テーブル名を指定
    public static function __callStatic(string $name, array $args): self {
        return new self($name);
    }

    // インスタンス呼び出し: whereXxx()
    public function __call(string $name, array $args): self|string {
        if (str_starts_with($name, "where")) {
            $col = strtolower(substr($name, 5));
            echo "  WHERE {$col} = '{$args[0]}'\n";
            return $this;
        }
        if ($name === "get") {
            return "SELECT * FROM {$this->table}";
        }
        throw new BadMethodCallException("未定義: {$name}");
    }
}

echo "クエリ構築:\n";
$query = DB::users()->whereStatus("active")->whereRole("admin")->get();
echo "結果: {$query}\n";
実行結果
クエリ構築:
  WHERE status = 'active'
  WHERE role = 'admin'
結果: SELECT * FROM users
ポイント

__callStatic() は必ず public static で定義する必要があります。__call() とは独立しているため、同じクラスに両方を定義して、静的呼び出しとインスタンス呼び出しで異なる動作をさせることが可能です。

注意

__callStatic() はIDEの補完やリファクタリングツールとの相性が悪いです。@method static PHPDocアノテーションを記述して、仮想メソッドの情報をIDEに伝えることを推奨します。また、テストの可読性も下がるため、必要な場面に限定して使用しましょう。

まとめ

  • __callStatic() は未定義の静的メソッド呼び出しをキャッチする
  • public static で定義する必要がある
  • ファクトリメソッドの動的生成やFacadeパターンに最適
  • __call() と組み合わせて静的/インスタンス両方に対応できる
  • @method static PHPDocでIDEの補完を補助する