# [RFC] New in initializers

- Thread: https://externals.io/message/113347
- Original archive: https://news-web.php.net/php.internals/113347
- Started by Nikita Popov on 2021-03-03T15:03:53+00:00
- Messages: 39
- RFC: New in initializers (Implemented (PHP 8.1)) — https://externals.io/rfc/new_in_initializers.md
- Page 1 of 2: messages 1 to 25 (25 per page, add `?page=N` to the URL)
- Next page: https://externals.io/message/113347.md?page=2
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #113347 — Nikita Popov — 2021-03-03T15:03:53+00:00

URL: https://externals.io/message/113347#113347

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

---

## #113348 — drealecs@gmail.com — 2021-03-03T15:27:46+00:00

URL: https://externals.io/message/113347#113348 · In reply to: #113347

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

---

## #113351 — Nikita Popov — 2021-03-03T15:49:39+00:00

URL: https://externals.io/message/113347#113351 · In reply to: #113348

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,
> Alex
>
>
>
> 
>
> > […]

---

## #113352 — Ben Ramsey — 2021-03-03T15:52:12+00:00

URL: https://externals.io/message/113347#113352 · In reply to: #113347

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

---

## #113353 — drealecs@gmail.com — 2021-03-03T18:04:28+00:00

URL: https://externals.io/message/113347#113353 · In reply to: #113351

> On Wed, Mar 3, 2021 at 4:28 PM Alexandru Pătrănescu <drealecs@gmail.com>
> wrote:
>
> > […]
>
> 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

---

## #113355 — Benjamin Eberlei — 2021-03-03T18:25:32+00:00

URL: https://externals.io/message/113347#113355 · In reply to: #113347

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_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 :)


> 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

---

## #113361 — michal.brzuchalski@gmail.com — 2021-03-04T05:21:24+00:00

URL: https://externals.io/message/113347#113361 · In reply to: #113347

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

---

## #113376 — Nikita Popov — 2021-03-04T13:53:30+00:00

URL: https://externals.io/message/113347#113376 · In reply to: #113361

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ł:
>
> > […]
>
> 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

---

## #113378 — Nikita Popov — 2021-03-04T14:10:45+00:00

URL: https://externals.io/message/113347#113378 · In reply to: #113353

On Wed, Mar 3, 2021 at 7:04 PM Alexandru Pătrănescu <drealecs@gmail.com>
wrote:

>
> 
>
> > […]
> 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

---

## #113379 — Gabriel O — 2021-03-04T15:26:08+00:00

URL: https://externals.io/message/113347#113379 · In reply to: #113347

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

---

## #113380 — Nikita Popov — 2021-03-04T15:54:56+00:00

URL: https://externals.io/message/113347#113380 · In reply to: #113379

On Thu, Mar 4, 2021 at 4:26 PM Gabriel O <gabriel.ostrolucky@gmail.com>
wrote:

>
> 
>
> > […]
>
>  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

---

## #113382 — Larry Garfield — 2021-03-04T16:25:37+00:00

URL: https://externals.io/message/113347#113382 · In reply to: #113376

> 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

---

## #113397 — Nikita Popov — 2021-03-05T09:51:45+00:00

URL: https://externals.io/message/113347#113397 · In reply to: #113382

On Thu, Mar 4, 2021 at 5:26 PM Larry Garfield <larry@garfieldtech.com>
wrote:

> 
>
> > […]
> as
> > […]
> new
> > […]
> it
> > […]
> "uninitialized"
> > […]
> supported
> > […]
>
> 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

---

## #113404 — Andrea Faulds — 2021-03-06T12:09:37+00:00

URL: https://externals.io/message/113347#113404 · In reply to: #113347

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

---

## #113405 — Rowan Tommins — 2021-03-06T13:28:34+00:00

URL: https://externals.io/message/113347#113405

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


That was my initial reaction, too, but it's actually pretty trivial to 
do static initialisation already, thanks to the way autoloading works. 
For instance if you put the below in "Foo.php", then it will be run once 
the first time class "Foo" is loaded:

class Foo {
    public static $bar;
}

Foo::$bar = new Whatever();


This has essentially the same effect as writing this:

class Foo {
    public static $bar = new Whatever();
}


Which is absolutely horrible if Whatever's constructor has side effects, 
but then most things are going to be horrible if your constructors have 
side effects...

Regards,

-- 
Rowan Tommins
[IMSoP]

---

## #113606 — Nikita Popov — 2021-03-19T11:22:42+00:00

URL: https://externals.io/message/113347#113606 · In reply to: #113353

On Wed, Mar 3, 2021 at 7:04 PM Alexandru Pătrănescu <drealecs@gmail.com>
wrote:

>
> 
>
> > […]
> 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

---

## #113607 — Nikita Popov — 2021-03-19T11:57:28+00:00

URL: https://externals.io/message/113347#113607 · In reply to: #113606

> On Wed, Mar 3, 2021 at 7:04 PM Alexandru Pătrănescu <drealecs@gmail.com>
> wrote:
>
> > […]
>
> 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

---

## #113608 — Nikita Popov — 2021-03-19T13:02:49+00:00

URL: https://externals.io/message/113347#113608 · In reply to: #113607

> On Fri, Mar 19, 2021 at 12:22 PM Nikita Popov <nikita.ppv@gmail.com>
> wrote:
>
> > […]
>
> 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

---

## #113642 — Claude Pache — 2021-03-20T18:58:21+00:00

URL: https://externals.io/message/113347#113642 · In reply to: #113606

> 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

---

## #114371 — Nikita Popov — 2021-05-11T09:56:36+00:00

URL: https://externals.io/message/113347#114371 · In reply to: #113608

> On Fri, Mar 19, 2021 at 12:57 PM Nikita Popov <nikita.ppv@gmail.com>
> wrote:
>
> > […]
>
> 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

---

## #114379 — Dik Takken — 2021-05-11T13:12:43+00:00

URL: https://externals.io/message/113347#114379 · In reply to: #114371

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

---

## #114380 — Rowan Tommins — 2021-05-11T13:57:40+00:00

URL: https://externals.io/message/113347#114380 · In reply to: #114379

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

---

## #114389 — Larry Garfield — 2021-05-11T14:41:41+00:00

URL: https://externals.io/message/113347#114389 · In reply to: #114380

> 
> > […]
> 
> 
> 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

---

## #114831 — Larry Garfield — 2021-06-11T15:02:03+00:00

URL: https://externals.io/message/113347#114831 · In reply to: #113347

> 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

---

## #114890 — Nikita Popov — 2021-06-15T15:06:06+00:00

URL: https://externals.io/message/113347#114890 · In reply to: #114831

On Fri, Jun 11, 2021 at 5:02 PM Larry Garfield <larry@garfieldtech.com>
wrote:

> 
> > […]
> properties
> > […]
>
> 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

---

Next page: https://externals.io/message/113347.md?page=2
