炼乳是什么东西| 空调外机风扇不转是什么原因| 脾胃有火是什么症状| 阴挺是什么意思| 喉结大是什么原因| 抽动症是什么引起的| 左侧上颌窦囊肿是什么意思| 心跳太慢吃什么药| 男士圆脸适合什么发型| pwr是什么意思| 刺激性干咳是什么症状| 头晕什么原因引起的| 拍ct挂什么科| 2月12日什么星座| 宋朝之前是什么朝代| 顾问是什么意思| 怀孕为什么会恶心想吐| 吃什么缓解便秘| 歌声什么| 神什么气什么| 脂肪肝是什么意思啊| 眼睛散瞳有什么危害| 12年属什么生肖| 做美甲师容易得什么病| 梦见芝麻是什么意思| 双十一是什么节日| 无为而治什么意思| 青霉素过敏不能吃什么药| 知了吃什么食物| 婴儿吃手是什么原因| 尿隐血是什么意思| 什么还珠| 腿肿脚肿是什么病的前兆| 什么是淋巴| 尿的是白色米汤是什么病| 胸闷要做什么检查| 轻度脂肪肝有什么症状| 铂金是什么材质| 肾构错瘤要注意什么| 秋天的风像什么| 自渎什么意思| 喝陈皮水有什么好处| 雷锋属什么生肖| esp是什么意思| 松塔有什么用| 猪肝色是什么颜色| 11.18是什么星座| 生理期不能吃什么| 卵泡生成素高是什么原因| 五台山在什么地方| 谈恋爱是为了什么| 贪恋是什么意思| 月寸读什么| 生肠是什么| 三伏天什么时候最热| 样本是什么意思| ab型血可以接受什么血型| 佞臣是什么意思| 献血有什么坏处| 装是什么意思| 5月23日是什么星座| 经常落枕是什么原因引起的| 生化八项是检查什么| 基因病是什么意思| 孵化器公司是干什么的| 检查血常规挂什么科| 名节是什么意思| 喜新厌旧是什么生肖| 纹眉失败擦什么淡化| 请人帮忙用什么词| 为什么老是肚子疼| 大器晚成什么意思| 反流性食管炎挂什么科| 什么菜是发物不能吃| 什么情况下吃速效救心丸| 武夷肉桂茶属于什么茶| 考试前吃什么提神醒脑| 睾酮是什么| 巴利属于什么档次的| 尿的正常颜色是什么样| 十二生肖各代表什么花| 社会公德的主要内容是什么| 创伤性关节炎有什么症状| 孕妇什么水果不能吃| 万马奔腾是什么生肖| 低血压高什么原因| 手胶是什么| 一龙一什么填十二生肖| 前列腺挂什么科| 女人做梦梦到蛇是什么意思| 笔画最多的字是什么| 智齿发炎吃什么| 巴子是什么意思| o型血不能和什么血型的人生孩子| 多吃蔬菜对身体有什么好处| 对头是什么意思| 转氨酶偏高有什么症状| 梵文是什么意思| vampire是什么意思| 眼底出血有什么症状| 杏仁是什么树的果实| 心律不齐吃什么食物好| 奶粉水解什么意思| 每天吃葡萄有什么好处和坏处| 柠檬苦是什么原因| 太多的借口太多的理由是什么歌| ige高是什么意思| 豆五行属什么| 离线缓存是什么意思| 台风什么时候到上海| 山水有相逢是什么意思| 孕妇建档是什么意思| 老司机什么意思| 9月份什么星座| 伽马刀是什么| 什么是生物制剂| 什么是氮肥| 观音菩萨是保佑什么的| 双克是什么药| 年柱金舆是什么意思| 自给自足是什么意思| 霉菌性阴道炎有什么症状| 胃酸吃什么药效果最好| 发热挂什么科| 66年属马是什么命| 上升星座是什么| 高血压可以吃什么| 瘦人吃什么能长胖| 狐臭是什么人种的后代| 样本是什么意思| 沉不住气什么意思| 做胃镜之前需要做什么准备| 为什么月经每个月提前| 多金是什么意思| 怀孕打黄体酮针有什么作用| 海绵是什么材料做的| 叶五行属什么| 为什么清真不吃猪肉| 精子是什么样的| 腕管综合征吃什么药| 烟嗓是什么意思| 胃胀是什么症状| 梦见坟墓是什么预兆| 属虎的幸运色是什么颜色| 什么的长江| 肺胀是什么病| 左侧肋骨下方疼痛是什么原因| 人体缺甲是什么症状| 印度以什么人种为主| 玻璃什么时候传入中国| 阁是什么意思| 胚由什么发育而来| 君王是什么意思| 憋气2分钟算什么水平| 肠粉是什么做的| 竹子可以做什么玩具| 姨太太是什么意思| 舌根发硬是什么原因| 兰花用什么肥料最好| 胃癌挂什么科| 一个金字旁一个川读什么| 参保是什么意思| 梅毒什么症状| 脚心发凉是什么原因| 天天喝白酒对身体有什么危害| 什么花最大| 乔字五行属什么| 才美不外见的见是什么意思| 口腔挂什么科| les是什么意思| 女人带貔貅有什么讲究| 健康证长什么样| 木是什么颜色| 梦见老虎是什么预兆| 腿麻脚麻用什么药能治| 了是什么词性| 胎记是什么| 血热吃什么药好| 赎罪是什么意思| 枇杷是什么季节的水果| 月子吃什么最下奶| pbc是什么| 肝脏在人体的什么位置| 年柱比肩是什么意思| bb粥指的是什么意思| 机油什么牌子的好| 三白眼是什么意思| 手电筒的金属外壳相当于电路中的什么| 皮肤发白一块一块的是什么病| 门静脉增宽是什么意思| 暗卫是什么意思| 农历9月17日是什么星座| 什么是什么| 12月10日什么星座| 606是什么意思| 脾虚吃什么| 胡萝卜含有什么维生素| 金族念什么| 什么是阴唇| 神母是什么病| 扬琴属于什么乐器| tpo是什么| 梦见蛇是什么意思| 9月24号什么星座| 骨加后念什么| 疏通血管吃什么药最好| 药店最怕什么样的举报| 情人和小三的区别是什么| rem睡眠是什么意思| 注会什么时候考试| 早上打碎碗是什么兆头| 甘油三酯高吃什么药效果好| 安吉白茶属于什么茶| 存是什么生肖| 咳嗽咳到吐是什么原因| 吃什么水果可以美白| 弱肉强食是什么意思| 消化道出血吃什么药| 桃花长什么样| 门良念什么| 头骨凹陷是什么原因| 什么样的天安门| 四月十六日是什么星座| 脑内多发缺血灶是什么意思| 生殖激素常规检查是查什么的| 拔罐后发痒是什么原因| 梦见吃酒席是什么预兆| 肾虚吃什么食物能补| 肝穿刺检查是什么意思| 中性粒细胞百分比低是什么原因| 电商五行属什么| 什么瓜不能吃| 缺蛋白质吃什么补得快| 藏在我回忆里的那个人什么歌| 什么情况会导致月经推迟不来| 什么食物含钙| sk是什么意思| 缺锌会导致什么| 有什么神话故事| 迪士尼狗狗叫什么名字| 痔疮是什么样子| 照影是什么检查| 男性内分泌失调吃什么药| 转载是什么意思| 血压高是什么原因引起的| 落花生为什么叫落花生| 为什么我| 娇小是什么意思| 梦见买手表是什么预兆| 痤疮用什么药| 什么是点映| 男性尿频尿急吃什么药| 沫沫是什么意思| 为什么插不进去| xg是什么意思| 支气管炎吃什么药有效| 欲钱知吃月饼是什么生肖| 头皮长疙瘩是什么原因| 斑秃是什么原因引起的| 橙色预警是什么级别| 凝血高是什么原因| 四级警长是什么级别| dr是什么| 原始鳞状上皮成熟是什么意思| 百度
rfc:asymmetric-visibility-v2

