PHPのクロージャについて

採用はこちら

PHPのクロージャとは、簡単に言うと変数として扱える関数です。

通常の関数は、あらかじめ名前を付けて定義します。

function greet($name) {
    return "Hello, {$name}";
}

echo greet("Taro");

一方、クロージャは名前を持たない関数を変数に代入できます。

$greet = function ($name) {
    return "Hello, {$name}";
};

echo $greet("Taro");

このように、関数そのものを変数に入れて、あとから呼び出せるのがクロージャの特徴です。

PHPでは、このような無名関数は内部的に Closure クラスのオブジェクトとして扱われます。

実務では、配列処理、コールバック、Laravelなどのフレームワーク、条件処理の切り替えなど、さまざまな場面で使われます。

目次

クロージャの基本構文

変数に代入して使う

クロージャの基本形は次の通りです。

$変数名 = function (引数) {
    処理
};

例えば、数値を2倍にするクロージャは次のように書けます。

$double = function ($number) {
    return $number * 2;
};

echo $double(5); // 10

$double には関数が代入されているため、通常の関数のように $double(5) と呼び出せます。

型宣言もできる

クロージャでも、通常の関数と同じように引数や戻り値の型を指定できます。

$double = function (int $number): int {
    return $number * 2;
};

echo $double(5); // 10

実務では、型を明示しておくことで、意図しない値が渡されたときのバグを防ぎやすくなります。

クロージャがよく使われる場面

array_map() で配列を加工する

クロージャは、配列の各要素を加工するときによく使われます。

$numbers = [1, 2, 3, 4, 5];

$doubled = array_map(function ($number) {
    return $number * 2;
}, $numbers);

print_r($doubled);

実行結果は次のようになります。

Array
(
    [0] => 2
    [1] => 4
    [2] => 6
    [3] => 8
    [4] => 10
)

array_map() は、配列の各要素に対して指定した処理を実行し、その結果を新しい配列として返します。

array_filter() で条件に合う要素だけ残す

条件に合う要素だけを残したい場合は、array_filter() とクロージャを組み合わせます。

$numbers = [1, 2, 3, 4, 5, 6];

$evenNumbers = array_filter($numbers, function ($number) {
    return $number % 2 === 0;
});

print_r($evenNumbers);

実行結果は次のようになります。

Array
(
    [1] => 2
    [3] => 4
    [5] => 6
)

ここでは、2で割り切れる数だけを残しています。

なお、array_filter() は元の配列のキーを維持します。

連番に振り直したい場合は、array_values() を使います。

$evenNumbers = array_values(array_filter($numbers, function ($number) {
    return $number % 2 === 0;
}));

usort() で並び替える

クロージャは、配列の並び替え条件を指定するときにも使われます。

$users = [
    ['name' => 'Taro', 'age' => 20],
    ['name' => 'Jiro', 'age' => 17],
    ['name' => 'Saburo', 'age' => 25],
];

usort($users, function ($a, $b) {
    return $a['age'] <=> $b['age'];
});

print_r($users);

<=> は宇宙船演算子と呼ばれる比較演算子です。

左辺が小さければ -1、等しければ 0、大きければ 1 を返します。

年齢の昇順ではなく降順にしたい場合は、比較する順番を逆にします。

usort($users, function ($a, $b) {
    return $b['age'] <=> $a['age'];
});

外側の変数を使うには use が必要

クロージャ内では外側の変数をそのまま使えない

PHPのクロージャで特に重要なのが、外側の変数を使う場合の書き方です。

次のコードは、期待通りには動きません。

$message = "Hello";

$greet = function ($name) {
    return $message . ", " . $name;
};

echo $greet("Taro");

クロージャの中では、外側で定義した $message をそのまま参照できません。

この場合、$message はクロージャ内で未定義の変数として扱われます。

外側の変数をクロージャ内で使いたい場合は、use を使います。

$message = "Hello";

$greet = function ($name) use ($message) {
    return $message . ", " . $name;
};

echo $greet("Taro"); // Hello, Taro

use と引数は役割が違う

次の例を見てください。

$taxRate = 0.1;

$calculatePrice = function ($price) use ($taxRate) {
    return $price + ($price * $taxRate);
};

echo $calculatePrice(1000); // 1100

このコードでは、$price$taxRate の役割が違います。

$price は、クロージャを呼び出すときに渡される引数です。

function ($price)

一方、$taxRate は外側のスコープから取り込む変数です。

