【PHP9】ついに未定義プロパティが使えなくなる

先日[PHP9で未定義変数が使えなくなる](https://qiita.com/rana_kualu/items/7f84fcea5c70248ac99e)ことが決まりましたが、それのプロパティ版です。

```php
class HOGE{
  public function __construct(){
    $this->a = 1;  // PHP8.2以降エラー
    echo $this->b; // PHP9.0以降エラー
  }
}
```

このソースコード、PHP8.1まではE_WARNINGが出るものの動作はします。
しかしこのうち、未定義プロパティへの代入`$this->a=1`は[PHP8.2で禁止](https://qiita.com/rana_kualu/items/53012c8f84e0f92220c1)されました。
そしてPHP9では、未定義プロパティの取得`$this->b`が禁止されることとなります。

以下は該当のRFC、[Undefined Property Error Promotion](https://wiki.php.net/rfc/undefined_property_error_promotion)の日本語訳です。


# Undefined Property Error Promotion

## Introduction

未定義プロパティとは、まだ定義されていないプロパティのことです。

未定義プロパティにアクセスすると`Undefined property <className>::$varName`のE_WARNINGが表示され、その値はNULLであるかのように扱われますが、実行が中断されることはありません。
しかし、これは意図しない挙動である可能性が高いでしょう。

未定義プロパティへのアクセスは、かつてはE_NOTICEでしたが、[Reclassifying engine warnings](https://wiki.php.net/rfc/engine_warnings)のRFCへの対応の結果、PHP8.0においてE_WARNINGになりました。
PHP9がリリースされる頃には、未定義プロパティへのアクセスがE_WARNINGになってから既に5年以上が経つことになります。


## Proposal

このRFCは、次のメジャーバージョンアップにおいて未定義プロパティへのアクセスを禁止し、アクセスした場合はError例外をスローすることを提案しています。
未定義である可能性を考慮せずプロパティにアクセスした際に発生するもので、`Undefined property <className>::$varName`のE_WARNINGが起こるか否かで区別されます。
isset() / empty() / null合体演算子等は、プロパティが未定義であるかを考慮するため、本RFCによる影響はありません。

マジックメソッド`__get`が存在する場合は現在と同じ動作となり、未定義プロパティへのアクセスはエラーにならず`__get`に転送されます。


## Considerations

PHPは伝統的に、データ構造内の未定義値へのアクセスについては非常に緩い態度を取っていました。

値を格納する場所としては、主に以下の3か所があります。

・ **変数** スタックフレーム
・ **プロパティ** HashTable
・ **配列** バケツ

このうち変数については、[Undefined Variable Error Promotion](https://qiita.com/rana_kualu/items/7f84fcea5c70248ac99e)によってPHP9.0でエラーになる予定です。

次のターゲットがプロパティになるのは自明なことでしょう。

また未定義プロパティへの書き込みは[Deprecate dynamic properties](https://qiita.com/rana_kualu/items/53012c8f84e0f92220c1)において禁止されました。
PHP9.0以降、`#[AllowDynamicProperties]`アトリビュートを指定しないかぎりエラーになります。

未定義プロパティへの書き込みがエラーになるのであれば、未定義プロパティからの読み取りも同じようにエラーになるのが自然な成り行きというものです。

なお、書き込みのルールについては暗黙的な例外があり、それは`stdClass`です。
`stdClass`は未知のプロパティを保持することを目的としたクラスであるからです。
たとえば配列をオブジェクトにキャストしたり、`json_decode`したときに生成されます。

ユーザ定義クラスに対して未定義プロパティを読み込むのはほとんどの場合プログラマのミスですが、たとえばJSONを扱うときに以下のようなコードを書くのはよくあることでしょう。

```php
if ($obj->name) { 
  echo "Hello " . $obj->name;
}
```

これは`$obj->name`が未定義である場合はE_WARNINGが発生するのでお勧めはできませんが、値はnullと評価されるため処理自体は実行されます。

このRFCが採択された場合、今後は`isset`や`empty`等を用いて、以下のように書き直す必要があります。

```php
if (isset($obj->name)) { 
  echo "Hello " . $obj->name;
}
```

このRFCでは、`stdClass`についても例外を設けず、読み取りについては一貫してエラーを出します。

## Backward Incompatible Changes

`undefined property`のE_WARNINGが発生する方法で未定義のプロパティにアクセスしている場合、PHP9以降ではエラーになります。

## Proposed PHP Version(s)

PHP9.0。

ターゲットのバージョン指定としては類を見ないものですが、このRFCの意図として、この変更をあらかじめ告知しておくことで開発者へのコード修正の期間を最大限に増やす目的があります。

次のマイナーバージョンアップにおいて、PHP9においてエラーになる旨の警告メッセージを表示します。


## Voting

投票期間は2022/04/22から2022/05/05まで。

このRFCは、賛成31反対5の賛成多数で受理されました。


# 感想

Q：どうして[Undefined Variable Error Promotion](https://qiita.com/rana_kualu/items/7f84fcea5c70248ac99e)で一緒にエラーにしなかったの？
A：エラーメッセージが`Warning: Undefined variable $varname`ではなかったからじゃよ。

Undefined Variable Error PromotionのRFCでは、エラーになるのはメッセージが`Warning: Undefined variable $varname`である場合だけだと定義していました。
プロパティの場合、メッセージが`Warning: Undefined property: HOGE::$varname`となるので対象外だったというわけですね。

そんなわけで、未定義変数および未定義プロパティへの読み取りアクセスはPHP9以降エラーになります。
定義されていない値へのアクセスは今後もびしばし厳しくなることが予想されます。
そのようなコードは書かないようにしましょう。

といっても、まだ残っている未定義値へのアクセスって配列インデックスくらいですかね。

それに現在でも未定義値へのアクセスはE_WARNINGになるので、普通に書いているかぎりでは対処しているはずです。
これが影響するのは、主にPHP5時代から残っているような古き悪きコードで、そしておそらくそういうコードはそもそも他の変更で既に動かなくなっていることでしょう。