1. ホーム
  2. PHP

【PHP】無名関数(クロージャ)と fn(アロー関数)の使い方|use で外の変数を取り込む方法

Share

PHP には、名前を付けずにその場で定義できる関数があります。これが無名関数(クロージャ)で、array_map()usort() のようにコールバックを受け取る関数と組み合わせると、わざわざ関数を1つ定義しなくても処理をその場に書けます。さらに PHP 7.4 からは、より短く書ける fn()(アロー関数)も使えるようになりました。この記事では、無名関数の基本の書き方から、use で外側の変数を取り込む方法、アロー関数との違い、クラス内で使うときの $this の扱いまでを、動かせるコードつきで解説します。

無名関数(クロージャ)とは

無名関数は、その名のとおり関数名を持たない関数です。function のあとに名前を書かず、いきなり引数リストを書きます。実体は Closure クラスのオブジェクトで、変数に入れたり、他の関数の引数として渡したりできます。

通常の関数はスクリプト全体から呼べる名前を1つ占有しますが、無名関数は「ここでしか使わない小さな処理」を名前を考えずに書けるのが利点です。並び替えの比較ルールや、配列の各要素に対する加工など、一度きりの処理を書くのに向いています。

無名関数の基本の書き方と変数への代入

もっとも基本的な使い方は、変数に代入して使う形です。代入した変数は、そのまま関数のように $変数名(引数) と書いて呼び出せます。

closure-basic.php
<?php
// 無名関数を変数に代入する
$greet = function (string $name): string {
    return 'こんにちは、' . $name . 'さん';
};

echo $greet('田中'); // こんにちは、田中さん

ここで見落としやすいのが、閉じ波かっこのあとの ;(セミコロン)です。名前付き関数の定義には末尾のセミコロンは不要ですが、無名関数の場合は「変数に値を代入する文」なので、文の終わりを示すセミコロンが必要になります。これを忘れると次の行と地続きの1文とみなされ、パースエラー(構文エラー)になります。引数や戻り値の型宣言は名前付き関数と同じように書けるので、書けるところは書いておくと安全です。

引数としてその場で渡す(array_map・array_filter・usort)

無名関数の本領は、コールバック(呼び出してもらう処理)を引数として渡す場面です。配列を扱う標準関数の多くはコールバックを受け取るので、変数に入れずに引数の位置へ直接書いてしまうのが一般的です。

callback.php
<?php
$prices = [120, 80, 300, 250];

// array_map: 各要素を加工した新しい配列を作る
$taxIncluded = array_map(function (int $price): int {
    return (int) round($price * 1.1);
}, $prices);
// [132, 88, 330, 275]

// array_filter: 条件に合う要素だけを残す(キーはそのまま)
$expensive = array_filter($prices, function (int $price): bool {
    return $price >= 200;
});
// [2 => 300, 3 => 250]

// usort: 比較関数で並び替える(負・0・正の整数を返す)
usort($prices, function (int $a, int $b): int {
    return $b <=> $a; // 降順に並べる
});
// [300, 250, 120, 80]

array_map() はコールバックが第1引数、配列が第2引数という順番で、array_filter()usort() は配列が先という点だけ注意してください。usort() のコールバックは、第1引数のほうが小さければ負の数、等しければ 0、大きければ正の数を返すという約束になっており、宇宙船演算子 <=> を使うと1行で書けます。

use で外側の変数を取り込む

無名関数の中は、名前付き関数と同じように独立したスコープです。関数の外で定義した変数は、そのままでは中から見えません。外の変数を使いたいときは、引数リストのあとに use (...) を書いて、取り込む変数を明示的に指定します。

use-basic.php
<?php
$taxRate = 1.1;

// use で $taxRate を無名関数の中に取り込む
$withTax = function (int $price) use ($taxRate): int {
    return (int) round($price * $taxRate);
};

echo $withTax(100); // 110

複数の変数を取り込むときは use ($a, $b) のようにカンマで区切ります。「関数の外の状態を閉じ込めて持ち歩ける」というこの性質が、無名関数がクロージャ(closure=閉包)と呼ばれる理由です。

値渡しは「定義した時点」の値がコピーされる

use ($x) と書いた場合、変数は値としてコピーされます。しかもコピーされるタイミングは無名関数を定義した時点であって、呼び出した時点ではありません。そのため定義したあとに元の変数を書き換えても、無名関数の中の値は変わりません。

use-by-value.php
<?php
$rate = 1.1;

$calc = function (int $price) use ($rate): float {
    return $price * $rate;
};

$rate = 1.5; // 定義したあとに変更しても影響しない

echo $calc(100); // 110(150 にはならない)

