#06 オブジェクト指向プログラミング入門

静的プロパティとメソッド——インスタンスなしで使えるもの

static とは何か

これまでのクラスは new でインスタンスを作ってから使っていました。しかし「インスタンスを作るほどでもない、クラスにまとめておきたい便利な関数」というものがあります。

静的(static)メソッドやプロパティは、インスタンスを作らずにクラスから直接使えます。

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;
    }
}

// new MathHelper() は不要
echo MathHelper::add(3, 5);      // 8
echo MathHelper::multiply(4, 6); // 24

クラス名::メソッド名() という :: 演算子(スコープ解決演算子)で呼び出します。


静的プロパティ

静的プロパティはクラス全体で共有される変数です。どのインスタンスからアクセスしても同じ値を参照します。

class Counter
{
    private static int $count = 0;

    public static function increment(): void
    {
        self::$count++;
    }

    public static function getCount(): int
    {
        return self::$count;
    }
}

Counter::increment();
Counter::increment();
Counter::increment();
echo Counter::getCount(); // 3

self::$プロパティ名 でクラス内から静的プロパティにアクセスします。

インスタンスプロパティとの違い

class Person
{
    public static int $totalCount = 0; // クラス全体で共有
    public string $name;               // インスタンスごとに別

    public function __construct(string $name)
    {
        $this->name = $name;
        self::$totalCount++;           // 人が増えるたびにカウント
    }
}

$alice = new Person('Alice');
$bob   = new Person('Bob');

echo Person::$totalCount; // 2(全インスタンスで共有)
echo $alice->name;        // Alice(このインスタンスだけ)
echo $bob->name;          // Bob(このインスタンスだけ)

クラス定数

変わることのない値はクラス定数で定義します。const キーワードを使います。

class HttpStatus
{
    const OK                    = 200;
    const CREATED               = 201;
    const BAD_REQUEST           = 400;
    const UNAUTHORIZED          = 401;
    const NOT_FOUND             = 404;
    const INTERNAL_SERVER_ERROR = 500;
}

function sendResponse(int $statusCode, mixed $data): void
{
    http_response_code($statusCode);
    echo json_encode($data);
}

sendResponse(HttpStatus::OK, ['message' => 'Success']);
sendResponse(HttpStatus::NOT_FOUND, ['error' => 'Resource not found']);

マジックナンバー(意味不明な数字)をコードに直接書く代わりに定数を使うと、コードの意図が明確になります。

PHP 8.3 の型付き定数

PHP 8.3から定数に型を付けられます。

class Config
{
    const int    MAX_RETRY  = 3;
    const string APP_NAME   = 'MyApp';
    const float  TAX_RATE   = 0.1;
}

ユーティリティクラス

関連するユーティリティ関数をまとめたクラスです。インスタンス化は想定しないため、コンストラクタを private にして直接インスタンス化できないようにします。

class StringHelper
{
    // インスタンス化を禁止
    private function __construct() {}

    public static function truncate(string $text, int $length, string $suffix = '...'): string
    {
        if (mb_strlen($text) <= $length) {
            return $text;
        }
        return mb_substr($text, 0, $length) . $suffix;
    }

    public static function toSnakeCase(string $str): string
    {
        return strtolower(preg_replace('/[A-Z]/', '_$0', lcfirst($str)));
    }

    public static function toCamelCase(string $str): string
    {
        return lcfirst(str_replace('_', '', ucwords($str, '_')));
    }
}

echo StringHelper::truncate('オブジェクト指向プログラミング入門', 10);
// オブジェクト指向プロ...

echo StringHelper::toSnakeCase('myVariableName');
// my_variable_name

echo StringHelper::toCamelCase('my_variable_name');
// myVariableName

シングルトンパターン入門

シングルトンはアプリケーション全体で「インスタンスが1つだけ」であることを保証するパターンです。設定クラスやログクラスに使われます。

class AppConfig
{
    private static ?AppConfig $instance = null;
    private array $settings = [];

    // コンストラクタを private に → 外から new できない
    private function __construct()
    {
        // 設定ファイルを読み込む
        $this->settings = [
            'app_name'  => 'MyApp',
            'debug'     => false,
            'timezone'  => 'Asia/Tokyo',
        ];
    }

    // クローンも禁止
    private function __clone() {}

    // 唯一のインスタンスを返す
    public static function getInstance(): static
    {
        if (static::$instance === null) {
            static::$instance = new static();
        }
        return static::$instance;
    }

    public function get(string $key, mixed $default = null): mixed
    {
        return $this->settings[$key] ?? $default;
    }

    public function set(string $key, mixed $value): void
    {
        $this->settings[$key] = $value;
    }
}
$config1 = AppConfig::getInstance();
$config2 = AppConfig::getInstance();

var_dump($config1 === $config2); // bool(true):同じインスタンス

$config1->set('debug', true);
echo $config2->get('debug') ? 'true' : 'false'; // true(同じインスタンスなので)

シングルトンの注意点

シングルトンは便利ですが、テストが難しくなるという欠点があります。グローバル状態を持つため、テスト間で状態が残ってしまうことがあります。現代的なPHP開発ではDIコンテナ(第9回で解説)でインスタンス管理することが多く、シングルトンパターンの使用頻度は下がっています。


self と static の違い

静的メソッド内で self::static:: は微妙に異なります。

class Base
{
    public static function create(): static
    {
        return new static(); // static:: = 実際に呼ばれたクラス
    }

    public static function className(): string
    {
        return static::class; // 遅延静的束縛
    }
}

class Child extends Base {}

$base  = Base::create();   // Base のインスタンス
$child = Child::create();  // Child のインスタンス

echo Base::className();  // Base
echo Child::className(); // Child

static:: は「実際に呼ばれたクラス」を指します(遅延静的束縛)。継承と組み合わせるときは static:: を使うことが多いです。


まとめ

  • static メソッド・プロパティはインスタンスなしに クラス名:: で呼び出す
  • 静的プロパティはクラス全体で共有される
  • const で定数を定義してマジックナンバーを排除する
  • ユーティリティクラスのコンストラクタは private にしてインスタンス化を禁止する
  • シングルトンパターンはインスタンスが1つだけになることを保証する(テストへの注意が必要)
  • self:: はクラス定義時のクラス、static:: は呼び出し時のクラスを指す

次回は名前空間とオートローディング——大きなプロジェクトでのファイル・クラス整理術を学びます。