はじめに
これまでの記事で、__LINE__、__FILE__、__CLASS__、__METHOD__、__TRAIT__ といった、PHPの歴史を通じて使われてきたマジック定数を解説してきました。今回取り上げる __PROPERTY__ は、その中でも最も新しく、PHP 8.4で追加されたマジック定数です。
__PROPERTY__ は、同じくPHP 8.4で導入された新機能「プロパティフック(Property Hooks)」の中で、現在処理対象になっているプロパティの名前を取得するために使われます。プロパティフックとは、__get()/__set() のような汎用的なマジックメソッドを使わずに、特定のプロパティ1つ1つに対して直接 get/set の振る舞いを定義できる、PHP 8.4の目玉機能の一つです。本記事では、まずプロパティフックの基本を押さえた上で、__PROPERTY__ の役割と実践的な活用例を詳しく解説します。
定数概要
| 項目 | 内容 |
|---|---|
| 定数名 | __PROPERTY__ |
| 種別 | マジック定数 |
| 値の型 | 文字列(string) |
| 値の内容 | 現在のプロパティフック内で対象となっているプロパティの名前 |
| 対応バージョン | PHP 8.4.0以降 |
| 有効な場所 | プロパティフック(get/setフック)の本体内 |
| フック外での挙動 | 空文字列が返る |
プロパティフックと__PROPERTY__の関係(イメージ図)
class User {
public string $lastName {
set(string $value) {
echo __PROPERTY__; ← "lastName" が出力される
$this->lastName = ucfirst($value);
}
get {
return strtoupper($this->lastName);
}
}
}
ポイントは、__PROPERTY__ が従来の __get()/__set() マジックメソッドとは異なるアプローチの中で使われるという点です。__set() は1つのメソッドで「すべての」未定義プロパティへの書き込みをまとめて処理し、プロパティ名を引数として受け取っていましたが、プロパティフックは各プロパティごとに直接 set/get のロジックを書けます。それでも、複数のプロパティで共通のフックロジックを再利用したい場合などに、「このフックは今どのプロパティに対して動いているか」を知る手段として __PROPERTY__ が用意されています。
実践サンプル7選
例1:基本的な使い方
<?php
class BasicPropertyHookDemo
{
public string $name {
set(string $value) {
echo __PROPERTY__ . "に値を設定しようとしています: {$value}" . PHP_EOL;
$this->name = $value;
}
}
}
$demo = new BasicPropertyHookDemo();
$demo->name = '太郎';
例2:共通のバリデーションロジックをトレイトで再利用する
<?php
trait RequiredStringValidation
{
/**
* 複数のプロパティフックから呼び出される共通ロジックの中で、
* __PROPERTY__を使ってどのプロパティの検証中かを特定する
*/
private function assertNotEmpty(string $value): string
{
if (trim($value) === '') {
throw new InvalidArgumentException(__PROPERTY__ . 'は空にできません');
}
return $value;
}
}
class Profile
{
use RequiredStringValidation;
public string $firstName {
set(string $value) => $this->firstName = $this->assertNotEmpty($value);
}
public string $lastName {
set(string $value) => $this->lastName = $this->assertNotEmpty($value);
}
}
$profile = new Profile();
$profile->firstName = '太郎';
try {
$profile->lastName = '';
} catch (InvalidArgumentException $e) {
echo 'エラー: ' . $e->getMessage() . PHP_EOL;
}
例3:デバッグログにプロパティ名を自動記録するフック
<?php
class AuditedEntity
{
private array $changeLog = [];
public string $status {
set(string $value) {
$this->logChange($value);
$this->status = $value;
}
}
/**
* __PROPERTY__を使って、変更ログにどのプロパティが
* 変更されたかを自動的に記録する
*/
private function logChange(mixed $newValue): void
{
$this->changeLog[] = [
'property' => __PROPERTY__,
'new_value' => $newValue,
'time' => date('H:i:s'),
];
}
public function getChangeLog(): array
{
return $this->changeLog;
}
}
$entity = new AuditedEntity();
$entity->status = 'active';
print_r($entity->getChangeLog());
例4:getフック内での__PROPERTY__の利用
<?php
class LazyLoadedData
{
private array $cache = [];
public string $heavyData {
get {
if (!isset($this->cache[__PROPERTY__])) {
echo __PROPERTY__ . 'を初めて読み込みます(重い処理をシミュレート)' . PHP_EOL;
$this->cache[__PROPERTY__] = '読み込まれたデータ';
}
return $this->cache[__PROPERTY__];
}
}
}
$data = new LazyLoadedData();
echo $data->heavyData . PHP_EOL; // 初回読み込みが発生
echo $data->heavyData . PHP_EOL; // 2回目はキャッシュから返る
例5:フック外(通常のプロパティ)での挙動確認
<?php
class RegularPropertyDemo
{
public string $normalProperty = '通常のプロパティ';
public function checkOutsideHook(): string
{
// プロパティフックの外側で使うと空文字列になる
return '値: "' . __PROPERTY__ . '"';
}
}
echo (new RegularPropertyDemo())->checkOutsideHook() . PHP_EOL; // 値: ""
例6:複数プロパティの正規化処理を1つのメソッドに集約する
<?php
class NormalizedAddress
{
public string $city {
set(string $value) => $this->city = $this->normalize($value);
}
public string $prefecture {
set(string $value) => $this->prefecture = $this->normalize($value);
}
/**
* __PROPERTY__によって、将来ログ出力やプロパティごとの
* 例外処理を追加する際にも、共通メソッドのまま拡張できる
*/
private function normalize(string $value): string
{
$trimmed = trim($value);
// 必要であれば ここで __PROPERTY__ を使い分岐できる
return $trimmed;
}
}
$address = new NormalizedAddress();
$address->city = ' 渋谷区 ';
$address->prefecture = ' 東京都 ';
echo "{$address->prefecture}{$address->city}" . PHP_EOL;
例7:__METHOD__や__CLASS__と組み合わせた詳細なトレースログ
<?php
class FullyTracedEntity
{
public string $email {
set(string $value) {
printf(
"[%s::%s] プロパティ'%s'が更新されます: %s\n",
__CLASS__,
'set', // プロパティフックには__METHOD__が別の意味を持つ場合があるため明示的に記述
__PROPERTY__,
$value
);
$this->email = $value;
}
}
}
(new FullyTracedEntity())->email = 'taro@example.com';
関連機能との比較
| 機能 | 役割 | __PROPERTY__との違い |
|---|---|---|
__PROPERTY__ | プロパティフック内で対象プロパティ名を取得 | 本記事の対象。PHP 8.4以降の新機能専用 |
プロパティフック(get/set) | 特定のプロパティに対する直接的なアクセサ定義(PHP 8.4新機能) | __PROPERTY__が使われる文脈そのもの |
__get() / __set() | 未定義・アクセス不可プロパティを汎用的に処理する従来のマジックメソッド | すべてのプロパティをまとめて1つのメソッドで処理する点が異なる |
__CLASS__ | 現在のクラス名を取得 | プロパティ名ではなくクラス名の情報を提供する |
__METHOD__ | 現在のメソッド名を取得 | プロパティフックとは異なる、通常のメソッドの文脈で使われる |
よくある落とし穴(注意点)
- PHP 8.4未満の環境では使用できない
__PROPERTY__もプロパティフック機能自体も、PHP 8.4で初めて導入された新しい仕組みです。それより前のバージョンとの互換性が必要なコードでは使用できません。 - プロパティフックの外側では空文字列になる 通常のプロパティやメソッド内で
__PROPERTY__を使っても、意味のある値は返らず空文字列になります(例5を参照)。他のマジック定数と同様、コンテキスト外では静かに空の値を返すだけでエラーにはならない点に注意しましょう。 __get()/__set()の完全な代替というわけではない プロパティフックは、各プロパティに対して個別にロジックを書く機能であり、「存在しないプロパティへの動的アクセス」を処理する__get()/__set()とは設計思想が異なります。用途に応じて使い分ける必要があります。- トレイトでの再利用が特に有効な場面を見極める 複数のプロパティで同じようなバリデーションや正規化ロジックを適用したい場合、
__PROPERTY__を使った共通メソッドの再利用が効果的です(例2・例6を参照)。単一のプロパティにしか使わないロジックであれば、わざわざ__PROPERTY__を使わなくても問題ありません。 - 比較的新しい機能であるため、エコシステム全体の対応状況に注意する PHP 8.4のプロパティフックおよび
__PROPERTY__は非常に新しい機能のため、IDEの補完、静的解析ツール、フレームワークの対応状況によっては、期待通りのサポートが得られない場合があります。導入前に利用しているツールチェーンの対応状況を確認することをおすすめします。
まとめ
| 観点 | まとめ |
|---|---|
| 何をする定数か | PHP 8.4のプロパティフック内で、対象となっているプロパティの名前を取得する |
| 主な用途 | 複数プロパティで共有するバリデーション・正規化ロジックの中での対象特定、変更ログの記録 |
| 対応バージョン | PHP 8.4.0以降(プロパティフック機能と同時に導入) |
| 関連する新機能 | プロパティフック(get/setの直接定義) — __get()/__set()とは異なるアプローチ |
| 注意点 | PHP 8.4未満では使用不可、フック外では空文字列、ツールチェーンの対応状況 |
__PROPERTY__ は、PHP 8.4で導入されたプロパティフックという新しいパラダイムを支える、最も新しいマジック定数です。従来の __get()/__set() による汎用的なプロパティオーバーロードとは異なり、各プロパティに直接ロジックを書きながらも、共通処理を再利用したい場合に、現在のプロパティ名を知るための重要な手がかりを提供してくれます。