王沪宁参加青海代表团审议

Introduction

百度 四是与内部疏导和外部引导相结合。

PHP has long had the ability to control the visibility of object properties -- public, private, or protected. However, that control is always the same for both get and set operations. That is, they are “symmetric.” This RFC proposes to allow properties to have separate (“asymmetric”) visibility, with separate visibility for read and write operations. The syntax is mostly borrowed from Swift.

Proposal

This RFC provides a new syntax for declaring the “set” operation visibility of an object property. Specifically:

class Foo
{
    public private(set) string $bar = 'baz';
}
 
$foo = new Foo();
var_dump($foo->bar); // prints "baz"
$foo->bar = 'beep'; // Visibility error

This code declares a property $bar that may be read from public scope but may only be modified from private scope. It may also be declared protected(set) to allow the property to be set from any protected scope (that is, child classes).

The behavior of the property is otherwise unchanged, aside from the restricted visibility.

Asymmetric visibility properties may also be used with constructor property promotion:

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

Abbreviated form

One of the most common use cases, we expect, will be a property that is publicly readable but has a reduced set-visibility. PHP properties are already implicitly publicly readable if not otherwise specified. To support this case, if a set visibility is specified then a public get visibility may be omitted.

That is, the following pairs are equivalent:

public private(set) string $foo;
private(set) string $foo;
 