use ($taxRate)

つまり、次のように考えると分かりやすいです。

function ($price) use ($taxRate)

これは、「呼び出し時に $price を受け取り、外側にある $taxRate も使う」という意味です。

use は基本的に値渡し

クロージャ作成時点の値が取り込まれる

use ($value) で取り込んだ変数は、基本的にクロージャを作成した時点の値として扱われます。

$message = "Hello";

$greet = function ($name) use ($message) {
    return $message . ", " . $name;
};

$message = "Hi";

echo $greet("Taro"); // Hello, Taro

この例では、クロージャを作成した時点で $message の値は "Hello" です。

その後、外側の $message"Hi" に変更しても、クロージャ内で使われる値は "Hello" のままです。

そのため、use ($message) は「クロージャ作成時点の値を取り込む」と理解するとよいでしょう。

オブジェクトを取り込む場合は注意が必要

ただし、オブジェクトを use で取り込む場合は注意が必要です。

$user = new stdClass();
$user->name = 'Taro';

$fn = function () use ($user) {
    $user->name = 'Jiro';
};

$fn();

echo $user->name; // Jiro

この例では、use ($user) と値渡しで取り込んでいますが、外側の $user->name も変更されています。

これは、$user という変数が同じオブジェクトを指しているためです。

use ($user) が参照渡しになっているわけではありません。

一方、クロージャ内で $user 自体を別のオブジェクトに差し替えても、外側の $user は変わりません。

$user = new stdClass();
$user->name = 'Taro';

$fn = function () use ($user) {
    $user = new stdClass();
    $user->name = 'Jiro';
};

$fn();

echo $user->name; // Taro

このように、オブジェクトの場合は「変数そのものがコピーされる」というより、「同じオブジェクトを指す値が取り込まれる」と理解すると正確です。

外側の変数を変更したい場合は参照渡しを使う

use (&$value) で外側の変数を変更できる

クロージャの中で外側の変数そのものを変更したい場合は、& を付けて参照渡しにします。

$count = 0;

$increment = function () use (&$count) {
    $count++;
};

$increment();
$increment();

echo $count; // 2

use (&$count) と書くことで、外側の $count をクロージャ内から直接変更できます。

use ($value) では外側の変数は変わらない

一方、& を付けない場合、外側の変数は変わりません。

$count = 0;

$increment = function () use ($count) {
    $count++;
};

$increment();
$increment();

echo $count; // 0

この場合、クロージャ内で $count++ を実行しても、外側の $count0 のままです。

さらに、値渡しで取り込んだ $count は、呼び出しのたびに保持され続けるわけではありません。

$count = 0;

$fn = function () use ($count) {
    $count++;
    echo $count . PHP_EOL;
};

$fn(); // 1
$fn(); // 1

echo $count; // 0

外側の値を継続的に変更・保持したい場合は、use (&$count) を使います。

アロー関数 fn との違い

アロー関数は短く書けるクロージャ

PHP 7.4以降では、アロー関数を使えます。

通常のクロージャは次のように書きます。

$numbers = [1, 2, 3];

$doubled = array_map(function ($number) {
    return $number * 2;
}, $numbers);

アロー関数を使うと、次のように短く書けます。

$numbers = [1, 2, 3];

$doubled = array_map(fn($number) => $number * 2, $numbers);

単純な処理であれば、アロー関数のほうが読みやすくなります。

アロー関数は外側の変数を自動で取り込む

通常のクロージャでは、外側の変数を使うために use が必要です。

$taxRate = 0.1;
$prices = [1000, 2000, 3000];

$withTax = array_map(function ($price) use ($taxRate) {
    return $price + ($price * $taxRate);
}, $prices);

アロー関数では、外側の変数を自動的に取り込めます。

$taxRate = 0.1;
$prices = [1000, 2000, 3000];

$withTax = array_map(
    fn($price) => $price + ($price * $taxRate),
    $prices
);

use ($taxRate) を書かなくても、外側の $taxRate を使えます。

アロー関数の取り込みは値渡し

アロー関数は外側の変数を自動で取り込みますが、その取り込みは値渡しです。

そのため、外側の変数を書き換える用途には向いていません。

$count = 0;

$fn = fn() => $count++;

$fn();

echo $count; // 0

外側の $count を変更したい場合は、通常のクロージャで use (&$count) を使います。

$count = 0;

$fn = function () use (&$count) {
    $count++;
};

$fn();

echo $count; // 1

