[RFC] New in initializers

php.internals

Nikita Popov

5 years ago
Hi internals, I would like to propose allowing the use of "new" inside various initializer expressions: https://wiki.php.net/rfc/new_in_initializers In particular, this allows specifying object default values for properties and parameters, and allows the use of objects as attribute arguments. The RFC is narrow in scope in that it only adds support for "new". An extension to other call kinds should be straightforward though. Regards, Nikita

Alexandru Pătrănescu

5 years ago
Hi, This looks very nice and I'm interested in further steps where not only new can be used :). The only thing I think it would be good to improve is to have a deterministic order for running initialization. Yes, this can be done at a later point, I guess. But maybe there is already an order of initialization right now and people would start replying on it and it would be good to mention it. Or maybe I didn't understand what this refers to: "this is not guaranteed behavior, and code should not rely on a specific point of evaluation." Also, in https://wiki.php.net/rfc/new_in_initializers#evaluation_of_expressions I think that for the static initialization the text should say "are evaluated once." instead of "are evaluated once per request." so we are more general, including CLI cases. I'm used with dynamic initialization coming from other languages, learning it in Java 13-14 years ago, including the clearly defined order of initialization. Regards, Alex On Wed, Mar 3, 2021 at 5:04 PM Nikita Popov <nikita.ppv@gmail.com> wrote:

Nikita Popov

5 years ago
On Wed, Mar 3, 2021 at 4:28 PM Alexandru Pătrănescu <drealecs@gmail.com> wrote:
> Hi, > > This looks very nice and I'm interested in further steps where not only > new can be used :). > > The only thing I think it would be good to improve is to have a > deterministic order for running initialization. > Yes, this can be done at a later point, I guess. But maybe there is > already an order of initialization right now and people would start > replying on it and it would be good to mention it. > Or maybe I didn't understand what this refers to: "this is not guaranteed > behavior, and code should not rely on a specific point of evaluation." >
Which particular cases would you like to see specified? There are five cases that have clearly defined behavior, and that I could explicitly specify if desired: * Non-class constants: Are evaluated immediately when declared (i.e. when control flow reaches the declaration). * Attribute arguments: Are evaluated in the order of the arguments. * Parameter defaults: Are evaluated in the order of the parameters. * Non-static property defaults: Are evaluated in order of declaration, with parent properties first. The constructor is run after defaults are evaluated. * Static variables: Are evaluated immediately when declared (i.e. when control flow reaches the declaration). And then there are the two problematic cases: Class constants and static properties. Currently, PHP evaluates these semi-lazily. All class constants and static properties are evaluated at the same time, on first "use" of the class. I would consider this to be something of an implementation detail. That's what I meant by that sentence. Now, if we allow "new" expressions, then I could see an argument in favor of requiring class constant and static property initializers to be evaluated eagerly, i.e. directly after the class has been declared. This would be a (minor) backwards-compatibility break, because invalid constant/property declarations would error out immediately, even if they don't get used. However, I do think that this would be the most predictable behavior once potentially side-effecting expressions are involved (we already support side-effecting expressions right now, but less explicitly). Regards, Nikita Also, in

Alexandru Pătrănescu

5 years ago
On Wed, Mar 3, 2021 at 5:49 PM Nikita Popov <nikita.ppv@gmail.com> wrote:
> On Wed, Mar 3, 2021 at 4:28 PM Alexandru Pătrănescu <drealecs@gmail.com> > wrote: > >> Hi, >> >> This looks very nice and I'm interested in further steps where not only >> new can be used :). >> >> The only thing I think it would be good to improve is to have a >> deterministic order for running initialization. >> Yes, this can be done at a later point, I guess. But maybe there is >> already an order of initialization right now and people would start >> replying on it and it would be good to mention it. >> Or maybe I didn't understand what this refers to: "this is not guaranteed >> behavior, and code should not rely on a specific point of evaluation." >> > > Which particular cases would you like to see specified? There are five > cases that have clearly defined behavior, and that I could explicitly > specify if desired: > > * Non-class constants: Are evaluated immediately when declared (i.e. when > control flow reaches the declaration). > * Attribute arguments: Are evaluated in the order of the arguments. > * Parameter defaults: Are evaluated in the order of the parameters. > * Non-static property defaults: Are evaluated in order of declaration, > with parent properties first. The constructor is run after defaults are > evaluated. > * Static variables: Are evaluated immediately when declared (i.e. when > control flow reaches the declaration). > > And then there are the two problematic cases: Class constants and static > properties. Currently, PHP evaluates these semi-lazily. All class constants > and static properties are evaluated at the same time, on first "use" of the > class. I would consider this to be something of an implementation detail. > That's what I meant by that sentence. > > Now, if we allow "new" expressions, then I could see an argument in favor > of requiring class constant and static property initializers to be > evaluated eagerly, i.e. directly after the class has been declared. This > would be a (minor) backwards-compatibility break, because invalid > constant/property declarations would error out immediately, even if they > don't get used. However, I do think that this would be the most predictable > behavior once potentially side-effecting expressions are involved (we > already support side-effecting expressions right now, but less explicitly). > >
Yes, this is what I was thinking about, to have a clear stated order of initialization. Yes, I agree that class constants and static properties should be eagerly declared when class is declared. So the order would be: - constants and static variables, when reaching the statement that does the declaration - class constants and static property, when class is declared, in order of their declaration in the class - instance property, when class is instantiated, in order of their declaration in the class, before construct - parameter defaults and attribute arguments defaults, when function/method/attribute construct is called, in order of the declared parameter/arguments. That sounds good to me. Thanks! Alex

Nikita Popov

