プログラミング言語では関数はすべて外部的なものとして存在する。
一方、変数の場合は外部的なものと内部的なものがある。したがって参照できるスコープとそうでないスコープが変数にはある。
関数などのブロック構造の外側で変数を定義すればグローバルなスコープになるし、ブロック内で変数を定義すればローカルなスコープとなる。
一般的に関数はすべての関数の外側で定義することになっている。つまり関数の内部に別の関数を定義できない。
しかしPHPの場合は関数内に別の関数を定義することができる仕様になっている。
でもこのことは関数がすべて外部的なものとして存在していることに変わりはない。
■ ケース1
<?php function foo() { } function foo() { }
同じfoo()という関数名で定義しています。
これは関数の二重宣言という重大なエラー(Fatal error: Cannot redeclare)となる。
■ ケース2
<?php function foo() { } function bar() { function foo() { } } bar();
外側にfoo()関数定義をして、bar()関数内部に同じfoo()という関数名で定義しています。
これはbar()関数を使うと内部のfoo()も定義されてやはり二重宣言のエラーになってしまいます。
このことは関数内部で定義した関数でも、外部からそのまま使うことができることを意味しています。
つまり関数内部に定義した関数も外部的と言えます。
なのでこうすれば内部で定義された関数でも使うことができます。
<?php function bar() { function foo() { echo "foo()<br />\n"; } } bar(); // これで内部のfoo()関数を定義 foo(); // foo()関数が実行できる
■ ケース3
<?php function foo() { } class bar { function fuga() { function foo() { } } } $obj = new bar(); // インスタンス化 $obj->fuga(); // bar::fuga()メソッド使用時にfoo()関数を定義
クラスの場合も同様、メソッド内に定義した関数は外部の関数と同じ名前の時、二重定義エラーとなります。
つまり関数は関数内に定義しても、同じ外部的なスコープで存在することになると言えます。
では関数を定義してそれを隠蔽したい場合はどうすればいいかと言うと、無名関数(クロージャ)を使います。
無名なので関数名がカブりようがないですね。
■ 無名関数(クロージャ)
<?php $noName = function ($param) { echo $param * 10; }; $noName(3); // 30
変数に、無名の関数定義を代入した後、その変数を関数的に使うことができるようになります。
コールバック関数を引数にとるような関数へは、変数代入すること無しに直接引数として記述できたりします。
--- 奇数のみをフィルタリングする例
<?php $array = range(1, 10); $newArray = array_filter($array, function($element){ return($element % 2 === 1); } );
これで関数のスコープを自在に操ることができそうです。
特に関数内部に関数を定義したい場合など、外部の関数と競合しないようにできることには意義があります。
■ 無名関数を使った再帰処理
再帰処理ということで、配列のすべての値同士を結合することを考えてみます。
一次元の配列ならimplode()関数を使って以下のようにすれば簡単に結合できます。
<?php $array = array(1, 3, 5, 7, 9); $join = implode($array);
が、一次元とは限らない配列($_GET $_POSTなどでも有り得ます)の場合は恐らく専用の関数は用意されていないので、再帰を使った関数を自作することになります。
<?php $noName = function ($array) { $join = ''; foreach ($array as $value) { if (is_array($value)) { $join .= $noName($value); } else { $join .= $value; } } return $join; }; $array = array(1, 3, 5, array(7, 9));; $result = $noName($array); // 使用中にエラー発生 echo $result;
結果これは致命的なエラー(Fatal error: Function name must be a string)となります。
無名関数のブロック外で定義した$noNameをそのままブロック内でも再帰用に使っているので当然です。
このような場合にはブロック内でも外部変数を使うことを知らせる必要があります。
そのためにはuseキーワードを使って親スコープから引き継ぐことを関数ヘッダで宣言をする必要があります。
<?php $noName = function (&$array) use (&$noName) { $join = ''; foreach ($array as &$value) { if (is_array($value)) { $join .= $noName($value); } else { $join .= $value; } } return $join; }; $array = array(1, 3, 5, array(7, 9)); // 多次元配列セット $result = $noName($array); // 定義した無名関数を使う echo $result; // "13579"
useキーワードを使うことで、無名関数を代入した変数を引き継ぐ宣言をしています。
その他、値コピーが頻発しないように関数引数やforeachでも参照を使っています。
この配列の値同士を結合する処理は、mb_convert_variables()関数が文字コード判別のために内部で行っているような動作です。
なのでより厳密な文字コード判定をしたい場合は以下のようになります。