2016/10/18 20:38:22

PHPではクロージャは内部的にクラスに変換されるそうです

php-logo
目次(クリックするとジャンプします)
  • 1:JavaScriptでクロージャの勉強してた
  • 2:PHPのクロージャ
  • 3:補足・ハマったポイント
  • 3.1:スコープ
  • 3.2:参照
  • 4:まとめ

JavaScriptでクロージャの勉強してた

PHPとほぼ同時期にJavaScriptも勉強し始めましたが、最初あまりにプログラミングの仕方が違うので(違うように感じたので)とても戸惑った記憶があります。

JavaScriptでは関数の引数に関数を使えたり、戻り値で関数を返したりができます。JavaScriptではクロージャとか高階関数とかいろいろな言葉で戸惑いましたが、とにかく関数を自由に操れるということはわかりました。

JavaScriptを書く時はそれが普通だと思って書いていたのですが、PHPを書く時にはクロージャとか高階関数を使っていませんでした。いや使えるって知らなかった…。

PHPにも無名関数の機能があります。5.3以降で使えるようになったそうなので2009年以降ということですね。なんだ随分まえからつかえるじゃないか…。

PHPのクロージャ

JavaScriptのクロージャに慣れきってしまったせいか、PHPのクロージャは最初わかりにくかったです。何がわかりにくかったかと言うと、PHPの内部の動きです。

PHPのクロージャは内部的にClosureクラスに変換されているとのことです。

PHPマニュアル Closure クラス

例えばこんな風にクロージャを書くとPHPが認識して、この関数の構造をClosureクラスに変換してくれるのです。

function closure()
{
    $i = 0;
    return function () use (&$i){
        return $i++;
    };
}

そのためクロージャを実装した関数は内部でクラスになっちゃうので、そのままの形では実行できなくなります。

クロージャとして機能させるためには変数に代入してあげる必要があります。

つまり変数に代入するのは、

$closure = new Closure();

みたいなことを内部でやっているようです(たぶん)。

ただしClosureクラスのコンストラクタは任意には使えないようになっているそうで、コンストラクタをつかってクロージャインスタンスを自由に作ることはできません。

クロージャとして定義した関数をそのままの形で実行してみても、クロージャとしての機能は果たさずvar_dump等で変数の中身をみてみるとクロージャのインスタンスが収まっているのがわかります。

クロージャとして値をカウントしてくれるはずのこのコードを実行してみると、

<?php

function closure()
{
    $i = 0;
    return function () use (&$i){
        return $i++;
    };
}
var_dump($closure());
var_dump($closure());
var_dump($closure());
var_dump($closure());

こんな感じの結果になり、値をカウントしてくれません。

object(Closure)#1 (1) {
  ["static"]=>
  array(1) {
    ["i"]=>
    int(0)
  }
}
object(Closure)#1 (1) {
  ["static"]=>
  array(1) {
    ["i"]=>
    int(0)
  }
}
object(Closure)#1 (1) {
  ["static"]=>
  array(1) {
    ["i"]=>
    int(0)
  }
}
object(Closure)#1 (1) {
  ["static"]=>
  array(1) {
    ["i"]=>
    int(0)
  }
}

実際にはこのように代入をして使うと…

<?php

function closure()
{
    $i = 0;
    return function () use (&$i){
        return $i++;
    };
}
//代入する
$closure = closure();
var_dump($closure());
var_dump($closure());
var_dump($closure());
var_dump($closure());

このように値をカウントしてくれます。

int(0)
int(1)
int(2)
int(3)

少し使うために手続きがありますが、PHPでもクロージャが使えるのは嬉しいです。 無名関数って結構無敵な感じがありますよね。

補足・ハマったポイント

スコープ

コードの中でuseを使って無名関数の外のスコープの変数を引き継いでいます。JavaScriptでは関数の外の変数はスコープになりますが、PHPはJavaScriptとスコープが違うので、このようにuseを使わないと$iを使うことができません。

これでは関数外の$iを使っていることにはならないデス。というか引数エラーでまふ。

function closure()
{
    $i = 0;
    return function ($i) {
        return $i++;
    };
}

参照

useで変数を引き継ぐ時、&演算子で参照にしています。これはPHPの関数引数は基本、値渡しなのでuseで引き継いでも単に値がコピーして渡ってしまい値の増減を表現できなくなるからです。

参照であれば値がインクリメントされた分$iに反映されるので、カウントとして機能することになります。

この辺もJavaScriptとは違うところなので最初ハマりました。

値渡しだと当たり前ですが…

function closure()
{
    $i = 0;
    return function () use ($i){
        return $i++;
    };
}
$closure = closure();
var_dump($closure());
var_dump($closure());
var_dump($closure());
var_dump($closure());

カウントされません…。

int(0)
int(0)
int(0)
int(0)

しょんぼり。

まとめ

  • PHPのクロージャは“`Closure“`クラスによって作られる
  • インスタンスを得るために変数に代入する必要がある
  • PHPはJavaScriptとはスコープが違う部分があるので“`use“`や参照を使う場面がある