5 years ago
On Wed, Mar 3, 2021 at 7:04 PM Alexandru Pătrănescu <drealecs@gmail.com> wrote:
> > On Wed, Mar 3, 2021 at 5:49 PM Nikita Popov <nikita.ppv@gmail.com> wrote: > >> On Wed, Mar 3, 2021 at 4:28 PM Alexandru Pătrănescu <drealecs@gmail.com> >> wrote: >> >>> Hi, >>> >>> This looks very nice and I'm interested in further steps where not only >>> new can be used :). >>> >>> The only thing I think it would be good to improve is to have a >>> deterministic order for running initialization. >>> Yes, this can be done at a later point, I guess. But maybe there is >>> already an order of initialization right now and people would start >>> replying on it and it would be good to mention it. >>> Or maybe I didn't understand what this refers to: "this is not >>> guaranteed behavior, and code should not rely on a specific point of >>> evaluation." >>> >> >> Which particular cases would you like to see specified? There are five >> cases that have clearly defined behavior, and that I could explicitly >> specify if desired: >> >> * Non-class constants: Are evaluated immediately when declared (i.e. >> when control flow reaches the declaration). >> * Attribute arguments: Are evaluated in the order of the arguments. >> * Parameter defaults: Are evaluated in the order of the parameters. >> * Non-static property defaults: Are evaluated in order of declaration, >> with parent properties first. The constructor is run after defaults are >> evaluated. >> * Static variables: Are evaluated immediately when declared (i.e. when >> control flow reaches the declaration). >> >> And then there are the two problematic cases: Class constants and static >> properties. Currently, PHP evaluates these semi-lazily. All class constants >> and static properties are evaluated at the same time, on first "use" of the >> class. I would consider this to be something of an implementation detail. >> That's what I meant by that sentence. >> >> Now, if we allow "new" expressions, then I could see an argument in favor >> of requiring class constant and static property initializers to be >> evaluated eagerly, i.e. directly after the class has been declared. This >> would be a (minor) backwards-compatibility break, because invalid >> constant/property declarations would error out immediately, even if they >> don't get used. However, I do think that this would be the most predictable >> behavior once potentially side-effecting expressions are involved (we >> already support side-effecting expressions right now, but less explicitly). >> >> > Yes, this is what I was thinking about, to have a clear stated order of > initialization. > Yes, I agree that class constants and static properties should be eagerly > declared when class is declared. > > So the order would be: > - constants and static variables, when reaching the statement that does > the declaration > - class constants and static property, when class is declared, in order of > their declaration in the class > - instance property, when class is instantiated, in order of their > declaration in the class, before construct > - parameter defaults and attribute arguments defaults, when > function/method/attribute construct is called, in order of the declared > parameter/arguments. >
If we wanted to eagerly evaluate class constants and static properties when the class is declared, I wonder what the expected behavior would be if evaluation fails. If you have something like try { class A { const B = new Foo; // Let's assume this throws an Error. const C = 1; } } catch (Error) {} var_dump(A::C); then what would happen? Some initial thoughts would be: 1. If evaluation fails, don't declare the class. This is problematic because we may want to use the class while evaluating, e.g. for a "self::FOO" default. So I don't think registering the class only after evaluation is an option. We can also not un-register the class on failure (technically impossible). 2. If evaluation fails, set the remaining values to UNDEF. For static properties this will act like an uninitialized typed property, for constants we'd have to add an error condition for this ("Class constant not available due to initialization failure"). Also, an unrelated realization that I had is that our trait property compatibility check currently performs evaluation when checking whether two properties are the same -- but this seems problematic if the default value uses "new". Regards, Nikita

Nikita Popov

5 years ago
On Wed, Mar 3, 2021 at 7:04 PM Alexandru Pătrănescu <drealecs@gmail.com> wrote:
> > On Wed, Mar 3, 2021 at 5:49 PM Nikita Popov <nikita.ppv@gmail.com> wrote: > >> On Wed, Mar 3, 2021 at 4:28 PM Alexandru Pătrănescu <drealecs@gmail.com> >> wrote: >> >>> Hi, >>> >>> This looks very nice and I'm interested in further steps where not only >>> new can be used :). >>> >>> The only thing I think it would be good to improve is to have a >>> deterministic order for running initialization. >>> Yes, this can be done at a later point, I guess. But maybe there is >>> already an order of initialization right now and people would start >>> replying on it and it would be good to mention it. >>> Or maybe I didn't understand what this refers to: "this is not >>> guaranteed behavior, and code should not rely on a specific point of >>> evaluation." >>> >> >> Which particular cases would you like to see specified? There are five >> cases that have clearly defined behavior, and that I could explicitly >> specify if desired: >> >> * Non-class constants: Are evaluated immediately when declared (i.e. >> when control flow reaches the declaration). >> * Attribute arguments: Are evaluated in the order of the arguments. >> * Parameter defaults: Are evaluated in the order of the parameters. >> * Non-static property defaults: Are evaluated in order of declaration, >> with parent properties first. The constructor is run after defaults are >> evaluated. >> * Static variables: Are evaluated immediately when declared (i.e. when >> control flow reaches the declaration). >> >> And then there are the two problematic cases: Class constants and static >> properties. Currently, PHP evaluates these semi-lazily. All class constants >> and static properties are evaluated at the same time, on first "use" of the >> class. I would consider this to be something of an implementation detail. >> That's what I meant by that sentence. >> >> Now, if we allow "new" expressions, then I could see an argument in favor >> of requiring class constant and static property initializers to be >> evaluated eagerly, i.e. directly after the class has been declared. This >> would be a (minor) backwards-compatibility break, because invalid >> constant/property declarations would error out immediately, even if they >> don't get used. However, I do think that this would be the most predictable >> behavior once potentially side-effecting expressions are involved (we >> already support side-effecting expressions right now, but less explicitly). >> >> > Yes, this is what I was thinking about, to have a clear stated order of > initialization. > Yes, I agree that class constants and static properties should be eagerly > declared when class is declared. > > So the order would be: > - constants and static variables, when reaching the statement that does > the declaration > - class constants and static property, when class is declared, in order of > their declaration in the class > - instance property, when class is instantiated, in order of their > declaration in the class, before construct > - parameter defaults and attribute arguments defaults, when > function/method/attribute construct is called, in order of the declared > parameter/arguments. > > That sounds good to me. > Thanks! > Alex >
I've updated the RFC (and implementation) to evaluate class constants and static properties at time of class declaration. As such, everything should have a well-defined evaluation order now. However, this also means that this RFC now also contains a backwards-compatibility break: Anything used inside class constant / static property initializers needs to actually be available at the time the class is declared. You can't first declare the class, then declare some additional constants it uses, and then use it. Nikita

Nikita Popov

5 years ago
On Fri, Mar 19, 2021 at 12:22 PM Nikita Popov <nikita.ppv@gmail.com> wrote:
> On Wed, Mar 3, 2021 at 7:04 PM Alexandru Pătrănescu <drealecs@gmail.com> > wrote: > >> >> On Wed, Mar 3, 2021 at 5:49 PM Nikita Popov <nikita.ppv@gmail.com> wrote: >> >>> On Wed, Mar 3, 2021 at 4:28 PM Alexandru Pătrănescu <drealecs@gmail.com> >>> wrote: >>> >>>> Hi, >>>> >>>> This looks very nice and I'm interested in further steps where not only >>>> new can be used :). >>>> >>>> The only thing I think it would be good to improve is to have a >>>> deterministic order for running initialization. >>>> Yes, this can be done at a later point, I guess. But maybe there is >>>> already an order of initialization right now and people would start >>>> replying on it and it would be good to mention it. >>>> Or maybe I didn't understand what this refers to: "this is not >>>> guaranteed behavior, and code should not rely on a specific point of >>>> evaluation." >>>> >>> >>> Which particular cases would you like to see specified? There are five >>> cases that have clearly defined behavior, and that I could explicitly >>> specify if desired: >>> >>> * Non-class constants: Are evaluated immediately when declared (i.e. >>> when control flow reaches the declaration). >>> * Attribute arguments: Are evaluated in the order of the arguments. >>> * Parameter defaults: Are evaluated in the order of the parameters. >>> * Non-static property defaults: Are evaluated in order of declaration, >>> with parent properties first. The constructor is run after defaults are >>> evaluated. >>> * Static variables: Are evaluated immediately when declared (i.e. when >>> control flow reaches the declaration). >>> >>> And then there are the two problematic cases: Class constants and static >>> properties. Currently, PHP evaluates these semi-lazily. All class constants >>> and static properties are evaluated at the same time, on first "use" of the >>> class. I would consider this to be something of an implementation detail. >>> That's what I meant by that sentence. >>> >>> Now, if we allow "new" expressions, then I could see an argument in >>> favor of requiring class constant and static property initializers to be >>> evaluated eagerly, i.e. directly after the class has been declared. This >>> would be a (minor) backwards-compatibility break, because invalid >>> constant/property declarations would error out immediately, even if they >>> don't get used. However, I do think that this would be the most predictable >>> behavior once potentially side-effecting expressions are involved (we >>> already support side-effecting expressions right now, but less explicitly). >>> >>> >> Yes, this is what I was thinking about, to have a clear stated order of >> initialization. >> Yes, I agree that class constants and static properties should be eagerly >> declared when class is declared. >> >> So the order would be: >> - constants and static variables, when reaching the statement that does >> the declaration >> - class constants and static property, when class is declared, in order >> of their declaration in the class >> - instance property, when class is instantiated, in order of their >> declaration in the class, before construct >> - parameter defaults and attribute arguments defaults, when >> function/method/attribute construct is called, in order of the declared >> parameter/arguments. >> >> That sounds good to me. >> Thanks! >> Alex >> > > I've updated the RFC (and implementation) to evaluate class constants and > static properties at time of class declaration. As such, everything should > have a well-defined evaluation order now. > > However, this also means that this RFC now also contains a > backwards-compatibility break: Anything used inside class constant / static > property initializers needs to actually be available at the time the class > is declared. You can't first declare the class, then declare some > additional constants it uses, and then use it. >
Another complication here is preloading. The current semantics of evaluation on first use work well there, because class loading (during preloading) is decoupled from evaluation (during request). Now, we can't evaluate initializers during "opcache_compile_file" style preloading, so we'd have to delay this to the start of the request. And then we'd have to evaluate initializers for all preloaded classes, regardless of whether they will be used in this particular request or not. Also opens up the question of the order in which the classes should be evaluated. I initially liked the idea of evaluating everything at the time of class declaration, but now get the impression that this causes more problems than it solves, and we should go back to the previous lazy evaluation approach. Ultimately, my view here is that side-effecting constructors are a terrible idea, and if you use them then you should also carefully manage those side-effects yourself. Regards, Nikita

Nikita Popov

5 years ago
On Fri, Mar 19, 2021 at 12:57 PM Nikita Popov <nikita.ppv@gmail.com> wrote:
> On Fri, Mar 19, 2021 at 12:22 PM Nikita Popov <nikita.ppv@gmail.com> > wrote: > >> On Wed, Mar 3, 2021 at 7:04 PM Alexandru Pătrănescu <drealecs@gmail.com> >> wrote: >> >>> >>> On Wed, Mar 3, 2021 at 5:49 PM Nikita Popov <nikita.ppv@gmail.com> >>> wrote: >>> >>>> On Wed, Mar 3, 2021 at 4:28 PM Alexandru Pătrănescu <drealecs@gmail.com> >>>> wrote: >>>> >>>>> Hi, >>>>> >>>>> This looks very nice and I'm interested in further steps where not >>>>> only new can be used :). >>>>> >>>>> The only thing I think it would be good to improve is to have a >>>>> deterministic order for running initialization. >>>>> Yes, this can be done at a later point, I guess. But maybe there is >>>>> already an order of initialization right now and people would start >>>>> replying on it and it would be good to mention it. >>>>> Or maybe I didn't understand what this refers to: "this is not >>>>> guaranteed behavior, and code should not rely on a specific point of >>>>> evaluation." >>>>> >>>> >>>> Which particular cases would you like to see specified? There are five >>>> cases that have clearly defined behavior, and that I could explicitly >>>> specify if desired: >>>> >>>> * Non-class constants: Are evaluated immediately when declared (i.e. >>>> when control flow reaches the declaration). >>>> * Attribute arguments: Are evaluated in the order of the arguments. >>>> * Parameter defaults: Are evaluated in the order of the parameters. >>>> * Non-static property defaults: Are evaluated in order of declaration, >>>> with parent properties first. The constructor is run after defaults are >>>> evaluated. >>>> * Static variables: Are evaluated immediately when declared (i.e. when >>>> control flow reaches the declaration). >>>> >>>> And then there are the two problematic cases: Class constants and >>>> static properties. Currently, PHP evaluates these semi-lazily. All class >>>> constants and static properties are evaluated at the same time, on first >>>> "use" of the class. I would consider this to be something of an >>>> implementation detail. That's what I meant by that sentence. >>>> >>>> Now, if we allow "new" expressions, then I could see an argument in >>>> favor of requiring class constant and static property initializers to be >>>> evaluated eagerly, i.e. directly after the class has been declared. This >>>> would be a (minor) backwards-compatibility break, because invalid >>>> constant/property declarations would error out immediately, even if they >>>> don't get used. However, I do think that this would be the most predictable >>>> behavior once potentially side-effecting expressions are involved (we >>>> already support side-effecting expressions right now, but less explicitly). >>>> >>>> >>> Yes, this is what I was thinking about, to have a clear stated order of >>> initialization. >>> Yes, I agree that class constants and static properties should be >>> eagerly declared when class is declared. >>> >>> So the order would be: >>> - constants and static variables, when reaching the statement that does >>> the declaration >>> - class constants and static property, when class is declared, in order >>> of their declaration in the class >>> - instance property, when class is instantiated, in order of their >>> declaration in the class, before construct >>> - parameter defaults and attribute arguments defaults, when >>> function/method/attribute construct is called, in order of the declared >>> parameter/arguments. >>> >>> That sounds good to me. >>> Thanks! >>> Alex >>> >> >> I've updated the RFC (and implementation) to evaluate class constants and >> static properties at time of class declaration. As such, everything should >> have a well-defined evaluation order now. >> >> However, this also means that this RFC now also contains a >> backwards-compatibility break: Anything used inside class constant / static >> property initializers needs to actually be available at the time the class >> is declared. You can't first declare the class, then declare some >> additional constants it uses, and then use it. >> > > Another complication here is preloading. The current semantics of > evaluation on first use work well there, because class loading (during > preloading) is decoupled from evaluation (during request). Now, we can't > evaluate initializers during "opcache_compile_file" style preloading, so > we'd have to delay this to the start of the request. And then we'd have to > evaluate initializers for all preloaded classes, regardless of whether they > will be used in this particular request or not. Also opens up the question > of the order in which the classes should be evaluated. > > I initially liked the idea of evaluating everything at the time of class > declaration, but now get the impression that this causes more problems than > it solves, and we should go back to the previous lazy evaluation approach. > Ultimately, my view here is that side-effecting constructors are a terrible > idea, and if you use them then you should also carefully manage those > side-effects yourself. >
Just to throw it out there, another option would be to not support "new" for static properties and class constants, where the semantics are less clear cut than in all the other cases. For me personally the important use cases here are initializers for non-static properties and parameters, as well as attribute arguments, and those all have unambiguous semantics. Of course, there is also a lot of value in having consistent support. Regards, Nikita

Nikita Popov

5 years ago
On Fri, Mar 19, 2021 at 2:02 PM Nikita Popov <nikita.ppv@gmail.com> wrote:
> On Fri, Mar 19, 2021 at 12:57 PM Nikita Popov <nikita.ppv@gmail.com> > wrote: > >> On Fri, Mar 19, 2021 at 12:22 PM Nikita Popov <nikita.ppv@gmail.com> >> wrote: >> >>> On Wed, Mar 3, 2021 at 7:04 PM Alexandru Pătrănescu <drealecs@gmail.com> >>> wrote: >>> >>>> >>>> On Wed, Mar 3, 2021 at 5:49 PM Nikita Popov <nikita.ppv@gmail.com> >>>> wrote: >>>> >>>>> On Wed, Mar 3, 2021 at 4:28 PM Alexandru Pătrănescu < >>>>> drealecs@gmail.com> wrote: >>>>> >>>>>> Hi, >>>>>> >>>>>> This looks very nice and I'm interested in further steps where not >>>>>> only new can be used :). >>>>>> >>>>>> The only thing I think it would be good to improve is to have a >>>>>> deterministic order for running initialization. >>>>>> Yes, this can be done at a later point, I guess. But maybe there is >>>>>> already an order of initialization right now and people would start >>>>>> replying on it and it would be good to mention it. >>>>>> Or maybe I didn't understand what this refers to: "this is not >>>>>> guaranteed behavior, and code should not rely on a specific point of >>>>>> evaluation." >>>>>> >>>>> >>>>> Which particular cases would you like to see specified? There are five >>>>> cases that have clearly defined behavior, and that I could explicitly >>>>> specify if desired: >>>>> >>>>> * Non-class constants: Are evaluated immediately when declared (i.e. >>>>> when control flow reaches the declaration). >>>>> * Attribute arguments: Are evaluated in the order of the arguments. >>>>> * Parameter defaults: Are evaluated in the order of the parameters. >>>>> * Non-static property defaults: Are evaluated in order of >>>>> declaration, with parent properties first. The constructor is run after >>>>> defaults are evaluated. >>>>> * Static variables: Are evaluated immediately when declared (i.e. >>>>> when control flow reaches the declaration). >>>>> >>>>> And then there are the two problematic cases: Class constants and >>>>> static properties. Currently, PHP evaluates these semi-lazily. All class >>>>> constants and static properties are evaluated at the same time, on first >>>>> "use" of the class. I would consider this to be something of an >>>>> implementation detail. That's what I meant by that sentence. >>>>> >>>>> Now, if we allow "new" expressions, then I could see an argument in >>>>> favor of requiring class constant and static property initializers to be >>>>> evaluated eagerly, i.e. directly after the class has been declared. This >>>>> would be a (minor) backwards-compatibility break, because invalid >>>>> constant/property declarations would error out immediately, even if they >>>>> don't get used. However, I do think that this would be the most predictable >>>>> behavior once potentially side-effecting expressions are involved (we >>>>> already support side-effecting expressions right now, but less explicitly). >>>>> >>>>> >>>> Yes, this is what I was thinking about, to have a clear stated order of >>>> initialization. >>>> Yes, I agree that class constants and static properties should be >>>> eagerly declared when class is declared. >>>> >>>> So the order would be: >>>> - constants and static variables, when reaching the statement that does >>>> the declaration >>>> - class constants and static property, when class is declared, in order >>>> of their declaration in the class >>>> - instance property, when class is instantiated, in order of their >>>> declaration in the class, before construct >>>> - parameter defaults and attribute arguments defaults, when >>>> function/method/attribute construct is called, in order of the declared >>>> parameter/arguments. >>>> >>>> That sounds good to me. >>>> Thanks! >>>> Alex >>>> >>> >>> I've updated the RFC (and implementation) to evaluate class constants >>> and static properties at time of class declaration. As such, everything >>> should have a well-defined evaluation order now. >>> >>> However, this also means that this RFC now also contains a >>> backwards-compatibility break: Anything used inside class constant / static >>> property initializers needs to actually be available at the time the class >>> is declared. You can't first declare the class, then declare some >>> additional constants it uses, and then use it. >>> >> >> Another complication here is preloading. The current semantics of >> evaluation on first use work well there, because class loading (during >> preloading) is decoupled from evaluation (during request). Now, we can't >> evaluate initializers during "opcache_compile_file" style preloading, so >> we'd have to delay this to the start of the request. And then we'd have to >> evaluate initializers for all preloaded classes, regardless of whether they >> will be used in this particular request or not. Also opens up the question >> of the order in which the classes should be evaluated. >> >> I initially liked the idea of evaluating everything at the time of class >> declaration, but now get the impression that this causes more problems than >> it solves, and we should go back to the previous lazy evaluation approach. >> Ultimately, my view here is that side-effecting constructors are a terrible >> idea, and if you use them then you should also carefully manage those >> side-effects yourself. >> > > Just to throw it out there, another option would be to not support "new" > for static properties and class constants, where the semantics are less > clear cut than in all the other cases. For me personally the important use > cases here are initializers for non-static properties and parameters, as > well as attribute arguments, and those all have unambiguous semantics. Of > course, there is also a lot of value in having consistent support. >
An issue for using new in (non-static) properties is that these objects are going to get created even if the constructor doesn't run. This could be a problem for things like unserialize() and anything that implements related functionality in userland (e.g. Symfony's VarCloner). In particular, this means that the default is *always* going to be constructed and populated, even if you're bypassing the constructor. Of course, this also happens with other property defaults, but in this case object construction may be more expensive (and possibly side-effecting). That's not great, and there wouldn't really be any way to avoid that. The alternative would be to instead only initialize such properties in the constructor, which comes with its own caveats: We'd have to make all classes actually have a constructor, and you'd have to make sure to call parent::__construct() even if there is no explicit parent constructor, otherwise you might be missing some property initializations. This also causes something of an asymmetry between "simple" default values (initialized on object creation) and "complex" defaults (initialized during constructor call). TBH, I'm not quite sure what to do with this RFC. I'm considering whether it may make sense to cut it down to the bare minimum, which is support for new inside parameter defaults and attribute arguments (and possibly global constants and static variables -- I don't think those have any issues, but they're also not terribly useful). Parameter defaults happen to also cover the common case of property defaults, through constructor promotion. This would still work fine, because the default is on the parameter, not on the property: class Test { public function __construct( private Logger $logger = new NullLogger, ) {} } The main argument against this is that it will result in some places using "constant expressions" being more powerful, and some being less powerful. Regards, Nikita

Dik Takken

5 years ago
On 11-05-2021 11:56, Nikita Popov wrote:
> > An issue for using new in (non-static) properties is that these objects are > going to get created even if the constructor doesn't run. This could be a > problem for things like unserialize() and anything that implements related > functionality in userland (e.g. Symfony's VarCloner). In particular, this > means that the default is *always* going to be constructed and populated, > even if you're bypassing the constructor. Of course, this also happens with > other property defaults, but in this case object construction may be more > expensive (and possibly side-effecting). That's not great, and there > wouldn't really be any way to avoid that.
That sounds like a bad situation to me.
> The alternative would be to instead only initialize such properties in the > constructor, which comes with its own caveats: We'd have to make all > classes actually have a constructor, and you'd have to make sure to call > parent::__construct() even if there is no explicit parent constructor, > otherwise you might be missing some property initializations. This also > causes something of an asymmetry between "simple" default values > (initialized on object creation) and "complex" defaults (initialized during > constructor call).
The requirement of having to call the parent constructor would start making sense in case we had a built-in base class that represents all of the features that any PHP class has by default, including a constructor. This class could act like an explicit base class that you can extend in order to inherit the property initialization feature. The idea is similar to the so called new style classes in Python where you extend "object". This base class basically contains default implementations of magic methods like __init__(). So the use of "new" in initializers would require extending this base class and call the parent constructor. Maybe forgetting to do so could throw, preventing initializers from silently not working. Regards, Dik Takken

