【PHP】PHPの静的メソッド・静的プロパティ(static)とは

phpアイキャッチ PHP

こんにちは、かつコーチです。

前回はアクセス修飾子(public / protected / private)について解説しました。

今回は、オブジェクト指向でよく登場するもう1つのキーワード「static」を扱います。

Laravelのコードを読んでいると、User::find(1) のように、インスタンスを作らずにクラス名から直接メソッドを呼んでいる場面をよく見かけると思います。

この「インスタンスを作らずに使える」仕組みが、今回学ぶ static(静的)です。

staticとは?

インスタンスごとではなく、クラス全体で共有する

これまでのプロパティやメソッドは、new でインスタンスを作ってから ->(アロー演算子)でアクセスしていました。

これは「インスタンスごとに別々のデータを持つ」通常のプロパティ・メソッドです。

一方、static を付けたプロパティ・メソッドは、インスタンスを作らなくても、クラス名から直接 ::(スコープ演算子)で呼び出せます。

さらに static プロパティは、すべてのインスタンスで1つの値を共有します。

通常のプロパティとの違いを確認する

言葉だけだと分かりづらいので、実際のコードで比較してみましょう。

<?php
class Counter
{
    public int $instanceCount = 0;   // インスタンスごとに持つ
    public static int $totalCount = 0; // クラス全体で1つだけ共有する

    public function __construct()
    {
        $this->instanceCount++;
        self::$totalCount++;
    }
}

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

echo $a->instanceCount;   // 1($aだけの値)
echo Counter::$totalCount; // 3(全インスタンスで共有される値)
?>

$instanceCount はインスタンスごとに別々の値を持ちますが、$totalCount はすべてのインスタンスで1つの値を共有しています。

Counter クラスから何個インスタンスを作っても、$totalCount は「これまで作られたインスタンスの総数」を正しくカウントし続けます。

静的メソッドの書き方

self:: でクラス自身にアクセスする

静的メソッドの中からは、$this は使えません(インスタンスに紐づかないためです)。

代わりに self:: を使って、クラス自身の静的プロパティ・静的メソッドにアクセスします。

<?php
class MathHelper
{
    public static function add(int $a, int $b): int
    {
        return $a + $b;
    }

    public static function multiply(int $a, int $b): int
    {
        return $a * $b;
    }
}

// インスタンスを作らずに、クラス名::メソッド名で呼び出せる
echo MathHelper::add(3, 5);      // 8
echo MathHelper::multiply(3, 5); // 15
?>

MathHelper クラスは、何らかの「状態」を持つ必要がなく、ただ計算をするだけのクラスです。

こういう「状態を持たない、純粋な処理のまとまり」は、静的メソッドとして定義するのに向いています。

静的メソッドをどんな場面で使うべきか

インスタンス化する必要がない処理に使う

静的メソッドが向いているのは、「インスタンスごとの状態を持つ必要がない処理」です。

たとえば日付のフォーマット処理や、簡単な計算処理などが典型例です。

❌ Before:状態を持たないのに毎回インスタンス化している

<?php
class DateFormatter
{
    public function format(string $date): string
    {
        $dt = new DateTime($date);
        return $dt->format('Y年n月j日');
    }
}

// 毎回インスタンス化する必要があるが、状態は何も保持していない
$formatter = new DateFormatter();
echo $formatter->format('2026-08-11'); // 2026年8月11日
?>

DateFormatter はインスタンスごとに保持すべき状態(プロパティ)を何も持っていません。

それにもかかわらず、使うたびに new でインスタンス化しなければならず、無駄が生じています。

✅ After:静的メソッドにしてインスタンス化を不要にする

<?php
class DateFormatter
{
    public static function format(string $date): string
    {
        $dt = new DateTime($date);
        return $dt->format('Y年n月j日');
    }
}

// インスタンス化せずにそのまま使える
echo DateFormatter::format('2026-08-11'); // 2026年8月11日
?>

static にすることで、new DateFormatter() という不要な手順を省略でき、呼び出し側のコードもシンプルになります。

