明滅するプログラマの思索

WEBエンジニアとして勤務している一介の男が、日々気づいたことをまとめるブログです

クラス内 static プロパティについてまとめ

PHP のクラスが持つプロパティ(メンバ変数)には、static 修飾子を付けることができます。

<?php
class A
{
    public static $HOGE = 'hoge';
}

static 修飾子をつけたプロパティとそうでないプロパティは、何が違うのでしょうか。

実装条件

ソフトウェア バージョン
PHP 5.6.30

プロパティはインスタンスごとに初期化される

通常、クラスが持つプロパティはインスタンスごとにメモリが割り当てられます。
同じクラスから複数のインスタンスを生成した場合、それぞれは独立した値を持つことになります。

<?php
class A
{
    public $int = 0;
}

$a = new A();

var_dump($a->int); // int(0) が返却される

$b = new A();
$b->int = 1;

var_dump($b->int); // int(1) が返却される

var_dump($a->int); // int(0) が返却される

上記のコードでは同じクラス A のインスタンス $a、$b があります。
そしてそれぞれが持つ int プロパティは独立しており、$b のプロパティを更新しても $a のプロパティには影響がないことがわかります。

static 変数へのアクセス

static 修飾子を付けた場合はどうなるでしょうか。

<?php
class A
{
    public static $int = 0;
}

$a = new A();

var_dump($a->int); // PHP Notice:  Undefined property: A::$int が出力される

static 修飾子を付けたプロパティは、通常のプロパティと異なりアロー演算子( -> )で指示することができません。
その場合 NULL が返却され、上記の Notice が出力されます。
正しくプロパティにアクセスするには、スコープ演算子( :: )を使用します。

<?php
class A
{
    public static $int = 0;
}

$a = new A();

var_dump($a::$int); // int(0) が返却される

static 修飾子を付けるとプロパティはクラスごとに初期化されるようになる

では、同じクラスの別のインスタンスとのかかわりも見てみます。

<?php
class A
{
    public static $int = 0;
}

$a = new A();

var_dump($a::$int); // int(0) が返却される

$b = new A();
$b::$int = 1;

var_dump($b::$int); // int(1) が返却される

var_dump($a::$int); // int(1) が返却される

クラス A の static プロパティ $int の初期値は 0 でした。
$a インスタンスに対する1回目の var_dump() でも、0 が返却されています。
しかし、同じクラスの別のインスタンス $b において $int の値を 1 に更新すると、$a インスタンスの $int の値も同時に書き換わったことがわかります。

子クラスの static プロパティを親クラスで呼び出す

クラスメソッド内で同じクラスの static プロパティにアクセスする場合、通常、self 修飾子とスコープ演算子( :: )を利用します。

<?php
class ParentClass
{
    protected static $int = 0;
    public function echoInt()
    {
        echo self::$int;
    }
}
$parent = new ParentClass();
$parent->echoInt(); // 0 が出力される

では、上記コードにおける Parent クラスを継承した子クラスが、static 変数 $int を書き換えた場合どうなるでしょうか。

<?php
class ParentClass
{
    protected static $int = 0;
    public function echoInt()
    {
        echo self::$int;
    }
}

class Child extends ParentClass
{
    protected static $int = 9999;
}

$child = new Child();
$child->echoInt(); // 0 が出力される

子クラスで設定した $int = 9999 は無視され、親クラスの $int = 0 が出力されました。
このことから self 修飾子はあくまでそのクラスのstatic プロパティにアクセスしていることがわかります。
では、子クラスで定義した static プロパティにアクセスするためには、どのようにすればよいでしょうか。
ここでも、static 修飾子を使用します。

<?php
class ParentClass
{
    protected static $int = 0;
    public function echoInt()
    {
        echo static::$int;
    }
}

class Child extends ParentClass
{
    protected static $int = 9999;
}

$child = new Child();
$child->echoInt(); // 9999 が出力される

ここで利用される static 修飾子は遅延静的束縛によって保存されたクラスを表しています。
遅延静的束縛とは、「非転送コール」によって呼ばれたメソッド・クラス名を保持し、そのコールで使いまわすこと、なのですが、文章にするとわかりにくいです。

ここに出てくる「非転送コール」とは、クラスやインスタンスを指定して行われるメソッドコールのこと。「転送コール」とはメソッド内で self::、parent::、static:: を利用して行われるコールのことです。

上記のコードでは、echoInt() メソッドをコールしています。
このとき、echoInt() は $child インスタンスを指定していますので「非転送コール」であり、そのタイミングで static が表すクラスは Child となります。
つまり、ParentClass の echoInt() メソッドがコールされた際の static が指示しているクラスは Child となるわけです。

PHP: 遅延静的束縛 (Late Static Bindings) - Manual