Rowan Collins

5 years ago
On 11/05/2021 14:12, Dik Takken wrote:
> So the use of "new" in initializers would require extending this base > class and call the parent constructor. Maybe forgetting to do so could > throw, preventing initializers from silently not working.
If we managed to get that working, I wonder if we could also include a check for uninitialized typed properties, a bit like Swift's "two-phase initializers". In short, make this throw an error: class Foo extends \PHP\Base {     private Logger $logger;     public function __construct() {         parent::construct(); // "Error: Property $logger has no default and was not initialized before calling Base constructor"     } } This would catch bugs closer to their source, where currently there is no error until $logger is accessed. However, the "if" at the beginning of this message is quite a big one: it's not obvious where to assert any of this, because the constructor is just called as a normal method *after* the engine considers the object to be "created". Regards,
-- Rowan Tommins [IMSoP]

Claude Pache

5 years ago
> Le 19 mars 2021 à 12:22, Nikita Popov <nikita.ppv@gmail.com <mailto:nikita.ppv@gmail.com>> a écrit : > > I've updated the RFC (and implementation) to evaluate class constants and > static properties at time of class declaration. As such, everything should > have a well-defined evaluation order now. > > However, this also means that this RFC now also contains a > backwards-compatibility break: Anything used inside class constant / static > property initializers needs to actually be available at the time the class > is declared. You can't first declare the class, then declare some > additional constants it uses, and then use it. >
I don’t like having the static property initialisers and constants evaluated eagerly. I typically declare the main class followed by zero or more class helpers in the same file: with the proposed semantics, I may be forced to occasionally reorder my code. One particular case I have in mind is the following refactoring that I may perform with the advent of enums: ```php class Foo { const STATE_OPEN = 1; const STATE_CLOSE = 2; function setState(int $state) { // .... } } ``` into: ```php class Foo { #[\Deprecated] const STATE_OPEN = FooState::OPEN; #[\Deprecated] const STATE_CLOSE = FooState::CLOSE; function setState(FooState $state) { // .... } } enum FooState { case OPEN; case CLOSE; } ``` —Claude