public protected(set) string $foo;
protected(set) string $foo;

In practice, this means a “limited mutability” struct class could be implemented as follows:

class Book
{
    public function __construct(
        private(set) string $title,
        private(set) Author $author,
        private(set) int $pubYear,
    ) {}
}

Which is very similar to the common pattern of all-readonly properties, but does allow for controlled mutation within the class's own methods.

References

While a reference to a property with restricted set visibility may still be obtained, it may only be obtained from a scope that would allow writing. Put another way, obtaining a reference to a property follows the set visibility, not the get visibility. Trying to obtain a reference to a property with a more restrictive scope will result in an error.

For example:

class Foo
{
    public protected(set) int $bar = 0;
 
    public function test() {
        // This is allowed, because it's private scope.
        $bar = &$this->bar;
        $bar++;
    }
}
 
class Baz extends Foo
{
    public function stuff() {
        // This is allowed, because it's protected scope.
        $bar = &$this->bar;
        $bar++;
    }
}
 
$foo = new Foo();
 
// This is fine, because the update via reference is 
// inside the method, thus private scope.
$foo->test();
 
// This is also fine.
$baz = new Baz();
$baz->stuff();
 
// Getting this reference is not allowed here, because this is public
// scope but the property is only settable from protected scope.
$bar = &$foo->bar;

Array properties

As with property hooks, arrays require special consideration with asymmetric visibility, and for the same reason: writing to an array property technically involves obtaining a reference to it first, which as noted in the previous section means it will follow set visibility, not get visibility. That means an array property may not be appended or written to unless it's public-set.

class Test
{
    public function __construct(public private(set) array $arr = []) {}
 
    public function addItem(string $v): void
    {
        // This is in private scope, so fully legal.
        $this->arr[] = $v;
    }
}
 
$t = new Test();
 
$t->addItem('beep'); // Legal
var_dump($t->arr);   // Legal
$t->arr[] = 'boop';  // Not allowed.

This behavior should be less surprising than with hooks, since writing to an array with private set-visibility is already self-evidently wrong.

Object properties

If the property is an object, the restricted visibility applies only to changing the object referenced by the property. It does not impact the object itself. That is consistent with the behavior of the readonly marker.

Example:

class Bar
{
    public string $name = 'beep';
}
 
class Foo
{
    public private(set) Bar $bar;
}
 
$f = new Foo();
 
// This is allowed
$f->bar->name = 'boop';
 
// This is NOT allowed
$f->bar = new Bar();

Permitted visibility

The set visibility, if specified explicitly, MUST be equal to or lesser than the main (get) visibility. That is, protected public(set) string $foo is not allowed.

Explicitly setting the get and set visibilities to the same scope is redundant, but not harmful. However, it is sometimes necessary with readonly properties. (See the section below on readonly.)

Inheritance

PHP already allows child classes to redeclare parent class properties, if and only if they have the same type and their visibility is the same or wider. That is, a protected string $foo can be overridden with public string $foo but not private string $foo. This RFC continues that rule, but independently for get and set operations.

One caveat, however, is that, currently, a private field is “shadowed” if redeclared in a child class; its visibility is not widened, it's a completely different property. If a property is protected private(set) only, then it's not clear if a reimplementation should create a new property (following the private rule) or not (following the protected rule). Neither one makes sense. Additionally, a private field implies the developer doesn't want to allow others to modify it, even if they extend it, and that intent should be respected.

For that reason, a private(set) property is automatically final and may not be redeclared at all. (Explicitly declaring such a property final is still allowed, just redundant.)

In code:

