変数スコープ(global / static)
変数が参照・代入できる有効範囲を「スコープ」と呼びます。『PHP』では関数の内側と外側でスコープが完全に分離されており、他の言語と比べて厳格なルールが適用されます。global宣言やstaticローカル変数を使うことで、スコープをまたいだ状態管理が可能になりますが、使い方を誤るとバグの温床になるため注意が必要です。
スコープの種類
| スコープの種類 | 概要 |
|---|---|
| グローバルスコープ | 関数・クラスの外側で定義された変数が属するスコープです。スクリプトのトップレベルで宣言した変数はここに属します。 |
| ローカルスコープ | 関数の内側で有効なスコープです。関数内で定義した変数は関数の外からアクセスできません。また、関数の外で定義したグローバル変数も、関数内では自動的には参照できません。 |
| 静的ローカル変数(static) | 関数内で『static』宣言した変数です。関数を抜けても値が破棄されず、次回の呼び出し時に前回の値を引き継ぎます。 |
| グローバル変数(global宣言) | 関数内で『global $変数名』と宣言することで、グローバルスコープの変数にアクセスできるようになります。 |
| クロージャのuseキャプチャ | 無名関数(クロージャ)が外部変数を取り込む仕組みです。『use ($変数名)』で値渡し、『use (&$変数名)』で参照渡しを指定できます。 |
スコープのルール
| 構文・キーワード | 動作 |
|---|---|
| 関数内からグローバル変数への直接アクセス | できません。関数内でグローバル変数名と同じ変数名を使っても、別の変数として扱われます。 |
| global $変数名 | 関数内でグローバルスコープの変数へのエイリアスを作成します。読み書き両方が可能になります。 |
| $GLOBALS['変数名'] | グローバル変数を参照するスーパーグローバル配列です。『global』宣言なしにどこからでもアクセスできます。 |
| static $変数名 | 関数内で宣言します。関数が終了しても値が保持され、同じ関数を再度呼び出したときに前回の値が使えます。 |
| use ($変数名) | クロージャが定義された時点の変数の値をコピーしてキャプチャします。 |
| use (&$変数名) | クロージャが外部変数を参照渡しでキャプチャします。クロージャ内での変更が外部変数にも反映されます。 |
サンプルコード
関数スコープの基本(外の変数には触れない)
scope_basic.php
<?php
// PHPの関数スコープの基本を確認するサンプルです
// 関数の外で定義したグローバル変数は、関数内から直接参照できません
$pilotName = "碇シンジ"; // グローバル変数です
function launchEva(): void {
// $pilotName はグローバル変数ですが、関数内では参照できません
// ここでの $pilotName は未定義の別変数として扱われます
echo "パイロット: " . $pilotName . "\n"; // 何も出力されません(undefined variable)
echo "出撃します。\n";
}
launchEva();
// 関数の外側では正常に参照できます
echo "グローバル: " . $pilotName . "\n";
実行すると次のように出力されます。
php scope_basic.php パイロット: 出撃します。 グローバル: 碇シンジ
global宣言でグローバル変数にアクセスする
scope_global.php
<?php
// global宣言を使ってグローバル変数にアクセスするサンプルです
// global宣言をすると、関数内でグローバル変数を読み書きできるようになります
$syncRate = 0.0; // パイロットの同期率(グローバル変数)
$missionLog = ""; // ミッションログ(グローバル変数)
function updateSyncRate(float $rate): void {
// global宣言でグローバル変数への参照を作成します
global $syncRate, $missionLog;
$syncRate = $rate;
$missionLog .= "同期率を " . $rate . "% に更新しました。\n";
}
function reportStatus(): void {
// こちらも global宣言が必要です
global $syncRate, $missionLog;
echo "現在の同期率: " . $syncRate . "%\n";
echo "ログ:\n" . $missionLog;
}
updateSyncRate(41.3);
updateSyncRate(141.7);
reportStatus();
実行すると次のように出力されます。
php scope_global.php 現在の同期率: 141.7% ログ: 同期率を 41.3% に更新しました。 同期率を 141.7% に更新しました。
$GLOBALSスーパーグローバル配列でアクセスする
scope_globals.php
<?php
// $GLOBALS 配列を使ったグローバル変数アクセスのサンプルです
// global宣言なしにどこからでもグローバル変数を参照・変更できます
$nerv_hq = "第3新東京市";
$alertLevel = 1;
function raiseAlert(int $level): void {
// $GLOBALS を使えば global宣言なしにグローバル変数へアクセスできます
$GLOBALS['alertLevel'] = $level;
echo $GLOBALS['nerv_hq'] . ": 警戒レベルを " . $level . " に引き上げました。\n";
}
raiseAlert(3);
raiseAlert(5);
echo "最終警戒レベル: " . $alertLevel . "\n";
実行すると次のように出力されます。
php scope_globals.php 第3新東京市: 警戒レベルを 3 に引き上げました。 第3新東京市: 警戒レベルを 5 に引き上げました。 最終警戒レベル: 5
staticローカル変数で状態を保持する
scope_static.php
<?php
// static変数を使って関数呼び出し間で状態を保持するサンプルです
// staticローカル変数は関数が終了しても値が保持されます
function countAngel(): int {
// static宣言した変数は、最初の呼び出し時だけ初期化されます
// 2回目以降は前回の値を引き継ぎます
static $count = 0;
$count++;
return $count;
}
// 関数を繰り返し呼び出しても $count の値は累積されます
echo "第" . countAngel() . "の使徒 接近中\n";
echo "第" . countAngel() . "の使徒 接近中\n";
echo "第" . countAngel() . "の使徒 接近中\n";
echo "第" . countAngel() . "の使徒 接近中\n";
echo "\n";
// static変数を使ったキャッシュの例です
// 同じ入力に対して2回目以降は再計算せずに保存済みの結果を返します
function getEvaUnit(int $unitNo): string {
static $cache = []; // 結果をキャッシュする配列(staticで保持)
if (isset($cache[$unitNo])) {
echo "(キャッシュから返します)\n";
return $cache[$unitNo];
}
// 実際には重い計算や外部アクセスが入ると想定してください
$result = "エヴァンゲリオン第" . $unitNo . "号機";
$cache[$unitNo] = $result;
return $result;
}
echo getEvaUnit(1) . "\n";
echo getEvaUnit(2) . "\n";
echo getEvaUnit(1) . "\n"; // キャッシュが使われます
実行すると次のように出力されます。
php scope_static.php 第1の使徒 接近中 第2の使徒 接近中 第3の使徒 接近中 第4の使徒 接近中 エヴァンゲリオン第1号機 エヴァンゲリオン第2号機 (キャッシュから返します) エヴァンゲリオン第1号機
クロージャのuseキャプチャとスコープの関係
scope_closure_use.php
<?php
// クロージャ(無名関数)が外部変数をキャプチャするサンプルです
// use による値渡しと参照渡しの違いに注目してください
$pilotName = "綾波レイ";
$unitNo = 0;
// --- 値渡し(useのデフォルト)---
// クロージャ定義時の値がコピーされます
// 後から $pilotName を変更してもクロージャ内には反映されません
$greetByValue = function() use ($pilotName, $unitNo): string {
return $pilotName . "、第" . $unitNo . "号機で出撃します。";
};
// $pilotName を変更してもクロージャに影響しません
$pilotName = "惣流・アスカ・ラングレー";
$unitNo = 2;
echo $greetByValue() . "\n"; // 変更前の値(綾波レイ、0号機)が使われます
echo "\n";
// --- 参照渡し(use (&$変数))---
// クロージャが外部変数を参照で保持します
// 外部での変更がクロージャ内に反映されます
$syncRate = 0.0;
$updateSync = function(float $rate) use (&$syncRate): void {
// &$syncRate は外部の $syncRate を直接参照しています
$syncRate = $rate;
};
$updateSync(83.2);
echo "参照渡し後の同期率: " . $syncRate . "%\n"; // 83.2 が反映されます
$updateSync(141.7);
echo "参照渡し後の同期率: " . $syncRate . "%\n"; // 141.7 が反映されます
実行すると次のように出力されます。
php scope_closure_use.php 綾波レイ、第0号機で出撃します。 参照渡し後の同期率: 83.2% 参照渡し後の同期率: 141.7%
よくあるミス
よくあるミス1: 関数内からグローバル変数が見えない罠(globalキーワード忘れ)
PHPの関数は外側のグローバル変数を自動的に参照しません。他の言語(JavaScriptなど)ではスコープチェーンで外の変数を参照できる場合がありますが、PHPでは関数内から外の変数に触れるには『global』宣言が必要です。宣言を忘れると変数は未定義として扱われます。
ng_global_missing.php
<?php
$pilotName = "碇シンジ";
function launch(): void {
// global 宣言なしでは $pilotName は未定義の別変数
echo "パイロット: " . $pilotName . "\n"; // 何も出力されない
}
launch();
実行すると次のように出力されます。
php ng_global_missing.php Warning: Undefined variable $pilotName in ... パイロット:
関数内でグローバル変数を使う場合は『global』宣言を先頭に記述します。
ok_global_missing.php
<?php
$pilotName = "碇シンジ";
function launch(): void {
global $pilotName; // グローバル変数への参照を作成する
echo "パイロット: " . $pilotName . "\n";
}
launch();
実行すると次のように出力されます。
php ok_global_missing.php パイロット: 碇シンジ
よくあるミス2: static変数の値が次の呼び出しに引き継がれる挙動
『static』変数は関数を抜けても値が保持されます。これは意図的な動作ですが、テスト時や複数のリクエストにまたがる処理では、前の呼び出しの値が残っていることに気づかず予期しない結果になることがあります。特にユニットテストで同じ関数を複数回呼ぶと、static変数がリセットされないため挙動が変わります。
ng_static_carry.php
<?php
function countAngel(): int {
static $count = 0;
$count++;
return $count;
}
// 1回目の呼び出し
echo countAngel() . "\n"; // 1
// 2回目以降は前の値を引き継ぐ
echo countAngel() . "\n"; // 2(テストで「毎回1を返す」と期待しているとバグになる)
echo countAngel() . "\n"; // 3
実行すると次のように出力されます。
php ng_static_carry.php 1 2 3
カウンターや状態保持を意図的に使う場合は問題ありませんが、「毎回リセットしたい」場合はstaticを使わず通常の変数にします。
ok_static_carry.php
<?php
// リセットが必要な場合は外部から値を渡す設計にする
function countAngelFrom(int $start): int {
return $start + 1;
}
// 呼び出し側でカウンターを管理する
$count = 0;
$count = countAngelFrom($count); // 1
echo $count . "\n";
$count = countAngelFrom($count); // 2
echo $count . "\n";
$count = 0; // いつでもリセットできる
$count = countAngelFrom($count); // 1
echo $count . "\n";
実行すると次のように出力されます。
php ok_static_carry.php 1 2 1
よくあるミス3: クロージャのuse値渡しで外の変更が反映されない罠
クロージャの『use』はデフォルトで値渡しです。クロージャを定義した時点の変数の値がコピーされるため、その後に外部で変数を変更してもクロージャ内には反映されません。「外の変数が変わったのにクロージャが古い値を使っている」というバグはこの挙動が原因です。
ng_closure_use_value.php
<?php
$pilotName = "綾波レイ";
// use の値渡し: クロージャ定義時の "綾波レイ" がコピーされる
$greet = function() use ($pilotName): void {
echo "パイロット: " . $pilotName . "\n";
};
// クロージャ定義後に外部の変数を変更しても反映されない
$pilotName = "惣流・アスカ・ラングレー";
$greet(); // "綾波レイ" が出力される(外の変更が反映されない)
実行すると次のように出力されます。
php ng_closure_use_value.php パイロット: 綾波レイ
外部変数の変更をクロージャ内に反映させたい場合は参照渡し(『use (&$変数名)』)を使います。
ok_closure_use_ref.php
<?php
$pilotName = "綾波レイ";
// use (&$pilotName) で参照渡し: 外部変数への参照を保持する
$greet = function() use (&$pilotName): void {
echo "パイロット: " . $pilotName . "\n";
};
// クロージャ定義後に外部の変数を変更すると反映される
$pilotName = "惣流・アスカ・ラングレー";
$greet(); // "惣流・アスカ・ラングレー" が出力される
実行すると次のように出力されます。
php ok_closure_use_ref.php パイロット: 惣流・アスカ・ラングレー
概要
『PHP』の関数スコープはほかの多くの言語よりも厳格で、関数内から外側のグローバル変数を直接参照することはできません。グローバル変数を関数内で使うには『global』宣言か『$GLOBALS』配列を使う必要があります。どちらも関数と外部の状態が密結合になるため、テストが難しくなりバグの原因になりやすいです。引数で値を受け取り、戻り値で結果を返す設計が一般的です。
『static』ローカル変数は関数の呼び出し回数のカウントや、重い処理の結果キャッシュなど、「関数内だけで完結した状態保持」に向いています。グローバル変数と異なり外部から直接変更できないため、副作用の範囲を関数内に限定できます。
クロージャの『use』キャプチャはデフォルトが値渡しのため、クロージャを定義した時点の値がコピーされます。外部変数をクロージャ内から変更したい場合は『use (&$変数名)』と参照渡しを明示する必要があります。参照渡しは便利ですが、意図しない副作用を引き起こしやすいため使いすぎに注意が必要です。アロー関数(『fn』)は外部変数を自動的に値渡しでキャプチャしますが、参照渡しには対応していません。詳細は『アロー関数(fn)』を参照してください。関数定義の基本については『関数定義(function)』を参照してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。