Ben Ramsey

5 years ago
> On Mar 3, 2021, at 09:03, Nikita Popov <nikita.ppv@gmail.com> wrote: > > Hi internals, > > I would like to propose allowing the use of "new" inside various > initializer expressions: https://wiki.php.net/rfc/new_in_initializers > > In particular, this allows specifying object default values for properties > and parameters, and allows the use of objects as attribute arguments. > > The RFC is narrow in scope in that it only adds support for "new". An > extension to other call kinds should be straightforward though.
I like this proposal! Are nested new expressions allowed? function test($foo = new A(new B(new C))) {} Cheers, Ben

Benjamin Eberlei

5 years ago
This is On Wed, Mar 3, 2021 at 4:04 PM Nikita Popov <nikita.ppv@gmail.com> wrote:
> Hi internals, > > I would like to propose allowing the use of "new" inside various > initializer expressions: https://wiki.php.net/rfc/new_in_initializers > > In particular, this allows specifying object default values for properties > and parameters, and allows the use of objects as attribute arguments. >
This is the missing puzzle piece that i imagined would be needed for nested attributes. This is really awesome! I look forward to going over the patch to see how it works, specially since Martin's and my attempts failed to break through last year for Attributes :)

Michał Brzuchalski

5 years ago
Hi Nikita, śr., 3 mar 2021, 16:04 użytkownik Nikita Popov <nikita.ppv@gmail.com> napisał:
> Hi internals, > > I would like to propose allowing the use of "new" inside various > initializer expressions: https://wiki.php.net/rfc/new_in_initializers > > In particular, this allows specifying object default values for properties > and parameters, and allows the use of objects as attribute arguments. > > The RFC is narrow in scope in that it only adds support for "new". An > extension to other call kinds should be straightforward though. >
The reflection mechanism for properties, constants and parameters is not described in RFC but I do believe it should for constants part. Correct me if I'm wrong there. The reflection for ReflectionProperty::getDefaultValue the same for ReflectionParameter is similar and I get that it possibly should evaluate the value on each call. But what about constants which by their nature don't change? Does that mean that each time ReflectionClass::getConstant is called the value is already evaluated and the return value always refer to the same object instance. Is that right? I guess it is. IMO it deserves to be described in the RFC. In general I love the proposal was especially thinking of it for static variables. Cheers, Michał Marcin Brzuchalski

Nikita Popov

5 years ago
On Thu, Mar 4, 2021 at 6:21 AM Michał Marcin Brzuchalski < michal.brzuchalski@gmail.com> wrote:
> Hi Nikita, > > śr., 3 mar 2021, 16:04 użytkownik Nikita Popov <nikita.ppv@gmail.com> > napisał: > >> Hi internals, >> >> I would like to propose allowing the use of "new" inside various >> initializer expressions: https://wiki.php.net/rfc/new_in_initializers >> >> In particular, this allows specifying object default values for properties >> and parameters, and allows the use of objects as attribute arguments. >> >> The RFC is narrow in scope in that it only adds support for "new". An >> extension to other call kinds should be straightforward though. >> > > The reflection mechanism for properties, constants and parameters is not > described in RFC but I do believe it should for constants part. Correct me > if I'm wrong there. > > The reflection for ReflectionProperty::getDefaultValue the same for > ReflectionParameter is similar and I get that it possibly should evaluate > the value on each call. But what about constants which by their nature > don't change? > Does that mean that each time ReflectionClass::getConstant is called the > value is already evaluated and the return value always refer to the same > object instance. Is that right? I guess it is. > > IMO it deserves to be described in the RFC. > > In general I love the proposal was especially thinking of it for static > variables. > > Cheers, > Michał Marcin Brzuchalski >
I've added a section that describes reflection methods. It works exactly as you say. There is one interesting case though: How should ReflectionObject::newInstanceWithoutConstructor() work? In the current implementation, it will still evaluate the property defaults, including new expressions. This makes sense to me, but I can also see an argument that this method should not evaluate them -- however, in that case I believe it should not initialize any properties at all (leave them in "uninitialized" state). Populating property default values depending on what kind of expression they contain seems like a no-go to me. @Ben Ramsey: Yes, it's possible to use nested new. Generally, all supported constant expressions can be freely combined. Regards, Nikita