class A {
    private string $foo;
}
class B extends A {
    public private(set) string $foo; // Implicitly final
}
 
class C extends A {
    public protected(set) string $foo;
}
 
class D extends C {
   public string $foo;
}
 
class E extends B {
    public protected(set) $foo; // Illegal, as it's widening a private/final property.
}

In this case, B::$foo is a new variable that shadows A::$foo. (That is existing PHP behavior.) So is C::$foo. D::$foo is the same property as C::$foo, just with a wider visibility. B itself can be extended, but the $foo property cannot be redeclared in a child of B.

Narrowing the visibility is not allowed.

class A {
    public string $foo;
}
 
class B extends A {
    // This is an error, as it's narrowing the visibility.
    public protected(set) string $foo;
}

Note that, for the purposes of visibility inheritance, the absence of an operation is treated as “never” (or narrower than private). That means the following is allowed, because in the parent class there is no set operation at all, so child visibility on set will be wider than that.

   class P {
       public $answer { get => 42; }
   }
   class C extends P {
       public protected(set) $answer { get => 42; set => print "Why?"; }
   }

Interaction with property hooks

The Property Hooks RFC introduced the ability to insert arbitrary behavior into the get or set behavior of a property. Hooks have no impact on who may access a property, just on what happens when they do.

In contrast, asymmetric visibility allows varying who may read and who may write a property independently, but has no impact on what happens when they are accessed legally.

In short, the behavior of asymmetric visibility and property hooks do not interact at all, and both are fully independent of each other.

There is one caveat regarding virtual properties that have no set operation. If there is no set operation defined on a property, then it is nonsensical to specify a visibility for it. That case will trigger a compile error. For example:

// This will generate a compile error, as there is 
// no set operation on which to specify visibility.
class Universe
{
    public private(set) $answer { get => 42; }
}

Interaction with interface properties

The Property Hooks RFC also introduced the ability for interfaces and abstract classes to declare a requirement for a public or protected (for abstract classes only) property, with get and set operations separate. It is a deliberately “lightweight” requirement. As noted in that RFC, it may be satisfied by either a hook or a traditional property, as long as the operation is available in the relevant scope. One reason for the separation of get and set requirements was to enable asymmetric visibility to satisfy the requirement as well.

For example, the following is fully legal:

interface Named
{
    public string $name { get; }
}
 
class ExampleA implements Named
{
    public protected(set) string $name;
}
 
class ExampleB implements Named
{
    public string $name { get => 'Larry'; }
}
 
class ExampleC implements Named
{
    public string $name;
}
 
class ExampleD implements Named
{
    public readonly string $name;
}

In each case, ExampleX::$name can be read from public scope, so the interface is satisfied.

If a property on an interface requires public set, however, then specifying asymmetric visibility is not permitted, except in the special case of readonly (below). (Hooks, of course, are.)

interface Aged
{
    public string $age { get; set; }
}
 
class ExampleE implements Aged
{
    // Error, because it is not publicly settable.
    public protected(set) $age;
}

Relationship with readonly

The readonly flag, introduced in PHP 8.1, is really two flags in one: write-once and private(set). While that is sometimes sufficient, there are cases where protected-set is desired, and while few, there are use cases for public-set-once. However, it also has special case handling to allow a readonly property to be overridden by a child class without creating an entirely new property. This effectively sidesteps the whole point of private anyway. It also would create an inconsistency with an explicit private(set), which as noted above is necessarily also final.

We have decided the best way forward is to change the behavior of readonly to imply protected(set), not private(set). That eliminates the inconsistency in readonly's existing behavior, as well as eliminating an inconsistency with private(set).

A readonly property may still be explicitly declared private(set), in which case it will also be implicitly final. That is closer to what the intended behavior would have been without the odd workaround.

If a readonly property is declared private generally, with no qualifier, then it will also be private(set), and thus final.

In code:

// These create a public-read, protected-write, write-once property.
public protected(set) readonly string $foo;
public readonly string $foo;
readonly string $foo;
 
// These creates a public-read, private-set, write-once, final property.
public private(set) readonly string $foo;
private(set) readonly string $foo;
 
// These create a public-read, public-write, write-once property.
// While use cases for this configuration are likely few, 
// there's no intrinsic reason it should be forbidden.
public public(set) readonly string $foo;
public(set) readonly string $foo;
 