ループの中で無名関数を作って配列に貯めるようなコードでは、この「定義時のコピー」が意図どおりに働き、1周ごとの値がそれぞれ保持されます。逆に「最新の値を使ってほしい」つもりで書くと期待とずれるので、どちらの挙動が欲しいのかを意識しておきましょう。

use (&$x) にすると参照渡しになる

変数名の前に & を付けて use (&$x) と書くと、コピーではなく参照として取り込まれます。この場合は無名関数の中で書き換えた結果が外側の変数にも反映され、逆に外で変更した値も無名関数の中から見えるようになります。合計値を積み上げるような処理で使われます。

use-by-reference.php
<?php
$total = 0;

// & を付けて参照で取り込む
$add = function (int $price) use (&$total): void {
    $total += $price;
};

$add(120);
$add(80);

echo $total; // 200

便利ではありますが、関数の外にある変数を裏側で書き換えることになるため、処理の流れが追いにくくなります。値を返して受け取る形(array_sum()array_reduce() を使うなど)で書けるなら、そちらのほうが読みやすいコードになります。

アロー関数 fn() で短く書く(PHP 7.4 以降)

PHP 7.4 で追加されたアロー関数は、fn (引数) => 式 という形で書く短縮記法です。波かっこも return も書かず、=> の右側に書いた式1つの結果がそのまま戻り値になります。そして最大の特徴が、use を書かなくても外側の変数を自動的に使えることです。

arrow-basic.php
<?php
$taxRate = 1.1;

// use を書かなくても $taxRate をそのまま使える
$withTax = fn (int $price): int => (int) round($price * $taxRate);

echo $withTax(100); // 110

// コールバックとして渡すとかなり短くなる
$prices = [120, 80, 300, 250];

$taxIncluded = array_map(fn (int $price): int => (int) round($price * 1.1), $prices);

usort($prices, fn (int $a, int $b): int => $b <=> $a);

自動で取り込まれるのは、アロー関数の中で実際に使われている変数だけです。取り込み方は use ($x) と同じ値渡しに固定されていて、参照で取り込む方法はありません。つまりアロー関数の中から外側の変数を書き換えることはできず、書き換えたつもりでも外の値はそのままです。

arrow-by-value.php
<?php
$total = 0;

// $total は値としてコピーされる
$add = fn (int $price): int => $total + $price;

echo $add(120); // 120
echo $total;    // 0 のまま(外の変数は変わらない)

無名関数とアロー関数の違い

どちらも中身は Closure オブジェクトで、コールバックとして渡せる点は同じです。違いは「外の変数の取り込み方」と「書ける処理の量」に集約されます。

項目無名関数 function () {}アロー関数 fn () =>
使えるバージョンPHP 5.3 以降PHP 7.4 以降
外の変数の取り込みuse で明示的に指定する使った変数を自動で取り込む
参照での取り込みuse (&$x) でできるできない(常に値でコピー)
書ける処理複数行の文を自由に書ける式1つだけ
return必要不要(式の結果が戻り値)
引数・戻り値の型宣言書ける書ける
クラス内での $this自動でバインドされる自動でバインドされる

使い分けの目安はシンプルで、1つの式で書き切れる短い処理はアロー関数、条件分岐やループを含む複数行の処理は無名関数、と考えておけば困りません。外の変数を書き換える必要がある場合も、参照で取り込める無名関数の側を選ぶことになります。

クラスの中で使うときの $this の扱い

クラスのメソッドの中で無名関数やアロー関数を定義すると、その関数には自動的に「定義された場所のオブジェクト」がバインドされます。つまり use ($this) のような記述をしなくても、中で $this を使ってプロパティや他のメソッドにアクセスできます。

Cart.php
<?php
class Cart
{
    private array $items = [];
    private float $taxRate = 1.1;

    public function add(string $name, int $price): void
    {
        $this->items[] = ['name' => $name, 'price' => $price];
    }

    public function totalWithTax(): int
    {
        // 無名関数の中でも $this がそのまま使える
        $prices = array_map(function (array $item): int {
            return (int) round($item['price'] * $this->taxRate);
        }, $this->items);

        return array_sum($prices);
    }
}

$cart = new Cart();
$cart->add('書籍', 1200);
$cart->add('文具', 300);

echo $cart->totalWithTax(); // 1650

逆に、オブジェクトと結び付けたくない場合は static function () {}static fn () => のように static を付けて定義します。こうすると $this はバインドされず、中で使おうとするとエラーになります。オブジェクトを不要に保持し続けないため、コールバックをどこかに保存しておくような場面では有効な書き方です。

コールバックを受け取る側の型指定(callable と Closure)

