
gotoの次にグローバル変数は嫌われているのかな

偶然にもどちらもG始まりね
PhpStormのリファクタ機能
まだ関数に分けていない状態なら、PhpStormのリファクタ機能を使うと便利です。引数を間違うことなく、関数に分けたり、クラスとプロパティにできますよ。
関数にする
関数に分けたい行を選択しておき、メニューのRefactor>Refactor this...>7. Extract method...でダイアログを表示します。
<?php
$map = [];
$a = 1;
$map['foo'] = 'bar'; // ココと
$a += 2; // ココを選択します
echo $map;
echo $a;
Code language: PHP (php)
表示されたダイアログで関数名を入力して、参照渡し/返り値を選択することで、リファクタしてくれます。

返り値を選択して、自動リファクタした例
<?php
$map = [];
$a = 1;
/**
* @param array $map
* @param int $a
* @return array
*/
function foo(array $map, int $a): array
{
$map['foo'] = 'bar';
$a += 2;
return array($map, $a);
}
list($map, $a) = foo($map, $a);
echo $map;
echo $a;
Code language: PHP (php)

PhpStormすごい!
クラスやプロパティにする
クラスにしたり、$mapをプロパティにして、コンストラクタ渡しにしたり、さらにメソッドに分けたりできます。
元の状態です。
<?php
$map = [];
$a = 1;
$map['foo'] = 'bar';
$a += 2;
echo $map;
echo $a;
Code language: PHP (php)
まず「<?php」を除く全行を選択して、Refactor>Refactor this…>7. Extract method…で、ダイアログを表示して、関数にします。
<?php
function main(): void
{
$map = [];
$a = 1;
$map['foo'] = 'bar';
$a += 2;
echo $map;
echo $a;
}
main();
Code language: PHP (php)
main関数の内部を全て選択して、Refactor>Refactor this…>8. Extract class…で、ダイアログを表示して、クラスにします。
<?php
Main::main();
Code language: PHP (php)
<?php
class Main
{
public static function main(): void
{
$map = [];
$a = 1;
$map['foo'] = 'bar';
$a += 2;
echo $map;
echo $a;
}
}
Code language: PHP (php)
手作業で、mainメソッドのstaticを削除し、呼ぶ側も修正します。
<?php
$main = new Main();
$main->main();
Code language: PHP (php)
public function main(): void
{
Code language: PHP (php)
PhpStormのリファクタ機能を使って、mainメソッド内の$mapをインスタンスプロパティにします。
mainメソッド内の$mapをクリックし、Refactor>Refactor this…>6. Introduce Field…を選択します。
小さい選択ウィンドウが表示されて、$map、$map = [] のどちらをプロパティにするか聞かれます。$mapを選択します。
class Main
{
private $map;
public function main(): void
{
$this->map = [];
$a = 1;
$this->map['foo'] = 'bar';
$a += 2;
echo $this->map;
echo $a;
}
}
Code language: PHP (php)
$mapが $this->map に変化していますね!
手作業で、空のコンストラクタを追加します。
class Main
{
private $map;
public function __construct()
{
}
public function main(): void
{
Code language: PHP (php)
PhpStromのリファクタ機能で、コンストラクタの引数に$mapを追加します。
private $map;行の$mapを右クリックし、Show Context Actionsをクリックします。
class Main
{
private $map;
public function __construct($map)
{
$this->map = $map;
}
public function main(): void
{
$this->map = [];
$a = 1;
$this->map['foo'] = 'bar';
$a += 2;
echo $this->map;
echo $a;
}
}
Code language: PHP (php)
次の2行を選択して、Refactor>Refactor this…>7. Extract method…で、fooメソッドにします。
$this->map['foo'] = 'bar';
$a += 2;
Code language: PHP (php)
<?php
class Main
{
private $map;
public function __construct($map)
{
$this->map = $map;
}
public function main(): void
{
$this->map = [];
$a = 1;
$a = $this->foo($a);
echo $this->map;
echo $a;
}
/**
* @param int $a
* @return int
*/
public function foo(int $a): int
{
$this->map['foo'] = 'bar';
$a += 2;
return $a;
}
}
Code language: PHP (php)
$aが引数で渡されて、返り値で戻ってきています。すごいですね!

PhpStormべんり〜
引数で渡す
グローバル変数を使った関数の例です。
<?php
function foo()
{
global $map;
global $a;
$map['foo'] = 'bar';
$a += 2;
}
$map = [];
$a = 1;
foo();
echo $map;
echo $a;
Code language: PHP (php)
関数内で、グローバル変数の値を変更していたり、配列の要素を追加している場合は、引数を参照渡しする方法と、返り値で返す方法があります。
引数を参照渡し
引数を参照渡しする方法です。
<?php
function foo(&$map, &$a)
{
$map['foo'] = 'bar';
$a += 2;
}
$map = [];
$a = 1;
foo($map, $a);
echo $map;
echo $a;
Code language: PHP (php)
引数を値渡し+返り値
引数は値渡しして、変更した結果を返り値で返す方法です。
<?php
function foo($map, $a)
{
$map['foo'] = 'bar';
$a += 2;
return [$map, $a];
}
$map = [];
$a = 1;
list($map, $a) = foo($map, $a);
echo $map;
echo $a;
Code language: PHP (php)
コンストラクタの引数で渡して、privateプロパティでアクセスする
さきほどと同じ例ですが、関数は長い処理をしています。いずれクラスにして複数のメソッドに分けてリファクタしたい、そんな関数です。
<?php
function foo()
{
global $map;
global $a;
$map['foo'] = 'bar';
$a += 2;
// 長い処理
}
$map = [];
$a = 1;
foo();
echo $map;
echo $a;
Code language: PHP (php)
コンストラクタに参照渡し
まず、参照渡しバージョンです。
引数並びに & をつけ、privateプロパティに代入するときも & をつけます。
<?php
class FooAction
{
private $map;
private $a;
public function __construct(&$map, &$a) // & で参照渡し
{
$this->map = &$map; // & で参照渡し
$this->a = &$a; // & で参照渡し
}
public function foo()
{
$this->map['foo'] = 'bar';
$this->a += 2;
// 長い処理
}
}
$map = [];
$a = 1;
$fooAction = new FooAction($map, $a);
$fooAction->foo();
echo $map;
echo $a;
Code language: PHP (php)
コンストラクタに値渡し+resultメソッド
コンストラクタで値渡しする方法です。呼んだ側に値を戻すために、resultメソッドを用意した例です。
<?php
class FooAction
{
private $map;
private $a;
public function __construct($map, $a)
{
$this->map = &$map;
$this->a = &$a;
}
public function foo()
{
$this->map['foo'] = 'bar';
$this->a += 2;
}
public function results()
{
return [$this->map, $this->a];
}
}
$map = [];
$a = 1;
$fooAction = new FooAction($map, $a);
$fooAction->foo();
list($map, $a) = $fooAction->results();
echo $map;
echo $a;
Code language: PHP (php)
別クラスのメソッドにしたので、リファクタしやすくなりました。
コールバック関数
PHPは関数の内側と外側で変数スコープが分かれています。今なら、クロージャとuseを使うことが多いでしょう。PHP 5.2以前はクロージャがないので、コールバック関数を使うのが面倒な場面がありました。
1つめの問題は、array_walkのコールバック関数には、3つめの引数$userdataがありますが、array_mapやusortのコールバック関数には$userdataがないことです。そのため、array_mapやusortを呼んでいる側の変数をコールバック関数は参照できません。そのため、global宣言していることがあります。
2つめの問題は、array_walkの$userdataを使うとき、コールバック関数で$userdataの値を変更しても、呼んだ側の$userdataに値が反映されないことです。そのため、global宣言していることがあります。
for文、foreach文にもどす
$arrの各要素に$foo->calc()を計算して、新しい配列を$new_arrを作る例です。
<?php
class Foo
{
public function calc($x, $a)
{
return $x + $a;
}
}
global $foo;
$foo = new Foo();
$a = 2;
$arr = [1, 2, 3, 4];
$new_arr = array_map('my_callback', $arr);
// $new_arr = [3, 4, 5, 6];
function my_callback($val)
{
global $foo;
global $a;
return $foo->calc($val, $a);
}
Code language: PHP (php)
コールバック関数が1行なら、foreach文に戻してもいいでしょう。
foreach文にしました。$fooや$aをglobal宣言する必要がありません。
$new_arr = [];
foreach ($arr as $val) {
$new_arr[] = $foo->calc($val, $a);
}
Code language: PHP (php)
クロージャのuseを使う
もともとのコールバック関数はやめ、クロージャとuseを使った例です。useで、$fooと$aを渡します。
$new_arr = array_map(function ($val) use ($foo, $a) {
return $foo->calc($val, $a);
}, $arr);
Code language: PHP (php)
もともとのコールバック関数が数行以上ある場合は残しておきます。引数に $foo を追加して、クロージャから元のコールバック関数を呼びます。
$new_arr = array_map(function ($val) use ($foo, $a) {
return my_callback($val, $foo, $a);
}, $arr);
function my_callback($val, $foo, $a)
{
// 何か処理
return $foo->calc($val, $a);
}
Code language: PHP (php)
クラスのインスタンスプロパティを使う
メインロジックにクロージャやコールバック関数が並んでいると、メインロジックがゴチャゴチャした感じになります。
そこで、別クラスにまとめて、グローバル変数だった$fooをインスタンスプロパティで置き換えた例です。
$foo = new Foo();
$bar = new Bar($foo, $a);
$new_arr = $bar->array_map($arr);
Code language: PHP (php)
<?php
class Bar
{
private $foo;
private $a;
public function __construct($foo, $a)
{
$this->foo = $foo;
$this->a = $a;
}
public function array_map($arr)
{
return array_map([$this,'my_callback'], $arr);
}
private function my_callback($val)
{
// 何か処理
return $this->foo->calc($val, $this->a);
}
}
Code language: PHP (php)
userdataと参照を使う
説明のため、array_walkで合計を計算します。(本来はarray_sumを使って、配列の合計を計算できます)
<?php
$arr = [1, 2, 3, 4];
global $g_sum;
$g_sum = 0;
array_walk($arr, 'my_callback');
var_dump($g_sum);
function my_callback($value, $index)
{
global $g_sum;
$g_sum += $value;
}
Code language: PHP (php)
まず、反映されない使い方です。
コールバック内で3つめの引数$userdataの値を読むだけなら、これで問題ありません。
しかし、値を変更したい場合、次の使い方では、変更が反映されず、結果は 0 と表示されます。
ところが、コールバック内で、$g_sumを表示すると、3, 6, 10と増えていきます。何が悪いのでしょうか?
<?php
$arr = [1, 2, 3, 4];
$g_sum = 0;
array_walk($arr, 'my_callback', $g_sum);
var_dump($g_sum);
function my_callback($value, $index, &$g_sum)
{
$g_sum += $value;
var_dump($g_sum);
}
Code language: PHP (php)
反映される使い方です。
$userdataを参照のコンテナとして使います。
<?php
$arr = [1, 2, 3, 4];
$g_sum = 0;
$userdata = [
'sum' => &$g_sum, // & をつける
];
array_walk($arr, 'my_callback', $userdata);
var_dump($g_sum);
// $userdataに & をつけなくてもいい
function my_callback($value, $index, $userdata)
{
$userdata['sum'] += $value;
}
Code language: PHP (php)
IO処理のオブジェクトのメソッドに移す
話しがずれますが、一つのメソッドや関数で、
「データを加工」+「IO処理で保存」
または
「IO処理から読み込み」+「データを加工」
しているのは、よく見かけます。
何が気になるかというと、IO処理があるとユニットテストを作りにくいんですね。テストケースでIO処理のモックをセットアップしていると、何をテストしているのか、よくわからないコードになりがちです。
さて、グローバル変数がIO処理のオブジェクトの場合です。foo関数は「データを加工」と「IO処理で保存」をしています。
<?php
$bar = new Bar();
foo("foo", 123);
function bar($name, $num)
{
global $bar;
$str = $name . ":" . $num;
$bar->save($str);
}
Code language: PHP (php)
foo関数は、グローバル変数の$barを引数で渡すようにリファクタしても、ユニットケースを作るのは相変わらず難しいです。
そこで、foo関数から「IO処理で保存」をなくしてしまいます。グローバル変数は必要なくなり、foo関数のユニットテストを書きやすくなりました。
<?php
$bar = new Bar();
$str = foo("foo", 123);
$bar->save($str);
function foo($name, $num)
{
$str = $name . ":" . $num;
return $str;
}
Code language: PHP (php)
さらに、このfoo()、$bar->save()の処理が、定形処理なら、Barクラスのメソッドにするのもいいと思います。
<?php
$bar = new Bar();
$bar->fooSave("foo", 123);
Code language: PHP (php)
<?php
class Bar
{
public function fooSave($name, $num)
{
$str = self::foo($name, $num);
$this->save($str);
}
public static function foo($name, $num)
{
$str = $name . ":" . $num;
return $str;
}
Code language: PHP (php)
コールスタックの深いところから、呼び元へ値を返す
レガシープロジェクトに限らず、コールスタックの深いところから、呼び元へ値を返したいことがあります。そのとき、グローバル変数の代わりに使ってしまいがちなのが、$_SESSIONです。

だめなの?思いついたときは、ぼくってやるじゃん!って思ったけど。
本来は、返り値で返しながら、バブルアップで上位まで返したいのですが、途中のメソッドを変更できないことも多く、返り値で返せません。
$_SESSIONは時間的な生存期間が長いので、グローバル変数より注意が必要です。
また、global宣言よりも$GLOBALSのほうがいいです(記事の最後で説明します)。
直接$GLOBALSを読み書きしていいですし、
// コールスタックの深いところで
$GLOBALS['FooReturnValue'] = 'bar';
// 呼んだ側で
$retVal = $GLOBALS['FooReturnValue'] ?: '';
Code language: PHP (php)
$GLOBALSを読み書きするgetter/setterを作ってもいいと思います。
// コールスタックの深いところで
Foo::setReturnValue('bar');
// 呼んだ側で
$retVal = Foo::getReturnValue();
class Foo
{
public static function setReturnValue($value)
{
$GLOBALS['FooReturnValue'] = $value;
}
public static function getReturnValue()
{
return $GLOBALS['FooReturnValue'] ?: '';
}
}
Code language: PHP (php)
実はグローバル変数
$_SESSION、staticプロパティ、関数のstatic変数は、実はグローバル変数です。globalや$GLOBALSの代わりに使うのはNGです。
putenv/getenv もグローバル変数として使うことができますが、もちろんグローバル変数として使うのはNGです。
遠く離れたオブジェクトに値を送るには、返り値で返すか、そのためのオブジェクトを引数で渡すしかありません。何のつながりもないオブジェクト間で、第三のオブジェクトを経由して値を交換できるとしたら、第三のオブジェクト自体がSingletonだったり、裏でstatic変数やグローバル変数的なものを保持しているはずです。
第三のオブジェクトを用意して、依存関係やセットアップコードを複雑にするよりは、$GLOBALSを使うほうがいいように思います。
$_SESSION
コールスタックの深いところから上位へ値を返すとき、使われがちなのが、$_SESSIONです。
同じスーパーグローバル変数でも、$_GET、$_POST、$_SERVERなどは、PHPが設定し、アプリケーションは読み出すだけです。通常は、値を読み出すだけであり、値が変わることはないので、グローバルでも安全です。
$GLOBALSや$_SESSIONは、いつでもどこからでも値を変更可能で、実際そのように使います。
さらに、$_SESSION変数の生存期間は、アプリケーションの実行期間より長いので、 global変数や$GLOBALSを使うより、注意が必要です。

数秒後か数分後の次のページアクセスのための変数を保存していいなら、コンマ何秒後のために保存してもいいと思うんだけど?

そのデータは、本当にコンマ何秒か前に保存されたデータなのかしら?もっと古いデータかもね?
staticプロパティ

staticプロパティは、global宣言しなくても使えるよ!
たしかに、グローバル変数をstaticプロパティに置き換えることができます。
<?php
class MyVars
{
public static $map;
public static $a;
}
function foo()
{
MyVars::$map['foo'] = 'bar';
MyVars::$a += 2;
}
MyVars::$map = [];
MyVars::$a = 1;
foo();
echo MyVars::$map;
echo MyVars::$a;
Code language: PHP (php)

なにが良くないの?
global宣言は削除できましたが、グローバル変数を使っていることに変わりありません。
次のコードは何の問題もないように見えますが、
MyVars::$map = [];
MyVars::$a = 1;
foo();
doSomething();
Code language: PHP (php)
doSomething()内で、MyVars::$mapを書き換えてしまっています。
function doSomething()
{
MyVars::$map = [];
}
Code language: PHP (php)
public staticプロパティは、グローバル変数と同じです。通常は、使わないほうが無難です。
関数のstatic変数

関数はglobal宣言なしで使えるよ?
もともとは、static変数にリテラル配列を定義していた例です。
<?php
function myVars($num)
{
static $map = [
1 => 'foo',
2 => 'bar',
];
return $map[$num];
}
Code language: PHP (php)
<?php
echo myVars(1);
echo myVars(2);
Code language: PHP (php)
ユーザーが編集したくなったのか、コードに書くには配列が大きくなってしまったのか、配列データをDBから読み込むようにしました。
しかし、static変数に設定する方法がわからなかったのか、リファクタが面倒だったのか、global宣言を使ってしまったようです。
<?php
function myVars($num)
{
global $g_map;
return $g_map[$num];
}
Code language: PHP (php)
<?php
global $g_map;
$g_map = load_from_db();
echo myVars(1);
echo myVars(2);
Code language: PHP (php)
まず、関数のstatic変数に外部から設定する方法です。
<?php
function myVars($num)
{
static $map = [];
if (is_array($num)) {
$map = $num;
} else {
return $map[$num];
}
}
Code language: PHP (php)
呼ぶ側のコードです。
<?php
$g_map = load_from_db();
myVars($g_map);
echo myVars(1);
echo myVars(2);
Code language: PHP (php)
ところが、このケースの関数のstatic変数は、グローバル変数と同じです。
次のコードは問題ないように見えますが、
<?php
$g_map = load_from_db();
myVars($g_map);
doSomething();
echo myVars(1);
echo myVars(2);
Code language: PHP (php)
doSomething関数内で、myVars関数を呼びぶことができ、値を書き換えることができます。
function doSomething()
{
myVars([]);
}
Code language: PHP (php)
このケースの場合は、ふつうのクラスのインスタンスプロパティにしたほうがいいです。
<?php
class MyVars
{
private $map;
public function __construct($map)
{
$this->map = $map;
}
public function myVars($num)
{
return $this->map[$num];
}
}
Code language: PHP (php)
呼ぶ側のコードです。
<?php
$g_map = load_from_db();
$myVars = new MyVars($g_map);
foo();
echo $myVars->myVars(1);
echo $myVars->myVars(2);
Code language: PHP (php)
使うならglobal宣言より$GLOBALS
やむをえずグローバル変数を使う場合は、global宣言よりも、$GLOBALSのほうがベターです。
$GLOBALSをvar_dumpしたり、デバッガで見ることで、ある時点のグローバル変数の利用状況を調べるのが簡単です。
global宣言は、ふつうのコード内の変数をグローバル化してしまいます。グローバル変数を表す変数名プリフィックス $g_をつけたとしても、その変数が「グローバル変数です、要注意!」と言っているようには見えません。
それに比べて、$GLOBALSがコード内にあると、大文字で目立ちます。周囲の変数と見た目が違うので「グローバル変数です、要注意!」と言っています。あやしいことをしているので、目立ったほうがいいです。
もうひとつ、global宣言のよくないところは、グローバル領域(関数やクラスの外側)の変数は、関数側でglobal宣言するだけで見えてしまうことです。
<?php // page1.php
// 関数やクラスの外側
$x = 1; // 他の関数からglobal宣言で見えてしまうことを防げない
bar();
// bar.php
function bar()
{
global $x;// グローバル領域の$xが見える
$x += 2;
}
Code language: PHP (php)
逆に、foo関数とbar関数だけで共有したいグローバル変数なのに、グローバル領域のコードが知らずに上書きしてしまうかもしれません。
<?php // page1.php
// 関数やクラスの外側
foo();
$x = 1; // foo関数とbar関数のグローバル共有に気づかずに上書き
bar();
// foo.php
function foo()
{
global $x; // bar関数と共有
$x += 2;
}
// bar.php
function bar()
{
global $x; // foo関数と共有
$x += 3;
}
Code language: PHP (php)
$GLOBALSは、グローバル領域、関数側のどちらも$GLOBALS['x']のように書く必要があります。グローバル変数と知らずに上書きすることはありません。
<?php // page1.php
// 関数やクラスの外側
$GLOBALS['x'] = 1; // グローバル変数であることが一目瞭然
$x = 2;
bar();
// bar.php
function bar()
{
$GLOBALS['x'] = 2; //
$x = 3;
}
Code language: PHP (php)

いろいろ説明しているけど、global宣言も$GLOBALSも50歩100歩ね、使わないにことしたことはないわ
グローバル領域に変数を書かない
グローバル領域に変数を書かないようにします。main関数を書き、main関数を呼ぶようにします。main関数側が知らないうちに、他の関数からglobalで読まれたり、値を変更されたりすることを防げます。
<?php // page1.php
function main()
{
$x = 1; // 他の関数でglobal宣言しても見えない
foo();
$GLOBALS['x'] += 3;
}
main();
// bar.php
function bar()
{
global $x; // main関数の$xは見えない
$x = 2;
$GLOBALS['x'] = 3;
}
Code language: PHP (php)
即時関数にしてもいいと思います。
<?php // page1.php
(function ()
{
$x = 1; // 他の関数でglobal宣言しても見えない
foo();
$GLOBALS['x'] += 3;
})();
// bar.php
function bar()
{
global $x; // main関数の$xは見えない
$x = 2;
$GLOBALS['x'] = 3;
}
Code language: PHP (php)

JavaScriptも即時関数をよく使うよね