// These create a private-read, private-write, write-once, final property.
private private(set) readonly string $foo;
private readonly string $foo;
 
// These create a protected-read, protected-write, write-once property.
protected protected(set) readonly string $foo;
protected readonly string $foo;
 
// This is illegal - set cannot be wider than get.
protected public(set) readonly string $foo;

If a class is marked readonly, then by design its impact is identical to if every property was individually marked readonly. There is no special logic there.

Interaction with __set and __unset

The behavior of __set in PHP today is rather inconsistent. When accessing a property, if __set is defined, it will be called if:

  1. The accessed property does not exist.
  2. The accessed property's visibility is tighter than the calling scope.
  3. The property has been explicitly unset()

The introduction of asymmetric visibility affects the second case, as it's possible now for a property to be visible but not writeable. We have opted to not modify the logic; that is, it is only the “overall” (get) visibility that can trigger calling __set. A public protected(set) property will not trigger a __set method if written to publicly; it will simply error, as though __set were not defined. A protected private(set) will, however, to be consistent with current behavior.

This approach has the least impact on existing behavior, including zero BC breaks for existing code. While we do not believe it would be appropriate to expand case 2 to be triggered by set-scope specifically, it would be possible to do in the future without a BC break (as it would change an error pathway to a functional pathway) if there is a consensus to do so.