静的メソッドを使いすぎることの注意点

便利な静的メソッドですが、何でも static にすればいいわけではありません。

インスタンスごとに違う状態(プロパティ)を扱う必要があるクラスまで無理に static にすると、データを共有すべきでない場面でも共有されてしまい、意図しないバグの原因になります。

「インスタンスごとの状態を持つ必要があるかどうか」を判断基準にすると、迷いにくくなります。

私がつまずいたポイント:静的プロパティの共有事故

私が実際に痛い目にあったのは、あるバッチ処理のクラスで、集計結果を static プロパティに保存していたときのことです。

<?php
class ReportGenerator
{
    private static array $errors = [];

    public static function addError(string $message): void
    {
        self::$errors[] = $message;
    }

    public static function getErrors(): array
    {
        return self::$errors;
    }
}
?>

このクラスを使って、複数の店舗(店舗A・店舗B・店舗C)のレポートを1つのバッチ処理内で順番に生成する処理を書いていました。

店舗Aの処理でエラーが出て addError() を呼んだ後、店舗Bの処理でエラーがまったく起きなかったにもかかわらず、getErrors() を呼ぶと店舗Aのエラーまで一緒に返ってきてしまいました。

原因は、$errorsstatic プロパティだったため、店舗Aの処理と店舗Bの処理で同じ配列を共有してしまっていたことでした。

Undefined error のような分かりやすいメッセージは出ず、「なぜかエラー件数が合わない」という気づきにくいバグだったので、原因特定に時間がかかりました。

このときの教訓は、「店舗ごとに独立して管理したい状態を static にしてはいけない」ということです。

修正としては、static プロパティをやめて通常のプロパティにし、店舗ごとに new ReportGenerator() でインスタンスを分けるようにしたところ、問題は解決しました。

staticファクトリメソッドという使い方

特殊なインスタンスの作り方を用意する

静的メソッドは、「特別な作り方でインスタンスを生成する」ためにもよく使われます。

これをstaticファクトリメソッドと呼びます。

<?php
class User
{
    private function __construct(
        public readonly string $name,
        public readonly string $role,
    ) {
    }

    // 通常ユーザーを作るための専用メソッド
    public static function createMember(string $name): self
    {
        return new self($name, 'member');
    }

    // 管理者ユーザーを作るための専用メソッド
    public static function createAdmin(string $name): self
    {
        return new self($name, 'admin');
    }
}

$member = User::createMember('田中太郎');
$admin = User::createAdmin('佐藤花子');

echo $member->role; // member
echo $admin->role;  // admin
?>

コンストラクタを private にすることで、new User(...) という直接的な生成方法を封じ、createMember()createAdmin() という意図が伝わりやすい名前のメソッド経由でしかインスタンスを作れないようにしています。

「どんな種類のユーザーを、どういう意図で作っているか」がメソッド名から一目で分かるようになるのが、このパターンのメリットです。

Laravelとの接点

Laravelでよく見る User::find(1)User::create([...]) は、まさに今回学んだ静的メソッドの仕組みそのものです。

Eloquentモデルの find()create() は静的メソッドとして定義されており、インスタンス化せずにいきなりデータベース操作を呼び出せるようになっています。

「なぜLaravelは new User() せずにいきなり User::find() と書けるのか」という疑問は、今回の static の理解で解消できるはずです。

まとめ

この記事のポイント

  • static を付けたプロパティ・メソッドは、インスタンス化せずにクラス名から :: で直接呼び出せる
  • 静的プロパティは全インスタンスで1つの値を共有する。店舗ごと・ユーザーごとなど独立して管理すべき状態には使わない
  • 状態を持たない純粋な処理(計算・フォーマットなど)は静的メソッドに向いている
  • 静的メソッド内では $this は使えず、self:: でクラス自身にアクセスする
  • コンストラクタを private にし、staticファクトリメソッドで意図の伝わる生成方法を用意するパターンもある

次に読むべき記事

→ 次の記事:PHPのトレイト(trait)で複数クラスに機能を共有する方法

タイトルとURLをコピーしました