◀ 2.

PHP の static メソッド|self と static の違い・ファクトリと注意点

編集
この記事の要点
  • public static function で宣言し、ClassName::method() で呼ぶ。インスタンス化が要らない
  • static メソッドの中では $this が使えない。使うと Error になる
  • 同じクラスの他の static は self::、継承先に差し替えさせたいなら static::
  • 入力から出力が決まるだけの処理(純粋な計算)に向く
  • 差し替えられないのでテストが難しくなる。外部に依存する処理は static にしない

基本

class TextUtil
{
    public static function slug(string $s): string
    {
        return strtolower(preg_replace('/[^a-zA-Z0-9]+/', '-', trim($s)));
    }

    public static function truncate(string $s, int $len = 20): string
    {
        return mb_strlen($s) <= $len ? $s : mb_substr($s, 0, $len) . '…';
    }
}

echo TextUtil::slug('Hello World');           // hello-world
echo TextUtil::truncate('長い文章です', 3);     // 長い文章…

// インスタンスを作る必要はない
$u = new TextUtil();
echo $u::slug('Hello');       // 呼べるが Class::method() と書くほうが明確

$this は使えない

class Sample
{
    public int $count = 0;
    public static int $total = 0;

    public static function bad(): void
    {
        echo $this->count;      // Error: Using $this when not in object context
    }

    public static function good(): void
    {
        echo self::$total;      // static プロパティなら読める
        echo self::helper();    // static メソッドも呼べる
    }

    private static function helper(): string { return 'ok'; }
}

static メソッドはインスタンスに属していないため、「どのインスタンスか」を表す $this が存在しません。逆に、インスタンスメソッドからは self::method() で static メソッドを呼べます。

self / static / parent の使い分け

class Base
{
    public static function create(): static
    {
        return new static();     // 呼ばれたクラスのインスタンスを作る
    }

    public static function createSelf(): self
    {
        return new self();       // 常に Base を作ってしまう
    }

    public static function label(): string { return 'Base'; }

    public static function show(): void
    {
        echo static::label();    // 子で上書きされたものが呼ばれる
        echo self::label();      // 常に Base::label()
    }
}

class Child extends Base
{
    public static function label(): string { return 'Child'; }
}

var_dump(Child::create());       // object(Child)
var_dump(Child::createSelf());   // object(Base)   ← 意図しないことが多い
Child::show();                   // Child, Base
書き方解決先使う場面
self::書かれているクラス絶対に差し替えさせたくないとき
static::呼び出されたクラス継承して上書きされる前提のとき
parent::親クラス親の実装も動かしたいとき
ClassName::指定したクラス他のクラスの static を呼ぶとき

ファクトリメソッドでは new self() ではなく new static() を使うのが定石です。

ファクトリメソッドとして使う

class Money
{
    private function __construct(
        public readonly int $amount,
        public readonly string $currency,
    ) {}

    public static function yen(int $amount): static
    {
        return new static($amount, 'JPY');
    }

    public static function fromString(string $s): static
    {
        [$amount, $currency] = explode(' ', $s);
        return new static((int)$amount, $currency);
    }
}

$a = Money::yen(1000);
$b = Money::fromString('50 USD');

コンストラクタは 1 つしか作れないため、作り方が複数あるときは名前の付いた static メソッドで表現します。__construct を private にすれば、作り方を限定できます(コンストラクタ)。

static にしてよい処理・避けるべき処理

処理判断理由
文字列の整形・単位変換・バリデーション向く入力だけで結果が決まる
ファクトリメソッド向く生成方法に名前を付けられる
DB アクセス・HTTP 通信・ファイル操作避けるテストで差し替えられない
現在時刻・乱数を直接使う処理避ける結果が毎回変わり検証できない
アプリ全体の状態を持つ避けるどこから変わるか追えない
// テストしづらい書き方
class OrderService
{
    public function total(array $items): int
    {
        $rate = TaxApi::fetchRate();      // 通信が固定されていて差し替えられない
        return (int)(array_sum($items) * $rate);
    }
}

// 差し替えられる書き方(依存注入)
class OrderService
{
    public function __construct(private TaxRateProvider $tax) {}

    public function total(array $items): int
    {
        return (int)(array_sum($items) * $this->tax->rate());
    }
}

static 呼び出しは呼び出し先がコードに焼き付きます。テストで偽物に差し替えたい処理は、コンストラクタで受け取る形にしてください。

抽象クラス・インターフェースとの関係

interface Formatter
{
    public static function format(string $s): string;   // 宣言はできる
}

class Upper implements Formatter
{
    public static function format(string $s): string { return strtoupper($s); }
}

function apply(string $class, string $s): string
{
    return $class::format($s);      // クラス名を変数で渡して呼べる
}

echo apply(Upper::class, 'abc');    // ABC

インターフェースに static メソッドを宣言することはできますが、ポリモーフィズムは効きません(呼び出し時にクラス名を決める必要がある)。差し替えを前提とするなら、インスタンスメソッドにするほうが素直です。

__callStatic による動的呼び出し

class Facade
{
    public static function __callStatic(string $name, array $args): mixed
    {
        // 未定義の static メソッドが呼ばれたときに実行される
        return (new Service())->$name(...$args);
    }
}

echo Facade::greet('田中');   // Service::greet('田中') が実行される

Laravel のファサード(DB::table() Cache::get())はこの仕組みです。実体はインスタンスで、呼び出し口だけを static に見せているため、テストでの差し替えもできます。

関連

編集
Post Share
子ページ

子ページはありません

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

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