Larry Garfield

5 years ago
On Thu, Mar 4, 2021, at 7:53 AM, Nikita Popov wrote:
> I've added a section that describes reflection methods. It works exactly as > you say. > > There is one interesting case though: How should > ReflectionObject::newInstanceWithoutConstructor() work? In the current > implementation, it will still evaluate the property defaults, including new > expressions. This makes sense to me, but I can also see an argument that > this method should not evaluate them -- however, in that case I believe it > should not initialize any properties at all (leave them in "uninitialized" > state). Populating property default values depending on what kind of > expression they contain seems like a no-go to me. > > @Ben Ramsey: Yes, it's possible to use nested new. Generally, all supported > constant expressions can be freely combined. > > Regards, > Nikita
Would that then end up allowing enum cases to have an object backing value? enum Suit { case Hearts = new Heart; case Spades = new Spade; } Or is there something else that would prevent that? (I know Ilija ran into some complications there.) Overall, I think the benefit to attributes alone justifies this RFC. Endorse. --Larry Garfield

Nikita Popov

5 years ago
On Thu, Mar 4, 2021 at 5:26 PM Larry Garfield <larry@garfieldtech.com> wrote:
> On Thu, Mar 4, 2021, at 7:53 AM, Nikita Popov wrote: > > > I've added a section that describes reflection methods. It works exactly > as > > you say. > > > > There is one interesting case though: How should > > ReflectionObject::newInstanceWithoutConstructor() work? In the current > > implementation, it will still evaluate the property defaults, including > new > > expressions. This makes sense to me, but I can also see an argument that > > this method should not evaluate them -- however, in that case I believe > it > > should not initialize any properties at all (leave them in > "uninitialized" > > state). Populating property default values depending on what kind of > > expression they contain seems like a no-go to me. > > > > @Ben Ramsey: Yes, it's possible to use nested new. Generally, all > supported > > constant expressions can be freely combined. > > > > Regards, > > Nikita > > Would that then end up allowing enum cases to have an object backing value? > > enum Suit { > case Hearts = new Heart; > case Spades = new Spade; > } > > Or is there something else that would prevent that? (I know Ilija ran > into some complications there.)
I believe enums enforce the limitation to strings and integers to allow an efficient mapping from backing value back to enum. If we added support for object array keys then we could theoretically support an object backing value -- but I don't really see why we should, or what that would be useful for. (Worth noting that this RFC does not introduce objects into initializer expressions -- the enum RFC does that. This RFC then removes the enum-only limitation.) Regards, Nikita

Gabriel O

5 years ago
On Wed, 3 Mar 2021 at 16:04, Nikita Popov <nikita.ppv@gmail.com> wrote:
> Hi internals, > > I would like to propose allowing the use of "new" inside various > initializer expressions: https://wiki.php.net/rfc/new_in_initializers > > In particular, this allows specifying object default values for properties > and parameters, and allows the use of objects as attribute arguments. > > The RFC is narrow in scope in that it only adds support for "new". An > extension to other call kinds should be straightforward though. > > Regards, > Nikita >
Does this support anonymous classes? RFC should probably mention that.

Nikita Popov

5 years ago
On Thu, Mar 4, 2021 at 4:26 PM Gabriel O <gabriel.ostrolucky@gmail.com> wrote:
> > On Wed, 3 Mar 2021 at 16:04, Nikita Popov <nikita.ppv@gmail.com> wrote: > >> Hi internals, >> >> I would like to propose allowing the use of "new" inside various >> initializer expressions: https://wiki.php.net/rfc/new_in_initializers >> >> In particular, this allows specifying object default values for properties >> and parameters, and allows the use of objects as attribute arguments. >> >> The RFC is narrow in scope in that it only adds support for "new". An >> extension to other call kinds should be straightforward though. >> >> Regards, >> Nikita >> > > Does this support anonymous classes? RFC should probably mention that. >
Anonymous classes are not supported. I've added an explicit mention of that fact. Regards, Nikita

Andrew Faulds

5 years ago
Hi Nikita, Nikita Popov wrote:
> Hi internals, > > I would like to propose allowing the use of "new" inside various > initializer expressions: https://wiki.php.net/rfc/new_in_initializers > > In particular, this allows specifying object default values for properties > and parameters, and allows the use of objects as attribute arguments. > > The RFC is narrow in scope in that it only adds support for "new". An > extension to other call kinds should be straightforward though. > > Regards, > Nikita >
This seems reasonable, but I fear it would create a similar mess to C++ with static initialisers, because a class constructor can do basically anything… I'm not sure what can be done about it, though. Thanks, Andrea

Larry Garfield

5 years ago
On Wed, Mar 3, 2021, at 9:03 AM, Nikita Popov wrote:
> Hi internals, > > I would like to propose allowing the use of "new" inside various > initializer expressions: https://wiki.php.net/rfc/new_in_initializers > > In particular, this allows specifying object default values for properties > and parameters, and allows the use of objects as attribute arguments. > > The RFC is narrow in scope in that it only adds support for "new". An > extension to other call kinds should be straightforward though. > > Regards, > Nikita
Hi Nikita. What's the status of this RFC? Are you going to bring it to a vote, or is something else blocking it? --Larry Garfield

Nikita Popov

5 years ago
On Fri, Jun 11, 2021 at 5:02 PM Larry Garfield <larry@garfieldtech.com> wrote:
> On Wed, Mar 3, 2021, at 9:03 AM, Nikita Popov wrote: > > Hi internals, > > > > I would like to propose allowing the use of "new" inside various > > initializer expressions: https://wiki.php.net/rfc/new_in_initializers > > > > In particular, this allows specifying object default values for > properties > > and parameters, and allows the use of objects as attribute arguments. > > > > The RFC is narrow in scope in that it only adds support for "new". An > > extension to other call kinds should be straightforward though. > > > > Regards, > > Nikita > > Hi Nikita. What's the status of this RFC? Are you going to bring it to a > vote, or is something else blocking it? >
I've just pushed a larger update to the RFC, which limits the places where new is supported. Supported: * Parameter default values (includes promoted properties) * Attribute arguments * Static variable initializers * Global constant initializers Not supported: * Static and non-static property initializers * Class constant initializers I believe the cases that are now supported should be completely unambiguous and uncontroversial. The other cases have evaluation order issues in one way or another. This is discussed in https://wiki.php.net/rfc/new_in_initializers#unsupported_positions. Regards, Nikita

Larry Garfield

5 years ago
On Tue, Jun 15, 2021, at 10:06 AM, Nikita Popov wrote:
> On Fri, Jun 11, 2021 at 5:02 PM Larry Garfield <larry@garfieldtech.com> > wrote: > > > On Wed, Mar 3, 2021, at 9:03 AM, Nikita Popov wrote: > > > Hi internals, > > > > > > I would like to propose allowing the use of "new" inside various > > > initializer expressions: https://wiki.php.net/rfc/new_in_initializers > > > > > > In particular, this allows specifying object default values for > > properties > > > and parameters, and allows the use of objects as attribute arguments. > > > > > > The RFC is narrow in scope in that it only adds support for "new". An > > > extension to other call kinds should be straightforward though. > > > > > > Regards, > > > Nikita > > > > Hi Nikita. What's the status of this RFC? Are you going to bring it to a > > vote, or is something else blocking it? > > > > I've just pushed a larger update to the RFC, which limits the places where > new is supported. > > Supported: > * Parameter default values (includes promoted properties) > * Attribute arguments > * Static variable initializers > * Global constant initializers > > Not supported: > * Static and non-static property initializers > * Class constant initializers > > I believe the cases that are now supported should be completely unambiguous > and uncontroversial. The other cases have evaluation order issues in one > way or another. This is discussed in > https://wiki.php.net/rfc/new_in_initializers#unsupported_positions.
Thanks, Nikita. I would vote for this as is, but I am saddened by the lack of static property initializers. That's the main use case I am interested in. (In particular, because sealed classes plus new-in-static-property would allow for something substantially similar to tagged unions, just not built on enums, and the latter is not making it into 8.1 it looks like.) Arguments and attributes are enough to justify this RFC on its own, but is there a way we can resolve the static property question? Right now the RFC says "these initializers are evaluated lazily the first time a class is used in a certain way." Can you be more specific about that certain way? Is there a certain way that would be minimally disruptive? Also, I think there's a typo in the preceding paragraph about property initializers. It says "the disciplined invocation of such constructors from potential parent constructors." Shouldn't that be potential child constructors? --Larry Garfield

