PHP 8.1: 読み取り専用プロパティ – Stitcher.io

in Vlog

(jp) =

重要な注意: PHP 8.2 では、クラス全体を一度に読み取り専用にする方法が追加されています: readonly classes.

PHP でデータ転送オブジェクトと値オブジェクトを作成することは、長年にわたって非常に簡単になりました。 たとえば、PHP 5.6 の DTO を見てみましょう。

class BlogData

    
    private $title;
    
    
    private $status;
    
    
    private $publishedAt;
   
   
    public function __construct(
        $title,
        $status,
        $publishedAt = null
    ) 
        $this->title = $title;
        $this->status = $status;
        $this->publishedAt = $publishedAt;
    
    
    
    public function getTitle()
    
        return $this->title;    
    
    
    
    public function getStatus() 
    
        return $this->status;    
    
    
    
    public function getPublishedAt() 
    
        return $this->publishedAt;    
    

そして、それを PHP 8.0 の同等のものと比較します。

class BlogData

    public function __construct(
        private string $title,
        private Status $status,
        private ?DateTimeImmutable $publishedAt = null,
    ) 
    
    public function getTitle(): string
    
        return $this->title;    
    
    
    public function getStatus(): Status 
    
        return $this->status;    
    
    
    public function getPublishedAt(): ?DateTimeImmutable
    
        return $this->publishedAt;    
    

それはすでにかなりの違いですが、まだ大きな問題が 1 つあります。それらすべてのゲッターです。 個人的には、昇格されたプロパティを持つ PHP 8.0 以降、それらを使用しなくなりました。 ゲッターを追加する代わりに、パブリック プロパティを使用することを単に好みます。

class BlogData

    public function __construct(
        public string $title,
        public Status $status,
        public ?DateTimeImmutable $publishedAt = null,
    ) 

ただし、オブジェクト指向の純粋主義者はこのアプローチを好みません。オブジェクトの内部ステータスを直接公開してはならず、外部から変更できないことは間違いありません。

Spatie での私たちのプロジェクトでは、パブリック プロパティを持つ DTO と VO を外部から変更してはならないという内部スタイル ガイド ルールがあります。 これはかなりうまく機能しているように見えますが、かなり長い間これを行ってきましたが、何の問題もありませんでした。

ただし、はい。 言語がパブリック プロパティをまったく上書きできないことを保証した方がよいことに同意します。 PHP 8.1 は、これらの問題をすべて解決するために、 readonly キーワード:

class BlogData

    public function __construct(
        public readonly string $title,
        public readonly Status $status,
        public readonly ?DateTimeImmutable $publishedAt = null,
    ) 

このキーワードは、基本的にその名前が示すとおりに機能します。プロパティが設定されると、それ以上上書きすることはできません。

$blog = new BlogData(
    title: 'PHP 8.1: readonly properties', 
    status: Status::PUBLISHED, 
    publishedAt: now()
);

$blog->title = 'Another title';

Error: Cannot modify readonly property BlogData::$title

オブジェクトが構築されると、オブジェクトはそれ以上変更されないことがわかっているため、コードを書くときにある程度の確実性と安心感が得られます。予期しないデータ変更の全範囲が発生することはもうありません。

もちろん、データを新しいオブジェクトにコピーしたり、途中でいくつかのプロパティを変更したりしたい場合もあります。 この投稿の後半で、読み取り専用プロパティを使用してこれを行う方法について説明します。 まず、それらを詳しく見てみましょう。

# 型指定されたプロパティのみ

読み取り専用プロパティは、型付きプロパティと組み合わせてのみ使用できます。

class BlogData

    public readonly string $title;
    
    public readonly $mixed;

ただし、使用できます mixed タイプヒントとして:

class BlogData

    public readonly string $title;
    
    public readonly mixed $mixed;

この制限の理由は、プロパティ タイプを省略すると、PHP が自動的にプロパティの値を null コンストラクターで明示的な値が指定されていない場合。 この動作が読み取り専用と組み合わされると、不必要な混乱が生じる可能性があります。

# 通常のプロパティと昇格されたプロパティの両方

