-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Basic accessors implementation (WIP) #6873
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
a72a9b0
to
9866518
Compare
This doesn't properly handle auto-generated accessors yet.
This allows us to treat all accessors uniformly in most places. This would make it easier to support additional accessor kinds in the future.
This allows them to be used from reflection.
This can be supported, but would require additional implementation effort.
This is ugly, but necessary.
This matches the behavior we'd get with an implicit implementation, but also means that these would be less efficient :/
Apparently inlining zend_is_true() adversely affects isset() here.
This doesn't quite hold if we switch to a shadowed parent property. At the very least the error message may differ in the write case.
To be consistent with the treatment of abstract.
This was previously only checked for accessors.
<?php | ||
|
||
class Test { | ||
final private $prop; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still reading this rfc - final protected $prop { private get; private set; }
is allowed and would work similarly to a private final property in terms of forbidding redefining it in subclasses? The only major difference would be serialize() output and ReflectionProperty behavior.
I guess that's consistent with private final methods recently being forbidden.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. I was against that change at the time, but now we have to follow suit...
"Accessor in interface cannot be explicitly abstract. " | ||
"All interface members are implicitly abstract"); | ||
} | ||
accessor->flags |= ZEND_ACC_ABSTRACT; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it also make sense to either (1) forbid the final qualifier on accessors in interfaces for now, or (2) to remove the abstract flag from accessors declared on properties of interfaces?
It'd be useful to have tests of the intended behavior.
E.g. right now, it seems impossible to override just the setter for an interface with a final getter
php > interface I { public $prop1 { final get; }}
php > class A implements I { public $prop1; }
php > class B implements I { public $prop1 { set($value) { echo "set $value\n"; } } }
Fatal error: Class B contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (I::$prop1::get) in php shell code on line 1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Final + abstract is definitely supposed to be forbidden -- and interfaces are a form of abstract. But because the abstract is implicit, it wasn't enforced correctly... I've now added some additional checks and tests for this.
public $_byVal = []; | ||
public $byVal { | ||
get { return $this->_byVal; } | ||
set { $this->_byVal = $value; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to reject $this
and the names of superglobals if the implicit parameter name would be forbidden if used as an explicit parameter name (only for checking set {
)
php > class B { public $_SERVER { set ($_SERVER) { var_dump($_SERVER); } }}
Fatal error: Cannot re-assign auto-global variable _SERVER in php shell code on line 1
php > class A { public $_SERVER { set { var_dump($_SERVER); } }}
php > $a = new A(); $a->_SERVER = 123;
array(78) {
// ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implicit parameter name is (currently) always $value
, not the property name. As this goes through the normal compilation code for functions, it should also enforce all the usual rules for parameter names.
$test2->prop3; | ||
$test2->prop3 = 42; | ||
|
||
// TODO: This should probably be forbidden. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd agree as a maintainer of igbinary -- for computed properties such as prop3
- __serialize
and __unserialize
could be added by applications in cases where data serialized for older versions of the classes was serialized with values for properties that later became magic accessors. (and saved to a cache/db to be read much later on)
- The alternative of supporting it by calling
set
sounds much worse, since computedget
wouldn't be serialized and php is moving towards forbidding calling functions in unserialize on objects that haven't been fully initialized yet.
You've already forbidden overriding plain properties with accessor properties, so it doesn't seem likely for applications to set something they couldn't read with the same definition of a class and ancestors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You've already forbidden overriding plain properties with accessor properties, so it doesn't seem likely for applications to set something they couldn't read with the same definition of a class and ancestors.
You are allowed to override a property with implicit accessors with one with explicit accessors though, so there might be a problem there...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I've implemented now is that this is not allowed for purely virtual properties, but is allowed if you had a parent with backing storage. In that case the child will also have backing storage, and I think that's reasonable in that the child could be making use of the parent (once there is some way to do that...)
The abstract + final combination is usually prohibited when the flags are parsed in the first place, but here the abstract is implicit, so we need to check it explicitly.
However, it is allowed if there is a parent for which the property was non-virtual.
zend_error_noreturn(E_COMPILE_ERROR, "Abstract accessor cannot have body"); | ||
} | ||
if (accessor->flags & ZEND_ACC_PRIVATE) { | ||
// TODO Trait exception? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think a trait should be able to impose a private visibility. But a trait implementing class should be able to override any property visibility and decide to implement the abstract property itself with a private visibility.
I.e. the trait itself should not care about who can implement its property. It's none of his business and we should not make it seem like the trait had any say in that.
In short: if there is an abstract property, the class which implements the trait shall be able to a) define the property with any visibility or b) not implement it at all and the default visibility of the property is retained. In scenario b) private
does not make any sense and hence should be forbidden.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that traits can already define private abstract
methods, so I think allowing this would be in line with the current behavior. https://3v4l.org/FA0Ar#v8.1.5
@@ -381,6 +395,7 @@ typedef struct _zend_property_info { | |||
HashTable *attributes; | |||
zend_class_entry *ce; | |||
zend_type type; | |||
zend_function **accessors; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this some sort of memory optimization or why double indirect pointer instead of zend_function *accessors[ZEND_ACCESSOR_COUNT];
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking ahead to when we have more accessors, and didn't want to allocate storage for them unconditionally. A bit weird if only two are involved.
Maybe a nice option would be to use a flexible array member.
public $prop = 0 { | ||
get { return $this->_prop; } | ||
set { $this->_prop = $value; } | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public $prop = 0 { | |
get { return $this->_prop; } | |
set { $this->_prop = $value; } | |
} | |
public $prop { | |
get { return $this->_prop; } | |
set { $this->_prop = $value; } | |
} = 0; |
is allowed by C#, why such syntax is not supported? Supporting default values would be very helpful.
Does it have sense to consider not only {get; set;} pair but also {get; init;} for immutable structures like in it is done in C# 9.0? It is much more easy to add such feature on the initial phase of adding property accessors into php language. |
@nikic to say that a large part of the community desires this change is an understatement. Thank you for taking it on. This PR seems to have stagnated a bit and although it is unlikely I can offer any tangible assistance if there is please do a shout out. |
Having to switch back and forth between developing C# and PHP code every now and then, I would love to have properties in a way, C# does have. Would also make it way more easier and straightforward to migrate C# code to PHP. |
rfcs working on it: https://wiki.php.net/rfc/property-hooks |
Awesome feature! Very excited to be approved. |
when will this be a thing? |
The RFC asymmetric-visibility can be incorporated into this RFC: * The example below is not the best (I got it from the RFC). class User implements Named
{
private bool $isModified = false;
public function __construct(private string $first, private string $last) {}
public string $fullName {
// Override the "read" action with arbitrary logic.
public get => $this->first . " " . $this->last;
// Override the "write" action with arbitrary logic.
private set($value) => [$this->first, $this->last] = explode(' ', $value);
}
} Asymmetric visibility in hooks |
I really hope this gets accepted. My company surely would benefit, and i'm sure this would be one of the most exciting features ever. being able to seemlesly transition from simple constructor property promotion based objects to occasionally add some logic after the fact to some of them would be very in keeping with the php tradition of minimal unceremonious code most of the time. |
This is the one feature that I feel PHP really needs to finally become the end-all of languages for back-end web development, and maybe more. |
Already accepted with 'RFC Property hooks' based on this RFC. |
I suppose we can close this PR as obsolete by now. |
No description provided.