インターフェースをひとつ実装するだけの小さなクラス、テストで1回しか使わない差し替え用のクラス。そのためだけにファイルを作るのは大げさだな、と感じたことはないでしょうか。PHP 7.0 から使える匿名クラスを使うと、名前を付けずにその場でクラスを定義してインスタンス化できます。
この記事では、匿名クラスの書き方、コンストラクターや継承の扱い、実際に役立つ場面、そして外側のクラスのプロパティに触れないといった注意点を解説します。
目次
匿名クラスは「定義と同時にインスタンス化する」クラス
通常のクラスは class 名前 { … } で定義し、new 名前 でインスタンスを作ります。匿名クラスはこの2つが一体になっていて、new class { … } と書くと、定義したその場でインスタンスがひとつ出来上がります。
<?php
$logger = new class {
public function info( string $message ): void {
echo '[INFO] ' . $message . PHP_EOL;
}
};
$logger->info( '処理を開始しました' ); // [INFO] 処理を開始しました
中に書ける内容は通常のクラスと変わりません。プロパティ、メソッド、定数、static メンバー、可視性の修飾子、すべて使えます。違うのは名前が無いことと、定義した場所でしかインスタンスを作れないことだけです。
最後にセミコロンが要る点には注意してください。new 式なので、通常のクラス定義と違って文の終わりに ; が必要です。
コンストラクターに引数を渡す
匿名クラスにも引数を渡せます。new class の直後に括弧を書き、その中身が __construct() に渡されます。
<?php
$prefix = 'APP';
$logger = new class( $prefix ) {
// コンストラクタプロモーションも使える
public function __construct(
private string $prefix
) {}
public function info( string $message ): void {
echo sprintf( '[%s] %s', $this->prefix, $message ) . PHP_EOL;
}
};
$logger->info( '起動しました' ); // [APP] 起動しました
括弧の位置を間違えて new class { … }( $prefix ) と書いてしまうとパースエラーになります。引数は必ず class の直後と覚えてください。
継承・インターフェース実装・トレイト
匿名クラスは、名前付きクラスと同じように extends・implements・use を書けます。これができるおかげで、単なる使い捨てのオブジェクト以上の使い道が生まれます。
<?php
interface Formatter {
public function format( string $text ): string;
}
trait Trimmable {
private function clean( string $text ): string {
return trim( $text );
}
}
abstract class BaseFormatter {
protected string $suffix = '';
}
$formatter = new class extends BaseFormatter implements Formatter {
use Trimmable;
public function format( string $text ): string {
return strtoupper( $this->clean( $text ) ) . $this->suffix;
}
};
var_dump( $formatter instanceof Formatter ); // bool(true)
echo $formatter->format( ' hello ' ); // HELLO
書く順序は名前付きクラスと同じで、extends が先、implements があと。トレイトの use はクラス本体の中に書きます。
instanceof がきちんと true を返すところがポイントです。型宣言でインターフェースを要求している関数にも、そのまま渡せます。
使いどころ1:インターフェースをその場で実装する
匿名クラスが一番活きるのは、インターフェースを要求されているが、実装は1か所でしか使わないという場面です。
<?php
interface Cache {
public function get( string $key ): ?string;
public function set( string $key, string $value ): void;
}
class Importer {
public function __construct( private Cache $cache ) {}
// …
}
// キャッシュを使いたくない場面では、何もしない実装を渡す
$importer = new Importer(
new class implements Cache {
public function get( string $key ): ?string {
return null;
}
public function set( string $key, string $value ): void {
// 何もしない
}
}
);
「何もしない実装(Null Object)」のためだけに NullCache.php を作らずに済みます。テストで外部 API の呼び出しを差し替えるスタブも、同じ要領でテストメソッドの中に直接書けます。実装が目の前にあるので、テストを読む人が別ファイルを開かなくても挙動を把握できます。
使いどころ2:小さな値のまとまりを作る
連想配列で値をまとめると、キーの打ち間違いに気づけません。匿名クラスなら、存在しないプロパティへのアクセスで警告が出ますし、型も宣言できます。PHP 8.3 以降なら readonly を付けて、作ったあと変更できないようにもできます。
<?php
// PHP 8.3 以降は readonly クラスにもできる
$config = new readonly class {
public int $timeout = 30;
public string $endpoint = 'https://example.com/api';
};
echo $config->timeout; // 30
// $config->timeout = 60; // Error: 変更できない
ただし、こうした値のまとまりが複数の場所で使われるようになったら、素直に名前付きのクラスに切り出すべきです。判断基準は次の「使わないほうがいい場面」で触れます。
外側のクラスの private には触れない
メソッドの中で匿名クラスを書くと、つい「同じクラスの中だから $this のプロパティが見えるはず」と思ってしまいますが、そうはなりません。匿名クラスは、外側のクラスとは無関係の別クラスとして扱われます。
<?php
class Outer {
private string $secret = 'ひみつ';
public function make(): object {
// 匿名クラスの中の $this は、匿名クラス自身を指す
return new class( $this->secret ) {
public function __construct(
private string $secret
) {}
public function reveal(): string {
return $this->secret;
}
};
}
}
echo ( new Outer() )->make()->reveal(); // ひみつ
外側の値を使いたければ、上のようにコンストラクターで渡すのが基本です。外側のクラスの protected メンバーを使いたい場合に限っては、匿名クラスで外側のクラスを extends するという手もありますが、継承関係としては不自然になりがちなので、素直に引数で渡すほうが読みやすくなります。
なお、外側のローカル変数も自動では取り込まれません。クロージャーの use のような仕組みは匿名クラスにはないので、必要なものはすべて引数で渡してください。
クラス名はどうなっているのか
「匿名」といっても、内部的には PHP が名前を割り当てています。get_class() で確認できます。
<?php
$obj = new class {};
echo get_class( $obj );
// class@anonymous/path/to/example.php:3$0 のような名前になる
この名前はファイルパスと行番号から自動生成される実装上の名前で、コードから書いたり、当てにしたりするためのものではありません。ログに出したときに「どこで定義された匿名クラスか」を判別する手がかり、くらいに考えてください。
同じ new class の行がループで何度も実行された場合は、毎回同じクラスからインスタンスが作られます。クラス定義そのものは1つで、インスタンスだけが増えるという動きです。
使わないほうがいい場面
2か所以上で同じものが必要になったとき
匿名クラスは書いた場所でしか使えないので、同じ実装が別の場所でも必要になったらコピーするしかありません。コピーが発生した時点で、名前付きクラスに切り出す合図です。匿名クラスは「1か所でしか使わない」という前提が守られている間だけ得をします。
中身が長くなってきたとき
メソッドが5つも6つもある匿名クラスがメソッドの途中に挟まっていると、本来の処理の流れが読み取れなくなります。数行から十数行に収まらないなら、名前を付けて別ファイルに出したほうが読みやすくなります。名前を付けること自体が、そのクラスの役割を説明するドキュメントにもなります。
シリアライズが必要なとき
匿名クラスのインスタンスは serialize() できません。実行するとエラーになります。セッションに入れる、キャッシュに保存する、update_option() でデータベースに保存するといった用途では使えないので注意してください。同じ理由で、クラス名を文字列で指定して復元するような処理とも相性が悪いです。
まとめ
匿名クラスは new class { … } と書くことで、名前を付けずにその場でクラスを定義してインスタンス化できる機能です。コンストラクター引数も渡せますし、extends・implements・トレイトの use も通常のクラスと同じように使えるので、instanceof による型チェックも通ります。
インターフェースの使い捨て実装、テストのスタブ、何もしない Null Object といった「1か所だけで必要な小さなクラス」を、ファイルを増やさずに書けるのが利点です。一方で、外側のクラスの private には触れないので値はコンストラクターで渡す必要があり、シリアライズもできません。1か所でしか使わない・短いという条件から外れてきたら、名前付きクラスに切り出す。この線引きさえ守れば、扱いやすい機能です。