16
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【PHP Next】非対称な可視性を導入したい

Last updated at Posted at 2022-10-10

Swiftでは読み込みと書き込みで異なる可視性を設定することができます

private (set) public var count: Int = 0 // 読み込みはpublic、書き込みはprivate

そんなわけで節操のないPHPは、この機能もPHPに取り込もうと企んでいます。
以下は該当のRFC、Asymmetric Visibilityの紹介です。

Asymmetric Visibility

Introduction

PHPには、前からプロパティの可視性を制御する機能があります。
しかし、その機能はgetとset両方に対して常に同じです。

本RFCでは、プロパティの読み取り操作と書き込み操作に対して異なる可視性を与えることを提案します。
具体的にはほぼSwiftから拝借しています。

Proposal

プロパティの書き込み操作の可視性を宣言するための新しいset構文を提供します。

class Foo
{
    public private(set) string $bar;
}

このプロパティ$barは、読み込みはpublicスコープですが、書き込みはprivateスコープです。
またprotected(set)とすればprotectedスコープになります。

このプロパティの挙動は、可視性が制限されていること以外、通常のプロパティと全く同じです。

プロパティのset可視性は、コンストラクタ引数昇格と共に使用することもできます。

class Foo
{
    public function __construct(
        public private(set) string $bar,
    ) {}
}

References

set可視性が指定されたプロパティへのリファレンスは、書き込みが可能なスコープからしか取得できません。
別の言い方をすると、リファレンスは読み込みの可視性ではなく、書き込みの可視性に従います。

class Foo
{
    public protected(set) int $bar = 0;
 
    public function test() {
        // OK 同クラスなのでprivateは見える
        $bar = &$this->bar;
        $bar++;
    }
}
 
class Baz extends Foo
{
    public function stuff() {
        // OK 子クラスなのでprotectedは見える
        $bar = &$this->bar;
        $bar++;
    }
}
 
$foo = new Foo();

// OK
$foo->test();
 
// これもOK
$baz = new Baz();
$baz->stuff();

// NG privateには外からアクセスできない
$bar = &$foo->bar;

Object properties

対象のプロパティがオブジェクトである場合、set可視性はオブジェクトの変更にのみ適用されます。
オブジェクト自体には影響を与えません。
これはreadonlyプロパティの挙動とも一致します。

class Bar
{
    public string $name = 'beep';
}
 
class Foo
{
    public private(set) Bar $bar;
}
 
$f = new Foo();
 
// OK これは許される
$f->bar->name = 'boop';
 
// NG Foo::$barは外から見れない
$f->bar = new Bar();

Permitted visibility

getとsetの可視性が異なる場合、setの可視性はgetより狭くなければなりません。
すなわち、setに設定可能な可視性はprotectedprivateだけです。
getの可視性がprotectedである場合、setに設定可能な可視性はprivateだけです。
違反した場合はコンパイルエラーになります。

Interaction with __set

クラスにマジックメソッド__setが定義されているときに、不正なスコープからアクセスがあった場合は__setが呼び出されます。
これは別の目的でクラスに__setを実装したときに、なるべく想定外の動作をしないためのものです。
これはreadonlyの実装においてもそうなっています。

従って、非対称な可視性についても同じ動作にします。
読み込みの可視性が一致しない場合は__setが呼び出されます。
書き込みの可視性が一致しない場合はエラーになります。

class Foo {
    protected private(set) string $prot;
    public private(set) string $pub;

    public function __set($name, $value) {
        var_dump($name, $value);
    }
}

$foo = new Foo();

$foo->prot = 'Foo'; // __setが呼ばれる

$foo->pub = 'Foo'; // エラーになる

Relationship with readonly

public private(set)は一見readonlyと同じに見えるかもしれませんが、実際は少々異なります。
readonlypublicからの書き込みは許されず、privateからの書き込みは一度だけ可能です。
public private(set)publicからの書き込みは許されず、privateからの書き込みは無制限に可能です。

ひとつのプロパティにreadonlyと非対称な可視性の両方を使用することはできません。

Typed properties

非対称な可視性は、型が明示されたプロパティにのみ適用可能です。
これは主に実装の複雑さに起因する制限です。

値が不明なプロパティにはmixed型を設定できるため、実用上の問題にはならないでしょう。

Reflection

ReflectionPropertyに2つのメソッド、isProtectedSet():boolisPrivateSet():boolが追加されます。
意味は自明でしょう。

class Test
{
    public string $open;
    public protected(set) string $restricted;
}
 
$rClass = new ReflectionClass(Test::class);
 
$rOpen = $rClass->getProperty('open');
print $rOpen->isProtectedSet() ? 'Yep' : 'Nope'; // Nope
 
$rRestricted = $rClass->getProperty('open');
print $rRestricted->isProtectedSet() ? 'Yep' : 'Nope'; // Yep

また定数ReflectionProperty::IS_PROTECTED_SETReflectionProperty::IS_PRIVATE_SETが追加されます。
これらの扱いはReflectionProperty::getModifiers()から返される他の可視性修飾子と同様です。

Backward Incompatible Changes

後方互換性のない変更はありません。

Proposed PHP Version(s)

PHP8.3?

Future Scope

この節は将来の展望であり、本RFCには含まれていません。

Alternate operations

現時点では、スコープに設定可能な操作は書き込みと読み込みだけです。
今後、他にも以下のような操作が追加されるかもしれません。

init__construct__clone__unserializeといった初期化処理からのみ設定可能なプロパティ。
once。一度だけ書き込み可能。public private(once)readonlyと完全に同一ですが、public protected(once)は子クラスからも書き込み可能です。
unset。プロパティのunsetを可能にします。

Additional visibility

パッケージレベルの可視性などが導入された場合、public package(set)などが使用可能になるでしょう。

Property accessors

非対称の可視性については、以前からProperty AccessorsのRFCとして提案されていました。
該当のRFCはC#の構文をモデルとして書かれています。

いっぽう本RFCはSwiftの構文を借用していますが、これはC#スタイルに比べて2つの利点があります。
・全ての可視性が一か所に固まっているため、混乱が少ない。
・プロパティアクセサのRFCが直面している問題は影響しない。

このRFCは、将来的なプロパティアクセサの導入を排除したり制限したりするものではありません。

References

Swiftの同機能

感想

かつてNikitaがPHP8.1への導入を目指していたものの諦めたProperty Accessors構文の代替品です。
RFCでは排除しないと言ってますが、ほぼ同じ機能なのでもし本RFCが採択されたとしたらプロパティアクセサを導入する意味はほとんどなくなるでしょう。

先日提出されたばっかりのRFCなのでまだどうなるかわかりませんが、個人的にはプロパティアクセサも含めてreadonlyでほぼカバーできる内容だと思っているので、本RFCの需要が何処まであるかはよくわかりません。

あとprivate public(set)とは定義できません。
書けるけど読めないプロパティにどんな意味があるかはわかりませんが。

16
4
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?