変数のスコープ

変数のスコープは、その変数が定義されたコンテキストです。 PHP には関数スコープとグローバルスコープがあります。 関数の外部で定義されたあらゆる変数は、グローバルスコープになります。 ファイルが include された場合、 そのファイルに含まれるコードは、 include が行われた行の変数のスコープを引き継ぎます。

例1 グローバル変数のスコープの例

<?php
$a = 1;

include 'b.inc'; // 変数 $a は b.inc で利用できます。
?>

名前付きの関数や 無名関数 の内部で作られた変数は、関数の内部のスコープでのみ使えます。 しかし、アロー関数 は親のスコープの変数を内部で利用できるようにするために、 変数をバインドします。 ファイルの include が呼び出し側のファイルで発生した場合、 呼び出されたファイルに含まれる変数は、 関数を呼び出す内部で定義されたかのように利用できます。

例2 ローカル変数のスコープ

<?php
$a = 1; // グローバルスコープ

function test()
{ 
    echo $a; // $a は、ローカルスコープの $a を参照しているので未定義です
} 
?>

このスクリプトは、未定義の変数があるという E_WARNING (PHP 8.0.0 より前のバージョンでは E_NOTICE) を生成します。 これは、echo 命令がローカル版の $a 変数を参照しているにもかかわらず、こ のスコープでは値が代入されていないからです。この動作は、特にローカ ルな定義で上書きしない限りグローバル変数が自動的に関数で使用可能で ある C 言語と少々異なっていると気がつかれるかもしれません。C言語の ような場合、グローバル変数を不注意で変更してしまうという問題を生じ る可能性があります。PHP では、グローバル変数は、関数の内部で使用す る場合、関数の内部でグローバルとして宣言する必要があります。

global キーワード

global キーワードは グローバルスコープの変数をローカルスコープにバインドするために使います。 このキーワードは変数のリストや、単一の変数を指定できます。 グローバル変数と同じ名前の変数を参照するローカル変数が作られます。 グローバル変数が存在しない場合、グローバルスコープの変数が作られ、 null が代入されます。

例3 global の使用

<?php
$a = 1;
$b = 2;

function Sum() 
{
    global $a, $b;

    $b = $a + $b;
} 

Sum();
echo $b;
?>

上の例の出力は以下となります。

3

関数の内部で $a$b をグローバル宣言を行うことにより、両変数への参照は、グローバル変数 の方を参照することになります。ある関数により操作できるグローバル変 数の数は無制限です。

グローバルスコープから変数をアクセスする2番目の方法は、PHPが定義す る配列$GLOBALSを使用することです。先の例は、次 のように書き換えることができます。

例4 globalのかわりに$GLOBALSを使用する

<?php
$a = 1;
$b = 2;

function Sum() 
{
    $GLOBALS['b'] = $GLOBALS['a'] + $GLOBALS['b'];
} 

Sum();
echo $b;
?>

配列$GLOBALSは連想配列であり、グローバル変数の 名前がキー、その変数の内容が配列要素の値となっています。 $GLOBALSスーパーグローバル であるため、$GLOBALSは全てのスコープに存在します。 以下にスーパーグローバルの効果を示す例を示します。

例5 スーパーグローバルとスコープの例

<?php
function test_superglobal()
{
    echo $_POST['name'];
}
?>

注意: global キーワードを関数の外部で使ってもエラーにはなりません。 そのファイルが関数の内部からインクルードされたときに使うことができます。

static 変数の使用

変数のスコープに関する別の重要な機能は、static (静的) 変数です。static 変数はローカル関数スコープのみに 存在しますが、プログラム実行がこのスコープの外で行われるようになっ てもその値を失いません。次の例を見てください。

例6 static 変数が必要な場面の例

<?php
function test()
{
    $a = 0;
    echo $a;
    $a++;
}
?>

この関数は、コールされる度に$a0にセットし、0 を出力するのでほとん ど役にたちません。変数を1増やす $a++ は、関数から外に出ると変数 $aが消えてしまうために目的を達成しません。現在 のカウントの追跡ができるようにカウント関数を使用できるようにするた めには、変数$aを static として宣言します。