アロー関数は短い処理に向いている

アロー関数は、1つの式で表せる短い処理に向いています。

$ids = array_map(fn($user) => $user['id'], $users);
$activeUsers = array_filter(fn($user) => $user['active'], $users);

ただし、処理が複数行になる場合や、条件分岐が複雑になる場合は、通常のクロージャやメソッドに切り出したほうが読みやすくなります。

$formattedUsers = array_map(function ($user) {
    $name = trim($user['name']);
    $email = strtolower($user['email']);

    return [
        'name' => $name,
        'email' => $email,
    ];
}, $users);

クロージャと $this

クラス内のクロージャでは $this を使える

クラスのメソッド内でクロージャを作ると、通常は $this を使えます。

class UserService
{
    private string $prefix = 'User: ';

    public function formatNames(array $names): array
    {
        return array_map(function ($name) {
            return $this->prefix . $name;
        }, $names);
    }
}

$service = new UserService();

print_r($service->formatNames(['Taro', 'Jiro']));

この例では、クロージャ内から $this->prefix を参照しています。

クラスのメソッド内で作られた非staticなクロージャは、通常そのオブジェクトに束縛されるため、$this を使えます。

static function では $this を使えない

クロージャに static を付けると、$this は使えません。

class UserService
{
    private string $prefix = 'User: ';

    public function formatNames(array $names): array
    {
        return array_map(static function ($name) {
            return $this->prefix . $name; // エラー
        }, $names);
    }
}

$this が不要な処理では、static function を使うことで「このクロージャはオブジェクトに依存しない」と明示できます。

class UserService
{
    public function formatNames(array $names): array
    {
        return array_map(static function ($name) {
            return strtoupper($name);
        }, $names);
    }
}

実務では、クラスのプロパティやメソッドを使わないクロージャは static function にしておくと、意図が明確になります。

クロージャを返す関数

関数からクロージャを返せる

クロージャは、関数の戻り値として返すこともできます。

function makeMultiplier($factor)
{
    return function ($number) use ($factor) {
        return $number * $factor;
    };
}

$double = makeMultiplier(2);
$triple = makeMultiplier(3);

echo $double(10); // 20
echo $triple(10); // 30

この例では、makeMultiplier() がクロージャを返しています。

makeMultiplier(2) で作られたクロージャは、$factor2 を取り込んでいます。

makeMultiplier(3) で作られたクロージャは、$factor3 を取り込んでいます。

作成時の値を保持できる

重要なのは、makeMultiplier() の実行が終わったあとも、クロージャが $factor の値を保持していることです。

$double = makeMultiplier(2);
echo $double(10); // 20

このように、クロージャは作成時の周辺情報を保持できます。

「クロージャ」という名前は、外側の変数を閉じ込める、つまり閉包する性質に由来します。

実務で使えるクロージャの例

条件を動的に作る

クロージャを使うと、条件を動的に作ることができます。

例えば、指定したロールのユーザーだけを抽出する条件を作る関数は、次のように書けます。

function filterByRole(string $role): Closure
{
    return function (array $user) use ($role): bool {
        return $user['role'] === $role;
    };
}

$users = [
    ['name' => 'Taro', 'role' => 'admin'],
    ['name' => 'Jiro', 'role' => 'editor'],
    ['name' => 'Saburo', 'role' => 'admin'],
];

$admins = array_filter($users, filterByRole('admin'));

print_r($admins);

filterByRole('admin') は、「role が admin のユーザーだけを通すクロージャ」を返します。

このように、クロージャを使うと、条件や処理内容をあとから柔軟に組み立てられます。

Laravelのルーティングで使う

Laravelでは、ルーティングでクロージャをよく使います。

Route::get('/hello', function () {
    return 'Hello World';
});

これは、/hello にアクセスされたときに実行する処理を、クロージャとして渡している例です。

Laravelのクエリビルダで使う

Laravelのクエリビルダでも、条件をまとめるためにクロージャがよく使われます。

$users = User::where(function ($query) {
    $query->where('role', 'admin')
          ->orWhere('role', 'editor');
})->get();

このように、複数の条件をグループ化したいときにもクロージャは便利です。

callableClosure の違い

callable は呼び出し可能なもの全般を表す

PHPには callable という型があります。

callable は、関数として呼び出せるもの全般を表します。

例えば、次のようなものが callable として扱えます。

function hello() {
    return 'Hello';
}

$callback1 = 'hello';

$callback2 = function () {
    return 'Hello';
};

