1. ホーム
  2. PHP

【PHP】static プロパティ・メソッドの使い方|self:: と static:: の違いも解説

Share

PHP のクラスには、インスタンスを作らなくても使えるプロパティとメソッドがあります。それが static を付けて宣言する静的メンバーで、ClassName::$propertyClassName::method() という形で呼び出します。この記事では、静的プロパティ・静的メソッドの基本の書き方から、静的メソッドで $this が使えない理由、self::static:: の違い(遅延静的束縛)、そしてカウンターやファクトリメソッドといった実践的な使いどころまでを、動かせるコードつきで解説します。

static はクラスそのものに属するメンバー

通常のプロパティは「インスタンスごと」に値を持ちます。new でオブジェクトを2つ作れば、値を入れる箱も2つできるわけです。これに対して static を付けたプロパティはクラス単位で1つだけ存在し、そのクラスから作られたすべてのインスタンスで同じ値を共有します。そもそもインスタンスを作らなくても、クラス名から直接読み書きできます。

言葉だけだと分かりにくいので、通常のプロパティと静的プロパティを1つのクラスに並べて比べてみます。

Counter.php
<?php
class Counter
{
    // クラス全体で1つだけ存在する
    public static int $count = 0;

    // インスタンスごとに存在する
    public int $number = 0;

    public function __construct()
    {
        self::$count++;              // 共有の値を1つ増やす
        $this->number = self::$count; // 自分が何番目かを覚える
    }
}

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

echo Counter::$count; // 2(クラスで共有されている)
echo $a->number;      // 1($a 個別の値)
echo $b->number;      // 2($b 個別の値)

$a$b$number はそれぞれ別の値になっていますが、Counter::$count はどちらのインスタンスから増やしても同じ箱を指しています。「インスタンスに紐づく値」なのか「クラスに紐づく値」なのかが、通常のメンバーと静的メンバーのいちばん大きな違いです。なお、上のように型を書いた静的プロパティ(型付きプロパティ)が使えるのは PHP 7.4 以降です。

静的プロパティの宣言と参照のしかた

宣言は public static int $count = 0; のように、アクセス修飾子と型宣言のあいだに static を挟むだけです(static public int $count のように順番を入れ替えても構いません)。参照するときは、書く場所によって次の3つの書き方を使い分けます。

access-static-property.php
<?php
class Counter
{
    public static int $count = 0;

    public function increment(): void
    {
        // クラスの内側からは self:: を使う
        self::$count++;
    }
}

$counter = new Counter();
$counter->increment();

// 1. クラス名から参照する
echo Counter::$count; // 1

// 2. 直接書き換えることもできる
Counter::$count = 10;
echo Counter::$count; // 10

// 3. インスタンスが入った変数からも参照できる
echo $counter::$count; // 10

ポイントは、:: のあとに $ が付くことです。定数のときは ClassName::CONST_NAME$ なしで書きますが、静的プロパティは変数なので ClassName::$count と書きます。ここを取り違えると、PHP は「そんな定数はない」というエラーを出します。

インスタンスからは $obj::$count で、$obj->count では読めない

上の3番目のように、インスタンスが入った変数からでも $counter::$count と書けば静的プロパティを読めます。これは「変数からクラス名を取り出して クラス名::$count と書いたのと同じ」と考えると分かりやすいでしょう。

一方で、いつもの感覚で $counter->count と書いても静的プロパティにはたどり着けません。-> は「このインスタンスが持っているプロパティ」を探す記法で、静的プロパティはインスタンスではなくクラスが持っているものだからです。PHP 8 では Warning: Undefined property が出て値は null になり、エラーで止まらない分だけ気づきにくい不具合になります。

静的メソッドの定義と呼び出し

メソッドも同じように static を付けるだけで静的メソッドになります。インスタンスの状態を使わない、入力から出力を決めるだけの処理は静的メソッドにしておくと、new の手間なく呼び出せて便利です。

Formatter.php
<?php
class Formatter
{
    public static function yen(int $price): string
    {
        return number_format($price) . '円';
    }

    public static function withTax(int $price): string
    {
        // 同じクラスの静的メソッドは self:: で呼べる
        return self::yen((int) round($price * 1.1));
    }
}

// インスタンスを作らずに呼び出せる
echo Formatter::yen(1200);     // 1,200円
echo Formatter::withTax(1200); // 1,320円

