マジックメソッド__call()と__callStatic()

マジックメソッド使ってますか?

私はあまり好きではありませんでした。意図せず呼ばれてしまい、デバッグが困難になってしまうイメージが強かったからです。

しかし、Laravel(Lumen)を使用し、__call() と __callStatic()に触れ、興味が湧いてきました。

マジックメソッドの公式ドキュメント

__call()

__call()の公式ドキュメント

このメソッドは、未定義のインスタンスメソッドを実行した時に呼び出されます。

例を記します。

__call()メソッドはインスタンス メソッドとして定義しなければなりません。

実行結果は以下となります。

このように未定義のインスタンス メソッドを実行した際、__call()メソッドが実行されます。

__callStatic

__callStatic()の公式ドキュメント

このメソッドは未定義のクラス メソッドを実行した際に実行されます。

例を記します。

__callStatic()メソッドはクラス メソッドとして定義しなければなりません。

このように未定義のクラス メソッドを実行した際、callStatic()メソッドが実行されます。

Laravel(Lumen)での実装例

Illuminate\Database\Eloquent\Model

https://github.com/laravel/framework/blob/5.6/src/Illuminate/Database/Eloquent/Model.php#L1573-L1583

自身のインスタンスを生成し、引数$methodが示すインスタンス メソッドの実行を試みます。

こうする事で、インスタンス メソッドをクラス メソッドのように実行しています。

https://github.com/laravel/framework/blob/b12feabda70f136cb2d7081af41441a0bf22edc7/src/Illuminate/Database/Eloquent/Model.php#L1557-L1571

__call()では、まずメソッド名が’increment’、または’decrement’ と一致するか判定します。

一致する場合、increment、またはdecrementメソッドが実行されます。

一致しなかった場合、newQuery()メソッドを実行し、Illuminate\Database\Eloquent\Builderクラスのインスタンスを生成します。そのインスタンスの$methodが示すメソッドを実行します。

これらの実装により、Illuminate\Database\Eloquent\Modelクラスのクラスメソッド としてwhere()、insert()を実行した場合、Illuminate\Database\Eloquent\Builderクラスのインスタンス メソッドが実行されます。

Illuminate\Support\Facades\Facade

https://github.com/laravel/framework/blob/b12feabda70f136cb2d7081af41441a0bf22edc7/src/Illuminate/Support/Facades/Facade.php#L206-L224

Facadeは__callStatic()メソッドを利用して、実体となるインスタンスのメソッドを実行しています。

この仕組みを利用して、実体となるクラスを継承し、メソッドを追加、そのクラスのインスタンスをファサードの実体とすることで、ファサード経由で追加したメソッドを呼び出せるようになります。

まとめ

正しく使えば、抽象化にずいぶん貢献できるように感じました。一つの実装案として頭の片隅に記憶しておくとよいでしょう。