次に紹介するマジックメソッドは、__invoke()です。__invoke()はインスタンスを関数のように実行した場合に呼ばれるメソッドです。まず、その仕組みをサンプルを作りながらみていきましょう。
__invoke()が記述された以下のInvokeSampleクラス、および、それを利用するuseInvokeSample.phpを作成、実行してください。
<?php class InvokeSample { public function __invoke(string $name): string //(1) { return $name."さんこんにちは!"; } }
<?php require_once("InvokeSample.php"); $is = new InvokeSample(); //(1) $ans = $is("しんぞう"); //(2) print($ans); //(3)
実行結果は以下の通りです。
しんぞうさんこんにちは!
リスト3のInvokeSampleクラスの(1)が__invoke()メソッドです。表1にあるように、__invoke()は引数も戻り値も自由に設定できます。ここでは、引数として名前を表す文字列を受け取り、戻り値として、その引数に「さんこんにちは!」を付与した文字列をリターンするように処理を記述しています。
このInvokeSampleクラスを利用しているのがリスト4です。(1)でInvokeSampleインスタンスを生成し、変数$isとしています。(2)が新しい記述です。クラスのインスタンスを表す変数に対して()を記述して引数を渡しています。さらに、その実行結果を受け取って変数$ansに代入しています。つまり、インスタンスを関数のように扱っている記述といえます。
このような場合、そのインスタンス内の__invoke()メソッドが実行される仕組みです。実際、戻り値を格納した$ansを(3)で表示させており、その結果からリスト3の__invoke()が処理されたことが分かります。
ここで、この__invoke()を使ったもう少し実用的な例を紹介します。それは、クロージャの代わりとして使うことです。
クロージャは、第11回で扱いました。その第11回のクロージャの説明の少し前、「コールバック関数を利用するメリット」で「array_map()」を紹介しました。
それと似たような関数で「array_walk()」というのがあります。リンクの「関数リファレンス」を参照すると、第1引数に配列、第2引数にコールバック関数を渡すことで、配列の各要素にその関数を適用してくれるというものです。array_mapとの違いは、array_map()が関数を適用した新たな配列をリターンしてくれるのに対して、array_walk()はオリジナルの配列に関数を適用し、戻り値はありません。
通常、このarray_walk()の第2引数のコールバック関数には以下のようにクロージャ(無名関数)が使われることが多いです。
array_walk($list, function(int $item): void { : });
あるいは、通常関数として、例えば「function forWalk(int $item) {…}」のようなものを用意しておいて、以下のように記述します。
array_walk($list, "forWalk");
今回、このarray_walk()の第2引数のコールバック関数に、上記クロージャや通常関数ではなく、インスタンスを適用したサンプルを見ていきます。
以下のInvokeControllerクラスを作成してください。
<?php class InvokeController { //コンストラクタで受け取った値を格納するプロパティ private $num = 1; //コンストラクタ public function __construct(int $num) { //引数として受け取った値をプロパティに格納。 $this->num = $num; } public function __invoke(int $item): void //(1) { $ans = $item * $this->num; print($this->num."倍した値: ".$ans."<br>"); } }
リスト5の(1)で__invoke()メソッドを記述しています。ここでは、array_walk()のコールバック関数の定義に合わせて、引数として配列内の各要素を受け取るようにし、戻り値はなしとしています。メソッド内の処理は、クラスのプロパティ$numと引数とを掛け算し、その答えを表示するというシンプルなものです。
次に、このクラスを利用する以下のarrayWalkAndObj.phpを作成、実行してください。
<?php require_once("InvokeController.php"); $ic = new InvokeController(6); //(1) $list = [1, 3, 5, 7, 9]; //(2) array_walk($list, $ic); //(3)
実行結果は以下の通りです。
6倍した値: 6 6倍した値: 18 6倍した値: 30 6倍した値: 42 6倍した値: 54
リスト6の(1)で、リスト5で定義したInvokeControllerのインスタンスを生成し、それを「$ic」としています。(2)でarray_walk()関数に適用する配列を定義しています。(3)でそのarray_walk()関数を利用していますが、第2引数として(1)で生成したInvokeControllerインスタンスを渡しています。
コールバック関数として、本来クロージャや通常関数が渡されるはずのところにクラスのインスタンスを渡すことができるのです。これは、リスト4で解説したインスタンスの関数化の現れであり、その際も、そのクラス内の__invoke()メソッドが実行されるようになってます。
では、コールバック関数としてクロージャや通常関数の代わりに__invoke()を利用するメリットは何でしょうか。
まず第一に、クロージャはその場限りの使い捨ての関数です。一方、クラスは再利用可能です。ただし、これは、クロージャを使わずに通常の関数を定義することで同じメリットを享受できます。
__invoke()の最大のメリットは、インスタンスであることです。第13回の話を思い出してください。インスタンスはおのおのがデータを保持しています。ということは、コールバック関数として__invoke()が実行される際には、そのインスタンス内の他のメンバを利用することが可能です。
リスト5では、コンストラクタで受け取った値をプロパティに格納し、その値を__invoke()内で利用しています。リスト6ではnewする際に6を渡しているため、実行結果もこの値を基にしたものになっています。もちろん、newする際に別の値を渡すと、実行結果も変わって来ます。これは、クロージャや通常の関数では無理であり、__invoke()のメリットです。
最近人気のPHPフレームワークである「Laravel」や軽量フレームワークである「Slim」などでは、ここで登場した__invoke()メソッドに処理を記述する場面が多々出てきます。そのときに、上の話を思い出してください。
次回は、クラスを拡張していく方法として継承を扱います。
Copyright © ITmedia, Inc. All Rights Reserved.