クラスの内側から同じクラスの静的メソッドを呼ぶときは self::yen() のように書きます。Formatter::yen() とクラス名を直接書いても動きますが、クラス名を変えたときに書き換える箇所が増えるので、内側からは self::(または後述の static::)を使うのが一般的です。

静的メソッドの中で $this が使えない理由

$this は「いま自分を呼び出しているインスタンス」を指す変数です。ところが静的メソッドは ClassName::method() という形で、インスタンスがまったく存在しない状態でも呼べてしまいます。そのため PHP は静的メソッドの中に $this を用意せず、使おうとすると Error: Using $this when not in object context という致命的なエラーになります。

no-this.php
<?php
class Tax
{
    private float $rate = 1.1;      // インスタンスのプロパティ
    private static float $srate = 1.1; // 静的プロパティ

    public static function ng(int $price): float
    {
        // Error: Using $this when not in object context
        return $price * $this->rate;
    }

    public static function ok(int $price): float
    {
        // 静的メソッドから触れるのは静的なメンバーだけ
        return $price * self::$srate;
    }
}

echo Tax::ok(1000); // 1100

つまり静的メソッドの中から素直に扱えるのは、静的プロパティ・クラス定数・他の静的メソッド、そして引数として受け取った値だけです。インスタンスのプロパティを使いたいのであれば、それはそもそも静的メソッドにすべき処理ではない、というサインだと考えるとよいでしょう。

self:: / static:: / parent:: はそれぞれ何を指すのか

:: の左側に書けるのは、クラス名だけではありません。クラスの内側では selfstaticparent という3つのキーワードが使えます。見た目は似ていますが、指しているクラスがそれぞれ違います。

書き方指すクラス決まるタイミング主な用途
self::そのコードが書かれているクラスコードを書いた時点で固定同じクラスの静的プロパティ・定数・静的メソッドの参照
static::実際に呼び出されたクラス実行時に決まる(遅延静的束縛)継承先で中身を差し替えたいとき、new static()
parent::そのクラスのクラスコードを書いた時点で固定オーバーライドした親のメソッドやコンストラクタの呼び出し
$this->いま自分を呼び出しているインスタンス実行時に決まる通常のプロパティ・メソッドの参照(静的メソッド内では使えない)

継承を使っていないうちは self::static:: の結果は完全に一致するので、違いを意識する必要はありません。差が出るのは、そのクラスを継承した子クラスから呼び出したときです。

遅延静的束縛(late static binding)で呼び出し元のクラスを指す

遅延静的束縛は、PHP 5.3 で追加された static:: の仕組みのことです。「遅延」というのは、どのクラスを指すかの決定をコードを書いた時点ではなく実行時まで後回しにする、という意味です。まずは、指しているクラス名をそのまま出力して違いを確かめてみます。

late-static-binding.php
<?php
class ParentModel
{
    public static function bySelf(): string
    {
        return self::class;   // 書かれているクラス
    }

    public static function byStatic(): string
    {
        return static::class; // 呼び出されたクラス
    }
}

class ChildModel extends ParentModel
{
}

echo ParentModel::bySelf();   // ParentModel
echo ParentModel::byStatic(); // ParentModel

echo ChildModel::bySelf();    // ParentModel(定義元のまま)
echo ChildModel::byStatic();  // ChildModel(呼び出したクラス)

ChildModel::bySelf() の結果に注目してください。ChildModel から呼んでいるのに、返ってくるのは ParentModel です。self は「そのコードが書かれているファイル・クラス」を見ているだけなので、どこから呼ばれたかには関心がありません。対して static は呼び出し側のクラス名を引き継ぐため、ChildModel を返します。

子クラスでメソッドを上書きしたときの違い

この違いが実際に効いてくるのは、親クラスのメソッドの中から、子クラスでオーバーライドされたメソッドを呼びたい場面です。self:: で書くと親の実装が固定され、子クラスで上書きしたはずの処理が呼ばれません。

Animal.php
<?php
class Animal
{
    public static function name(): string
    {
        return '動物';
    }

    public static function introBySelf(): string
    {
        // 常に Animal::name() が呼ばれる
        return 'これは' . self::name() . 'です';
    }

