無名関数と __invoke() と Closureクラス

まず「無名関数」というものがあります。
PHPの公式だと http://php.net/manual/ja/functions.anonymous.php 、英語版を見ると「Anonymous functions」と書いてあります。
端的には「"名前がついていない関数"を作れる機能」です。

「………名前がついてないのにどうやって使うの?」とかいう疑問が出てきそうなので、わかりやすいサンプルを。
usortとかでサンプルを拝見したりするのですが、個人的によく使うのが set_error_handler() だったりするので、その辺で。

set_error_handler()は「ユーザー定義のエラーハンドラ関数を設定する」というものです( http://php.net/manual/ja/function.set-error-handler.php )。
端的に「独自の処理をエラー時に行うとき」に使います。

で、これを使って「エラーが出たら、すべて"例外を投げる"ようにする」と、全体の統一感が図れるので、現場によっては大変に好まれます。
………ってのを、どうやって実装しましょうか?

まず「旧来からの書き方」ですと、このように書くことができます。

// 関数の定義
function customized_error_handler($errno, $errstr, $errfile, $errline)
{
    if (0 !== $errno & error_reporting()) {
        throw new ErrorException( $errstr, 0, $errno, $errfile, $errline);
    }
}
// 設定
set_error_handler('customized_error_handler');

まぁこの命名なので「関数名がほかとかぶる可能性は低そう」な気がするのですが。
「一か所でしか使わない処理のためにわざわざ関数書くのもなぁ……関数名考えるのも面倒だしなぁ……」というような発想が出てくるケースがありまして。

そんな時に「無名関数」を使うと、こんな風に書けます。

// 関数の定義
$func = function ($errno, $errstr, $errfile, $errline)
{
    if (0 !== $errno & error_reporting()) {
        throw new ErrorException( $errstr, 0, $errno, $errfile, $errline);
    }
};
// 設定
set_error_handler($func);

とりあえず「名前何にしようかなぁ??」という思考をせずに済むのは楽ですね(笑
で、これはさらに、以下のように書く事ができます。

set_error_handler(
    function ($errno, $errstr, $errfile, $errline) {
        if (0 !== $errno & error_reporting()) {
            throw new ErrorException( $errstr, 0, $errno, $errfile, $errline);
        }
    }
);

「ちょっとわかりにくいなぁ」と思った人は。
ひとつ前の「set_error_handler($func);」の、$funcの部分を、代入している

function ($errno, $errstr, $errfile, $errline)
{
    if (0 !== $errno & error_reporting()) {
        throw new ErrorException( $errstr, 0, $errno, $errfile, $errline);
    }
}

に置き換えてみると、理解が少ししやすいのではないか、と思います。

こんな風に、関数のcallableな引数を、「関数名」を渡す代わりに「ダイレクトに関数を書いちゃえ」ということをやる事が出来るんですね。
あんまり乱用するとテストが大変になったりしますが、「ちょっとした処理」を書くときには大変に便利なので、少し覚えておくと楽ができるのではないか、と思います。

さて。

// 関数の定義
$func = function ($errno, $errstr, $errfile, $errline)
{
    if (0 !== $errno & error_reporting()) {
        throw new ErrorException( $errstr, 0, $errno, $errfile, $errline);
    }
};

のコードに少し立ち戻りまして。
……この変数の中身、どうなってるんでしょうか?
var_dumpしてみると、こんな風になります。

object(Closure)#1 (1) {
  ["parameter"]=>
  array(4) {
    ["$errno"]=>
    string(10) "<required>"
    ["$errstr"]=>
    string(10) "<required>"
    ["$errfile"]=>
    string(10) "<required>"
    ["$errline"]=>
    string(10) "<required>"
  }
}

Closure、というクラスがあるんですね。ちょっと調べてみましょう。
http://php.net/manual/ja/class.closure.php
きちんと、クラスとして存在しています。

この「無名関数」ですが。
普段は「呼ぶ」側で使う事が多いと思うのですが、フレームワークなどcoreなところを実装すると「callableな引数を持つ関数を実装する」事もあるか、と思うので、使い方を。
$funcを「普通の関数名」と脳内で置き換えると、あとは「普通の関数をcallする」のと同じように使えます。

// 関数の定義
$func = function ($errno, $errstr, $errfile, $errline)
{
    if (0 !== $errno & error_reporting()) {
        throw new ErrorException( $errstr, 0, $errno, $errfile, $errline);
    }
};

//var_dump($func);

$func(1, 'ほげerror', 'dummy_file', 9999);

これで「明らかに嘘くさい」例外を吐き出させることができます(笑
ちなみに、関数はちゃんと定義したうえで「$funcに文字列を入れる」形でも、同様の動きをさせることができます。

// 関数の定義
function hoge($errno, $errstr, $errfile, $errline)
{
    if (0 !== $errno & error_reporting()) {
        throw new ErrorException( $errstr, 0, $errno, $errfile, $errline);
    }
};
//
$func = 'hoge';

//var_dump($func);

$func(1, 'ほげerror', 'dummy_file', 9999);

これは「可変関数」という名前の機能ですね( http://php.net/manual/ja/functions.variable-functions.php )。

こんな風に。
「変数に()を付けている」場合、可能性としては「無名関数かもしれないし、可変関数かもしれない」ので。
一番単純に見分けるのであれば、その変数の中身を確認してみるとよいでしょう。

さて……もう少し、知識を積み重ねていきましょう。

無名関数は「Closureクラスでしか実装できないのか?」というと、実はそんなこともなくて。
マジックメソッド __invoke() を使うことで、「自分が作ったオリジナルのクラスで無名関数を実装することができる」ようになります( http://php.net/manual/ja/language.oop5.magic.php#object.invoke )。
少し、コードを書いて実験してみましょう。

// クラスの定義
class hoge {
    // マジックメソッドの実装
    public function __invoke($errno, $errstr, $errfile, $errline)
    {
        if (0 !== $errno & error_reporting()) {
            throw new ErrorException( $errstr, 0, $errno, $errfile, $errline);
        }
    }
}

//
$func = new hoge();
//var_dump($func);

$func(1, 'ほげerror', 'dummy_file', 9999);

このように書くと、「同じように」使うことができます。

利用用途は比較的限定的なことが多いのですが。
とはいえ
・関数だと「自分の中で全部完結させなきゃいけない」けれども、クラスなら「メソッドなりプロパティなり切り出せるから見通しやテストがしやすくなる」
などの利点があるので、「全く使わない」ってものでもないです。

このあたりの言語機能になると割と本当に「PHP固有」なので、実務で「使うか、使わないか」は思案のしどころ、かとは思うのですが。
こういった機能を「最小限、最低限、適切に使う」と、やはり便利な事も多いので。
PHPの「中級~上級」以上であれば、知っておいて損のない機能なのではないか、と思います。

なお、実はClosure は「色々な事」が出来るんですが。
気をつけないと「黒魔術」になってしまうので、興味がある人は、節度をもって実験してみましょう。

この記事が気に入ったらサポートをしてみませんか?