class Greeter
{
    public function hello()
    {
        return 'Hello';
    }
}

$greeter = new Greeter();

$callback3 = [$greeter, 'hello'];

$callback1 は関数名の文字列です。

$callback2 はクロージャです。

$callback3 はオブジェクトのメソッドを指定した配列です。

これらはいずれも、関数のように呼び出せるため callable として扱えます。

Closure はクロージャオブジェクトを表す

一方、Closure は無名関数やアロー関数などによって作られるクロージャオブジェクトを表します。

$callback = function () {
    return 'Hello';
};

この $callbackClosure オブジェクトです。

型指定としては、広く受け取りたいなら callable、クロージャだけを受け取りたいなら Closure を使います。

function execute(callable $callback): mixed
{
    return $callback();
}
function executeClosure(Closure $callback): mixed
{
    return $callback();
}

実務では、関数名やメソッドも受け取りたい場合は callable、無名関数やアロー関数に限定したい場合は Closure、という使い分けになります。

Closure::fromCallable() と first-class callable syntax

既存の関数をクロージャに変換する

既存の関数やメソッドを Closure に変換したい場合は、Closure::fromCallable() を使えます。

function greet(string $name): string
{
    return "Hello, {$name}";
}

$closure = Closure::fromCallable('greet');

echo $closure('Taro'); // Hello, Taro

PHP 8.1以降では ... を使える

PHP 8.1以降では、first-class callable syntax を使って、より簡潔にクロージャを作れます。

function greet(string $name): string
{
    return "Hello, {$name}";
}

$closure = greet(...);

echo $closure('Taro'); // Hello, Taro

メソッドでも同じように使えます。

class Greeter
{
    public function greet(string $name): string
    {
        return "Hello, {$name}";
    }
}

$greeter = new Greeter();

$closure = $greeter->greet(...);

echo $closure('Taro');

クロージャを即時実行する

作ってすぐ実行できる

PHPでは、クロージャを作ってすぐに実行することもできます。

$result = (function () {
    $a = 10;
    $b = 20;

    return $a + $b;
})();

echo $result; // 30

このような書き方は、JavaScriptのIIFEに近い使い方です。

スコープを分けたいときに使える

例えば、一時的な変数を外側に出したくない場合に使えます。

$config = (function () {
    $env = getenv('APP_ENV');

    if ($env === 'production') {
        return ['debug' => false];
    }

    return ['debug' => true];
})();

ただし、PHPではこの書き方を多用するよりも、通常の関数やメソッドに分けたほうが読みやすい場合も多いです。

クロージャのメリット

処理を値として渡せる

クロージャの大きなメリットは、処理そのものを値として渡せることです。

function applyDiscount(array $prices, callable $discount): array
{
    return array_map($discount, $prices);
}

$prices = [1000, 2000, 3000];

$result = applyDiscount($prices, function ($price) {
    return $price * 0.9;
});

この例では、割引処理を外から渡しています。

処理内容を差し替えられるため、柔軟なコードを書きやすくなります。

小さな処理をその場に書ける

クロージャを使うと、短い処理をその場で書けます。

$activeUsers = array_filter($users, function ($user) {
    return $user['status'] === 'active';
});

わざわざ isActiveUser() のような関数を別に作らなくても、処理の意図をその場で表現できます。

外側の値を保持できる

クロージャは、作成時に外側の値を取り込み、その値を保持できます。

function makePrefixer(string $prefix): Closure
{
    return function (string $text) use ($prefix): string {
        return $prefix . $text;
    };
}

$errorPrefixer = makePrefixer('[ERROR] ');

echo $errorPrefixer('Something went wrong.');

このように、設定値や条件を持った関数を作れるのもクロージャの便利な点です。

クロージャの注意点

複雑な処理を書くと読みづらくなる

クロージャは便利ですが、長くなりすぎると可読性が下がります。

$result = array_map(function ($user) use ($settings, $logger, $formatter) {
    if (!$user['active']) {
        $logger->info('inactive user');
        return null;
    }

    if ($settings['uppercase']) {
        $user['name'] = strtoupper($user['name']);
    }

    $user['label'] = $formatter->format($user);

    return $user;
}, $users);

このように処理が長い場合は、メソッドに切り出したほうが読みやすくなります。

$result = array_map([$this, 'formatUser'], $users);

参照渡しの多用に注意する

use (&$value) を使うと外側の変数を変更できますが、多用すると処理の流れが追いにくくなります。