Nikita Popov

5 years ago
On Tue, Jun 15, 2021 at 6:31 PM Larry Garfield <larry@garfieldtech.com> wrote:
> On Tue, Jun 15, 2021, at 10:06 AM, Nikita Popov wrote: > > On Fri, Jun 11, 2021 at 5:02 PM Larry Garfield <larry@garfieldtech.com> > > wrote: > > > > > On Wed, Mar 3, 2021, at 9:03 AM, Nikita Popov wrote: > > > > Hi internals, > > > > > > > > I would like to propose allowing the use of "new" inside various > > > > initializer expressions: > https://wiki.php.net/rfc/new_in_initializers > > > > > > > > In particular, this allows specifying object default values for > > > properties > > > > and parameters, and allows the use of objects as attribute arguments. > > > > > > > > The RFC is narrow in scope in that it only adds support for "new". An > > > > extension to other call kinds should be straightforward though. > > > > > > > > Regards, > > > > Nikita > > > > > > Hi Nikita. What's the status of this RFC? Are you going to bring it > to a > > > vote, or is something else blocking it? > > > > > > > I've just pushed a larger update to the RFC, which limits the places > where > > new is supported. > > > > Supported: > > * Parameter default values (includes promoted properties) > > * Attribute arguments > > * Static variable initializers > > * Global constant initializers > > > > Not supported: > > * Static and non-static property initializers > > * Class constant initializers > > > > I believe the cases that are now supported should be completely > unambiguous > > and uncontroversial. The other cases have evaluation order issues in one > > way or another. This is discussed in > > https://wiki.php.net/rfc/new_in_initializers#unsupported_positions. > > Thanks, Nikita. I would vote for this as is, but I am saddened by the > lack of static property initializers. That's the main use case I am > interested in. (In particular, because sealed classes plus > new-in-static-property would allow for something substantially similar to > tagged unions, just not built on enums, and the latter is not making it > into 8.1 it looks like.) > > Arguments and attributes are enough to justify this RFC on its own, but is > there a way we can resolve the static property question? Right now the RFC > says "these initializers are evaluated lazily the first time a class is > used in a certain way." Can you be more specific about that certain way? > Is there a certain way that would be minimally disruptive?
Well, here is a non-exhaustive description of current behavior: * If you access a class constant, only that constant is evaluated. * If you access a static property, all initializers in the class and parent classes are evaluated. * If you instantiate a class, all initializers are evaluated. * Inheriting from a class or calling a static method doesn't evaluate anything. As you can see, the rules are rather ad-hoc. To the user, it's probably not obvious why instantiating an object of a class would require evaluating class constants at that point. The reason is that instantiation requires resolved property defaults, and we happen to evaluate all initializers at once. The options where static properties and class constants are concerned are: 1. Eagerly evaluate initializers on declaration. This is what I tried in an earlier revision of the RFC, and I don't think that approach works. It breaks existing code and has various other unpleasant complications. 2. Precisely specify the current behavior. I don't want to do this either, because the exact places where evaluation happens are something of an implementation detail. If in the future we find it convenient to separate evaluation of non-static properties on object instantiation from evaluation of static properties and class constants (which are not strictly needed at that point), I'd like to retain the liberty to make such a change. 3. Do not specify an evaluation order, beyond that evaluation happens at certain uses of the class. Evaluation order may change across PHP versions. If your code relies on any particular order, your code is broken. Unless I'm missing a fourth option here, option 3 is the only one I would be willing to go for at this time. Also, I think there's a typo in the preceding paragraph about property
> initializers. It says "the disciplined invocation of such constructors > from potential parent constructors." Shouldn't that be potential child > constructors? >
Yeah, that's right. Fixed! Regards, Nikita

Larry Garfield

5 years ago
On Wed, Jun 16, 2021, at 3:16 AM, Nikita Popov wrote:
> > Arguments and attributes are enough to justify this RFC on its own, but is > > there a way we can resolve the static property question? Right now the RFC > > says "these initializers are evaluated lazily the first time a class is > > used in a certain way." Can you be more specific about that certain way? > > Is there a certain way that would be minimally disruptive? > > > Well, here is a non-exhaustive description of current behavior: > > * If you access a class constant, only that constant is evaluated. > * If you access a static property, all initializers in the class and > parent classes are evaluated. > * If you instantiate a class, all initializers are evaluated. > * Inheriting from a class or calling a static method doesn't evaluate > anything. > > As you can see, the rules are rather ad-hoc. To the user, it's probably not > obvious why instantiating an object of a class would require evaluating > class constants at that point. The reason is that instantiation requires > resolved property defaults, and we happen to evaluate all initializers at > once. > > The options where static properties and class constants are concerned are: > > 1. Eagerly evaluate initializers on declaration. This is what I tried in an > earlier revision of the RFC, and I don't think that approach works. It > breaks existing code and has various other unpleasant complications. > 2. Precisely specify the current behavior. I don't want to do this either, > because the exact places where evaluation happens are something of an > implementation detail. If in the future we find it convenient to separate > evaluation of non-static properties on object instantiation from evaluation > of static properties and class constants (which are not strictly needed at > that point), I'd like to retain the liberty to make such a change. > 3. Do not specify an evaluation order, beyond that evaluation happens at > certain uses of the class. Evaluation order may change across PHP versions. > If your code relies on any particular order, your code is broken. > > Unless I'm missing a fourth option here, option 3 is the only one I would > be willing to go for at this time.
Thanks. To clarify, the concern about evaluation order is only relevant if you are initializing a class whose constructor has some kind of side effect, right? Writing to disk or printing or something like that. Otherwise, at worst you may initialize a few more objects than you expect there should be no behavioral change. Given that constructors that have side effects are arguably broken to begin with (modulo debugging), I'd be comfortable with explicitly saying that the evaluation order is undefined, and nothing is guaranteed except that the value will be there when you first access it. In the future, if function initializers or something like that are added we can revisit that question, though I would be tempted to say the same thing in those cases; if you want to do some kind of DB read in a function that is a default value for a property or a parameter, frankly odds are you're already doing something wrong to begin with. But that's a bridge we can cross if and when we get to it. Would others be comfortable with that, if it allowed new-initializers for static properties and class constants? --Larry Garfield

Mike Schinkel

5 years ago
> On Jun 16, 2021, at 7:47 AM, Larry Garfield <larry@garfieldtech.com> wrote: > > On Wed, Jun 16, 2021, at 3:16 AM, Nikita Popov wrote: > > Given that constructors that have side effects are arguably broken to begin with (modulo debugging), I'd be comfortable with explicitly saying that the evaluation order is undefined, and nothing is guaranteed except that the value will be there when you first access it.
A big thumbs up from me. I cannot think of a scenario in userland where evaluation order prior to access would matter and not be able to be worked around. -Mike

Nicolas Grekas