例7 static 変数の使用例

<?php
function test()
{
    static $a = 0;
    echo $a;
    $a++;
}
?>

こうすると、$a は関数が最初にコールされたときにのみ初期化され、 test() 関数がコールされるたびに $a の値を出力してその値を増加させます。

static 変数は、再帰関数を実現する1つの手段としても使用されます。 次の簡単な関数は、中止す るタイミングを知るためにstatic変数$countを用いて、 10 回まで再帰を行います。

例8 再帰関数でのstatic変数の使用

<?php
function test()
{
    static $count = 0;

    $count++;
    echo $count;
    if ($count < 10) {
        test();
    }
    $count--;
}
?>

PHP 8.3.0 より前のバージョンでは、 static 変数には、定数式でのみ初期化できていました。 PHP 8.3.0 以降では、動的な式(例: 関数呼び出し) での初期化も許可されています。

例9 static 変数の宣言

<?php
function foo(){
    static $int = 0;          // 正しい
    static $int = 1+2;        // 正しい
    static $int = sqrt(121);  // PHP 8.3.0 以降は正しい

    $int++;
    echo $int;
}
?>

(オーバーライドされていない場合) 内部でstatic 変数を使ったメソッドも継承されます。 PHP 8.1.0 以降は、継承されたメソッドは、 親クラスのメソッドとstatic 変数を共有するようになりました。 つまり、メソッド内でのstatic 変数も、 static プロパティと同じ振る舞いになったということです。

例10 継承されたメソッドのstatic 変数を使う

<?php
class Foo {
    public static function counter() {
        static $counter = 0;
        $counter++;
        return $counter;
    }
}
class Bar extends Foo {}
var_dump(Foo::counter()); // int(1)
var_dump(Foo::counter()); // int(2)
var_dump(Bar::counter()); // int(3), PHP 8.1.0 より前のバージョンでは int(1)
var_dump(Bar::counter()); // int(4), PHP 8.1.0 より前のバージョンでは int(2)
?>

グローバル変数とstatic 変数のリファレンス

PHP は、 リファレンス 変数の修正子として static および global を実装しています。 例えば、関数スコープ内にglobal 命令により実際にインポートされた真のグローバル変数は、 実際にグローバル変数へのリファレンスを作成します。 これにより、以下の例が示すように予測できない動作を引き起こす可能性 があります。

<?php
function test_global_ref() {
    global $obj;
    $new = new stdClass;
    $obj = &$new;
}

function test_global_noref() {
    global $obj;
    $new = new stdClass;
    $obj = $new;
}

test_global_ref();
var_dump($obj);
test_global_noref();
var_dump($obj);
?>

上の例の出力は以下となります。

NULL
object(stdClass)#1 (0) {
}

類似の動作がstatic命令にも適用されます。 リファレンスはstaitc 変数に保存することができません。

<?php
function &get_instance_ref() {
    static $obj;

    echo 'Static object: ';
    var_dump($obj);
    if (!isset($obj)) {
        $new = new stdClass;
        // Assign a reference to the static variable
        $obj = &$new;
    }
    if (!isset($obj->property)) {
        $obj->property = 1;
    } else {
        $obj->property++;
    }
    return $obj;
}

function &get_instance_noref() {
    static $obj;

    echo 'Static object: ';
    var_dump($obj);
    if (!isset($obj)) {
        $new = new stdClass;
        // Assign the object to the static variable
        $obj = $new;
    }
    if (!isset($obj->property)) {
        $obj->property = 1;
    } else {
        $obj->property++;
    }
    return $obj;
}

$obj1 = get_instance_ref();
$still_obj1 = get_instance_ref();
echo "\n";
$obj2 = get_instance_noref();
$still_obj2 = get_instance_noref();
?>

この例を実行すると以下の出力となります。

Static object: NULL
Static object: NULL

Static object: NULL
Static object: object(stdClass)#3 (1) {
  ["property"]=>
  int(1)
}
+

この例は、static変数にリファレンスを代入した時に &get_instance_ref()関数を2回目に コールした際に保持されていないことを示しています。