$count = 0;

$fn = function () use (&$count) {
    $count++;
};

外側の状態を変更するコードは、副作用が分かりにくくなることがあります。

可能であれば、外側の変数を直接変更するよりも、戻り値として結果を返す設計のほうが安全です。

$this を意図せず保持する場合がある

クラス内でクロージャを作ると、$this を保持する場合があります。

class ReportService
{
    public function getFormatter(): Closure
    {
        return function ($value) {
            return $this->format($value);
        };
    }

    private function format($value): string
    {
        return strtoupper($value);
    }
}

この場合、返されたクロージャは ReportService のインスタンスに依存します。

$this が不要な場合は、static function を使うことで、不要な束縛を避けられます。

return static function ($value) {
    return strtoupper($value);
};

クロージャ・アロー関数・通常関数の使い分け

短い処理ならアロー関数

1行で意味が分かる短い処理なら、アロー関数が向いています。

$ids = array_map(fn($user) => $user['id'], $users);
$activeUsers = array_filter($users, fn($user) => $user['active']);

複数行の処理なら通常のクロージャ

複数行の処理を書く場合は、通常のクロージャのほうが読みやすいです。

$formattedUsers = array_map(function ($user) {
    $name = trim($user['name']);
    $email = strtolower($user['email']);

    return [
        'name' => $name,
        'email' => $email,
    ];
}, $users);

再利用するなら関数やメソッドに切り出す

同じ処理を複数箇所で使う場合や、処理に名前を付けたほうが分かりやすい場合は、関数やメソッドに切り出すのがおすすめです。

$formattedUsers = array_map([$this, 'formatUser'], $users);
private function formatUser(array $user): array
{
    return [
        'name' => trim($user['name']),
        'email' => strtolower($user['email']),
    ];
}

PHPのクロージャでよくあるミス

use を書き忘れる

外側の変数を使うのに use を書き忘れるケースはよくあります。

$rate = 0.1;

$prices = array_map(function ($price) {
    return $price * (1 + $rate);
}, [1000, 2000]);

この場合、$rate はクロージャ内で未定義になります。

正しくは次のように書きます。

$rate = 0.1;

$prices = array_map(function ($price) use ($rate) {
    return $price * (1 + $rate);
}, [1000, 2000]);

値渡しなのに外側の変数が変わると思ってしまう

次のコードでは、外側の $count は変わりません。

$count = 0;

$fn = function () use ($count) {
    $count++;
};

$fn();

echo $count; // 0

外側の $count を変更したい場合は、参照渡しにします。

$count = 0;

$fn = function () use (&$count) {
    $count++;
};

$fn();

echo $count; // 1

アロー関数で複雑な処理を書こうとする

アロー関数は短く書けて便利ですが、複雑な処理には向いていません。

$result = array_map(fn($user) => [
    'name' => strtoupper(trim($user['name'])),
    'status' => $user['active'] ? 'active' : 'inactive',
    'score' => $user['score'] * 1.1,
], $users);

この程度であればまだ読めますが、条件分岐やログ出力、例外処理が増える場合は、通常のクロージャやメソッドに切り出したほうがよいです。

まとめ

クロージャは関数を値として扱う仕組み

PHPのクロージャは、関数を変数に入れたり、引数として渡したり、戻り値として返したりできる仕組みです。

基本形は次の通りです。

$fn = function ($arg) use ($outerValue) {
    return $outerValue . $arg;
};

use の理解が重要

クロージャを理解するうえで、特に重要なのは use です。

外側の変数をクロージャ内で使いたい場合は、次のように書きます。

$message = 'Hello';

$fn = function ($name) use ($message) {
    return "{$message}, {$name}";
};

外側の変数を変更したい場合は、参照渡しにします。

$count = 0;

$fn = function () use (&$count) {
    $count++;
};

アロー関数との使い分けも大切

短い処理ならアロー関数が便利です。

fn($x) => $x * 2

複数行の処理や、明示的に use を使いたい場合は通常のクロージャが向いています。

function ($x) use ($y) {
    return $x + $y;
}

処理が長い場合や再利用する場合は、関数やメソッドに切り出すと読みやすくなります。

PHPのクロージャは、配列処理やコールバック、Laravelなどのフレームワークで頻繁に使われる重要な機能です。

特に use、値渡し、参照渡し、アロー関数との違いを押さえておくと、実務でも迷わず使えるようになります。

以上、PHPのクロージャについてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次