    public static function introByStatic(): string
    {
        // 呼び出したクラスの name() が呼ばれる
        return 'これは' . static::name() . 'です';
    }
}

class Dog extends Animal
{
    public static function name(): string
    {
        return '犬';
    }
}

echo Animal::introByStatic(); // これは動物です
echo Dog::introBySelf();      // これは動物です(上書きが効かない)
echo Dog::introByStatic();    // これは犬です

Dog::introBySelf() が「これは動物です」になってしまうのが、self:: でつまずく典型的なパターンです。親クラスの中で「この処理は子クラスによって変わってほしい」と考えているなら static:: を、「この処理は必ずこのクラスの実装を使う」と決めているなら self:: を選びます。判断に迷うときは、意図せず上書きされたくない場合だけ self::、それ以外は static:: と考えておくと大きく外しません。

なお静的プロパティも同じで、static::$items と書いても、子クラスが自分の $items を宣言していない限り親のプロパティを共有します。プロパティを子クラスごとに分けたいなら、子クラス側で同名の静的プロパティを改めて宣言する必要があります。

static が活きる4つの実践パターン

ここまでの内容を踏まえて、実際のコードでよく見かける使い方を4つ紹介します。どれも「インスタンスごとではなくクラス単位で扱いたい」という共通点があります。

1. 生成されたインスタンスの数を数える

もっとも分かりやすいのがカウンターです。コンストラクタで静的プロパティを増やしていけば、そのクラスから何個のインスタンスが作られたかをクラス側で把握できます。処理の重いオブジェクトが想定外の回数生成されていないかを調べるときなどに使えます。

Connection.php
<?php
class Connection
{
    private static int $created = 0;

    public function __construct(public string $host)
    {
        self::$created++;
    }

    public static function createdCount(): int
    {
        return self::$created;
    }
}

new Connection('db1.example.com');
new Connection('db2.example.com');

echo Connection::createdCount(); // 2

静的プロパティを private にして、読み取り用の静的メソッドだけを公開しているのがポイントです。外部から Connection::$created = 100; と勝手に書き換えられると意味のない数字になってしまうため、書き換えの入り口はクラスの内側に限定しておきます。ちなみにコンストラクタの引数に public string $host と書いているのは PHP 8.0 のコンストラクタプロパティプロモーションで、プロパティの宣言と代入をまとめて書ける記法です。

2. new static() を使ったファクトリメソッド

「インスタンスを作る専用の静的メソッド」をクラスに用意する書き方をファクトリメソッドと呼びます。new ClassName(...) と書く代わりに ClassName::create(...) と書けるようにするもので、生成時の準備処理をクラス側にまとめられます。ここで new static() を使うと、継承した子クラスから呼んだときに子クラスのインスタンスが返るようになります。

Model.php
<?php
class Model
{
    public function __construct(protected array $attributes = [])
    {
    }

    // static 型は「呼び出したクラス自身」を表す(PHP 8.0 以降)
    public static function create(array $attributes = []): static
    {
        return new static($attributes);
    }

    // こちらは常に Model のインスタンスを返してしまう
    public static function createBySelf(array $attributes = []): self
    {
        return new self($attributes);
    }
}

class User extends Model
{
}

echo get_class(User::create(['name' => '田中']));        // User
echo get_class(User::createBySelf(['name' => '田中'])); // Model

new self() だと、子クラスから呼んでも親クラスのインスタンスしか返ってきません。フレームワークの基底クラスなどで new static() が使われているのは、まさにこの理由です。戻り値の型に static と書けるのは PHP 8.0 以降で、それ以前は型を省略するか self 以外の書き方を選ぶ必要があります。

3. 設定値をまとめて持つユーティリティクラス

アプリ全体で共有したい設定値を、静的プロパティと静的メソッドだけのクラスにまとめる書き方もよく使われます。どこからでも Config::get('site_name') と書けるので、値を持ち回る必要がありません。

Config.php
<?php
class Config
{
    private static array $items = [
        'site_name' => 'webool',
        'per_page'  => 10,
    ];

    public static function get(string $key, mixed $default = null): mixed
    {
        return self::$items[$key] ?? $default;
    }

    public static function set(string $key, mixed $value): void
    {
        self::$items[$key] = $value;
    }

    public static function has(string $key): bool
    {
        return array_key_exists($key, self::$items);
    }
}

