ツイート
シェア
LINEで送る
B! はてぶでブックマーク
Pocketでブックマーク
RSSフィード

PHP, Closure::fromCallable(), 関数名の文字列から関数を実行する。

php
イラストダウンロードサイト【イラストAC】
の画像をもとに加工しています。

PHPにかぎらず、プログラム言語には関数名の文字列から関数を実行することができます。そのさいの関数名文字列をコールバックと言います。

PHPでは無名関数の型、Closureクラスにその処理がある。

ってことは分かりますね? PHPでは無名関数を使います。

PHPのコールバックは文字列だけどstring型ではない

コールバックは、関数名の文字列から関数を実行する際の、その文字列のことを言います。

文字列なのでstring型で指定しますが、内部ではcallable型にキャストされる。

そして、コールバックから実行される関数のことをコールバック関数といいます。

といっても何か特殊な関数ではなく普通の関数。たんにコールバックから実行される関数という意味。

(クラスのメソッドであればコールバックメソッドという。)

コールバック関数の関数名には、コールバックから実行されることを明示するために 'callback', 'callable' を付けたりします。

これはコーディングルールというよりプログラミングの慣習に近い。

コードを読んでてそういった関数名を見つけたときは、コールバックで使われるんだな、ぐらいの感覚でいい。

Closure::fromCallbale()よりも有名なコールバック実行方法がある。

PHPのコールバックからの関数実行には、call_user_func(), call_user_func_array()があり、こっちのほうが有名です。

でも、無名関数の型のクラスClosureでもコールバックが実行できます。

それがClosure::fromCallbable()です。メソッド名がそのままですね?

call_user_func(), call_user_func_array()は関数名とパラメータを渡せばコールバック関数を実行して結果を返します。

それに対しClosure::fromCallble()は、いったん無名関数を作って変数へ代入するところまでしか行いません。

そのあとにその変数からコールバック関数を実行します。

呼び出し可能なら標準関数でも自作でも良い

コールバック関数は自作の関数だけでなく標準関数でも実行できます。

(関数はすべて実行可能。)

サンプルコードを見てみましょう。

function test_callback($val1, $val2)
{
    return $val1 * $val2;
}

// 自作関数のコールバック
$closure = Closure::fromCallable('test_callback');
var_dump($closure(3, 5));

// 標準関数のコールバック
$closure = Closure::fromCallable('strlen');
var_dump($closure('sample'));
実行結果
int(15)
int(6)

同じことをcall_user_func()でもやってみましょう。

// 自作関数のコールバック
var_dump(call_user_func('test_callback', 3, 5));

// 標準関数のコールバック
var_dump(call_user_func('strlen', 'sample'));
実行結果
int(15)
int(6)

ちがいが明確に出てますね? call_user_func()は1行で済みますが、Closure::fromCallable()は、無名関数になったコールバック関数の実行の分、2行必要です。

...といっても1行でも書けますが。

// 自作関数のコールバック
$result = Closure::fromCallable('test_callback')(3, 5);
var_dump($result);

// 標準関数のコールバック
$result = Closure::fromCallable('strlen')('sample');
var_dump($result);

Closure::fromCallable()は変数に代入しなければ、それ自体が無名関数のオブジェクト(Closureクラス)なので、その後ろに '()' を付けられます。

これはClosureの仕様ではなくオブジェクトの仕様。クラスのインスタンスとかでも同じような使い方があるので迷うことはないでしょう。

ぱっと見、何をしているの? と思ってしまいますが。

実行結果
int(15)
int(6)

呼び出し不可の関数なら例外スロー

関数がスコープ内にない、または未定義の関数をコールバックしようとしたときは、TypeError例外がスローされます。

$closure = Closure::fromCallable('aaa');
実行結果
PHP Fatal error:  Uncaught TypeError: Failed to create closure from callable: function "aaa" not found or invalid function name in /home/vagrant/php-test.php:1
Stack trace:
#0 /home/vagrant/php-test.php(1): Closure::fromCallable()
#1 {main}
  thrown in /home/vagrant/php-test.php on line 1

クラスメソッドのコールバック

コールバックには、クラスメソッドも指定できます。

class Test
{
    public static function static_callback($val1, $val2)
    {
        return $val1 + $val2;
    }

