rfc:property_accessors
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
rfc:property_accessors [2021/05/03 17:08] – nikic | rfc:property_accessors [2022/04/28 09:30] (current) – Fix typos ilutov | ||
---|---|---|---|
Line 3: | Line 3: | ||
* Author: Nikita Popov < | * Author: Nikita Popov < | ||
* Proposed Version: PHP 8.1 | * Proposed Version: PHP 8.1 | ||
- | * Implementation: | + | * Implementation: |
- | * Status: | + | * Status: |
===== Introduction ===== | ===== Introduction ===== | ||
Line 363: | Line 363: | ||
</ | </ | ||
- | Specifying the same accessor multiple | + | Specifying the same accessor multiple |
=== By-reference getter === | === By-reference getter === | ||
Line 387: | Line 387: | ||
</ | </ | ||
- | These indirect | + | These indirect |
However, indirect array modification, | However, indirect array modification, | ||
Line 420: | Line 420: | ||
While nominally well-defined, | While nominally well-defined, | ||
+ | |||
+ | TODO: An open problem is how implicit by-value '' | ||
+ | |||
+ | <PHP> | ||
+ | class Test { | ||
+ | public $prop = null { get; private set; } | ||
+ | } | ||
+ | |||
+ | $test = new Test; | ||
+ | foreach ($test as &$prop) { | ||
+ | $prop = 1; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | This needs to be prevented in some way. This could either error, or it could silently return a value for properties for which no reference can be acquired. Otherwise it would no longer be possible to iterate arbitrary objects by-reference. | ||
=== Isset and unset === | === Isset and unset === | ||
Line 496: | Line 511: | ||
var_dump($test-> | var_dump($test-> | ||
</ | </ | ||
+ | |||
+ | === Interaction with by-reference getter === | ||
+ | |||
+ | It is worth pointing out that the combination of ''& | ||
+ | |||
+ | <PHP> | ||
+ | class Test { | ||
+ | public array $prop = [] { &get; private set; } | ||
+ | } | ||
+ | $test = new Test; | ||
+ | $test-> | ||
+ | $ref =& $test-> | ||
+ | $ref = [1, 2, 3]; // Allowed! | ||
+ | </ | ||
+ | |||
+ | It would be possible to make this do something more meaningful by making the " | ||
==== Inheritance ==== | ==== Inheritance ==== | ||
Line 508: | Line 539: | ||
class A { | class A { | ||
public $prop { | public $prop { | ||
- | get { echo __METHOD, " | + | get { echo __METHOD__, " |
- | set { echo __METHOD, " | + | set { echo __METHOD__, " |
} | } | ||
} | } | ||
class B extends A { | class B extends A { | ||
public $prop { | public $prop { | ||
- | set { echo __METHOD, " | + | set { echo __METHOD__, " |
} | } | ||
} | } | ||
Line 534: | Line 565: | ||
public int|string $covariant { get; } | public int|string $covariant { get; } | ||
// This property is useless, but will serve for the sake of illustration. | // This property is useless, but will serve for the sake of illustration. | ||
- | public int|string $contravariant { set {} } | + | public int|string $contravariant { set { /* ... */ } } |
} | } | ||
Line 634: | Line 665: | ||
<PHP> | <PHP> | ||
class A { | class A { | ||
- | | + | |
- | final public $prop { get; } | + | final public $prop2 { get; } |
} | } | ||
class B extends A { | class B extends A { | ||
- | // Illegal, | + | // Illegal, |
- | public $prop { set; } | + | public $prop1 { get; set; } |
+ | public $prop2 | ||
} | } | ||
</ | </ | ||
Line 757: | Line 789: | ||
This matches the current behavior of an incompatible (non-accessor) property in a used trait and a using class. I believe the current behavior is a bug and should be addressed generally. For now, this proposal is " | This matches the current behavior of an incompatible (non-accessor) property in a used trait and a using class. I believe the current behavior is a bug and should be addressed generally. For now, this proposal is " | ||
+ | |||
+ | === Private accessor shadowing === | ||
+ | |||
+ | Private accessors can be shadowed by accessors in child classes. In line with usual behavior, accessing the property from the class where the private accessor is defined, will use the private accessor rather than a public shadower in a child class: | ||
+ | |||
+ | <PHP> | ||
+ | class A { | ||
+ | public $prop { | ||
+ | get { echo __METHOD__, " | ||
+ | private set { echo __METHOD__, " | ||
+ | } | ||
+ | |||
+ | public function test() { | ||
+ | $this-> | ||
+ | $this-> | ||
+ | } | ||
+ | } | ||
+ | |||
+ | class B extends A { | ||
+ | public $prop { | ||
+ | get { echo __METHOD__, " | ||
+ | set { echo __METHOD__, " | ||
+ | } | ||
+ | } | ||
+ | |||
+ | $a = new A; | ||
+ | $a-> | ||
+ | $b = new B; | ||
+ | $b-> | ||
+ | |||
+ | // Prints: | ||
+ | // A:: | ||
+ | // A:: | ||
+ | // B:: | ||
+ | // A:: | ||
+ | </ | ||
+ | |||
+ | When accessed on an instance of '' | ||
+ | |||
+ | This should also extend to the case where the child class replaces the accessor property with an ordinary property: | ||
+ | |||
+ | <PHP> | ||
+ | class A { | ||
+ | public $prop { | ||
+ | get { echo __METHOD__, " | ||
+ | private set { echo __METHOD__, " | ||
+ | } | ||
+ | |||
+ | public function test() { | ||
+ | $this-> | ||
+ | $this-> | ||
+ | } | ||
+ | } | ||
+ | |||
+ | class B extends A { | ||
+ | public $prop; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | TODO: The current implementation does not handle this case correctly. | ||
=== TODO: Parent accessors === | === TODO: Parent accessors === | ||
Line 764: | Line 856: | ||
This syntax can't extend to other accessor types though, so those would either have no way to invoke the parent accessor, or invoke it implicitly. For possible '' | This syntax can't extend to other accessor types though, so those would either have no way to invoke the parent accessor, or invoke it implicitly. For possible '' | ||
- | Other syntax choices like '' | + | Other syntax choices like '' |
In any case, adding support for parent accessors will be technically non-trivial, | In any case, adding support for parent accessors will be technically non-trivial, | ||
Line 807: | Line 899: | ||
</ | </ | ||
- | However, it is possible to specify only a '' | + | However, it is possible to specify only a '' |
<PHP> | <PHP> | ||
Line 828: | Line 920: | ||
class Test { | class Test { | ||
// Illegal: Implicit get, explicit set. | // Illegal: Implicit get, explicit set. | ||
- | public string $prop { get; set {} } | + | public string $prop { |
+ | | ||
+ | | ||
+ | | ||
// Illegal: Implicit set, explicit get. | // Illegal: Implicit set, explicit get. | ||
- | public string $prop { get {} set; } | + | public string $prop { |
+ | | ||
+ | | ||
+ | | ||
} | } | ||
</ | </ | ||
Line 853: | Line 951: | ||
// Illegal: Default value on property with explicit accessors. | // Illegal: Default value on property with explicit accessors. | ||
public $prop = "" | public $prop = "" | ||
- | get {} | + | get { /* ... */ } |
} | } | ||
} | } | ||
Line 886: | Line 984: | ||
This limitation exists to prevent embedding of very large property declarations in the constructor signature. | This limitation exists to prevent embedding of very large property declarations in the constructor signature. | ||
- | === var_dump, get_object_vars() etc === | + | === var_dump(), array cast, foreach |
- | '' | + | '' |
<PHP> | <PHP> | ||
Line 902: | Line 1000: | ||
// | // | ||
// } | // } | ||
+ | |||
+ | var_dump((array) $test); | ||
+ | // array(1) { | ||
+ | // | ||
+ | // | ||
+ | // } | ||
+ | |||
+ | foreach ($test as $name => $value) { | ||
+ | echo " | ||
+ | } | ||
+ | // prop1: 42 | ||
</ | </ | ||
Line 921: | Line 1030: | ||
</ | </ | ||
- | This differs from the behavior of '' | + | This differs from the behavior of '' |
As we can only enter this kind of recursion if no backing property is present, using the same behavior as magic get/set would mean that a dynamic property with the same name of the accessor should be created. This is very inefficient, | As we can only enter this kind of recursion if no backing property is present, using the same behavior as magic get/set would mean that a dynamic property with the same name of the accessor should be created. This is very inefficient, | ||
Line 976: | Line 1085: | ||
We can see that using a property with automatically generated accessors is slightly slower than one without accessors (though the impact might be higher on compound operations). Explicit accessors are much more expensive, but cheaper than magic methods. Getter/ | We can see that using a property with automatically generated accessors is slightly slower than one without accessors (though the impact might be higher on compound operations). Explicit accessors are much more expensive, but cheaper than magic methods. Getter/ | ||
- | Explicit accessors are more expensive than methods, because they are executed through VM reentry, | + | Explicit accessors are more expensive than methods, because they are executed through VM reentry, need to manage recursion guards, and go through the slow-path of property access. |
===== Backward Incompatible Changes ===== | ===== Backward Incompatible Changes ===== | ||
Line 982: | Line 1091: | ||
No backwards-incompatible changes are known. Due to the existence of magic get/set the introduction of accessors also shouldn' | No backwards-incompatible changes are known. Due to the existence of magic get/set the introduction of accessors also shouldn' | ||
- | ==== Reserved | + | ==== Reserved |
The accessor names '' | The accessor names '' | ||
Line 998: | Line 1107: | ||
This is not a backwards-compatibility break, but mentioned for completeness. | This is not a backwards-compatibility break, but mentioned for completeness. | ||
+ | |||
+ | ===== Discussion ===== | ||
+ | |||
+ | This is a fairly complex proposal, and is likely to grow more complex as remaining details are ironed out. I have some doubts that the complexity is truly justified. | ||
+ | |||
+ | I think there are really two parts to this proposal, even if they are tightly related in the form presented here: | ||
+ | |||
+ | - Implicit accessors, which allow finely controlling property access. They support both read-only and private-write properties. | ||
+ | - Explicit accessors, which allow implementing arbitrary behavior. | ||
+ | |||
+ | My expectation is that implicit accessors will see heavy use, as read-only properties are a very common requirement. Explicit accessors on the other hand should be used rarely, and primarily as a means to maintain backwards-compatibility with an existing API. If it is known in advance that a property needs to be associated with non-trivial behavior, then it is preferable to implement it using methods in the first place. | ||
+ | |||
+ | We could likely get 80% of the value of accessors by supporting read-only properties and 90% by also supporting private-write properties. A previous proposal for read-only properties was the [[rfc: | ||
===== Vote ===== | ===== Vote ===== | ||
+ | TBD. |
rfc/property_accessors.1620061735.txt.gz · Last modified: 2021/05/03 17:08 by nikic