echo Config::get('site_name');        // webool
echo Config::get('theme', 'default'); // default(未設定なので既定値)

Config::set('per_page', 20);
echo Config::get('per_page');         // 20
var_dump(Config::has('theme'));       // bool(false)

mixed という型が使えるのは PHP 8.0 以降です。それ以前のバージョンでは、その部分の型宣言を省いて書きます。

4. シングルトン(インスタンスを1つだけにする)

シングルトンは、そのクラスのインスタンスがアプリ内で必ず1つだけになるようにする書き方です。コンストラクタを private にして外部からの new を禁止し、代わりに用意した静的メソッドが、初回だけインスタンスを作って静的プロパティに保存し、2回目以降は同じものを返します。

Logger.php
<?php
class Logger
{
    private static ?Logger $instance = null;

    private array $logs = [];

    // 外部から new させない
    private function __construct()
    {
    }

    public static function getInstance(): self
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }

        return self::$instance;
    }

    public function add(string $message): void
    {
        $this->logs[] = $message;
    }

    public function count(): int
    {
        return count($this->logs);
    }
}

Logger::getInstance()->add('処理を開始しました');
Logger::getInstance()->add('処理が完了しました');

// どこから呼んでも同じインスタンス
echo Logger::getInstance()->count(); // 2

// new Logger(); // Error: Call to private Logger::__construct()

getInstance() の中は静的メソッドなので $this は使えませんが、self::$instance に入っているのは通常のインスタンスなので、返したあとは ->add() のようにインスタンスメソッドを呼べます。「静的メソッドがインスタンスを返し、そこから先はいつも通り」という流れは、ファクトリメソッドとも共通する形です。

クラス定数(const)と静的プロパティは別物

どちらも ClassName:: で参照するため混同されがちですが、クラス定数 const と静的プロパティはまったく別の仕組みです。定数は名前のとおりあとから書き換えられない値で、参照するときに $ を付けません。静的プロパティは変数なので $ が必要で、実行中に値を変えられます。

HttpStatus.php
<?php
class HttpStatus
{
    public const OK = 200;        // 定数($ なし・書き換え不可)
    public const NOT_FOUND = 404;

    public static int $lastCode = 0; // 静的プロパティ($ あり・書き換え可)

    public static function isOk(int $code): bool
    {
        return $code === self::OK; // 定数も self:: で参照する
    }
}

echo HttpStatus::OK;             // 200
var_dump(HttpStatus::isOk(200)); // bool(true)

HttpStatus::$lastCode = 404;     // 静的プロパティは代入できる
echo HttpStatus::$lastCode;      // 404

// HttpStatus::OK = 500;         // 定数には代入できない(Parse error)

使い分けの目安は単純で、実行中に変わらない値は const、変わる値は静的プロパティです。ステータスコードや設定のキー名のように固定された値であれば、うっかり書き換えられる心配のない定数のほうが安全です。

static を増やしすぎないほうがよい理由

どこからでも呼べて手軽なので、慣れてくると何でも静的メソッドにしたくなります。ただ、この「どこからでも触れる」という性質は、裏を返すとグローバル変数に近い状態でもあります。静的プロパティはクラス単位で1つしかないため、ある処理が書き換えた値が、まったく別の場所の処理に影響することがあり得ます。値がいつ・どこで変わったのかを追いにくくなるのは、多用したときの分かりやすいデメリットです。

依存関係が見えづらくなる点も意識しておきたいところです。メソッドの中で Config::get() のように静的メソッドを直接呼んでいると、そのクラスが何に依存しているのかは中身を読むまで分かりません。コンストラクタの引数でオブジェクトを受け取る形(依存性の注入)にしておけば、クラスの宣言部分を見るだけで必要なものが把握できます。

テストのしづらさも同じ理由から生まれます。引数で渡されたオブジェクトなら、テスト時に偽物(モック)に差し替えられますが、静的メソッドの呼び出しはコードに直接埋め込まれているため差し替えが難しく、さらに静的プロパティの値がテスト間で引き継がれて結果が不安定になることもあります。

とはいえ、静的メソッドが常に悪いわけではありません。冒頭の Formatter::yen() のように、外部の状態に依存せず引数だけで結果が決まる処理は、静的メソッドにしても副作用が生まれません。「状態を持たない小さな道具は static、状態や依存を持つものは通常のクラス」というあたりを目安に選ぶと、扱いやすいコードになりやすいはずです。