5 years ago
Le mer. 16 juin 2021 à 13:47, Larry Garfield <larry@garfieldtech.com> a écrit :
> On Wed, Jun 16, 2021, at 3:16 AM, Nikita Popov wrote: > > > > Arguments and attributes are enough to justify this RFC on its own, > but is > > > there a way we can resolve the static property question? Right now > the RFC > > > says "these initializers are evaluated lazily the first time a class is > > > used in a certain way." Can you be more specific about that certain > way? > > > Is there a certain way that would be minimally disruptive? > > > > > > Well, here is a non-exhaustive description of current behavior: > > > > * If you access a class constant, only that constant is evaluated. > > * If you access a static property, all initializers in the class and > > parent classes are evaluated. > > * If you instantiate a class, all initializers are evaluated. > > * Inheriting from a class or calling a static method doesn't evaluate > > anything. > > > > As you can see, the rules are rather ad-hoc. To the user, it's probably > not > > obvious why instantiating an object of a class would require evaluating > > class constants at that point. The reason is that instantiation requires > > resolved property defaults, and we happen to evaluate all initializers at > > once. > > > > The options where static properties and class constants are concerned > are: > > > > 1. Eagerly evaluate initializers on declaration. This is what I tried in > an > > earlier revision of the RFC, and I don't think that approach works. It > > breaks existing code and has various other unpleasant complications. > > 2. Precisely specify the current behavior. I don't want to do this > either, > > because the exact places where evaluation happens are something of an > > implementation detail. If in the future we find it convenient to separate > > evaluation of non-static properties on object instantiation from > evaluation > > of static properties and class constants (which are not strictly needed > at > > that point), I'd like to retain the liberty to make such a change. > > 3. Do not specify an evaluation order, beyond that evaluation happens at > > certain uses of the class. Evaluation order may change across PHP > versions. > > If your code relies on any particular order, your code is broken. > > > > Unless I'm missing a fourth option here, option 3 is the only one I would > > be willing to go for at this time. > > Thanks. To clarify, the concern about evaluation order is only relevant > if you are initializing a class whose constructor has some kind of side > effect, right? Writing to disk or printing or something like that. > Otherwise, at worst you may initialize a few more objects than you expect > there should be no behavioral change. > > Given that constructors that have side effects are arguably broken to > begin with (modulo debugging), I'd be comfortable with explicitly saying > that the evaluation order is undefined, and nothing is guaranteed except > that the value will be there when you first access it. > > In the future, if function initializers or something like that are added > we can revisit that question, though I would be tempted to say the same > thing in those cases; if you want to do some kind of DB read in a function > that is a default value for a property or a parameter, frankly odds are > you're already doing something wrong to begin with. But that's a bridge we > can cross if and when we get to it. > > Would others be comfortable with that, if it allowed new-initializers for > static properties and class constants? >
Honestly, I don't know. Instantiation might fail because of either a throwing constructor or because of a throwing autoloader. Being able to know where to put the try/catch to recover from these might be important when writing generic code. With the current state of the RFC, it's fine. With "undefined evaluation time", it might make things fragile without any way to make them resilient enough. We should think twice before going this way IMHO. I'm not sold yet this is a compromise we should make. Nicolas

Mike Schinkel

5 years ago
> On Jun 17, 2021, at 5:14 AM, Nicolas Grekas <nicolas.grekas+php@gmail.com> wrote: > > Le mer. 16 juin 2021 à 13:47, Larry Garfield <larry@garfieldtech.com> a > écrit : >> Would others be comfortable with that, if it allowed new-initializers for >> static properties and class constants? >> > > Honestly, I don't know. > > Instantiation might fail because of either a throwing constructor or > because of a throwing autoloader. > > Being able to know where to put the try/catch to recover from these might > be important when writing generic code. >
> With the current state of the RFC, it's fine. With "undefined evaluation > time", it might make things fragile without any way to make them resilient > enough.
Wouldn't the simple way to make them resilient enough be to wrap a try-catch block around any code that potentially throws an exception within the class being instantiated on initialization? -Mike

Nikita Popov

5 years ago
On Thu, Jun 17, 2021 at 11:14 AM Nicolas Grekas < nicolas.grekas+php@gmail.com> wrote:
> Le mer. 16 juin 2021 à 13:47, Larry Garfield <larry@garfieldtech.com> a > écrit : > > > On Wed, Jun 16, 2021, at 3:16 AM, Nikita Popov wrote: > > > > > > Arguments and attributes are enough to justify this RFC on its own, > > but is > > > > there a way we can resolve the static property question? Right now > > the RFC > > > > says "these initializers are evaluated lazily the first time a class > is > > > > used in a certain way." Can you be more specific about that certain > > way? > > > > Is there a certain way that would be minimally disruptive? > > > > > > > > > Well, here is a non-exhaustive description of current behavior: > > > > > > * If you access a class constant, only that constant is evaluated. > > > * If you access a static property, all initializers in the class and > > > parent classes are evaluated. > > > * If you instantiate a class, all initializers are evaluated. > > > * Inheriting from a class or calling a static method doesn't evaluate > > > anything. > > > > > > As you can see, the rules are rather ad-hoc. To the user, it's probably > > not > > > obvious why instantiating an object of a class would require evaluating > > > class constants at that point. The reason is that instantiation > requires > > > resolved property defaults, and we happen to evaluate all initializers > at > > > once. > > > > > > The options where static properties and class constants are concerned > > are: > > > > > > 1. Eagerly evaluate initializers on declaration. This is what I tried > in > > an > > > earlier revision of the RFC, and I don't think that approach works. It > > > breaks existing code and has various other unpleasant complications. > > > 2. Precisely specify the current behavior. I don't want to do this > > either, > > > because the exact places where evaluation happens are something of an > > > implementation detail. If in the future we find it convenient to > separate > > > evaluation of non-static properties on object instantiation from > > evaluation > > > of static properties and class constants (which are not strictly needed > > at > > > that point), I'd like to retain the liberty to make such a change. > > > 3. Do not specify an evaluation order, beyond that evaluation happens > at > > > certain uses of the class. Evaluation order may change across PHP > > versions. > > > If your code relies on any particular order, your code is broken. > > > > > > Unless I'm missing a fourth option here, option 3 is the only one I > would > > > be willing to go for at this time. > > > > Thanks. To clarify, the concern about evaluation order is only relevant > > if you are initializing a class whose constructor has some kind of side > > effect, right? Writing to disk or printing or something like that. > > Otherwise, at worst you may initialize a few more objects than you expect > > there should be no behavioral change. > > > > Given that constructors that have side effects are arguably broken to > > begin with (modulo debugging), I'd be comfortable with explicitly saying > > that the evaluation order is undefined, and nothing is guaranteed except > > that the value will be there when you first access it. > > > > In the future, if function initializers or something like that are added > > we can revisit that question, though I would be tempted to say the same > > thing in those cases; if you want to do some kind of DB read in a > function > > that is a default value for a property or a parameter, frankly odds are > > you're already doing something wrong to begin with. But that's a bridge > we > > can cross if and when we get to it. > > > > Would others be comfortable with that, if it allowed new-initializers for > > static properties and class constants? > > > > Honestly, I don't know. > > Instantiation might fail because of either a throwing constructor or > because of a throwing autoloader. > > Being able to know where to put the try/catch to recover from these might > be important when writing generic code. > > With the current state of the RFC, it's fine. With "undefined evaluation > time", it might make things fragile without any way to make them resilient > enough. > > We should think twice before going this way IMHO. I'm not sold yet this is > a compromise we should make. >
Worth noting that static prop / class constant initializers can already throw, e.g. if you write something like "const Foo = [] + 1;". Of course, with constructors this may become more prevalent. Even if we went with evaluation during class declaration, the result would be pretty awkward, in that the only way to catch such an exception would be to wrap the whole class declaration: try { class X { const Foo = [] + 1; } } catch (Error) {} Regards, Nikita

Alexandru Pătrănescu

5 years ago
On Wed, Jun 16, 2021 at 11:17 AM Nikita Popov <nikita.ppv@gmail.com> wrote:
> > > The options where static properties and class constants are concerned are: > > 1. Eagerly evaluate initializers on declaration. This is what I tried in an > earlier revision of the RFC, and I don't think that approach works. It > breaks existing code and has various other unpleasant complications. > 2. Precisely specify the current behavior. I don't want to do this either, > because the exact places where evaluation happens are something of an > implementation detail. If in the future we find it convenient to separate > evaluation of non-static properties on object instantiation from evaluation > of static properties and class constants (which are not strictly needed at > that point), I'd like to retain the liberty to make such a change. > 3. Do not specify an evaluation order, beyond that evaluation happens at > certain uses of the class. Evaluation order may change across PHP versions. > If your code relies on any particular order, your code is broken. > > Unless I'm missing a fourth option here, option 3 is the only one I would > be willing to go for at this time. >
I think I was one of the people that came up on the mailing list with the idea that it would be good to have the order of evaluating the initializers clarified in the specifications. Little did I know at that time how constants are "evaluated" until now. I watched the topic and comments you added few months ago on the PR while you were doing the implementation and got into trouble with preloading that really can't work nicely with eager initialization. I didn't say anything at that point but my ideas went towards option 3 as well, specifying that the evaluation order is not well determined and can change in the future so I agree with others here. It's not the first time this happens in PHP (https://3v4l.org/fQhgB) and I think we will be fine with it. Alex

Côme Chilliet

