ツイート
シェア
LINEで送る
B! はてぶでブックマーク
Pocketでブックマーク
RSSフィード

PHP8.1, 読み取り専用のプロパティ追加。一度値を代入すると上書きできない。

php
イラストダウンロードサイト【イラストAC】
の画像をもとに加工しています。

PHP8.1から、クラスオブジェクトのプロパティを読み取り専用にできるようになりました。

プロパティ変数名の左に readonly キーワードを付けるだけ。

簡単そうに見えますが(じっさいに簡単。)、いろいろと制約があります。サンプルを交えながら見ていきましょう。

読み取り専用プロパティの定義

読み取り専用のプロパティはかんたんです。

<?php

class Test
{
    public readonly string $prop;

    public function __construct( string $prop ) {
        $this->prop = $prop;
    }
}

$test = new Test('obj1');
var_dump($test->prop);

// 読み取り専用プロパティ値の変更。エラーになる。
$test->prop = 'obj2';
実行結果
PHP Fatal error:  Uncaught Error: Cannot modify readonly property Test::$prop in /home/vagrant/php-test.php:16
Stack trace:
#0 {main}
  thrown in /home/vagrant/php-test.php on line 16

読み取り専用プロパティで値を変更しようとするとError例外がスローされます。

使い方の説明はこれだけ。簡単すぎて屁が出そう。

(『屁が出そう』に意味はない。ちょっとふざけたかっただけ。そこは突っ込まないで。)

ただし、readonlyにはいろいろな制約があります。ひとつずつ見ていきましょう。

クラス内で変更するとどうなる?

上記のサンプルでは、クラスオブジェクトの外から値を変更しようとしました。今度はクラス内部で変更してみましょう。

class Test
{
    private readonly string $prop;

    public function __construct( string $prop ) {
        $this->prop = $prop;
        $this->prop = $prop; // <--- 2度目の変更
    }
}
実行結果
PHP Fatal error:  Uncaught Error: Cannot modify readonly property Test::$prop in /home/vagrant/php-test.php:26
Stack trace:
#0 /home/vagrant/php-test/php8-test.php(30): Test->__construct()
#1 {main}
  thrown in /home/vagrant/php-test.php on line 26

クラスオブジェクト内部で変更してもエラーになります。当たり前ですね?

ちなみに、読み取り専用プロパティはpublic以外でも使えます。

プロパティの型宣言をしないと使えない。

readonly キーワードは、型宣言をしたプロパティじゃないと使えません。

class Test
{
    public readonly $prop;

    public function __construct( $prop ) {
        $this->prop = $prop;
    }
}
実行結果
PHP Fatal error:  Readonly property Test::$prop must have type in /home/vagrant/php-test.php on line 35

エラー例外はスローされません。つまり文法エラー。readonly キーワードの右隣りには必ず型が必要です。

ちなみに、型宣言したくない場合は、mixed型が使えます。

型を指定しないreadonly
class Test
{
    public readonly mixed $prop;

    public function __construct( $prop ) {
        $this->prop = $prop;
    }
}

$test = new Test('obj1');
var_dump($test->prop);
実行結果
string(4) "obj1"

変数の型の厳密さはPHP8のひとつの方向性です。ゆるい型で作るのはそろそろやめましょう。

staticプロパティでは使えない。

readonly は、staticキーワードとは併用できません。

class Test
{
    public static readonly mixed $prop;

    public function __construct( $prop ) {
        $this->prop = $prop;
    }
}
PHP Fatal error:  Static property Test::$prop cannot be readonly in /home/vagrant/php-test.php on line 49

これも文法エラー。

なんでダメなんだろう。オブジェクトのメモリの静的領域でできない理由が分からない。

同じクラスのインスタンスで共通のreadonlyプロパティは必要ないじゃん、ってことらしい。たしかに定数と変わらない気がする。

仕様なんだからしょうがない。インスタンス上の固有プロパティしか使えません。

外からは初期値を入れられない。

readonlyプロパティは、クラスオブジェクト内だけでしか値を入れられません。

class Test
{
    public readonly mixed $prop;
}

$test = new Test();
$test->prop = 'initial value';
実行結果
PHP Fatal error:  Uncaught Error: Cannot initialize readonly property Test::$prop from global scope in /home/vagrant/php-test.php:64
Stack trace:
#0 {main}
  thrown in /home/vagrant/php-test.php on line 64

これはエラー例外がスローされるので文法エラーではありません。

クラス作成時はエラーにならず、じっさいにクラスを使ったときに初めてエラーになります。

