LoginSignup
4
0

More than 3 years have passed since last update.

static 変数の初期値に注意

Posted at

static 変数の初期値に注意

php でコードを書いていて「クラス/インスタンス間の static 変数ってどういう挙動だっけ?」と思って調べていたところ PHPの静的変数 (static変数) の挙動まとめを見つけて、「あぁ、なるほど」と納得したんですが、微妙におかしな挙動を示したので備忘として残しておきます。

  • OS: CentOS7
  • php: 7.2.14

結論を言うと、上述の記事の通り確かに static 変数はクラス間で独立した挙動を示しますが、その初期値が状況によって異なります

再現コード

上述の記事から引用して乗っかります。

では、Test1 を継承した子クラスから実行した場合はどうなるでしょうか。結果は以下のコードから。(PHP 5.1.0 - 5.5.13 で確認)

<?php
class Test1
{
    public static function testStaticMethod()
    {
        static $num = 0;
        $num++;
        echo __METHOD__ . $num . PHP_EOL;
    }
}

class Test2 extends Test1
{
}

class Test3 extends Test1
{
}

Test1::testStaticMethod();
Test2::testStaticMethod();
Test3::testStaticMethod();
Test1::testStaticMethod();
Test2::testStaticMethod();
Test3::testStaticMethod();

このコードは以下を出力します。

Test1::testStaticMethod1
Test1::testStaticMethod1
Test1::testStaticMethod1
Test1::testStaticMethod2
Test1::testStaticMethod2
Test1::testStaticMethod2

この出力内容から分かる通り、Test1, Test2, Test3 のそれぞれのクラスで $num が独立して管理されていることが分かります。

この動作に全くおかしなところはありません。

ただし、クラスがロードされていないと static 変数の初期値が「既に設定されている親クラスの static 変数」に設定されてしまうようです。

<?php
class Test1
{
    public static function testStaticMethod()
    {
        static $num = 0;
        $num++;
        echo __METHOD__ . $num . PHP_EOL;
    }
}

Test1::testStaticMethod();

if (true) {
    class Test2 extends Test1
    {
    }
}
Test2::testStaticMethod();
Test2::testStaticMethod();

if (true) {
    class Test3 extends Test1
    {
    }
}
Test3::testStaticMethod();
Test3::testStaticMethod();

php は一旦ソース全体をなめてシンボルの解決を行うので、それを防ぐためにクラス定義を if (true) で包んでいます。
要するにクラスのオートロードと同じ挙動になるような模倣です(≒必要になったときに読み込む)。

このコードは以下を出力します。

Test1::testStaticMethod1
Test1::testStaticMethod2
Test1::testStaticMethod3
Test1::testStaticMethod2
Test1::testStaticMethod3

Test2::testStaticMethod() は初回呼び出しにも関わらず、値が 2 になっています。
Test3::testStaticMethod() も同様です。
その後の呼び出しは独立して管理されているので、それぞれで 3 になります。

これは Test1 の既に代入されている static $num に影響を受けているためだと思われます。
実際、 Test1::testStaticMethod() を呼ばなければ Test2Test3 もそれぞれ 1 から始まります。

一応実行結果を置いておきます。

所感

公式ドキュメントに「クラス間の static 変数の挙動」や「継承した場合の static 変数の初期値」などの記載を見つけることは出来なかったんですが、おそらくこれはクラスローディングの際の php 不具合のような気がします。
(ロード時に static 部分が共用されてしまっている?)。

不具合ならば修正されるでしょうが、現在のところ「クラスがロードされているか?」で挙動が変わってしまうので、下手すると深刻・非常に分かりにくいバグの原因になります。
「クラスがロードされているか?」は状況によってかなり異なるためです。
とりあえず頭の片隅にでも入れておくと良いかもしれません。

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0