5 years ago
Le Wed, 16 Jun 2021 10:16:37 +0200, Nikita Popov <nikita.ppv@gmail.com> a écrit :
> 1. Eagerly evaluate initializers on declaration. This is what I tried in an > earlier revision of the RFC, and I don't think that approach works. It > breaks existing code and has various other unpleasant complications. > 2. Precisely specify the current behavior. I don't want to do this either, > because the exact places where evaluation happens are something of an > implementation detail. If in the future we find it convenient to separate > evaluation of non-static properties on object instantiation from evaluation > of static properties and class constants (which are not strictly needed at > that point), I'd like to retain the liberty to make such a change. > 3. Do not specify an evaluation order, beyond that evaluation happens at > certain uses of the class. Evaluation order may change across PHP versions. > If your code relies on any particular order, your code is broken.
If option 3 is considered, it means the evaluation order may change, if we can change the evaluation order, why not go for option 1? I do not understand which existing code can break with option 1 and be fine with option 3, it means this code relies on undefined behaviors, no? Côme

Alexandru Pătrănescu

5 years ago
On Thu, Jun 17, 2021 at 12:53 PM Côme Chilliet < come.chilliet@fusiondirectory.org> wrote:
> Le Wed, 16 Jun 2021 10:16:37 +0200, > Nikita Popov <nikita.ppv@gmail.com> a écrit : > > > 1. Eagerly evaluate initializers on declaration. This is what I tried in > an > > earlier revision of the RFC, and I don't think that approach works. It > > breaks existing code and has various other unpleasant complications. > > 2. Precisely specify the current behavior. I don't want to do this > either, > > because the exact places where evaluation happens are something of an > > implementation detail. If in the future we find it convenient to separate > > evaluation of non-static properties on object instantiation from > evaluation > > of static properties and class constants (which are not strictly needed > at > > that point), I'd like to retain the liberty to make such a change. > > 3. Do not specify an evaluation order, beyond that evaluation happens at > > certain uses of the class. Evaluation order may change across PHP > versions. > > If your code relies on any particular order, your code is broken. > > If option 3 is considered, it means the evaluation order may change, if we > can > change the evaluation order, why not go for option 1? I do not understand > which > existing code can break with option 1 and be fine with option 3, it means > this > code relies on undefined behaviors, no? > > Côme > > I think this message might have more details for why option 1 is not
simple to solve: https://externals.io/message/113347#113607

Nikita Popov

5 years ago
On Thu, Jun 17, 2021 at 11:53 AM Côme Chilliet < come.chilliet@fusiondirectory.org> wrote:
> Le Wed, 16 Jun 2021 10:16:37 +0200, > Nikita Popov <nikita.ppv@gmail.com> a écrit : > > > 1. Eagerly evaluate initializers on declaration. This is what I tried in > an > > earlier revision of the RFC, and I don't think that approach works. It > > breaks existing code and has various other unpleasant complications. > > 2. Precisely specify the current behavior. I don't want to do this > either, > > because the exact places where evaluation happens are something of an > > implementation detail. If in the future we find it convenient to separate > > evaluation of non-static properties on object instantiation from > evaluation > > of static properties and class constants (which are not strictly needed > at > > that point), I'd like to retain the liberty to make such a change. > > 3. Do not specify an evaluation order, beyond that evaluation happens at > > certain uses of the class. Evaluation order may change across PHP > versions. > > If your code relies on any particular order, your code is broken. > > If option 3 is considered, it means the evaluation order may change, if we > can > change the evaluation order, why not go for option 1? I do not understand > which > existing code can break with option 1 and be fine with option 3, it means > this > code relies on undefined behaviors, no? >
See the example in https://externals.io/message/113347#113642. I'm not sure how common this is, but I expect this kind of pattern does get used in PHP code not using autoloading and declaring multiple classes perf file. What this relies on is not so much that evaluation happens at a specific place, but that it doesn't happen during initial class declaration, which is exactly the assumption that would be broken by option 1. Regards, Nikita

Unnamed Person

5 years ago
Den 2021-06-17 kl. 12:08, skrev Nikita Popov:
> On Thu, Jun 17, 2021 at 11:53 AM Côme Chilliet < > come.chilliet@fusiondirectory.org> wrote: > >> Le Wed, 16 Jun 2021 10:16:37 +0200, >> Nikita Popov <nikita.ppv@gmail.com> a écrit : >> >>> 1. Eagerly evaluate initializers on declaration. This is what I tried in >> an >>> earlier revision of the RFC, and I don't think that approach works. It >>> breaks existing code and has various other unpleasant complications. >>> 2. Precisely specify the current behavior. I don't want to do this >> either, >>> because the exact places where evaluation happens are something of an >>> implementation detail. If in the future we find it convenient to separate >>> evaluation of non-static properties on object instantiation from >> evaluation >>> of static properties and class constants (which are not strictly needed >> at >>> that point), I'd like to retain the liberty to make such a change. >>> 3. Do not specify an evaluation order, beyond that evaluation happens at >>> certain uses of the class. Evaluation order may change across PHP >> versions. >>> If your code relies on any particular order, your code is broken. >> >> If option 3 is considered, it means the evaluation order may change, if we >> can >> change the evaluation order, why not go for option 1? I do not understand >> which >> existing code can break with option 1 and be fine with option 3, it means >> this >> code relies on undefined behaviors, no? >> > > See the example in https://externals.io/message/113347#113642. I'm not sure > how common this is, but I expect this kind of pattern does get used in PHP > code not using autoloading and declaring multiple classes perf file. > > What this relies on is not so much that evaluation happens at a specific > place, but that it doesn't happen during initial class declaration, which > is exactly the assumption that would be broken by option 1. > > Regards, > Nikita >
Is this a case where we go for the current scope in 8.1 and then extends it in 8.2 with static property and class constant initializers or do we include it already in 8.1? Regards //Björn L

Mike Schinkel

5 years ago
> On Jun 15, 2021, at 11:06 AM, Nikita Popov <nikita.ppv@gmail.com> wrote: > > On Fri, Jun 11, 2021 at 5:02 PM Larry Garfield <larry@garfieldtech.com <mailto:larry@garfieldtech.com>> > wrote: > >> On Wed, Mar 3, 2021, at 9:03 AM, Nikita Popov wrote: >>> Hi internals, >>> >>> I would like to propose allowing the use of "new" inside various >>> initializer expressions: https://wiki.php.net/rfc/new_in_initializers >>> >>> In particular, this allows specifying object default values for >> properties >>> and parameters, and allows the use of objects as attribute arguments. >>> >>> The RFC is narrow in scope in that it only adds support for "new". An >>> extension to other call kinds should be straightforward though. >>> >>> Regards, >>> Nikita >> >> Hi Nikita. What's the status of this RFC? Are you going to bring it to a >> vote, or is something else blocking it? >> > > I've just pushed a larger update to the RFC, which limits the places where > new is supported. > > Supported: > * Parameter default values (includes promoted properties) > * Attribute arguments > * Static variable initializers > * Global constant initializers > > Not supported: > * Static and non-static property initializers > * Class constant initializers
And I am saddened by the lack of class constant initializers. That's the main use case I am interested in and I had been watching this RFC anxiously for that one reason. . (In particular, because it would allow me to assign classes to constants with __ToString() methods to lazy load information.)
> I believe the cases that are now supported should be completely unambiguous > and uncontroversial.
Well, at least it is the former. :-) -Mike

Nikita Popov

5 years ago
On Tue, Jun 15, 2021 at 5:06 PM Nikita Popov <nikita.ppv@gmail.com> wrote:
> On Fri, Jun 11, 2021 at 5:02 PM Larry Garfield <larry@garfieldtech.com> > wrote: > >> On Wed, Mar 3, 2021, at 9:03 AM, Nikita Popov wrote: >> > Hi internals, >> > >> > I would like to propose allowing the use of "new" inside various >> > initializer expressions: https://wiki.php.net/rfc/new_in_initializers >> > >> > In particular, this allows specifying object default values for >> properties >> > and parameters, and allows the use of objects as attribute arguments. >> > >> > The RFC is narrow in scope in that it only adds support for "new". An >> > extension to other call kinds should be straightforward though. >> > >> > Regards, >> > Nikita >> >> Hi Nikita. What's the status of this RFC? Are you going to bring it to >> a vote, or is something else blocking it? >> > > I've just pushed a larger update to the RFC, which limits the places where > new is supported. > > Supported: > * Parameter default values (includes promoted properties) > * Attribute arguments > * Static variable initializers > * Global constant initializers > > Not supported: > * Static and non-static property initializers > * Class constant initializers > > I believe the cases that are now supported should be completely > unambiguous and uncontroversial. The other cases have evaluation order > issues in one way or another. This is discussed in > https://wiki.php.net/rfc/new_in_initializers#unsupported_positions. >
I plan to open voting on this RFC soon, with the current limitations in place. Based on the following discussion, there is interest in supporting static properties and class constants as well, but I don't think we're any closer to a consensus on how they should behave. Regards, Nikita