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に設定可能な可視性はprotected
とprivate
だけです。
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
と同じに見えるかもしれませんが、実際は少々異なります。
readonly
はpublic
からの書き込みは許されず、private
からの書き込みは一度だけ可能です。
public private(set)
はpublic
からの書き込みは許されず、private
からの書き込みは無制限に可能です。
ひとつのプロパティにreadonly
と非対称な可視性の両方を使用することはできません。
Typed properties
非対称な可視性は、型が明示されたプロパティにのみ適用可能です。
これは主に実装の複雑さに起因する制限です。
値が不明なプロパティにはmixed型を設定できるため、実用上の問題にはならないでしょう。
Reflection
ReflectionPropertyに2つのメソッド、isProtectedSet():bool
とisPrivateSet():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_SET
とReflectionProperty::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
感想
かつてNikitaがPHP8.1への導入を目指していたものの諦めたProperty Accessors構文の代替品です。
RFCでは排除しないと言ってますが、ほぼ同じ機能なのでもし本RFCが採択されたとしたらプロパティアクセサを導入する意味はほとんどなくなるでしょう。
先日提出されたばっかりのRFCなのでまだどうなるかわかりませんが、個人的にはプロパティアクセサも含めてreadonlyでほぼカバーできる内容だと思っているので、本RFCの需要が何処まであるかはよくわかりません。
あとprivate public(set)
とは定義できません。
書けるけど読めないプロパティにどんな意味があるかはわかりませんが。