static まわりでエラーが出るときの原因

静的メンバーのエラーは、メッセージを読めば原因がほぼ特定できます。よく遭遇する4つを見ておきましょう。

Using $this when not in object context

静的メソッドの中で $this を使ったときのエラーです。「オブジェクトの文脈ではないところで $this を使っている」という意味で、処理はそこで止まります。原因はほぼ2つに分かれます。1つは、通常のメソッドに誤って static を付けてしまったケース。この場合は static を外せば解決します。もう1つは、静的メソッドの中でインスタンスのプロパティを使おうとしたケースで、こちらは self::$プロパティ で参照できる静的プロパティに変えるか、必要な値を引数で受け取る形に書き換えます。

クラス内で定義した無名関数の中で発生することもあります。static function () {}static fn () => のように static を付けて定義したクロージャには $this がバインドされないため、同じエラーになります。

Accessing static property … as non static

静的プロパティを $obj->count のようにインスタンス経由で読み書きしようとしたときに出る警告です。PHP 8 では警告のため処理が止まらず、読み取り値は null、書き込んだ場合は静的プロパティとは別の動的プロパティが作られてしまいます(動的プロパティの作成は PHP 8.2 以降では非推奨の警告も出ます)。値が更新されないのに、エラーらしいエラーが出ないので原因を見失いがちです。

non-static-access.php
<?php
class Counter
{
    public static int $count = 5;
}

$counter = new Counter();

// NG: -> ではクラスの静的プロパティを参照できない
var_dump($counter->count); // NULL(Warning が出る)

// OK: :: を使う
var_dump($counter::$count); // int(5)
var_dump(Counter::$count);  // int(5)

Non-static method … cannot be called statically

->:: の取り違えのうち、こちらは致命的なエラーで止まります。通常のメソッドを ClassName::method() と静的に呼ぼうとしたときのメッセージで、原因はメソッド定義に static が付いていないことです。そのメソッドが $this を使っていないなら定義に static を足せばよく、使っているなら new でインスタンスを作ってから -> で呼び出します。

ややこしいのは逆方向で、静的メソッドをインスタンスから $obj->staticMethod() と呼ぶのはエラーになりません。PHP はこれを許容していて普通に動いてしまうため、「動いているから静的メソッドではない」とは判断できません。定義側を確認するのが確実です。

Constant expression contains invalid operations

静的プロパティの初期値に、関数の呼び出し結果のような「実行してみないと決まらない値」を書いたときのエラーです。初期値として書けるのは、数値・文字列・配列リテラル・定数などのコンパイル時に決まる式(定数式)に限られます。date('Y-m-d')new DateTime() は書けません。

lazy-init.php
<?php
class Report
{
    // NG: Fatal error: Constant expression contains invalid operations
    // public static string $today = date('Y-m-d');

    // OK: 空で宣言しておき、必要になったときに入れる
    private static ?string $today = null;

    public static function today(): string
    {
        // まだ入っていなければここで初期化する
        self::$today ??= date('Y-m-d');

        return self::$today;
    }
}

echo Report::today(); // 例: 2026-08-16

このように null で宣言しておき、最初に必要になったタイミングで値を入れる書き方(遅延初期化)が定番です。??= は PHP 7.4 で追加された null 合体代入演算子で、「左辺が null のときだけ代入する」という意味になります。const の値も同じ制約を受けるので、定数に関数の結果を入れられない点も併せて覚えておくとよいでしょう。

まとめ

static を付けたプロパティ・メソッドはインスタンスではなくクラスに属し、ClassName::$countClassName::method() の形でインスタンスを作らずに使えます。値はクラス単位で1つだけ共有され、インスタンス変数からは $obj::$count で参照できますが $obj->count では届きません。静的メソッドの中にはインスタンスが存在しないため $this は使えず、扱えるのは静的プロパティ・定数・引数だけです。self:: は書かれているクラス、static:: は呼び出されたクラスを指し、この違い(遅延静的束縛)を使うと new static() で子クラスのインスタンスを返すファクトリメソッドが書けます。手軽な反面グローバル変数に近い性質を持つので、状態を持たない小さな処理を中心に使うのがおすすめです。

参考ページ