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,
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
Hi internals,
I would like to propose allowing the use of "new" inside various
initializer expressions: https://wiki.php.net/rfc/new_in_initializersIn 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
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
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,
AlexHi internals,
I would like to propose allowing the use of "new" inside various
initializer expressions: https://wiki.php.net/rfc/new_in_initializersIn 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
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
On Wed, Mar 3, 2021 at 7:04 PM Alexandru Pătrănescu drealecs@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:
-
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). -
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
On Wed, Mar 3, 2021 at 7:04 PM Alexandru Pătrănescu drealecs@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
On Wed, Mar 3, 2021 at 7:04 PM Alexandru Pătrănescu drealecs@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!
AlexI'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
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!
AlexI'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
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!
AlexI'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
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 likeunserialize()
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
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]
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".
In practice, I almost always see the parent constructor called first, then the child constructor does its own thing. Having the language require you to do it backwards in order to use a new feature would be... well, not technically a BC break per se, but certainly invalidating what is currently standard practice.
If I'm following correctly, the concern is cases like this:
class Point {
private Logger $logger = new FancyLogger();
public function __construct(public int $x, public int $y) {
$this->logger->log("Point made.");
}
}
class FancyLogger {
public function __construct() {
$this->db = new DatabaseConnection();
$this->db->write('Logger start.');
}
}
$p1 = new Point(); // This writes "Logger start" then "Point made."
$p2 = unserialize(serialize($p1)); // This writes just "Logger start."
Am I understanding that right? If so, I'm... unclear why that is any more of an issue than doing it the manual way now.
--Larry Garfield
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:
class Foo {
const STATE_OPEN = 1;
const STATE_CLOSE = 2;
function setState(int $state) {
// ....
}
}
into:
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
Hi internals,
I would like to propose allowing the use of "new" inside various
initializer expressions: https://wiki.php.net/rfc/new_in_initializersIn 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
This is
Hi internals,
I would like to propose allowing the use of "new" inside various
initializer expressions: https://wiki.php.net/rfc/new_in_initializersIn 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 :)
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,
ś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_initializersIn 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
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_initializersIn 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
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
On Thu, Mar 4, 2021 at 5:26 PM Larry Garfield larry@garfieldtech.com
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,
NikitaWould 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
Hi internals,
I would like to propose allowing the use of "new" inside various
initializer expressions: https://wiki.php.net/rfc/new_in_initializersIn 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.
On Thu, Mar 4, 2021 at 4:26 PM Gabriel O gabriel.ostrolucky@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_initializersIn 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,
NikitaDoes 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
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_initializersIn 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
Hi internals,
I would like to propose allowing the use of "new" inside various
initializer expressions: https://wiki.php.net/rfc/new_in_initializersIn 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
On Fri, Jun 11, 2021 at 5:02 PM Larry Garfield larry@garfieldtech.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_initializersIn 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,
NikitaHi 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
On Fri, Jun 11, 2021 at 5:02 PM Larry Garfield larry@garfieldtech.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_initializersIn 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,
NikitaHi 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
On Tue, Jun 15, 2021 at 6:31 PM Larry Garfield larry@garfieldtech.com
wrote:
On Fri, Jun 11, 2021 at 5:02 PM Larry Garfield larry@garfieldtech.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_initializersIn 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,
NikitaHi 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:
- 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. - 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. - 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
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:
- 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.- 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.- 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
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
Le mer. 16 juin 2021 à 13:47, Larry Garfield larry@garfieldtech.com a
écrit :
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:
- 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.- 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.- 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
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
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 :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:
- 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.- 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.- 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
The options where static properties and class constants are concerned are:
- 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.- 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.- 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
Le Wed, 16 Jun 2021 10:16:37 +0200,
Nikita Popov nikita.ppv@gmail.com a écrit :
- 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.- 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.- 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
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 :
- 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.- 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.- 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
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 :
- 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.- 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.- 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
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 :
- 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.- 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.- 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
On Fri, Jun 11, 2021 at 5:02 PM Larry Garfield <larry@garfieldtech.com mailto:larry@garfieldtech.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_initializersIn 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,
NikitaHi 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
On Fri, Jun 11, 2021 at 5:02 PM Larry Garfield larry@garfieldtech.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_initializersIn 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,
NikitaHi 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