[PHP]__PROPERTY__とは?PHP 8.4のプロパティフック内でプロパティ名を取得する方法を徹底解説

PHP

はじめに

これまでの記事で、__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__現在のメソッド名を取得プロパティフックとは異なる、通常のメソッドの文脈で使われる

よくある落とし穴(注意点)

  1. PHP 8.4未満の環境では使用できない __PROPERTY__ もプロパティフック機能自体も、PHP 8.4で初めて導入された新しい仕組みです。それより前のバージョンとの互換性が必要なコードでは使用できません。
  2. プロパティフックの外側では空文字列になる 通常のプロパティやメソッド内で __PROPERTY__ を使っても、意味のある値は返らず空文字列になります(例5を参照)。他のマジック定数と同様、コンテキスト外では静かに空の値を返すだけでエラーにはならない点に注意しましょう。
  3. __get()/__set()の完全な代替というわけではない プロパティフックは、各プロパティに対して個別にロジックを書く機能であり、「存在しないプロパティへの動的アクセス」を処理する __get()/__set() とは設計思想が異なります。用途に応じて使い分ける必要があります。
  4. トレイトでの再利用が特に有効な場面を見極める 複数のプロパティで同じようなバリデーションや正規化ロジックを適用したい場合、__PROPERTY__ を使った共通メソッドの再利用が効果的です(例2・例6を参照)。単一のプロパティにしか使わないロジックであれば、わざわざ __PROPERTY__ を使わなくても問題ありません。
  5. 比較的新しい機能であるため、エコシステム全体の対応状況に注意する 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() による汎用的なプロパティオーバーロードとは異なり、各プロパティに直接ロジックを書きながらも、共通処理を再利用したい場合に、現在のプロパティ名を知るための重要な手がかりを提供してくれます。

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