PHP には「静的変数」 (static 変数) と呼ばれる言語仕様があります。概要は PHP マニュアルの以下のページ
PHP: 変数のスコープ - Manual
を参照してください。
マニュアルでは関数内で使った場合の例しか載っていなかったので、クラスメソッド・インスタンスメソッド・trait などに使用した場合にどうなるのか調べてみました。
結論は、簡単に言うと
- クラス毎に独立
- インスタンス間で共通
となっています。
検証過程
関数で使用した場合
これは PHP マニュアルに載っている例と同じです。関数を呼び出すたびに $num
が 1 ずつ増加します。(PHP 5.1.0 - 5.5.13 で確認)
<?php
function testFunc()
{
static $num = 0;
$num++;
echo __FUNCTION__ . $num . PHP_EOL;
}
testFunc();
testFunc();
testFunc();
このコードは以下を出力します。
testFunc1
testFunc2
testFunc3
クラスメソッドで使用した場合
クラスメソッドで使用した場合も、基本は関数の場合と同じです。呼び出すたびに $num
が 1 ずつ増加します。(PHP 5.1.0 - 5.5.13 で確認)
<?php
class Test1
{
public static function testStaticMethod()
{
static $num = 0;
$num++;
echo __METHOD__ . $num . PHP_EOL;
}
}
Test1::testStaticMethod();
Test1::testStaticMethod();
Test1::testStaticMethod();
このコードは以下を出力します。
Test1::testStaticMethod1
Test1::testStaticMethod2
Test1::testStaticMethod3
では、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
が独立して管理されていることが分かります。
インスタンスメソッドで使用した場合
インスタンスメソッドでは以下のようになります。(PHP 5.1.0 - 5.5.13 で確認)
<?php
class TestObject
{
public function testMethod()
{
static $num = 0;
$num++;
echo __METHOD__ . $num . PHP_EOL;
}
}
$obj1 = new TestObject();
$obj2 = new TestObject();
$obj1->testMethod();
$obj2->testMethod();
$obj1->testMethod();
$obj2->testMethod();
$obj1->testMethod();
$obj2->testMethod();
このコードは以下を出力します。
TestObject::testMethod1
TestObject::testMethod2
TestObject::testMethod3
TestObject::testMethod4
TestObject::testMethod5
TestObject::testMethod6
testMethod()
を呼び出したインスタンスに関係なく $num
が 1 ずつ上昇しているため、全インスタンスで $num
を共有していることがわかります。
次に、親クラスと子クラスそれぞれのインスタンスで実行させた場合は以下のようになります。(PHP 5.1.0 - 5.5.13 で確認)
<?php
class TestParent
{
public function testMethod()
{
static $num = 0;
$num++;
echo __METHOD__ . $num . PHP_EOL;
}
}
class TestChildA extends TestParent
{
}
class TestChildB extends TestParent
{
}
echo "TestParent" . PHP_EOL;
$p1 = new TestParent();
$p2 = new TestParent();
$p1->testMethod();
$p2->testMethod();
$p1->testMethod();
$p2->testMethod();
$p1->testMethod();
$p2->testMethod();
echo "TestChildA" . PHP_EOL;
$a1 = new TestChildA();
$a2 = new TestChildA();
$a1->testMethod();
$a2->testMethod();
$a1->testMethod();
$a2->testMethod();
$a1->testMethod();
$a2->testMethod();
echo "TestChildB" . PHP_EOL;
$b1 = new TestChildB();
$b2 = new TestChildB();
$b1->testMethod();
$b2->testMethod();
$b1->testMethod();
$b2->testMethod();
$b1->testMethod();
$b2->testMethod();
このコードは以下を出力します。
TestParent
TestParent::testMethod1
TestParent::testMethod2
TestParent::testMethod3
TestParent::testMethod4
TestParent::testMethod5
TestParent::testMethod6
TestChildA
TestParent::testMethod1
TestParent::testMethod2
TestParent::testMethod3
TestParent::testMethod4
TestParent::testMethod5
TestParent::testMethod6
TestChildB
TestParent::testMethod1
TestParent::testMethod2
TestParent::testMethod3
TestParent::testMethod4
TestParent::testMethod5
TestParent::testMethod6
この結果から $num
が「クラス毎に独立」かつ「インスタンス間で共通」であることが分かりました。
trait 内のメソッドで使用した場合
trait 内で static 宣言した変数を、trait を実装した複数のクラスのインスタンスから呼び出した場合を見てみましょう。(PHP 5.4.0 - 5.5.13 で確認)
<?php
trait Trait1
{
public function testMethod()
{
static $num = 0;
$num++;
echo __METHOD__ . $num . PHP_EOL;
}
}
class UserA
{
use Trait1;
}
class UserB
{
use Trait1;
}
echo "UserA" . PHP_EOL;
$a1 = new UserA();
$a2 = new UserA();
$a1->testMethod();
$a2->testMethod();
$a1->testMethod();
$a2->testMethod();
$a1->testMethod();
$a2->testMethod();
echo "UserB" . PHP_EOL;
$b1 = new UserB();
$b2 = new UserB();
$b1->testMethod();
$b2->testMethod();
$b1->testMethod();
$b2->testMethod();
$b1->testMethod();
$b2->testMethod();
このコードは以下を出力します。
UserA
Trait1::testMethod1
Trait1::testMethod2
Trait1::testMethod3
Trait1::testMethod4
Trait1::testMethod5
Trait1::testMethod6
UserB
Trait1::testMethod1
Trait1::testMethod2
Trait1::testMethod3
Trait1::testMethod4
Trait1::testMethod5
Trait1::testMethod6
このように trait で宣言した場合でも、先ほどと同じく「クラス毎に独立」かつ「インスタンス間で共通」であることが確認できました。
応用編: Singleton パターンで静的変数を使う
先ほど確認した静的変数の挙動を応用して Singleton パターンのクラスを作ることが出来ます。PHP で Singleton なクラスを作る場合に Java のイディオムと同じく
<?php
class MySingleton
{
private static $instance;
private function __construct() {}
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
}
$my1 = MySingleton::getInstance();
$my2 = MySingleton::getInstance();
var_dump($my1 === $my2); // bool(true)
こんな感じの書き方をしている方は結構いるのではないでしょうか。
ここから 4 行目の private static $instance;
のクラス変数を getInstance()
の中に閉じ込めてしまいましょう。(PHP 5.1.0 - 5.5.13 で確認)
<?php
class MySingleton2
{
private function __construct() {}
public static function getInstance()
{
static $instance;
if ($instance === null) {
$instance = new self();
}
return $instance;
}
}
$my1 = MySingleton2::getInstance();
$my2 = MySingleton2::getInstance();
var_dump($my1 === $my2); // bool(true)
余計なクラス変数が消えて、よりスッキリした記述になりましたね!
さらに応用編: trait を利用した Singleton
さきほどのサンプルコードで trait を利用した場合でも static 変数がクラス毎に独立していることが確認できました。
ということは、trait を使って Singleton のイディオムを再利用することが出来ます!
以下にサンプルコードを掲載します。(PHP 5.4.0 - 5.5.13 で確認)
<?php
trait Singleton
{
private function __construct()
{
}
public static function getInstance()
{
static $instance = null;
if ($instance === null) {
$instance = new self();
}
return $instance;
}
}
class TestA
{
use Singleton;
}
class TestB
{
use Singleton;
}
$a1 = TestA::getInstance();
$a2 = TestA::getInstance();
$b1 = TestB::getInstance();
$b2 = TestB::getInstance();
var_dump($a1 === $a2); // bool(true)
var_dump($b1 === $b2); // bool(true)
var_dump($a1 === $b1); // bool(false)
var_dump($a1 === $b2); // bool(false)
var_dump($a2 === $b1); // bool(false)
var_dump($a2 === $b2); // bool(false)
$a3 = new TestA(); // Fatal error
以上です。この記事が、PHP の静的変数の有効活用に役立てられれば幸いです!