Conversely, an argument can be made that, with the advent of property types, property hooks, and asymmetric visibility, the use cases for the fallback to __set on a defined property are now better covered by dedicated, more self-descriptive functionality, and case 3 above is no longer necessary. (That's one of the reasons to not expand case 2 now.) That would be a larger discussion that is out of scope for this RFC, so it makes no changes to that logic at this time.

Typed properties

Asymmetric visibility is only compatible with properties that have an explicit type specified. This is mainly due to implementation complexity. However, as any property may now be typed mixed and defaulted to null, that is not a significant limitation.

Static properties

This functionality applies only to object properties. It does not apply to static properties. For various implementation reasons that is far harder, and also far less useful. It has therefore been omitted from this RFC.

Reflection

The ReflectionProperty object is given two new methods: isProtectedSet(): bool and isPrivateSet(): bool. Their meaning should be self-evident.

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

Additionally, the two constants ReflectionProperty::IS_PROTECTED_SET and ReflectionProperty::IS_PRIVATE_SET are added. They are returned from ReflectionProperty::getModifiers(), analogous to the other visibility modifiers.

Modifying asymmetric properties via ReflectionProperty::setValue() is allowed, just as it is for protected or private properties, even outside of the classes scope.

Similarly, other techniques that bypass visibility controls, such as binding a closure to an object, will also work as expected: Once bound, the closure will have access to private variables.

Syntax discussion

Asymmetric visibility exists as a feature in several languages, most notably Swift, C#, and Kotlin. The syntactic structure varies, however. Translated to PHP, the two models would look like:

// Prefix-style:
class A
{
    public private(set) string $name;
}
 
// Hook-embedded-style:
class A
{
    public string $name { private set; }
}

In Prefix style, the visibility is an aspect of the property itself. In Hook-embedded style, the visibility is an aspect of the property's set hook. We believe that, for PHP, Prefix-style (presented here) is the superior approach, for a number of reasons.

Prefix-style is more visually scannable

With Prefix-style, reading a property definition from left to right, one is presented with all visibility options together. By the time the user has reached the $, they know all visibility information. With Hook-embedded style, set visibility may or may not be known. It would appear at the end of the line, whereas get visibility is at the start of the line.

Worse, if there are actual hook implementations, the set visibility may be several lines later!

class PrefixStyle
{
    // All visibility is together in one obvious place.
    public private(set) string $phone {
        get {
            if (!$this->phone) {
                return '';
            }
            if ($this->phone[0] === 1) {
                return 'US ' . $this->phone;
            }
            return 'Intl +' . $this->phone;
        }
 
       set {
            $this->phone = implode('', array_filter(fn($c) => is_numeric($c), explode($value)))
        }
    }
}
 
class HookEmbeddedStyle
{
    public string $phone {
        get {
            if (!$this->phone) {
                return '';
            }
            if ($this->phone[0] === 1) {
                return 'US ' . $this->phone;
            }
            return 'Intl +' . $this->phone;
        }
 
        // The set visibility is 10 lines away from the get visibility!
        private set {
            $this->phone = implode('', array_filter(fn($c) => is_numeric($c), explode($value)))
        }
    }
}

Prefix-style is shorter

While not the most critical distinction, in the past, brevity of syntax has often been a consideration. In this case, the prefix-style is somewhat shorter:

public private(set) string $name;
public string $name { private set; }

This is more apparent in the abbreviated form, which is only viable on the prefix-style:

private(set) string $name;
public string $name { private set; }
var string $name { private set; }

Note that if both set-visibility and a set hook are implemented, it's possible that the hook-style version would be slightly shorter, but only when the overall code including hook body is long enough that 1-2 characters wouldn't matter.

Prefix-style doesn't presume a connection with hooks

As noted above in “Interaction with hooks”, visibility controls exist independently hooks. In fact, as implemented they do not interact at all. Using hook syntax for visibility controls, therefore, is surprising and confusing.

There is a mental model in which it is logical; that is, if the {} after the property is considered not the “hook block” but the “operations configuration” block. The “hook block” is then only the => or {} on the right of the operation name, whereas modifiers are on the left. That implementation would be straightforward.

However, that mental model is non-obvious, and the alternative mental model (that the {} block on a property indicates the presence of hooks) is equally valid. As someone reading the code for the first time, it is not at all obvious which interpretation of the syntax should be correct.

With the Prefix-style syntax, this problem does not exist and the code's meaning is self-evident. There is only one reasonable mental model: visibility modifiers go on the left, hooks go on the right.

It's non-obvious in Hook-embedded style what hook behavior should be implied

One of the caveats of hooks is that, sometimes, references on properties must be prevented. (The reasons for that are explained at length in the hooks RFC.) For example, this would be illegal:

class A
{
    public array $arr {
        set {
          if (array_filter(is_int(...), $value) === $value) {
            $this->arr = $value;
          }
          throw new \Exception();
        }
    }
}
 
$a = new A();
 
// This is illegal, as it would bypass the set hook.
$a->arr[] = 5;

So “arrays with hooks can do less” is already an established fact of the language. However, if the hook-style syntax is used for visibility:

class A
{
    public array $arr { protected set; }
}
 
class B extends A
{
    public function add(int $val)
    {
        // Should this be legal?
        $this->arr[] = $val;
    }
}
 
$b = new B();
 
$b->add(5);

The syntax suggests that there is a set hook, and thus the assignment should not be allowed. However, there isn't really a set hook, and thus assignment should be allowed. Or maybe it shouldn't be, because there is a hook defined, even if it's the default.

As noted above, there are mental models in which it's reasonable to assume there's no hook. However, that mental model is non-obvious, and there are equally valid mental models where it's reasonable to assume there is a hook. With the Prefix-style, there is only one mental model, and it's self-evident that references and array assignment are legal. There is no confusion.

Summary

For all of the above reasons, we believe that the proposed syntax, Prefix-style, is the objectively better approach for PHP.

Use cases and examples

Between readonly and property hooks, PHP already has a number of ways to do “advanced things” with properties. However, there are still gaps in capability, which this RFC aims to fill.

Readonly is limited

readonly offered the potential to have public properties that are guaranteed to not change unexpectedly. This has been a major benefit, and allowed the removal of a lot of needless boilerplate code. However, it also somewhat over-shoots: It prevents a property from changing at all, rather than just “unexpectedly.” While fully immutable objects have their place, they are not always the answer. It is still often desireable to have a public property (for ease of read) without making it write-once.

For example:

class Record
{
    private bool $dirty = false;
 
    private array $data = [];
 
    public function set($key, $val): void
    {
        $this->data[$key] = $val;
        $this->dirty = true;
    }
 
    public function isDirty(): bool
    {
        return $this->dirty;
    }
 
    public function save(): void
    {
        if ($this->dirty) {
            // Do something to save the object.
            $this->dirty = false;
        }
    }
}

It's very tempting to make $dirty a public property, as the dirty status of the object is a “property” of it. Especially with hooks, such a desire will become more common. However, that cannot be done with public or readonly. Making the property public would open it up to modification from anyone at any time, whereas making it readonly would make it impossible to unset in save(), and require using “uninitialized or true” as a quasi-boolean state. Both options are bad.

With asymmetric visibility, it can be easily simplified to:

class Record
{
    public private(set) bool $dirty = false;
 
    private array $data = [];
 
    public function set($key, $val): void
    {
        $this->data[$key] = $val;
        $this->dirty = true;
    }
 
    public function save(): void
    {
        if ($this->dirty) {
            // Do something to save the object.
            $this->dirty = false;
        }
    }
}

Which now offers a publicly-readable marker, internally modifiable, with no opportunity for it to change in an uncontrolled way, without any need for odd code contortions.

Readonly cannot use a custom sentinel

A common technique in many languages is to have sentinel values to indicate that a value is unset, invalid, or otherwise not a real value. Using a value within the scope of the property's type (e.g. 0, empty string, etc.) has a host of issues, discussed elsewhere. null has historically been used for that in PHP, although that has its share of problems, as discussed in Much Ado about Null. More recently, it has become increasingly popular to use a custom single-value enum as a sentinel value instead, to provide more contextual information.

public ?int $val = null;
 
enum Missing
{
    case Missing;
}
 
public int|Missing $val = Missing::Missing;

However, neither of those techniques is usable on a readonly property. Rather, readonly relies on the implicit and rather weird “uninitialized” state, available only to typed properties, to indicate that it is not yet populated with a valid value. Assigning either null or a custom sentinel to a readonly property would disallow it from being correctly set in the future. However, as of PHP 8.3, readonly is the only way to have a public-read, private-write property. That means either relying on uninitialized as a sentinel (the tooling support for which is quite lacking) or foregoing a public-read property just to be able to assign and re-assign a sentinel value.

Asymmetric visibility completely solves that issue, as properties can then be defaulted to a sentinel value (null or custom) while allowing only the object itself to fully-initialize the value later.

class A
{
    public private(set) int|Missing $value = Missing::Missing;
}

Tooling makes using readonly outside of constructors hard

There is a common but not universal belief that readonly properties, because of the weird “uninitialized” state described above, MUST always be set in the constructor, and not doing so is always an error. The language does not enforce this, but many static analyzer tools do. While there is validity to that argument usually, there are ample exceptions where that is not possible. (E.g., objects that necessarily require multi-stage construction, such as if you want to provide reflection information to an attribute.) That means, currently, a public-read, not-public-write property that needs to be set post-constructor is actively fighting against the language and tooling. Instead, the property must be left uninitialized, care must be taken when trying to read from it later to avoid uninitialized values, and you're fighting against common tooling.

A public private(set) property has none of these issues. As noted above, it can happily use a sentinel value (null or otherwise) to indicate that it is not yet set, or have a reasonable default, and still allow a “second set” when a real value is available. That is, it has all the benefits of readonly that we actually care about (the value cannot change externally) with none of the limitations (we can still control when it gets set ourselves within the class).

Readonly is incompatible with inheritance

As noted previously, the readonly flag is, on its own, two flags in one: write-once and private(set). While both have their use cases, there are ample times where only one or the other is desired. For instance, the following code from Crell/Serde (slightly simplified for this example) wants to use readonly, but because of the implied private(set) it causes issues:

abstract class Serde
{
    // ...
    protected readonly TypeMapper $typeMapper;
 
    protected readonly ClassAnalyzer $analyzer;
}
 
class SerdeCommon extends Serde
{
    // This must be redefined here so that it 
    // can be set from the constructor.
    protected readonly TypeMapper $typeMapper;
 
    public function __construct(
        // Normally repeating a property as a promoted
        // argument is an error, BUT because the property
        // is in the parent, this overrides it with a
        // new property definition that is now local
        // to this class.
        protected readonly ClassAnalyzer $analyzer = new Analyzer(),
        array $handlers = [],
        array $formatters = [],
        array $typeMaps = [],
    ) {
        // We just want to do this...
        $this->typeMapper = new TypeMapper($typeMaps, $this->analyzer);
 
        // ...
    }
}

With asymmetric visibility, the readonly usage here can be replaced with protected protected(set) or readonly protected protected(set), avoiding the need to double-declare properties. (There's actually about 6 such properties in practice, so the simplification is larger than shown here.)

abstract class Serde
{
    // ...
    protected protected(set) readonly TypeMapper $typeMapper;
 
    protected protected(set) readonly ClassAnalyzer $analyzer;
}

Hooks can be verbose

While every effort has been made to make hooks as compact as reasonable, there are some use cases that are still more clumsy than they need to be. For example, asymmetric visibility can be emulated with hooks like so:

class NamedThing
{
    private string $_name;
 
    public string $name { get => $this->_name; }
 
    public function __construct(string $name)
    {
        $this->_name = $name;
    }
}

But that's a lot of non-obvious work, and does have a small performance impact. It is much more straightforward to do this:

class NamedThing
{
    public function __construct(public private(set) string $name) {}
}

Backward Incompatible Changes

None. This syntax would have been a parse error before.

Proposed PHP Version(s)

PHP 8.4

RFC Impact

Future Scope

This RFC is kept very simple. However, it does allow for future expansion.

Alternate operations

At this time, there are only two possible operations to scope: read and write. In concept, additional operations could be added with their own visibility controls. Possible examples include:

  • protected(&get) - Vary whether a reference to a property can be obtained independently of getting the value. (Would override the set visibility if used.)
  • private(setref) - Allows a property to be set by reference only from certain scopes.

This RFC does NOT include any of the above examples; they are listed only to show that this syntax supports future expansion should a use be found.

Additional visibility

Should PHP ever adopt packages and package-level visibility, this syntax would be fully compatible with it. For example, public package(set) would be a natural syntax to use.

This RFC does NOT include any discussion of such expanded visibility definition, just notes that it in no way precludes such future developments.

Proposed Voting Choices

This is a simple yes-or-no vote to include this feature. 2/3 majority required to pass.

Implement asymmetric visibility?
Real name Yes No
alcaeus (alcaeus)  
alec (alec)  
ashnazg (ashnazg)  
beberlei (beberlei)  
bukka (bukka)  
bwoebi (bwoebi)  
cpriest (cpriest)  
crell (crell)  
derick (derick)  
ericmann (ericmann)  
galvao (galvao)  
girgias (girgias)  
ilutov (ilutov)  
jimw (jimw)  
kalle (kalle)  
kguest (kguest)  
kocsismate (kocsismate)  
levim (levim)  
mauricio (mauricio)  
nicolasgrekas (nicolasgrekas)  
nielsdos (nielsdos)  
ocramius (ocramius)  
petk (petk)  
ramsey (ramsey)  
reywob (reywob)  
saki (saki)  
santiagolizardo (santiagolizardo)  
seld (seld)  
sergey (sergey)  
theodorejb (theodorejb)  
timwolla (timwolla)  
Final result: 24 7
This poll has been closed.

References

This syntax is borrowed directly from Swift's access control system.

Syntax decisions in this RFC are supported by a poll conducted in September 2022. The results were posted to the Internals list.

rfc/asymmetric-visibility-v2.txt · Last modified: by 127.0.0.1

?
高处不胜寒什么意思 hba是什么意思 歼31为什么没消息了 国防部长有什么权利 棱长是什么意思
ep是什么 鱼腥草治什么病 darling什么意思 颠三倒四是什么意思 女性去泰国要注意什么
自来水养鱼为什么会死 11月什么星座 c是什么车 2月20号是什么星座 牙髓炎是什么
感冒头晕是什么原因 潜阳是什么意思 红景天是什么 ppi是什么 在家无聊可以做什么
左手经常发麻是什么原因引起的hcv7jop9ns5r.cn 子宫肌瘤吃什么好hcv7jop6ns3r.cn peg是什么意思hcv7jop7ns4r.cn 更年期吃什么药xianpinbao.com 云南有什么好吃的jinxinzhichuang.com
小麦是什么粮食hcv8jop3ns0r.cn 扁桃体发炎发烧吃什么药hcv8jop6ns5r.cn ct和拍片有什么区别hcv8jop7ns8r.cn 一库是什么意思hcv8jop8ns6r.cn 结膜炎滴什么眼药水hcv8jop3ns0r.cn
眼皮浮肿是什么原因hcv9jop3ns3r.cn 孕妇耻骨疼是什么原因hcv8jop5ns1r.cn 什么是邪淫imcecn.com 希尔福是什么药hcv8jop9ns4r.cn 人的牙齿为什么不能再生hcv7jop5ns2r.cn
兔子是什么意思ff14chat.com 黑洞里面有什么520myf.com 爱出者爱返福往者福来是什么意思hcv9jop6ns2r.cn hbeag阳性是什么意思hcv9jop7ns5r.cn hf医学上是什么意思hcv9jop4ns9r.cn
百度