すでに両方の例を見てきました: readonly 通常のプロパティと昇格されたプロパティの両方に追加できます。

class BlogData

    public readonly string $title;
    
    public function __construct(
        public readonly Status $status, 
    ) 

# デフォルト値なし

読み取り専用プロパティにデフォルト値を設定することはできません:

class BlogData

    public readonly string $title = 'Readonly properties';

つまり、プロモートされたプロパティでない限り:

class BlogData

    public function __construct(
        public readonly string $title = 'Readonly properties', 
    ) 

その理由は 昇格されたプロパティに対して許可されているのは、昇格されたプロパティの既定値がクラス プロパティの既定値として使用されず、コンストラクター引数に対してのみ使用されるためです。 内部的には、上記のコードは次のように変換されます。

class BlogData

    public readonly string $title;
    
    public function __construct(
        string $title = 'Readonly properties', 
    ) 
        $this->title = $title;
    

実際のプロパティにデフォルト値が割り当てられていないことがわかります。 ちなみに、読み取り専用プロパティにデフォルト値を許可しない理由は、その形式の定数と変わらないからです。

# 継承

継承中に読み取り専用フラグを変更することはできません。

class Foo

    public readonly int $prop;


class Bar extends Foo

    public int $prop;

このルールは両方向に適用されます。 readonly 継承時のフラグ。

# 未設定不可

読み取り専用プロパティが設定されると、設定を解除することも、変更することもできません。

$foo = new Foo('value');

unset($foo->prop);

# 反射

新しいのがあります ReflectionProperty::isReadOnly() メソッドだけでなく、 ReflectionProperty::IS_READONLY 国旗。

# クローニング

では、読み取り専用プロパティを変更できず、設定を解除できない場合、DTO または VO のコピーを作成し、そのデータの一部を変更するにはどうすればよいでしょうか? できません clone その値を上書きできないためです。 実際に追加するアイデアがあります clone with 将来、この動作を許可するように構築しますが、それでは現在の問題は解決しません。

さて、あなた できる リフレクション マジックに少し頼る場合は、読み取り専用プロパティが変更されたオブジェクトをコピーします。 オブジェクトを作成することで それなし そのコンストラクターを呼び出し (これはリフレクションを使用して可能です)、次に各プロパティを手動でコピーすることにより (場合によってはその値を上書きします)、実際にオブジェクトを「複製」し、その読み取り専用プロパティを変更できます。

まさにそれを行うために小さなパッケージを作成しました。これは次のようになります。

class BlogData

    use Cloneable;

    public function __construct(
        public readonly string $title,
    ) 


$dataA = new BlogData('Title');

$dataB = $dataA->with(title: 'Another title');

私は実際に、このすべての背後にあるメカニズムを説明する専用のブログ投稿を書きました。ここで読むことができます.

# 読み取り専用クラス

最後に、PHP 8.2 で追加された readonly クラスについても触れておきます。 クラスのすべてのプロパティが読み取り専用の場合 (DTO や VO でよく発生します)、クラス自体を読み取り専用としてマークできます。 これは、個々のプロパティをすべて読み取り専用として宣言する必要がないことを意味します。

readonly class BlogData

    public function __construct(
        public string $title,
        public Status $status,
        public ?DateTimeImmutable $publishedAt = null,
    ) 

tpyoに気づきましたか? PR を送信して修正することができます。 このブログの最新情報を知りたい場合は、私をフォローしてください。 ツイッター または私のニュースレターを購読してください:

したがって、読み取り専用プロパティについて言えることはこれですべてです。 多くの DTO と VO を扱うプロジェクトに取り組んでおり、コード全体のデータ フローを慎重に管理する必要がある場合、これらは優れた機能だと思います。 読み取り専用プロパティを持つ不変オブジェクトは、そうする上で非常に役立ちます。

使うのが楽しみですが、あなたはどうですか? 教えて ツイッター または電子メールで!

//platform.twitter.com/widgets.js

関連記事

前の投稿
フレームワークの誕生と死
次の投稿
re: PSR 抽象化の使用について