    public function instance_callback($val1, $val2)
    {
        return $val1 - $val2;
    }
}

// staticメソッドのコールバック
$closure = Closure::fromCallable('Test::static_callback');
var_dump($closure(3, 5));

// インスタンスメソッドのコールバック
$closure = Closure::fromCallable([new Test(), 'instance_callback']);
var_dump($closure(3, 5));
実行結果
int(8)
int(-2)

staticメソッドは従来どおり文字列で指定します。ただし、クラス名:: が必要。ポイントはインスタンスのメソッドをコールバックするとき。

PHPでは配列で表現しインスタンスとメソッド名文字列を渡します。

インスタンスは文字列で表現できません。正確には表現できるんですが、インスタンスをシリアライズしてアンシリアライズする手間があり、その分処理が遅くなる。

それを無くすために配列でインスタンスそのものを渡すようになっています。

このコールバックの表現はcallable型の仕様でcall_user_func()でも同じ。

まったく同じ処理をcall_user_func()でもしてみましょう。

// staticメソッドのコールバック
var_dump(call_user_func('Test::static_callback', 3, 5));

// インスタンスメソッドのコールバック
var_dump(call_user_func([new Test(), 'instance_callback'], 3, 5));
実行結果
int(8)
int(-2)

文字列・配列の後ろに '()' を付けると、Closure::fromCallable() と同じような処理になる。

最後に、コールバックの不思議な記法をご紹介します。

function test_callback($val1, $val2)
{
    return $val1 * $val2;
}

$result = 'test_callback'(3, 5);
var_dump($result);

class Test
{
    public static function static_callback($val1, $val2)
    {
        return $val1 + $val2;
    }

    public function instance_callback($val1, $val2)
    {
        return $val1 - $val2;
    }
}

// staticメソッドのコールバック
$result = 'Test::static_callback'(3, 5);
var_dump($result);

// インスタンスメソッドのコールバック
$result = [new Test(), 'instance_callback'](3, 5);
var_dump($result);
実行結果
int(15)
int(15)
int(8)
int(8)
int(-2)

PHPの関数コールでは、関数名をシングルクォーテーション(')やダブルクォーテーション(")でくくって文字列にしても実行可能です。

PHPの初心者にはよく説明される記法。

プログラミングを生業にしている人からすると、『何じゃそりゃ、その仕様』ですし(クォーテーションでくくらなくてもいいならその仕様はいらないと思う)、そんな記法使ったことない。

Closure::fromCallable()と中身は同じ?

そんな何じゃそりゃ記法はClosure::fromCallable()と同じような処理を行います。

ちがいは関数名の型が、callable型かClosureクラス型だけで。

それぞれ型はちがいますが、後ろに '()' を付けると関数を実行します。

文字列や配列としてみるとチンプンカンプンですが、その正体はcallable型のコールバックです。

ただの関数やstaticメソッドでは気づきませんが、インスタンスメソッドでそれに気づく。

インスタンスメソッドの場合は、配列のコールバック後ろに '()' を付けると、その配列が内部でcallable型へ変換してClosureオブジェクトのような動きをする。

何じゃそりゃ記法と言ってますが、配列の後ろに()を付いたものは意外とよく見ます。最初見たときまったく意味がわからなくて度肝を抜かれたけど。

Closure::fromCallable()って必要? call_user_func()があるのに。

Closure::fromCallable() と call_user_func() のどちらもコールバック処理を行うためのもので、同じ結果をもたらします。

じゃあ、どっちを使えばいいんだ? になるんですが、正解はClosure::fromCallable()。

使う頻度としてはcall_user_func()のほうが多い感覚があるので、いまいちピンとこないかもしれませんが、PHP8.1では、Closure::fromCallable()を発展させた第一級callableと呼ばれる記法が登場しました。

新しい記法を出す時点で、こっちを使ったほうがいいよ、と言っているようなもの。

だからといって、call_user_func()を使うのをやめましょうという話ではありません。非推奨になってないし。

将来は分からないな~。無くなる気配はあるけど。

前の投稿
PHP, Closure::call(), 一時的なバインドで無名関数の実行。'()'とはちがう。
PHP, Closureクラス, new演算子でインスタンスは生成できない
次の投稿

コメントを残す