自分で「コールバックを受け取る関数」を作るときは、引数に型を宣言できます。よく使われるのが callableClosure の2つです。callable は「呼び出せるもの」全般を表し、無名関数やアロー関数のほかに、関数名の文字列、[$object, 'method'] 形式の配列なども受け付けます。Closure は無名関数・アロー関数そのもののクラスなので、より限定的です。

callback-type.php
<?php
// callable なら関数名の文字列やメソッド配列も受け取れる
function applyAll(array $values, callable $callback): array
{
    return array_map($callback, $values);
}

print_r(applyAll([1, 2, 3], fn (int $n): int => $n * 2)); // [2, 4, 6]
print_r(applyAll(['a', 'b'], 'strtoupper'));             // ['A', 'B']

// Closure に限定すると、無名関数・アロー関数だけを受け取れる
function repeat(int $times, Closure $callback): void
{
    for ($i = 0; $i < $times; $i++) {
        $callback($i);
    }
}

repeat(3, function (int $i): void {
    echo $i; // 012
});

柔軟さを取るなら callable、確実にクロージャだけを扱いたいなら Closure という選び方になります。なお callable はプロパティの型宣言には使えないという制限があるため、コールバックをプロパティに保持しておきたい場合は Closure を型に指定します。名前空間を使ったファイルでは Closure がその名前空間の中で解決されてしまうので、先頭にバックスラッシュを付けて \Closure と書く点にも注意してください。

また PHP 8.1 以降では、既存の関数やメソッドを strtoupper(...) のように書くだけで Closure に変換できる第一級callable記法が使えます。文字列で関数名を渡すより誤字に気づきやすく、Closure 型の引数にもそのまま渡せます。

無名関数まわりでエラーが出るときの原因

無名関数とアロー関数は、書き方のルールを1つ外すとすぐにエラーや意図しない結果になります。とくにつまずきやすい3つのパターンを見ておきましょう。

use を書き忘れて Undefined variable になる

もっとも多いのが、外の変数をそのまま使おうとして use を書き忘れるケースです。無名関数の中は独立したスコープなので、外の変数は存在しない扱いになり、PHP 8 では「Warning: Undefined variable」が出て値は null になります。エラーではなく警告なので処理は止まらず、計算結果だけが 0 になっていた、という気づきにくい不具合になりがちです。

undefined-variable.php
<?php
$taxRate = 1.1;

// NG: use を書いていないので $taxRate は中から見えない
$ng = function (int $price): float {
    return $price * $taxRate; // Warning: Undefined variable $taxRate
};

// OK: use で取り込む
$ok = function (int $price) use ($taxRate): float {
    return $price * $taxRate;
};

// OK: アロー関数なら use は不要
$ok2 = fn (int $price): float => $price * $taxRate;

なお、$_POST のようなスーパーグローバルは use なしでもどこからでも参照できます。取り込みが必要なのは、あくまで自分で定義した通常の変数です。

アロー関数に複数行の処理を書こうとしてエラーになる

アロー関数の => の右側に書けるのは式1つだけです。波かっこで囲んで複数の文を書いたり、途中に return を書いたりすることはできず、パースエラーになります。処理が2行以上になった時点で、素直に無名関数へ書き換えるのが正解です。

arrow-multiline.php
<?php
// NG: アロー関数には複数行の処理を書けない(Parse error)
// $ng = fn (int $price) => {
//     $tax = $price * 0.1;
//     return $price + $tax;
// };

// OK: 複数行なら無名関数にする
$ok = function (int $price): float {
    $tax = $price * 0.1;

    return $price + $tax;
};

// OK: 三項演算子など「式」であれば1行に収まる
$label = fn (int $price): string => $price >= 200 ? '高価格' : '通常';

末尾のセミコロンを忘れてパースエラーになる

無名関数を変数に代入したときの }; も定番のつまずきどころです。名前付き関数を書き慣れているほど、閉じ波かっこで終わらせてしまいがちですが、代入は1つの文なのでセミコロンが要ります。エラーメッセージには次の行の内容が表示されることが多いため、「この行は間違っていないのにエラーになる」と感じたら、その直前で無名関数を閉じている箇所を確認してみてください。アロー関数も同じで、式の終わりにセミコロンが必要です。

まとめ

無名関数は名前を付けずにその場で定義できる関数で、実体は Closure クラスのオブジェクトです。変数に代入するときは末尾のセミコロンを忘れず、外側の変数を使いたいときは use ($x) で取り込みます。取り込みは定義時の値のコピーで、書き換えを反映させたい場合だけ use (&$x) の参照渡しを使います。PHP 7.4 以降のアロー関数 fn () => は、式1つしか書けない代わりに use が不要で、使った変数を自動的に値でキャプチャしてくれます。短い加工処理はアロー関数、複数行の処理や外の変数の書き換えが必要な処理は無名関数、と使い分けるとコードがすっきりします。

参考ページ