readonlyは、初期値を入れる処理は必ずクラス内で入れろよ、ということ。

デフォルト値は入れられない。

クラス内で初期値を入れることは分かりましたが、デフォルト値は入れられません。

class Test
{
    public readonly mixed $prop = 'initial';
}
実行結果
PHP Fatal error:  Readonly property Test::$prop cannot have default value in /home/vagrant/php-test.php on line 70

文法エラーになります。

これは納得。デフォルト値を入れるならそれは定数でいいじゃんってなるから。

unset() は初期値を入れる前なら使えるが。

unset() は、readonlyプロパティ値を入れる前なら実行することができます。

エラーにならない
class Test
{
    public readonly mixed $prop;

    public function __construct( $prop ) {
        unset($this->prop);
        $this->prop = $prop;
    }
}

$test = new Test('obj1');
var_dump($test->prop);
エラーになる
class Test
{
    public readonly mixed $prop;

    public function __construct( $prop ) {
        $this->prop = $prop;
        unset($this->prop);
    }
}

$test = new Test('obj1');
var_dump($test->prop);
実行結果
PHP Fatal error:  Uncaught Error: Cannot unset readonly property Test::$prop in /home/vagrant/php-test/php8-test.php:96
Stack trace:
#0 /home/vagrant/php-test.php(100): Test->__construct()
#1 {main}
  thrown in /home/vagrant/php-test.php on line 96

unset() は事実上、使えません。初期値を入れる前に実行する意味がないから。

PHP公式ドキュメント

Function - unset

読み取り専用プロパティのオブジェクトの内部だけは変更できる。

読み取り専用プロパティは初期値を入れると、その後はまったく変更できないというわけではありません。

プロパティ値がクラスオブジェクトのときだけは、そのオブジェクトの内部を変更できます。

class Test
{
    public readonly mixed $prop;
    public $name = 'name';

    public function __construct( $prop ) {
        $this->prop = $prop;
    }
}

$test = new Test(new Test('child'));
$test->prop->name = 'change !!!'; // <--- オブジェクト内部は変更できる。
var_dump($test->prop);
実行結果
object(Test)#2 (2) {
  ["prop"]=>
  string(5) "child"
  ["name"]=>
  string(10) "change !!!"
}

それなら、配列の一部は変更できるんじゃないかと思いがちですが、それはできません。

class Test
{
    public readonly mixed $prop;

    public function __construct( $prop ) {
        $this->prop = $prop;
    }
}

$arr = [
    'name' => 'name',
    'value' => '1',
];

$test = new Test($arr);
$test->prop['value'] = 'change !!!';
var_dump($test->prop);
実行結果
PHP Fatal error:  Uncaught Error: Cannot modify readonly property Test::$prop in /home/vagrant/php-test.php:134
Stack trace:
#0 {main}
  thrown in /home/vagrant/php-test.php on line 134

もちろんですが、いくらクラスオブジェクトでも、プロパティ値そのものは変更できません。

class Test
{
    public readonly mixed $prop;
    public $name = 'name';

    public function __construct( $prop ) {
        $this->prop = $prop;
    }
}

$test = new Test(new Test('child'));
$test->prop->name = 'change !!!';       // <--- オブジェクト内部は変更できる。
$test->prop = new Test('child change'); // <--- オブジェクトそのものは変更できない。
var_dump($test->prop);
PHP Fatal error:  Uncaught Error: Cannot modify readonly property Test::$prop in /home/vagrant/php-test.php:149
Stack trace:
#0 {main}
  thrown in /home/vagrant/php-test.php on line 149

PHP8.0以下の下位互換はない。

PHP8.1で追加された新機能なので、当然ですがPHP8.0以下では使えません。

class Test
{
    public readonly int $prop;

    public function __construct( $prop ) {
        $this->prop = $prop;
    }
}
PHP7.4の実行結果
PHP Parse error:  syntax error, unexpected 'int' (T_STRING), expecting variable (T_VARIABLE) in /home/vagrant/php-test.php on line 154
PHP8.0の実行結果
PHP Parse error:  syntax error, unexpected identifier "int", expecting variable in /home/vagrant/php-test.php on line 154

もちろんですが、どちらも文法エラーです。まったく動きません。

前の投稿
PHP8.1, 列挙型(Enum)のシリアライズ。JSON化の説明はちょっと違うんじゃないか?
PHP8.1, 第一級callable, コールバックの指定がより簡潔で分かりやすくなった。
次の投稿
コメントを残す

*