1.

PHP の static 変数|関数内 static とクラス変数・遅延静的束縛

▶
編集
この記事の要点
  • 関数内の static $n = 0; は、関数を抜けても値が残る変数
  • クラスの public static $count; は、インスタンスを作らず Class::$count で使う
  • クラス変数はインスタンスごとではなくクラスに 1 つだけ。全員で共有する
  • アクセスは self::$x か static::$x。$this->x では読めない
  • PHP の static はリクエストが終わると消える。永続化の手段ではない

関数内の static 変数

function counter(): int
{
    static $n = 0;      // 初期化は最初の 1 回だけ
    $n++;
    return $n;
}

echo counter();   // 1
echo counter();   // 2
echo counter();   // 3

function normal(): int
{
    $n = 0;          // 毎回 0 に戻る
    $n++;
    return $n;
}
echo normal();    // 1
echo normal();    // 1

static を付けた変数は関数を抜けても値を保持します。ただしスコープはその関数の中だけで、外からは触れません。

// 重い処理の結果を覚えておく(メモ化)
function config(): array
{
    static $cache = null;
    if ($cache === null) {
        $cache = require __DIR__ . '/config.php';   // 初回だけ読み込む
    }
    return $cache;
}

// 初期化に書けるのは定数式だけ
function bad()
{
    static $t = time();     // Fatal error: Constant expression contains invalid operations
}

初期値に関数呼び出しは書けません。実行時に決まる値は null で初期化して、中で入れるのが定石です。

クラスの static プロパティ

class Counter
{
    public static int $total = 0;     // クラスに 1 つだけ
    public int $count = 0;            // インスタンスごと

    public function __construct()
    {
        self::$total++;               // static は self:: で
        $this->count++;               // インスタンスは $this-> で
    }
}

$a = new Counter();
$b = new Counter();

echo Counter::$total;   // 2   共有されている
echo $a->count;         // 1   個別
echo $b->count;         // 1

// インスタンス経由でも読めるが混乱のもと
echo $a::$total;        // 2
// echo $a->total;     // Warning: Undefined property($this-> では読めない)
宣言アクセス実体の数
インスタンスプロパティpublic int $count;$this->countインスタンスごと
static プロパティpublic static int $total;self::$total / Counter::$totalクラスに 1 つ
クラス定数public const MAX = 10;self::MAXクラスに 1 つ(変更不可)

値が変わらないなら static ではなく const を使ってください。誰かに書き換えられる心配がなくなります。

self:: と static:: の違い(遅延静的束縛)

class Base
{
    public static string $name = 'Base';

    public static function selfName(): string   { return self::$name;   }
    public static function staticName(): string { return static::$name; }
}

class Child extends Base
{
    public static string $name = 'Child';       // 再宣言して上書き
}

echo Child::selfName();     // Base    ← self は「書かれたクラス」に固定
echo Child::staticName();   // Child   ← static は「呼ばれたクラス」を見る

static:: の解決を遅延静的束縛(Late Static Binding)といいます。継承して差し替えられる前提のコードでは static::、絶対に変えたくないものは self:: を使います。

再宣言しないと親と共有される

class A { public static int $n = 0; }
class B extends A {}          // 再宣言していない

B::$n = 5;
echo A::$n;   // 5   ← 同じ実体を共有している

class C extends A { public static int $n = 0; }   // 再宣言した
C::$n = 9;
echo A::$n;   // 5   影響しない

「子クラスごとにカウンタを持たせたつもりが全部同じ値になる」のはこれが原因です。子クラスで別々に持ちたいなら、子クラス側で再宣言する必要があります。

使ってよい場面・避けるべき場面

用途判断
1 リクエスト内のキャッシュ(設定・接続の使い回し)妥当
生成数のカウント・ID の採番妥当
定数的な値の保持const にする
アプリの状態を持ち回る避ける。どこから変わるか追えなくなる
ログインユーザーなどリクエスト固有の情報避ける。常駐環境では他の利用者に漏れる

リクエストをまたいで残らない

class Visit { public static int $count = 0; }

Visit::$count++;
echo Visit::$count;   // 何回アクセスしても 1

PHP はリクエストごとにプロセスの状態を捨てます。訪問回数のように残したい値は、セッション・データベース・ファイル・Redis などに保存してください。

逆に、Swoole や RoadRunner のようにプロセスを常駐させる構成では static の値が次のリクエストに残ります。利用者ごとの情報を static に入れていると、別の利用者に見えてしまいます。

テストのしづらさ

class Registry { public static array $items = []; }

// テスト A で追加した値が、テスト B にも残る
public function testA(): void { Registry::$items[] = 'x'; }
public function testB(): void { $this->assertSame([], Registry::$items); }  // 失敗する

// 対処: 毎テストで戻す
protected function setUp(): void { Registry::$items = []; }

static はテスト間で状態が引き継がれるため、実行順によって結果が変わるテストの原因になります。共有したい値は、コンストラクタで渡す(依存注入)ほうが扱いやすくなります。

関連

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. static変数
  2. static関数

最近更新/作成されたページ