Skip to content

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

Closed
wants to merge 44 commits into from
Closed

Conversation

nikic
Copy link
Member

@nikic nikic commented Apr 16, 2021

No description provided.

@nikic nikic added the RFC label Apr 16, 2021
@nikic nikic marked this pull request as draft April 16, 2021 16:13
@nikic nikic force-pushed the accessors-2 branch 4 times, most recently from a72a9b0 to 9866518 Compare April 27, 2021 16:00
nikic added 22 commits May 4, 2021 14:50
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 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;
Copy link
Contributor

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.

Copy link
Member Author

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;
Copy link
Contributor

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

Copy link
Member Author

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; }
Copy link
Contributor

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) {
// ...

Copy link
Member Author

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.
Copy link
Contributor

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 computed get 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.

Copy link
Member Author

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...

Copy link
Member Author

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...)

nikic added 4 commits May 5, 2021 11:01
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?
Copy link
Member

@bwoebi bwoebi May 6, 2021

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.

Copy link
Member

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;
Copy link
Member

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];?

Copy link
Member Author

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.

Comment on lines +8 to +11
public $prop = 0 {
get { return $this->_prop; }
set { $this->_prop = $value; }
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.

@skie
Copy link

skie commented Jul 3, 2021

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.

@NeilMasters
Copy link

@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.

@UweKeim
Copy link

UweKeim commented Dec 22, 2022

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.

@mx03
Copy link

mx03 commented Dec 22, 2022

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
https://wiki.php.net/rfc/asymmetric-visibility

@rodrigoslayertech
Copy link

Awesome feature! Very excited to be approved.

@3UR
Copy link

3UR commented Aug 21, 2023

when will this be a thing?

@rodrigoslayertech
Copy link

rodrigoslayertech commented Aug 22, 2023

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 public get() ... private set($value)!
Once you set the visibility of the hooks, the visibility on the property will always be public.

@AwokeKnowing
Copy link

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.

@6XGate
Copy link

6XGate commented May 2, 2024

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.

@rodrigoslayertech
Copy link

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.

@bwoebi
Copy link
Member

bwoebi commented May 2, 2024

I suppose we can close this PR as obsolete by now.

@bwoebi bwoebi closed this May 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.