PHP歴だけは無駄に長い私から見て、無名関数を使う機会が以前より増えていると感じます。個人的には、Laravel等のフレームワークではビシバシと使っていますね。
しかしながら多くの利用はコールバックとしてで、あまり詳しく知らなくても使えてしまいます。ですので「何となく無名関数使ってるよ!」という方も多いのではないでしょうか。
実は私がそうで、フリーランスとして日々忙殺されている中、詳しく公式マニュアルをじっくり読むような時間はありません。よって、なんとなくでこれまで使ってきました。
しかしそれではだめだ、と思い立ち、改めてマニュアルに目を通してみました。
本ページでは、始めて無名関数を学ぶ方はもちろん、私のようにすでに使っている方の再確認用としても読めるようにまとめています。少しでも参考になるところがあれば幸いです。
ブログを書くようになってから「何となく」を調べる癖がついて良かった!
無名関数(クロージャ)とは
無名関数は、別名クロージャとも呼ばれるものです。その名の通り、名前のない関数です。
通常の関数は名前を付けますが、つけるまでもないような場面で使われます。例えば一時的に使用される場面や、コールバック関数として利用することが一般的です。
基本的な無名関数の例
無名関数は、変数に代入して以下の様に使うことが可能です。
<?php
$greeting = function ($name) {
return "こんにちは、" . $name . "さん!";
};
echo $greeting("田中"); // 出力: こんにちは、田中さん!
JavaScriptも書いたことがある方なら、比較的スムーズに受けいれることができるかもしれません。
コールバックに使用する例
PHPでは主にこのコールバックとして使用する例が多いと思います。
多くの関数やライブラリのメソッドでは、コールバック関数を受け入れます。このような場合、わざわざ名前付きの関数を定義せずに、無名関数をコールバックとするのが便利です。
以下がシンプルな例です。
<?php
$mapped = array_map(function($value) {
return $value * 2;
}, [1, 2, 3, 4]);
print_r($mapped); // 出力: [2, 4, 6, 8]
冒頭でも触れたとおり、Laravel等のフレームワーク内でもコールバックは非常によく使われます。
外部変数を利用する例
通常の関数と同様、無名関数の中でのスコープは関数内に限定されます。関数外の変数は参照できません。
もちろんパラメーター(引数)を付与することができますが、それ以外の変数を使用したい……というシーンは多いものです。
以下の様に、use
文を使うことで、任意の変数を利用することも可能です。
<?php
$prefix = "こんにちは、";
$greeting = function ($name) use ($prefix) {
return $prefix . $name . "さん!";
};
echo $greeting("佐藤"); // 出力: こんにちは、佐藤さん!
ここでは$prefix
を親のスコープから引き継いで無名関数内で使用しています。
重要なのは、$prefix
は値渡しということです。つまり無名関数の中で$prefix
の値が変更されても、親スコープの$prefix
の値は変更されません。
俗に言う参照渡しにする場合は、&
が必要です。関数のパラメータと同じですね。
以下のようなイメージです。
$count = 0;
$increment = function() use ($count) {
// これを実行しても外部の$countは変わらない
// $countは値として渡されているため
$count++;
};
$incrementWithReference = function() use (&$count) {
// これを実行すると外部の$countも増分される
// $countは参照として渡されているため
$count++;
};
めったに無いと思いますので、参照渡しは必要があるときだけ行えば十分です。
use
は素のPHPを使っていると個人的にはあまり使いませんが、Laravelを利用する際はかなりの頻度で必要になります。ですから使いこなせるようになっておきたいところです。
ちなみに、戻り値の型を指定する場合は、以下の様にuse
の後に指定します。
$greeting = function ($name) use ($prefix): string {
メソッドとして利用する例
無名関数は、オブジェクトのメソッドとしても使用できます。また、$this
を利用することで、無名関数内からオブジェクトのプロパティやメソッドにアクセスすることもできます。
<?php
class Greeting {
private $prefix = "こんにちは、";
public function getGreeting() {
return function ($name) {
return $this->prefix . $name . "さん!";
};
}
}
$greetingObject = new Greeting();
$greetingClosure = $greetingObject->getGreeting();
echo $greetingClosure("鈴木"); // 出力: こんにちは、鈴木さん!
この例では単に無理矢理使った感がでていますが、設計パターンを工夫することで良いコードになる可能性を秘めています。
上級者向けの利用方法でしょうね。
実践的なコードを見てみる
基本的な利用方法で文法をご紹介したので、これからはより実践的なコードを見ていきましょう。
宣言的プログラミング風に
Laravelのコレクションを使用して、宣言的プログラミング風に使用することが可能です。
以下では、フィルタ、ソート、マップと繋げていますが、その中で無名関数を利用しています。
$products = collect([
['name' => 'りんご', 'category' => 'フルーツ', 'price' => 100],
['name' => 'オレンジ', 'category' => 'フルーツ', 'price' => 80],
['name' => 'レタス', 'category' => '野菜', 'price' => 50],
['name' => 'キャベツ', 'category' => '野菜', 'price' => 60],
]);
$result = $products
->filter(function ($product) {
return $product['category'] === 'フルーツ';
})
->sortBy('price')
->map(function ($product) {
return $product['name'];
});
print_r($result->all());
Laravelを利用するようになってから、無名関数を使いまくるようになった……という方は多いでしょうね。
配列の独自ソート
配列をソートする場合、usort()
に無名関数を与えることで、簡単に独自のソート条件を指定することができます。
<?php
$students = [
['name' => '田中', 'score' => 85],
['name' => '佐藤', 'score' => 90],
['name' => '鈴木', 'score' => 80],
];
// スコアの降順でソート
usort($students, function($a, $b) {
return $b['score'] - $a['score'];
});
print_r($students); // 点数の高い順にソートされた生徒のリストを表示
usort()
関数は”user-defined sort”(ユーザー定義のソート)という名称からもわかるとおり、独自の比較関数を使って配列の値をソートします。
10行目で、スコアを比較する(正確には負、0、正のいずれかの数値を返す)ことで、ソートしてくれます。
外部リソースのクリーンアップ
外部リソース(例: ファイルハンドル、データベース接続)をクリーンアップする際の処理を保持する例です。
<?php
function openFile($filename) {
$handle = fopen($filename, 'r');
if (!$handle) {
throw new Exception("ファイルを開けませんでした");
}
return [
'handle' => $handle,
'cleanup' => function() use ($handle) {
fclose($handle);
echo "ファイルを閉じました\n";
}
];
}
$file = openFile('sample.txt');
$cleanup = $file['cleanup'];
$cleanup(); // 出力: ファイルを閉じました
クラスにすれば出来ることなので無理して使う必要は無いですが、こんな風にもできるよ……的にお考えください。
遅延実行と部分適用
関数の実行を遅延させたり、引数を事前に指定(部分適用)して関数を生成することができます。
function multiplier($factor) {
return function($value) use ($factor) {
return $value * $factor;
};
}
$double = multiplier(2);
$triple = multiplier(3);
// 必要に応じて何か処理
echo $double(4); // 出力: 8
echo $triple(4); // 出力: 12
上記では7行目で、multiplier(2)
を呼び出します。
この呼び出しの結果として、以下の無名関数が返されます。この場合、$factor
には2が指定された関数となります。
// multiplier(2)なら、$factor = 2
function($value) use ($factor) {
return $value * $factor;
}
そして12行目で無名関数を実行することで、改めて関数が評価されています。
カプセル化
外部の状態を含めカプセル化し、動的な操作を行うことができます。
function counter() {
$count = 0;
return function() use (&$count) {
return ++$count;
};
}
$count1 = counter();
echo $count1(); // 出力: 1
echo $count1(); // 出力: 2
$count2 = counter();
echo $count2(); // 出力: 1
無名関数の外である$count
まで含めてカプセル化できていますね。
クラスを使えば造作もないことですが、その場限りの処理であればありかもしれません。工夫すれば、何か上手いことができそうです。
デコレーターパターン風に
関数やメソッドの実行前後に追加の処理を挟む、つまりデコレーターパターンのような実装ができます。
function loggingDecorator($func) {
return function(...$args) use ($func) {
echo "関数を実行開始...\n";
$result = $func(...$args);
echo "関数を実行終了\n";
return $result;
};
}
$loggedFunction = loggingDecorator(function($message) {
echo "メッセージ: $message\n";
});
$loggedFunction("こんにちは!");
// 出力:
// 関数を実行開始...
// メッセージ: こんにちは!
// 関数を実行終了
メソッドの生成
実行時に動的にメソッドをオブジェクトに追加することもできます。
class User {
private $data = [];
public function __set($name, $value) {
if ($value instanceof Closure) {
$this->$name = $value->bindTo($this);
} else {
$this->data[$name] = $value;
}
}
public function __get($name) {
return $this->data[$name] ?? null;
}
}
$user = new User();
$user->greet = function() {
echo "こんにちは、{$this->name}さん!\n";
};
$user->name = "田中";
$user->greet(); // 出力: こんにちは、田中さん!
通常のWebアプリケーションではなかなか有効な利用シーンが思い浮かびません。
……が、オレオレフレームワークでも作る際、あるクラスに自作メソッドを動的に追加するような時に役立つのかな、と思いました。
まとめ
以上、様々な使用例を見てきました。
実際のところ、最低限コールバックとしての利用を覚えておけば、PHPエンジニアとして大概の案件をこなすことはできると思います。
しかしながら、いろいろと複雑な使い方をすることで、より洗練されたコードを書くこともできるかもしれません。……でもクラスでも代用できることも多く、使いこなすのは難しいですね。
私もまだまだコールバック+α程度しか使いこなせているとは言えないので、精進したいと思います。
コメント