[RFC] Object Initializer

  106947
September 12, 2019 14:00 michal.brzuchalski@gmail.com (=?UTF-8?Q?Micha=C5=82_Brzuchalski?=)
Hi internals,

I'd like to open discussion about RFC: Object Initializer.

This proposal reduces boilerplate of object instantiation and properties
initialization in case of classes without required constructor arguments as
a single expression with initializer block.

https://wiki.php.net/rfc/object-initializer

I appreciate any feedback you all can provide.

Thanks,
--
Michał Brzuchalski
brzuchal@php.net
  106969
September 12, 2019 15:01 kjarli@gmail.com (Lynn)
On Thu, Sep 12, 2019 at 4:00 PM Michał Brzuchalski <
michal.brzuchalski@gmail.com> wrote:

> Hi internals, > > I'd like to open discussion about RFC: Object Initializer. > > This proposal reduces boilerplate of object instantiation and properties > initialization in case of classes without required constructor arguments as > a single expression with initializer block. > > https://wiki.php.net/rfc/object-initializer > > I appreciate any feedback you all can provide. > > Thanks, > -- > Michał Brzuchalski > brzuchal@php.net > > Heya,
What's the added benefit of this compared to implementing a constructor? The part I like is that this can be used to replace stdClass/structured arrays. Perhaps something like this would nice to have in PHP: ``` $people = []; foreach ($peopleFromDatabase as [$id, $username, $name]) { $people[] = { Uuid id => $id, string username => $username, string name => $name, }; // and possible automatic assignment: $people[] = {Uuid $id, string $username, string $name}; } ``` Regards, Lynn van der Berg
  106971
September 12, 2019 15:07 oludonsexy@gmail.com (Olumide Samson)
The RFC is a beautiful feature suggestion, but something is telling me as
beautiful and straightforward the syntax is, what can the use case be?

I really love Constructor or Object initialization to be implemented in
PHP, but is there something I think you're missing in the RFC?

Thanks for the RFC.

On Thu, Sep 12, 2019, 4:02 PM Lynn <kjarli@gmail.com> wrote:

> On Thu, Sep 12, 2019 at 4:00 PM Michał Brzuchalski < > michal.brzuchalski@gmail.com> wrote: > > > Hi internals, > > > > I'd like to open discussion about RFC: Object Initializer. > > > > This proposal reduces boilerplate of object instantiation and properties > > initialization in case of classes without required constructor arguments > as > > a single expression with initializer block. > > > > https://wiki.php.net/rfc/object-initializer > > > > I appreciate any feedback you all can provide. > > > > Thanks, > > -- > > Michał Brzuchalski > > brzuchal@php.net > > > > > Heya, > > What's the added benefit of this compared to implementing a constructor? > > The part I like is that this can be used to replace stdClass/structured > arrays. Perhaps something like this would nice to have in PHP: > > ``` > $people = []; > > foreach ($peopleFromDatabase as [$id, $username, $name]) { > $people[] = { > Uuid id => $id, > string username => $username, > string name => $name, > }; > // and possible automatic assignment: > $people[] = {Uuid $id, string $username, string $name}; > } > ``` > > Regards, > Lynn van der Berg >
  107042
September 13, 2019 06:18 michal.brzuchalski@gmail.com (=?UTF-8?Q?Micha=C5=82_Brzuchalski?=)
Hi Olumide,

czw., 12 wrz 2019 o 17:07 Olumide Samson <oludonsexy@gmail.com> napisał(a):

> The RFC is a beautiful feature suggestion, but something is telling me as > beautiful and straightforward the syntax is, what can the use case be? > > I really love Constructor or Object initialization to be implemented in > PHP, but is there something I think you're missing in the RFC? > > Thanks for the RFC. >
I'm open to suggestions. PHP is known for borrowing features from other languages and this feature is also known from other languages. * Rust allows whole struct initialization in one expression, although these are not classes but being able to instantiate, initialize and return instance directly after it's being initialized reduces a lot of boilerplate. * C# uses an object initializer for the same purposes, one expression which allows to instantiate, initialize and use the object directly after initialization. * Java users use hacks to do the same, even more, cause they're allowed to call logic through method calls inside "initializer" block. In some cases when you deal a lot with DTO objects, object instantiation and properties initialization is very verbose and introduces a lot of noise.. class CustomerDTO { public Uuid $id; public string $name; public Email $email; public Address $address; public City $city; public Country $country; public PhoneNumber $phoneNumber; public etc... } Given above DTO class instantiation and initialization now requires: * INSTANTIATE class and assign to a variable: $obj = new CustomerDTO(); * FOR EACH (can be many) needed property use the variable with object instance and assign the property value: $obj->property = $value; Now when you deal a lot with objects like that you can see you're constantly repeating yourself with $obj-> part all the lines down before you probably would use that instance. There is no need for the constructor in such objects, they're purpose is to transfer data between app layers. Last months I've been working on a project which often requires to create an entity with data decoded from JSON format, most of them had many fields|properties required and only some of them were optional (even those optional were nullable so possibly I could initialize them with null once again), so the case with which I had a lot to do was creating factories which instantiate and for each property initialize value just to be able to return newly created instance in the end. That's when I thought object initializer could reduce a lot of boilerplate. That's when I started thinking of many places where I could use that, where my IDE could help me to write code faster without all that noise of constantly repeating myself. The syntax is similar to other languages. The narrow case described in BC changes is already deprecated and in my opinion, probably even not used or very rare. The future scope features can potentially save even more strikes but that's not the main reason about that RFC - nice to have but let's start with something. Regards, Michał Brzuchalski
  107041
September 13, 2019 05:49 michal.brzuchalski@gmail.com (=?UTF-8?Q?Micha=C5=82_Brzuchalski?=)
Hi Lynn,

czw., 12 wrz 2019 o 17:01 Lynn <kjarli@gmail.com> napisał(a):

> Heya, > > What's the added benefit of this compared to implementing a constructor? > > The part I like is that this can be used to replace stdClass/structured > arrays. Perhaps something like this would nice to have in PHP: > > ``` > $people = []; > > foreach ($peopleFromDatabase as [$id, $username, $name]) { > $people[] = { > Uuid id => $id, > string username => $username, > string name => $name, > }; > // and possible automatic assignment: > $people[] = {Uuid $id, string $username, string $name}; > } > ``` >
Removing stdClass for instantiation and initialization of simple objects is one of a future scope proposal. This RFC tries to address instantiation and initialization boilerplate reduction with a syntax which would not be restricted to stdClass only. Although it's not a game-changer, simple addition to the language which reduces boilerplate when dealing with objects which don't need complex constructors like for eg. DTO objects.. Regards,
> Lynn van der Berg > >
Regards, Michał Brzuchalski
  107043
September 13, 2019 06:39 claude.pache@gmail.com (Claude Pache)
> Le 13 sept. 2019 à 07:49, Michał Brzuchalski brzuchalski@gmail.com> a écrit : > > Hi Lynn, > > czw., 12 wrz 2019 o 17:01 Lynn <kjarli@gmail.com <mailto:kjarli@gmail.com>> napisał(a): > >> Heya, >> >> What's the added benefit of this compared to implementing a constructor? >> >> The part I like is that this can be used to replace stdClass/structured >> arrays. Perhaps something like this would nice to have in PHP: >> >> ``` >> $people = []; >> >> foreach ($peopleFromDatabase as [$id, $username, $name]) { >> $people[] = { >> Uuid id => $id, >> string username => $username, >> string name => $name, >> }; >> // and possible automatic assignment: >> $people[] = {Uuid $id, string $username, string $name}; >> } >> ``` >> > > Removing stdClass for instantiation and initialization of simple objects is > one of a future scope proposal. > > This RFC tries to address instantiation and initialization boilerplate > reduction with a syntax which would > not be restricted to stdClass only. > > Although it's not a game-changer, simple addition to the language which > reduces boilerplate when dealing > with objects which don't need complex constructors like for eg. DTO objects. >
As for `stdClass`, PHP has already a syntax: $baz = "baz"; $obj = (object) [ "foo" => "bar", $baz => true ]; For other type of objects, that could be done with a simple helper function $customer = object_assign(new Customer, [ "id" => 123, "name" => "John Doe", ]); where: function object_assign(object $obj, iterable $data): object { foreach ($data as $key => $value) { $obj->$key = $value; } return $obj; } That said, I generally use arrays rather than DTO objects or such, so that I can’t speak from experience. —Claude
  107045
September 13, 2019 07:15 michal.brzuchalski@gmail.com (=?UTF-8?Q?Micha=C5=82_Brzuchalski?=)
Hi Claude,

pt., 13 wrz 2019 o 08:39 Claude Pache pache@gmail.com> napisał(a):

> > > Le 13 sept. 2019 à 07:49, Michał Brzuchalski brzuchalski@gmail.com> > a écrit : > > Hi Lynn, > > czw., 12 wrz 2019 o 17:01 Lynn <kjarli@gmail.com> napisał(a): > > Heya, > > What's the added benefit of this compared to implementing a constructor? > > The part I like is that this can be used to replace stdClass/structured > arrays. Perhaps something like this would nice to have in PHP: > > ``` > $people = []; > > foreach ($peopleFromDatabase as [$id, $username, $name]) { > $people[] = { > Uuid id => $id, > string username => $username, > string name => $name, > }; > // and possible automatic assignment: > $people[] = {Uuid $id, string $username, string $name}; > } > ``` > > > Removing stdClass for instantiation and initialization of simple objects is > one of a future scope proposal. > > This RFC tries to address instantiation and initialization boilerplate > reduction with a syntax which would > not be restricted to stdClass only. > > Although it's not a game-changer, simple addition to the language which > reduces boilerplate when dealing > with objects which don't need complex constructors like for eg. DTO > objects. > > > As for `stdClass`, PHP has already a syntax: > > $baz = "baz"; > $obj = (object) [ > "foo" => "bar", > $baz => true > ]; > > For other type of objects, that could be done with a simple helper function > > $customer = object_assign(new Customer, [ > "id" => 123, > "name" => "John Doe", > ]); > > where: > > function object_assign(object $obj, iterable $data): object { > foreach ($data as $key => $value) { > $obj->$key = $value; > } > return $obj; > } > > That said, I generally use arrays rather than DTO objects or such, so that > I can’t speak from experience. > > —Claude > > You're of course right this can be done more or less with a helper function
which does all the required type validation through reflection, but I believe that feature requests for language support which reduces boilerplate can easily spread that simplification and bring all the required type check validations out of the box, right? IMO the thing is as you said this would require a helper function and not all simply like them! Regards, Michał Brzuchalski
  107047
September 13, 2019 07:35 mike@newclarity.net (Mike Schinkel)
> That said, I generally use arrays rather than DTO objects
I too have often used arrays. But then I have always been rigorous to ensure that variables and array elements are initialized before use, and fortunately I do not have a lot of typos in code I write. However I recently took on a large PHP app to refactor where the previous developers had used arrays profusely with very little pre-initialization. So I started to add in all initialization and checking to be certain variables had been initialized and realized it was an overwhelming task. I changed course and am now replacing all all arrays that are emulating a struct (vs a collection) to instances of declared classes and it has made a major improvement in my ability to refactor without bloating the code to 2-3x the original size. I has also allowed me to consolidate lots of repeated code in one place in methods and constructors. Given that experience and knowing that using "args" for optional parameters means my function signatures are much less likely to change, it would be super helpful if I could do so with type-checked object initializers instead of array literals. -Mike
  107059
September 13, 2019 08:51 arnold.adaniels.nl@gmail.com (Arnold Daniels)
This can easily be done in a number of ways, like the suggested helper
function. The same can be said for many other features that were
implemented recently, like array unpacking. This feature is easy to
implement and will make the code for data objects much more readable, with
additional benefits for static analysis.

Comments:

I don't like how it works for anonymous classes. It's more difficult to
implement since the compiler doesn't know the meaning of the (first)
bracket. It's doesn't make the code more readable, for the same reason. I
think it's better to not support this syntax with anonymous classes.

The examples do not show how constructor arguments are passed. I'm assuming
it's

    $customer = new Customer("foo") {
        name = "John"
    };

About the idea of letting `{ foo = 10 }` create an `stdClass` object (not
in the RFC); While not used much since it has no effect, it's perfectly
okay to put your code in brackets eg `{ { { $foo = 10; } } }`. As such, I
don't think it's a good idea to allow `new stdClass` to be omitted.
  107063
September 13, 2019 09:04 mike@newclarity.net (Mike Schinkel)
Arnold,

> This can easily be done in a number of ways, like the suggested helper > function.
A helper function can simulate an object initializer but it cannot provide the same potential benefits. The helper function uses array keys to identify properties, and as array keys cannot be declared like class properties, neither IDE, tools, nor PHP itself can validate the names and types of those keys like they could if object initializers were used instead. Michal,
> I don't like how it works for anonymous classes. It's more difficult to > implement since the compiler doesn't know the meaning of the (first) > bracket. It's doesn't make the code more readable, for the same reason. I > think it's better to not support this syntax with anonymous classes. > > The examples do not show how constructor arguments are passed. I'm assuming > it's > > $customer = new Customer("foo") { > name = "John" > }; > > About the idea of letting `{ foo = 10 }` create an `stdClass` object (not > in the RFC); While not used much since it has no effect, it's perfectly > okay to put your code in brackets eg `{ { { $foo = 10; } } }`. As such, I > don't think it's a good idea to allow `new stdClass` to be omitted.
I am curious why your RFC uses "=" instead of "=>" for separating properties from values? If we used "=>" it is potential — with lookahead parsing — that the following could be unambiguous: { foo => 10 } -Mike
  107067
September 13, 2019 09:22 michal.brzuchalski@gmail.com (=?UTF-8?Q?Micha=C5=82_Brzuchalski?=)
Hi Mike,

I am curious why your RFC uses "=" instead of "=>" for separating
> properties from values? > > If we used "=>" it is potential — with lookahead parsing — that the > following could be unambiguous: > > { foo => 10 } > > The reason about choosing "=" was a simplification of instantiation and
properties initialisation and we use "=" to assign property values now. The "=>" is specific for array key pairs and IMO should stay specific for arrays only. Thanks, Michał Brzuchalski
  107068
September 13, 2019 09:29 mike@newclarity.net (Mike Schinkel)
> IMO should stay specific for arrays only.
Why? Is there an objective reason? Is there some aspect of the syntax that makes it appropriate for arrays but inappropriate for object initialization? These are honest question. My two cents: I would like to type less too, but I have always assumed that "=>" was the PHP-ish syntax for initialization of a structure so it seems very appropriate to use for object initialization too. That said, 1. I don't feel strongly about iti either way, 2. Unless using "=>" would make naked object initialization possible — i.e. { foo => 10 } — given that it seems, as Arnold said, this would conflict with other meanings: { foo = 10 } #fwiw -Mike
  107070
September 13, 2019 09:39 michal.brzuchalski@gmail.com (=?UTF-8?Q?Micha=C5=82_Brzuchalski?=)
pt., 13 wrz 2019 o 11:29 Mike Schinkel <mike@newclarity.net> napisał(a):

> IMO should stay specific for arrays only. > > > Why? Is there an objective reason? > > $obj->foo => 123;
Simply, that's not the way you initialize object property values. When you use "=>" in array context you're pushing a new value to array and it'll work without labelling it with key where the key could be number index or string index in what shape you want (whitespaces etc.).
> Is there some aspect of the syntax that makes it appropriate for arrays > but inappropriate for object initialization? > > > These are honest question. > > > My two cents: I would like to type less too, but I have always assumed > that "=>" was the PHP-ish syntax for initialization of a structure so it > seems very appropriate to use for object initialization too. > > > That said, > > > 1. I don't feel strongly about iti either way, > > 2. Unless using "=>" would make naked object initialization possible — > i.e. { foo => 10 } — given that it seems, as Arnold said, this would > conflict with other meanings: { foo = 10 } > > > { $foo = 123 }; // unexpected "}" cause of missing ";"
$bar = { $foo = 123 }; // unexpected "{" cause it's not allowed in this context Both examples are syntax error. You can use {} for separating blocks of code, but now if you wanna assign value. Everything considered syntax error can be used for feature shaping. Regards, Michał Brzuchalski
  107072
September 13, 2019 09:47 mike@newclarity.net (Mike Schinkel)
> On Sep 13, 2019, at 2:39 AM, Michał Brzuchalski brzuchalski@gmail.com> wrote: > $obj->foo => 123; > Simply, that's not the way you initialize object property values.
So by the same logic this should be PHP's way to initialize array elements, right? $arr['foo'] => 123; But it is not. Obviously we both know that this is the correct syntax: $arr['foo'] = 123; So that is why I think it would be more consistent in PHP for object initializers to use "=>" instead of "=." But, I myself will not belabor the point beyond this message. Either others will agree with you or they will suggest to change to "=>" too.
> { $foo = 123 }; // unexpected "}" cause of missing ";" > $bar = { $foo = 123 }; // unexpected "{" cause it's not allowed in this context > Both examples are syntax error. > You can use {} for separating blocks of code, but now if you wanna assign value. > Everything considered syntax error can be used for feature shaping.
Hmm. Ok, I will let Arnold reply to you on this if he feels that your reply did not address his concerns. -Mike
  107065
September 13, 2019 09:19 michal@brzuchalski.com (=?UTF-8?Q?Micha=C5=82_Brzuchalski?=)
Hi Arnold,

pt., 13 wrz 2019 o 10:51 Arnold Daniels nl@gmail.com>
napisał(a):

> This can easily be done in a number of ways, like the suggested helper > function. The same can be said for many other features that were > implemented recently, like array unpacking. This feature is easy to > implement and will make the code for data objects much more readable, with > additional benefits for static analysis. > > Comments: > > I don't like how it works for anonymous classes. It's more difficult to > implement since the compiler doesn't know the meaning of the (first) > bracket. It's doesn't make the code more readable, for the same reason. I > think it's better to not support this syntax with anonymous classes. > > Probably lexical scope for anon classes would be better here, but due to
fact that proposal is to use initializer block instead of constructor arguments, that was the reason why initializer block got before anon class definitions.
> The examples do not show how constructor arguments are passed. I'm assuming > it's > > $customer = new Customer("foo") { > name = "John" > }; > > The examples don't show that cause it's forbidden.
There is a note on that in RFC on purpose
> Note! Object instantiation allows only constructors without required arguments to be used.
> Any class which requires passing arguments to constructor cannot be used in combination with object initializer.
Using constructor arguments and object initializer would introduce noise and you'll be potentially initializing object twice: using object initializer block and using constructor args what may be misleading.
> About the idea of letting `{ foo = 10 }` create an `stdClass` object (not > in the RFC); While not used much since it has no effect, it's perfectly > okay to put your code in brackets eg `{ { { $foo = 10; } } }`. As such, I > don't think it's a good idea to allow `new stdClass` to be omitted. >
Future scope mentions only about letting to create stdClass with omitting of the class name only, nothing said about removing a new keyword. Thanks, Michał Brzuchalski
  107075
September 13, 2019 11:26 kjarli@gmail.com (Lynn)
On Fri, Sep 13, 2019 at 10:51 AM Arnold Daniels <
arnold.adaniels.nl@gmail.com> wrote:

> Comments: > > I don't like how it works for anonymous classes. It's more difficult to > implement since the compiler doesn't know the meaning of the (first) > bracket. It's doesn't make the code more readable, for the same reason. I > think it's better to not support this syntax with anonymous classes. > > The examples do not show how constructor arguments are passed. I'm assuming > it's > > $customer = new Customer("foo") { > name = "John" > }; > > About the idea of letting `{ foo = 10 }` create an `stdClass` object (not > in the RFC); While not used much since it has no effect, it's perfectly > okay to put your code in brackets eg `{ { { $foo = 10; } } }`. As such, I > don't think it's a good idea to allow `new stdClass` to be omitted. >
If the compiler implementation of `$foo = {...};` would give issues, `$foo = new {...};` could be an alternative for run-time declared anonymous classes (preferably with typed properties). In regards of stdClass, I don't think this should be used. Regards, Lynn van der Berg
  107046
September 13, 2019 07:22 mike@newclarity.net (Mike Schinkel)
> What's the added benefit of this compared to implementing a constructor?
Michal my have a different answer but my answer are the following two benefits plus a 3rd related benefit: 1. The ability to add strictness to object initialization vs. using arrays of properties. PHP itself could flag misspelled or missing properties with a warning or an error which is something PHP cannot really do given that array keys are strings and not identifiers. 2. The ability for tools like PhpStorm, PHStan and others to more easily identify and flag these errors. It is hard (impossible?) for a tool to validate array element keys because there is no way to declare named and typed array elements keys in PHP. 3. It would empowering developers to pass optional elements to functions and methods that can also be type checked. The 2nd example would allow type checking but the first would not: EXAMPLE 1 function foo( int $id, array $args ) { ... } foo( 1, array( "bar" => "abc", "baz" => 123, } EXAMPLE 2 class FooArgs { public string $bar public int $baz } function foo( int $id, FooArgs $args ) { ... } foo( 1, FooArgs{ bar = "abc", baz = 123, } -Mike
> The part I like is that this can be used to replace stdClass/structured > arrays. Perhaps something like this would nice to have in PHP: > > ``` > $people = []; > > foreach ($peopleFromDatabase as [$id, $username, $name]) { > $people[] = { > Uuid id => $id, > string username => $username, > string name => $name, > }; > // and possible automatic assignment: > $people[] = {Uuid $id, string $username, string $name}; > } > ``` > > Regards, > Lynn van der Berg
  107044
September 13, 2019 07:09 mike@newclarity.net (Mike Schinkel)
Hi Michal,



I recently joined this list again after many years away, and a primary reason was to eventually propose an RFC that is almost exactly what you are calling Object Initializers.

So a huge thumbs up on this RFC.  Kudos.  I wish I had a vote and I would certainly vote for inclusion.

-Mike
  107074
September 13, 2019 10:18 rasmus@mindplay.dk (Rasmus Schultz)
I'd like to address the examples - and why I think they don't demonstrate
that this feature is really useful in practice.

There are several examples similar to this one:

class Car
{
  public int $yearOfProduction;
  public string $vin;
}

This is either lacking a constructor to initialize the properties to the
defined types - or it's lacking nullable type-hints. As it is, this class
doesn't guarantee initialization according to it's own property type
constraints.

Assuming the fields of this entity are required, you would probably prefer
to add a constructor - but then property initializers aren't really useful
anymore. This seems to be the case for most of the examples.

Models often have other constraints besides just the type - in those cases,
your models would likely have private/protected fields and setter-methods
that implement those constraints. The visibility example demonstrates the
use of a static factory method:

class Customer
{
  private string $name = '';
  protected ?string $email = null;

  public static function create(string $name, ?string $email = null): self
  {
    return new self {
      name = $name, // assign private property within the same class
      email = $email, // assign protected property within the same class
    };
  }
}

This may be fine for some use-cases - but many model types are only going
to have one valid way to construct an instance, and constructors are the
idiomatic way to do that. Unfortunately, this language feature works for
the new-statement only, so a desire to use this language feature will drive
architecture.

All in all, I find this feature is useful or applicable only to a few,
select patterns within the language - it isn't general enough.

In my opinion, language features should be as general as possible - a
feature like this "looks nice", being very abbreviated and clean-looking,
and, as I believe the examples in the RFC itself demonstrates, this will
provide the wrong kind of motivation to make what are, effectively,
architectural decisions.

Some models are immutable by design. Those cases don't seem to be well
supported by this feature.

My strong preference over this feature would be named parameters, which can
provide the same abbreviated initializations, but works more consistently
with the language, e.g. for all of the use-cases I cited above.

It works for constructors:

class Car
{
  public int $yearOfProduction;
  public string $vin;

  public function __construct(int $yearOfProduction, string $vin) {
    if ($yearOfProduction < 1900 ||  $yearOfProduction > date("Y")) {
      throw new InvalidArgumentException("year of production out of range:
{$yearOfProduction}");
    }

    $this->yearOfProduction = $yearOfProduction;
    $this->vin = $vin;
  }
}

$car = new Car({ yearOfProduction = 1975, vin = "12345678"});

It works for static factory-methods:

$car = Car::create({ yearOfProduction = 1975, vin = "12345678"});

It works for models with private/protected fields, classes with accessors,
classes with validations in constructors or factory-methods, and so on.

In other words, it works more generally with all common patterns and
practices - in many ways, it just seems like a better fit for the language.

The common criticism against named parameters, is they create coupling to
parameter-names. Object initializers create coupling to property-names - if
you can live with that, I don't think named parameters or object
initializers are very different in that regard.

The only problem I see with named parameters as an alternative to object
initializers, is in terms of versioning - renaming an argument, today, is
not a breaking change, and now it would be. That could be addressed, for
example, by making named parameters explicit at the declaration site - for
example, use curly braces in the declaration to designate named arguments:

  function makeCar({ int $yearOfProduction, string $vin }) {
    // ...
  }

This way, adding named parameters is not a breaking change - it's an opt-in
feature for those cases where it's useful and meaningful, but it's still
applicable to all the patterns and cases that someone might want to use it
for.

Named parameters is just one possible alternative - I'm just naming it for
practical comparison, to demonstrate how some features may have more
general applications than others.

I'd prefer to see new features that work everywhere, all the time, for
everyone - and for existing code. Rather than adding more features and
syntax for very specific (even relatively rare) use-cases.


On Thu, Sep 12, 2019 at 4:00 PM Michał Brzuchalski <
michal.brzuchalski@gmail.com> wrote:

> Hi internals, > > I'd like to open discussion about RFC: Object Initializer. > > This proposal reduces boilerplate of object instantiation and properties > initialization in case of classes without required constructor arguments as > a single expression with initializer block. > > https://wiki.php.net/rfc/object-initializer > > I appreciate any feedback you all can provide. > > Thanks, > -- > Michał Brzuchalski > brzuchal@php.net >
  107077
September 13, 2019 12:30 arnold.adaniels.nl@gmail.com (Arnold Daniels)
On Fri, Sep 13, 2019 at 12:18 PM Rasmus Schultz <rasmus@mindplay.dk> wrote:

> I'd like to address the examples - and why I think they don't demonstrate > that this feature is really useful in practice. > > There are several examples similar to this one: > > class Car > { > public int $yearOfProduction; > public string $vin; > } > > This is either lacking a constructor to initialize the properties to the > defined types - or it's lacking nullable type-hints. As it is, this class > doesn't guarantee initialization according to it's own property type > constraints. > > Assuming the fields of this entity are required, you would probably prefer > to add a constructor - but then property initializers aren't really useful > anymore. This seems to be the case for most of the examples. >
Using initializers and a constructor and not mutually exclusive. class User { public string $id; public ?string $name; public ?string $email; public function __construct() { $this->id = bin2hex(random_bytes(16)); } }
> > Models often have other constraints besides just the type - in those cases, > your models would likely have private/protected fields and setter-methods > that implement those constraints. The visibility example demonstrates the > use of a static factory method: > > class Customer > { > private string $name = ''; > protected ?string $email = null; > > public static function create(string $name, ?string $email = null): self > { > return new self { > name = $name, // assign private property within the same class > email = $email, // assign protected property within the same class > }; > } > } > > This may be fine for some use-cases - but many model types are only going > to have one valid way to construct an instance, and constructors are the > idiomatic way to do that. Unfortunately, this language feature works for > the new-statement only, so a desire to use this language feature will drive > architecture. >
This example doesn't make a lot of sense. In this case, the `create` method has no added benefit. You can just as well just use the constructor. class Customer { private string $name; protected ?string $email; public function __construct(string $name = '', ?string $email = null) { $this->name = $name; $this->email = $email; } } Using constructor arguments isn't a great approach classes that have a large number of properties, which is typically the case with data objects.
> All in all, I find this feature is useful or applicable only to a few, > select patterns within the language - it isn't general enough.
> In my opinion, language features should be as general as possible - a > feature like this "looks nice", being very abbreviated and clean-looking, > and, as I believe the examples in the RFC itself demonstrates, this will > provide the wrong kind of motivation to make what are, effectively, > architectural decisions. >
It seems like you consider the use of public properties as bad practice in general. However, I do not think such a hard stance should be taken in the ongoing discussion about public properties vs getters and setters. There are valid arguments on both sides. If you don't use public properties, this RFC will not affect you at all. If you do, there is a clear benefit in this approach.
> > Some models are immutable by design. Those cases don't seem to be well > supported by this feature. >
Immutable objects are not well supported in general in PHP. This RFC doesn't affect it.
> My strong preference over this feature would be named parameters, which can > provide the same abbreviated initializations, but works more consistently > with the language, e.g. for all of the use-cases I cited above. > > It works for constructors: > > class Car > { > public int $yearOfProduction; > public string $vin; > > public function __construct(int $yearOfProduction, string $vin) { > if ($yearOfProduction < 1900 || $yearOfProduction > date("Y")) { > throw new InvalidArgumentException("year of production out of range: > {$yearOfProduction}"); > } > > $this->yearOfProduction = $yearOfProduction; > $this->vin = $vin; > } > } > > $car = new Car({ yearOfProduction = 1975, vin = "12345678"}); > > It works for static factory-methods: > > $car = Car::create({ yearOfProduction = 1975, vin = "12345678"}); > > It works for models with private/protected fields, classes with accessors, > classes with validations in constructors or factory-methods, and so on. > > In other words, it works more generally with all common patterns and > practices - in many ways, it just seems like a better fit for the language. >
I see how named parameters competes with this RFC. They are two different things, that may both be implemented. The need to define all properties as constructor arguments and then setting them all isn't a great approach for classes with 10+ properties as it really bloats the class. Also having property guard for public properties that are only in the constructor isn't that great. The common criticism against named parameters, is they create coupling to
> parameter-names. Object initializers create coupling to property-names - if > you can live with that, I don't think named parameters or object > initializers are very different in that regard. >
You always have coupling to public property names. Public properties and public methods are what make up the api of the class. Nothing in this RFC changes that.
> > The only problem I see with named parameters as an alternative to object > initializers, is in terms of versioning - renaming an argument, today, is > not a breaking change, and now it would be. That could be addressed, for > example, by making named parameters explicit at the declaration site - for > example, use curly braces in the declaration to designate named arguments: > > function makeCar({ int $yearOfProduction, string $vin }) { > // ... > } > > This way, adding named parameters is not a breaking change - it's an opt-in > feature for those cases where it's useful and meaningful, but it's still > applicable to all the patterns and cases that someone might want to use it > for. >
This is a discussion on itself. It has nothing to do with this RFC. Please create a new RFC to discuss it.
> > Named parameters is just one possible alternative - I'm just naming it for > practical comparison, to demonstrate how some features may have more > general applications than others.
> I'd prefer to see new features that work everywhere, all the time, for > everyone - and for existing code. Rather than adding more features and > syntax for very specific (even relatively rare) use-cases. >
There is really nothing rare about the use of public properties. The RFC shouldn't be invalidated because it doesn't benefit one particular programming style, especially if that style is under heavy debate.
  107080
September 13, 2019 14:35 rowan.collins@gmail.com (Rowan Tommins)
Hi Rasmus,

On Fri, 13 Sep 2019 at 11:18, Rasmus Schultz <rasmus@mindplay.dk> wrote:

> All in all, I find this feature is useful or applicable only to a few, > select patterns within the language - it isn't general enough. >
I've trimmed the quote for readability, but agree with basically everything in this message. :) I like the reasoning behind this RFC, but think it is unnecessarily limited. It's not about "public properties are bad", it's just that if we can come up with a feature that works for public properties *and* other programming styles, we should prefer that. A related proposal that's come up before is short-hand constructors: class Foo { private int $foo; public function __construct($this->foo) { // automatically assigns first parameter to $this->foo so body is simplified } } Combine that with opt-in named parameters, and the Customer example from the RFC might look something like this: class Customer { private $id; private $name; private DateTimeImmutable $createdAt; public function __construct(id => $this->id, name => $this->name, createdDate => string $dateString = 'now') { $this->createdAt = new DateTimeImmutable($dateString); } }$customer = new Customer( id => 123, name => 'John Doe', createdDate => '2019-01-01 12:34' ); It's slightly more verbose, but a lot more flexible. As a side note, I have always thought stdClass was a bit of a kludge, and now we have real anonymous classes I would love to see it gradually phased out. I would much rather see syntax for capturing variables in an anonymous class declaration than new ways to create stdClass objects. Regards, -- Rowan Tommins [IMSoP]
  107093
September 14, 2019 20:29 larry@garfieldtech.com ("Larry Garfield")
On Fri, Sep 13, 2019, at 9:35 AM, Rowan Tommins wrote:
> Hi Rasmus, > > On Fri, 13 Sep 2019 at 11:18, Rasmus Schultz <rasmus@mindplay.dk> wrote: > > > All in all, I find this feature is useful or applicable only to a few, > > select patterns within the language - it isn't general enough. > > > > > I've trimmed the quote for readability, but agree with basically everything > in this message. :) > > I like the reasoning behind this RFC, but think it is unnecessarily > limited. It's not about "public properties are bad", it's just that if we > can come up with a feature that works for public properties *and* other > programming styles, we should prefer that. > > A related proposal that's come up before is short-hand constructors: > > class Foo { > private int $foo; > public function __construct($this->foo) { > // automatically assigns first parameter to $this->foo so body is > simplified > } > } > > Combine that with opt-in named parameters, and the Customer example from > the RFC might look something like this: > > class Customer > { > private $id; > private $name; > private DateTimeImmutable $createdAt; > > public function __construct(id => $this->id, name => $this->name, > createdDate => string $dateString = 'now') > { > $this->createdAt = new DateTimeImmutable($dateString); > } > }$customer = new Customer( > id => 123, > name => 'John Doe', > createdDate => '2019-01-01 12:34' > ); > > > It's slightly more verbose, but a lot more flexible. > > > As a side note, I have always thought stdClass was a bit of a kludge, and > now we have real anonymous classes I would love to see it gradually phased > out. I would much rather see syntax for capturing variables in an anonymous > class declaration than new ways to create stdClass objects. > > Regards, > -- > Rowan Tommins > [IMSoP]
I'm a big fan of using defined classes over anon arrays for struct-like data, for various reasons (cf https://steemit.com/php/@crell/php-use-associative-arrays-basically-never), so I'm sympathetic toward anything that makes that easier. However, I have to agree with Rasmus and Rowan that the current RFC proposal is too "narrow" to solve that effectively. The problem to be solved is that this: class Employee { public int $age; public string $name; public ?Employee $boss; public function __construct(int $age, string $name, Employee $boss = null) { $this->age = $age; $this->name = $name; $this->boss = $boss; } } $e = new Employee(34, "Bob", $sally); // or $e = new Employee(); $e->age = 34; $e->name = 'Bob'; $e->boss = $sally; Is just annoyingly verbose and annoying to work with. I agree. However, initializers as presented solve only a subset of this problem space: The case where: * The properties are public. * The constructor can be viable with no parameters. * There is no needed validation, or we can expect a user to manually call validate() or similar afterward. While that case absolutely exists, it is only a subset of the relevant use cases. As Rasmus and Rowan note, however, breaking the problem apart into two pieces would allow it to handle a wider array of use cases while still solving the one presented. For example (and with no thought given to syntax here, 'hoist' is almost certainly the wrong word): class Employee { protected int $age; protected string $name; protected ?Employee $boss; public function __construct({hoist int $age, hoist string $name, hoist Employee $boss = null}) { if ($age < 18) throw new ChildLaborException(); } } $e = new Employee({ age = 34, name = 'Bob', boss = $sally, }); Solves the initializer use case with very similar syntax for the caller, BUT also works for: * Any function where named parameters would be useful, independent of object initialization. * Any object where the properties in question are not public. * Any object where validation is required and you don't trust the caller to remember to call it (which you shouldn't trust) * Any object where only some of the constructor parameters map to object properties, a fact that should be hidden from the caller. And probably other situations. I could envision taking it a step further and writing instead: class Employee { public function __construct({hoist private int $age, hoist protected string $name, hoist public ?Employee $boss = null}) { if ($age < 18) throw new ChildLaborException(); } } And now you have even less typing and repetition. If you wanted to go really really far, you could even: class Employee { protected int $age; protected string $name; protected ?Employee $boss; public function hoist __construct() { if ($age < 18) throw new ChildLaborException(); } } As a short hand that enables any property to be passed in, but only using a named call, and still enforce that some parameters are required. I could see that being very useful for service objects, as well as value objects, where there is almost always a 1:1 mappnig from properties to constructor parameters. That is, named arguments are useful on their own (and have been discussed before). Auto-populating properties from the constructor is useful on its own. With the combination of those, the need for a specialized initializer syntax goes away, because they are an emergent property of the other two features. So on net, we get more power and more flexibility for less new syntax. Implementing a more limited syntax (just initialization for a subset of use cases) makes implementing the more robust approach in the future harder, because we'd have to be super careful to avoid BC issues or else end up with two slightly different syntaxes for slightly different but really the same thing behaviors; that's not only bad for usability and learnability but we're running out of convenient sigils. :-) So I would be -1 on initializers as proposed, but +1 on addressing that problem space through the more robust combination of named arguments and auto-populating properties. --Larry Garfield
  107078
September 13, 2019 13:23 matthewmatthew@gmail.com (Matthew Brown)
Though this is truly a stylistic complaint, I think it will lead to
harder-to-analyse code.

Currently I have a static analysis tool that can verify (in 99% of cases)
whether all properties of a class have been initialised in the constructor.
This check is even more important in PHP 7.4, where use of a property
without instantiation is a fatal error.

Figuring out which properties have been instantiated in a constructor is
non-trivial, and it's only efficient to do it once per class.

If we adopt this into the language and people use both __construct and
object initializers for a given class, analysis would become much more
tricky.

In order to find the error in this:

abstract class A {
  public string $s;
  public int $t;
}

class B extends A {
  public bool $u;

  public function __construct() {
    $this->s = "hello";
  }
}

$b = new C {
  u = true
};

The analyzer needs to understand that the initialisation of B left a
property uninitialised - it warns about it in B's constructor (
https://psalm.dev/r/0e8e40fefc) but I worry that people will start to
dismiss those warnings as false-positives if this pattern becomes popular.

Best wishes,

Matt




On Thu, 12 Sep 2019 at 10:00, Michał Brzuchalski <
michal.brzuchalski@gmail.com> wrote:

> Hi internals, > > I'd like to open discussion about RFC: Object Initializer. > > This proposal reduces boilerplate of object instantiation and properties > initialization in case of classes without required constructor arguments as > a single expression with initializer block. > > https://wiki.php.net/rfc/object-initializer > > I appreciate any feedback you all can provide. > > Thanks, > -- > Michał Brzuchalski > brzuchal@php.net >
  107089
September 14, 2019 09:05 sebastian@php.net (Sebastian Bergmann)
Am 13.09.2019 um 15:23 schrieb Matthew Brown:
> Though this is truly a stylistic complaint, I think it will lead to > harder-to-analyse code.
Fully agreed, and not just harder-to-analyse for a static analysis tool but also for humans that read the code. -1 from me.
  107090
September 14, 2019 09:21 michal@brzuchalski.com (=?UTF-8?Q?Micha=C5=82_Brzuchalski?=)
Hi Sebastian,

sob., 14 wrz 2019 o 11:05 Sebastian Bergmann <sebastian@php.net> napisał(a):

> Am 13.09.2019 um 15:23 schrieb Matthew Brown: > > Though this is truly a stylistic complaint, I think it will lead to > > harder-to-analyse code. > > Fully agreed, and not just harder-to-analyse for a static analysis tool > but also for humans that read the code. -1 from me. > > Could you explain why additional noise caused by constantly repeating
instance variable name and arrow in front of property name and assignment part is easier to read? Just wondering why you see object initializer as harder to read which I've view as easier to read. Example - easy to read: function createCustomerDTO(): Customer { $customer = new Customer(); $customer->id = Uuid::uuid4; $customer->name = "John Doe"; $customer->address = "Customer Street 16"; $customer->city = "London"; $customer->country = "GB"; $customer->phoneNumber = PhoneNumber::fromString("+1555 010 020"); $customer->birthDate = new DateTimeImmutable("1983-01-01"); $customer->email = Email::fromString("john.doe@example.com"); return $customer; } Example - hard to read: Why? function createCustomerDTO(): Customer { return new Customer { id = Uuid::uuid4, name = "John Doe", address = "Customer Street 16", city = "London", country = "GB", phoneNumber = PhoneNumber::fromString("+1555 010 020"), birthDate = new DateTimeImmutable("1983-01-01"), email = Email::fromString("john.doe@example.com"), }; } Thanks in advance, Michał Brzuchalski
  107092
September 14, 2019 10:16 kontakt@beberlei.de (Benjamin Eberlei)
Hi Michal,

thank you for this RFC. In combination with typed properties an object
initializer syntax becomes highly thought after in my opinion, especially
if you consider classes consisting mostly of typed PUBLIC properties, for
example data transfer objects or view models.

I find this especially important, because it helps with definitions that
otherwise create unitialized variables:

class Customer
{
     public DateTime $created;
}

$foo = new Customer();
// $foo->created now unitialized

Using the object initializer would leave the object in a fully consistent
state:

$foo = new Customer{ created=new DateTime('now') }

Two points I want to mention:

1. Currently with typed properties RFC its possible to declare a public
property as not nullable, but don't assign it. PHP will make it use the new
"unitialized" state. This is also done to allow unset() properties later to
allow hooks via __get. But with this new syntax, we could enforce that if a
class is new'ed with the object initialzer, then at end of the object
initializer + constructor, all properties are initialized, otherwise
Exception. This could help with bugs where you add a property to the class,
but forget to assign it in all cases where the class is new'ed +
intialized. One problem could be with RuntimeException that this only
crashes when the code is run, which is often too late, and would rather
warrant to just proceed and assume the developer knows what they are doing.
Also since object initialiizer only sets public properties, the check
should be for all public variables are initialized.

2.
You should add mention how the Reflection API changes. I would assume both
ReflectionClass and ReflectionObject get a new method
newInstanceFields(array $fields) with the following behavior:

$reflectionClass = new ReflectionClass(Customer::class);
$customer = $reflectionClass->newInstanceFields(['name' => 'Bert']);
// the same as
$customer = new Customer {name = 'Bert'};



On Thu, Sep 12, 2019 at 4:01 PM Michał Brzuchalski <
michal.brzuchalski@gmail.com> wrote:

> Hi internals, > > I'd like to open discussion about RFC: Object Initializer. > > This proposal reduces boilerplate of object instantiation and properties > initialization in case of classes without required constructor arguments as > a single expression with initializer block. > > https://wiki.php.net/rfc/object-initializer > > I appreciate any feedback you all can provide. > > Thanks, > -- > Michał Brzuchalski > brzuchal@php.net >
  107108
September 15, 2019 13:48 pmjones@pmjones.io ("Paul M. Jones")
> On Sep 12, 2019, at 09:00, Michał Brzuchalski brzuchalski@gmail.com> wrote: > > Hi internals, > > I'd like to open discussion about RFC: Object Initializer. > > This proposal reduces boilerplate of object instantiation and properties > initialization in case of classes without required constructor arguments as > a single expression with initializer block. > > https://wiki.php.net/rfc/object-initializer
This reminds me of how Hack/HHVM would initialize object properties from constructor parameters; I remember finding it very appealing. IIRC, you would provide the property signature as a constructor parameter to trigger the initialization: ```php class Foo { protected int $bar; public function __construct(protected int $bar) { } public function getBar() : int { return $this->bar; } } $foo = new Foo(1); echo $foo->getBar(); // 1 ``` Perhaps something like that is worth adopting here. -- Paul M. Jones pmjones@pmjones.io http://paul-m-jones.com Modernizing Legacy Applications in PHP https://leanpub.com/mlaphp Solving the N+1 Problem in PHP https://leanpub.com/sn1php
  107137
September 16, 2019 06:22 michal.brzuchalski@gmail.com (=?UTF-8?Q?Micha=C5=82_Brzuchalski?=)
Hi Paul,

niedz., 15 wrz 2019 o 15:48 Paul M. Jones <pmjones@pmjones.io> napisał(a):

> > > > On Sep 12, 2019, at 09:00, Michał Brzuchalski < > michal.brzuchalski@gmail.com> wrote: > > > > Hi internals, > > > > I'd like to open discussion about RFC: Object Initializer. > > > > This proposal reduces boilerplate of object instantiation and properties > > initialization in case of classes without required constructor arguments > as > > a single expression with initializer block. > > > > https://wiki.php.net/rfc/object-initializer > > This reminds me of how Hack/HHVM would initialize object properties from > constructor parameters; I remember finding it very appealing. > > IIRC, you would provide the property signature as a constructor parameter > to trigger the initialization: > > ```php > class Foo > { > protected int $bar; > > public function __construct(protected int $bar) > { > } > > public function getBar() : int > { > return $this->bar; > } > } > > $foo = new Foo(1); > echo $foo->getBar(); // 1 > ``` > > Perhaps something like that is worth adopting here. >
What you're describing AFAIR was called "constructor argument promotion" and this is not kind of problem current RFC tries to solve. Argument promotion looks useful for constructors, in the name of good practice constructors and methods|functions in general should not require a lot of arguments. Thank you for your feedback but I believe the suggested solution is off-topic. Thanks, Michał Brzuchalski
  107142
September 16, 2019 07:41 arnold.adaniels.nl@gmail.com (Arnold Daniels)
Features like `func_get_args(ARGS_OBJECT)` and `hoist` aren't really needed. You can use `get_defined_vars()` instead.

Many of my classes have constructors like;

public function __construct(int $foo, string $bar, bool $flag = false)
{
set_object_vars($this, get_defined_vars());

}

FYI `set_object_vars` is a user-space function that does the reverse of `get_object_vars`. You could use a `foreach` loop instead.
----

However when a class for a data object has 20 properties, I don't really want to put all 20 as constructor arguments. The object initialize RFC be the preferred solution for me in that case, even if named parameters was also supported. As such, I don't think the two features are mutually exclusive.

[Arnold Daniels - Chat @ Spike](https://www.spikenow.com/?ref=spike-organic-signature&_ts=5w9qi)	[5w9qi]

On September 16, 2019 at 6:22 GMT, Michał Brzuchalski brzuchalski@gmail.com> wrote:
  107139
September 16, 2019 07:29 michal.brzuchalski@gmail.com (=?UTF-8?Q?Micha=C5=82_Brzuchalski?=)
Hi all,

czw., 12 wrz 2019 o 16:00 Michał Brzuchalski brzuchalski@gmail..com>
napisał(a):

> Hi internals, > > I'd like to open discussion about RFC: Object Initializer. > > This proposal reduces boilerplate of object instantiation and properties > initialization in case of classes without required constructor arguments as > a single expression with initializer block. > > https://wiki.php.net/rfc/object-initializer >
I'll try to refer to comments proposing other solutions which pop up on this thread in one. Suggested solutions which appeared were: * constructor argument promotion * named arguments While I see them useful in general I don't see them efficient in this specific case. They are not competing features and could live together better suited to different situations. Please be the author for such RFC, I'm looking forward to that. This RFC tries to solve a lot of noise caused by constantly repeating $object-> when assigning properties. Please keep in mind that initializing properties through object initializer applies to visible properties and is possible to assign protected or private properties from the class scope as well. Current RFC was meant to reduce boilerplate especially on DTO|Model classes mimicking "structs" or "data classes" empowering all goods from typed properties. I don't see the reasons why many of you consider public properties a bad solution. PHP language has support for public properties and with typed properties, it is possible to ensure valid object state using VO's which are valid all the time, consider Uuid userland implementation no one checks if that's valid UUID, cause it's ensured by VO. Any kind of features like promoting arguments from the constructor or named arguments are fine but not efficient in those cases. Any good practices suggest limiting function|method arguments while the case is where there is a significant amount of properties to be initialized and additionally which don't need any kind of validation due to VO, or simple scalars. There were also some positive appealing comments, especially like feedback from Benjamin Eberlei.
> Using the object initializer would leave the object in a fully consistent state:
I am asking myself why didn't include it yet in my RFC. Thank you for the suggestion. I decided to add to proposed RFC that using Object Initializer enforce that if a class is instantiated with the Object Initializer, then at end of the instantiation and properties initialization, all visible (depends on initialization scope) properties are initialized, otherwise a RuntimeException is thrown. This could help with bugs where you add a property to the class, but forget to assign it in all cases where the class is instantiated and initialized. This is described under separate section https://wiki.php.net/rfc/object-initializer#restrictions and mentioned in https://wiki.php.net/rfc/object-initializer#introduction
> You should add mention how the Reflection API changes. I would assume both ReflectionClass and ReflectionObject get a new method
newInstanceFields(array $fields) with the following behavior:
> > $reflectionClass = new ReflectionClass(Customer::class); > $customer = $reflectionClass->newInstanceFields(['name' => 'Bert']); > // the same as > $customer = new Customer {name = 'Bert'};
Done. Added in separate section https://wiki.php.net/rfc/object-initializer#reflection To all who considered this RFC missing some benefit, please try to find it in added restriction which likely would help to avoid bugs with uninitialized properties. To all who didn't like the syntax. I can see arguments in favour of the use of "=>" instead of "=" in initializer block syntax, and would like to propose an additional vote which will help to decide which form fits best. Thanks in advance, Michał Brzuchalski
  107169
September 16, 2019 13:46 rowan.collins@gmail.com (Rowan Tommins)
On Mon, 16 Sep 2019 at 08:29, Michał Brzuchalski <
michal.brzuchalski@gmail.com> wrote:

> Please keep in mind that initializing properties through object initializer > applies to visible properties and is possible to assign > protected or private properties from the class scope as well. >
The problem with that is that you need an extra static method to make use of it, and you still need to get the arguments into that method. It might be useful occasionally, but it still doesn't help constructors which are setting a large number of private / protected properties.
> I don't see the reasons why many of you consider public properties a bad > solution. PHP language has support for public properties > and with typed properties, it is possible to ensure valid object state > using VO's which are valid all the time, consider Uuid userland > implementation > no one checks if that's valid UUID, cause it's ensured by VO. >
Firstly, it's not necessarily a case of "considering public properties a bad solution"; it's about evaluating where the new feature could be used, and where it wouldn't help. If there was a feature which helped this use case *and* other use cases, I think that would be "better", but that doesn't make this feature "bad". Secondly, typed properties certainly make public properties more appealing, but there are still a bunch of things that you can't do with them, like declaring them readonly, or having logic other than type validation in getters and setters. Note that C#, which has object initializers, also has these features, making them a lot more powerful.
> Any kind of features like promoting arguments from the constructor or named > arguments are fine but not efficient in those cases. > Any good practices suggest limiting function|method arguments while the > case is where there is a significant amount of properties > to be initialized and additionally which don't need any kind of validation > due to VO, or simple scalars. >
That's a good point; named parameters make function calls scale much better to long lists of parameters, but *declaring* the constructor would still be unwieldy. The only way to improve that would be for the class to effectively opt into having an initializer using a special syntax for the constructor. Larry gave this example syntax: class Employee { protected int $age; protected string $name; protected ?Employee $boss; public function hoist __construct() { if ($age < 18) throw new ChildLaborException(); } } And Paul M Jones mentioned this version from Hack, where the parameters are listed in the constructor, but don't need to be re-listed outside it: class Employee { public function __construct( protected int $age, protected string $name, protected ?Employee $boss ) { if ($age < 18) throw new ChildLaborException(); } } Either of those, with named parameters, would be almost indistinguishable from object initializers at the call site. Depending on the syntax chosen, it might be as similar as: // Call initializer, requires public properties new Employee { age => 42, name => 'John Smith' }; // Call constructor, requires special constructor definition new Employee( age => 42, name => 'John Smith' ); That would require multiple new features, though, so initializers might be more achievable in the short term, and perhaps there is room for both, particularly if support for getters and setters improves. Regards, -- Rowan Tommins [IMSoP]
  107171
September 16, 2019 14:37 michal@brzuchalski.com (=?UTF-8?Q?Micha=C5=82_Brzuchalski?=)
Hi Rowan,

pon., 16 wrz 2019 o 15:47 Rowan Tommins collins@gmail.com>
napisał(a):

> On Mon, 16 Sep 2019 at 08:29, Michał Brzuchalski < > michal.brzuchalski@gmail.com> wrote: > > > Please keep in mind that initializing properties through object > initializer > > applies to visible properties and is possible to assign > > protected or private properties from the class scope as well. > > > > > The problem with that is that you need an extra static method to make use > of it, and you still need to get the arguments into that method. It might > be useful occasionally, but it still doesn't help constructors which are > setting a large number of private / protected properties. > > This RFC is not trying to help those constructors but tries to simplify
instantiation objects and initializing properties there where any kind of constructor won't help, but rather would be unnecessary at all. The proposed solution applies well in DTO, "structs" etc. where you need to deal with 15+ properties which don't need any other validation than types and those are completely valid uses of public properties.
> > > I don't see the reasons why many of you consider public properties a bad > > solution. PHP language has support for public properties > > and with typed properties, it is possible to ensure valid object state > > using VO's which are valid all the time, consider Uuid userland > > implementation > > no one checks if that's valid UUID, cause it's ensured by VO. > > > > > Firstly, it's not necessarily a case of "considering public properties a > bad solution"; it's about evaluating where the new feature could be used, > and where it wouldn't help. If there was a feature which helped this use > case *and* other use cases, I think that would be "better", but that > doesn't make this feature "bad". > > Secondly, typed properties certainly make public properties more appealing, > but there are still a bunch of things that you can't do with them, like > declaring them readonly, or having logic other than type validation in > getters and setters. Note that C#, which has object initializers, also has > these features, making them a lot more powerful. > > True, C# which ships with object initializers also having property
accessors, read-only etc. There are RFC's treating about named arguments, property accessors showing up occasionally on ML. But none of them was accepted. This is another feature and another talk. There is no RFC's which tries to solve all those issues in one big RFC, as such chances of success would be very low.
> > > Any kind of features like promoting arguments from the constructor or > named > > arguments are fine but not efficient in those cases. > > Any good practices suggest limiting function|method arguments while the > > case is where there is a significant amount of properties > > to be initialized and additionally which don't need any kind of > validation > > due to VO, or simple scalars. > > > > > > That's a good point; named parameters make function calls scale much better > to long lists of parameters, but *declaring* the constructor would still be > unwieldy. The only way to improve that would be for the class to > effectively opt into having an initializer using a special syntax for the > constructor. Larry gave this example syntax: > > Please define long lists of parameters, cause issue this RFC is trying to
solve is a simplification and ensuring properly initialized state of all required and visible properties and we talk here about DTO's like classes with tons of properties. You wouldn't want to put 15+ arguments in your constructor to initialize public properties which don't need other validation than proper type, right? Even if it would be just adding "public" keyword in front of them.
> class Employee { > protected int $age; > protected string $name; > protected ?Employee $boss; > > public function hoist __construct() { > if ($age < 18) throw new ChildLaborException(); > } > } > > And Paul M Jones mentioned this version from Hack, where the parameters are > listed in the constructor, but don't need to be re-listed outside it: > > class Employee { > public function __construct( > protected int $age, > protected string $name, > protected ?Employee $boss > ) { > if ($age < 18) throw new ChildLaborException(); > } > } > > > Either of those, with named parameters, would be almost indistinguishable > from object initializers at the call site. Depending on the syntax chosen, > it might be as similar as: > > // Call initializer, requires public properties > new Employee { age => 42, name => 'John Smith' }; > // Call constructor, requires special constructor definition > new Employee( age => 42, name => 'John Smith' ); > > Last RFC treating about named arguments has similar syntax with curly
braces, but all together with previous ones tries to solve the issue through additional syntax inside parentheses, which means both features can coexist together. Calling instantiation always used parentheses as the way to pass constructor arguments let's keep it that way. Using object-initializer would use curly braces - just like it's used to be solved in other languages.
> > That would require multiple new features, though, so initializers might be > more achievable in the short term, and perhaps there is room for both, > particularly if support for getters and setters improves. > > Here again, IIRC you're trying to solve the issue which is off-topic.
Improving protected and private properties initialization through constructor is not the main target of current RFC. Thanks, Michał Brzuchalski
  107172
September 16, 2019 14:57 rowan.collins@gmail.com (Rowan Tommins)
On Mon, 16 Sep 2019 at 15:37, Michał Brzuchalski <michal@brzuchalski.com>
wrote:

> > >> The problem with that is that you need an extra static method to make use >> of it, and you still need to get the arguments into that method. It might >> be useful occasionally, but it still doesn't help constructors which are >> setting a large number of private / protected properties. >> >> > This RFC is not trying to help those constructors but tries to simplify > instantiation objects and initializing properties > there where any kind of constructor won't help, but rather would be > unnecessary at all. >
I realize that, I was responding to a specific point: you said that the syntax would work for protected or private properties if used where those are visible. I was saying that I don't think that combination would be used very often, so it's easiest to just discuss the public property case.
> You wouldn't want to put 15+ arguments in your constructor to initialize > public properties which > don't need other validation than proper type, right? > > Even if it would be just adding "public" keyword in front of them. >
Why not? You've got to list those 15 properties somewhere; if the syntax was such that you only needed to list them once, it makes no difference whether we call the result "class initializer" or "automatic constructor with 15 named parameters", IMO.
> > >> Either of those, with named parameters, would be almost indistinguishable >> from object initializers at the call site. Depending on the syntax chosen, >> it might be as similar as: >> >> // Call initializer, requires public properties >> new Employee { age => 42, name => 'John Smith' }; >> // Call constructor, requires special constructor definition >> new Employee( age => 42, name => 'John Smith' ); >> >> > Last RFC treating about named arguments has similar syntax with curly > braces, but all together with previous ones > tries to solve the issue through additional syntax inside parentheses, > which means both features can coexist together. > > Calling instantiation always used parentheses as the way to pass > constructor arguments let's keep it that way. > Using object-initializer would use curly braces - just like it's used to > be solved in other languages. >
My intention here was just to show that using named parameters would be just as concise as using an object initializer; I just picked a pair of syntaxes that were as similar as possible to illustrate that.
> > >> >> That would require multiple new features, though, so initializers might be >> more achievable in the short term, and perhaps there is room for both, >> particularly if support for getters and setters improves. >> >> > Here again, IIRC you're trying to solve the issue which is off-topic. > Improving protected and private properties initialization through > constructor is not the main target of current RFC. >
I don't think it's off-topic to consider whether a related feature would make this one redundant. However, you've picked a weird sentence to reply to, because I'm agreeing with you, that the two features could exist side by side without being redundant. Regards, -- Rowan Tommins [IMSoP]
  107174
September 16, 2019 15:16 michal@brzuchalski.com (=?UTF-8?Q?Micha=C5=82_Brzuchalski?=)
Hi Rowan,

pon., 16 wrz 2019 o 16:57 Rowan Tommins collins@gmail.com>
napisał(a):

> > >> >>> >>> That would require multiple new features, though, so initializers might >>> be >>> more achievable in the short term, and perhaps there is room for both, >>> particularly if support for getters and setters improves. >>> >>> >> Here again, IIRC you're trying to solve the issue which is off-topic. >> Improving protected and private properties initialization through >> constructor is not the main target of current RFC. >> > > > > I don't think it's off-topic to consider whether a related feature would > make this one redundant. However, you've picked a weird sentence to reply > to, because I'm agreeing with you, that the two features could exist side > by side without being redundant. >
Sorry for that, the quoted sentence was left unintentionally and yes, it's not off-topic. Had to rethink what I was trying to say, but I do think both these features could exist. Regards, Michał Brzuchalski
  107181
September 16, 2019 21:55 larry@garfieldtech.com ("Larry Garfield")
On Mon, Sep 16, 2019, at 10:16 AM, Michał Brzuchalski wrote:
> Hi Rowan, > > pon., 16 wrz 2019 o 16:57 Rowan Tommins collins@gmail.com> > napisał(a): > > > > > > >> > >>> > >>> That would require multiple new features, though, so initializers might > >>> be > >>> more achievable in the short term, and perhaps there is room for both, > >>> particularly if support for getters and setters improves. > >>> > >>> > >> Here again, IIRC you're trying to solve the issue which is off-topic. > >> Improving protected and private properties initialization through > >> constructor is not the main target of current RFC. > >> > > > > > > > > I don't think it's off-topic to consider whether a related feature would > > make this one redundant. However, you've picked a weird sentence to reply > > to, because I'm agreeing with you, that the two features could exist side > > by side without being redundant. > > > > Sorry for that, the quoted sentence was left unintentionally and yes, it's > not off-topic. > Had to rethink what I was trying to say, but I do think both these features > could exist. > > Regards, > Michał Brzuchalski
I am not sure I agree here. As I noted earlier, we're running out of sigils. Suppose initializer syntax used: new Employee{prop = "val"}; Now later we want to add named parameters. What happens if we use the same syntax? Does it mean the same thing, really? Or does the meaning of that call syntax vary depending on whether the constructor is defined in such a way to make it support named parameters? What happens when it could mean either, but the result would be different? Or do we have to guarantee that the result is the same, even if that means limiting one or the other? That could be avoided by using some other syntax for named parameter calls, say: new Employee({ prop = "val"}); But now we have two syntaxes that do *almost* the same thing, but with subtle differences, and the existing positional syntax, which means there's now 3 ways to create an object that look very similar visually but mean slightly different things. That's... not good. I still hold that initializers as described, even though I like the problem they're solving being solved, are a strict subset of the combination of named parameters and auto-constructor promotion. If we had those, we would have more functionality and initializer functionality comes for free, and it would be more self-evident what was going on, with fewer syntax variations. Whereas adding initializers now as a one-off would likely make adding those later more difficult. Thus I would rather see time spent on adding those than on a one-off syntax. (And yes, I fully realize I am saying that as someone not currently working on any of the above syntaxes and so I'm talking about other people doing work, etc.) --Larry Garfield
  107189
September 17, 2019 11:07 arvids.godjuks@gmail.com (Arvids Godjuks)
чт, 12 сент. 2019 г. в 16:00, Michał Brzuchalski <
michal.brzuchalski@gmail.com>:

> Hi internals, > > I'd like to open discussion about RFC: Object Initializer. > > This proposal reduces boilerplate of object instantiation and properties > initialization in case of classes without required constructor arguments as > a single expression with initializer block. > > https://wiki.php.net/rfc/object-initializer > > I appreciate any feedback you all can provide. > > Thanks, > -- > Michał Brzuchalski > brzuchal@php.net >
Thank you Michał for the RFC, I've have been keeping up with the thread and at this point, I do somewhat agree with people that maybe the RFC should go into V2 version development. It really seems like the scope of the RFC is just too small for the feature and it might be a good idea to expand it a bit. Also, I have this question - is there any performance to be gained here? Cause right now object hydration is an issue and is somewhat slow. Can it be optimised? Also, usually I don't really want properties to be public (aka writeable), so that means `private/protected` and I need to use the methods to set the properties or reflection. I might have missed or don't remember it, but I don't think I saw a discussion about that use-case. Value object, DTO's - all have a big use case for it. -- Arvīds Godjuks +371 26 851 664 arvids.godjuks@gmail.com Skype: psihius Telegram: @psihius https://t.me/psihius
  107391
October 7, 2019 07:49 michal.brzuchalski@gmail.com (=?UTF-8?Q?Micha=C5=82_Brzuchalski?=)
Hi Arvids,

sorry for the late response, I've been busy recently.

wt., 17 wrz 2019 o 13:08 Arvids Godjuks godjuks@gmail.com>
napisał(a):

> > I've have been keeping up with the thread and at this point, I do somewhat > agree with people that maybe the RFC should go into V2 version development. > It really seems like the scope of the RFC is just too small for the feature > and it might be a good idea to expand it a bit. >
I'm gonna let it go as it currently is, there's still time before PHP 8.0 for improvements. IMO with all the goods like list of properties to set and restriction which forces to properly initialize object instance with all required fields is enough.
> Also, I have this question - is there any performance to be gained here? > Cause right now object hydration is an issue and is somewhat slow. Can it > be optimised? > Also, usually I don't really want properties to be public (aka writeable), > so that means `private/protected` and I need to use the methods to set the > properties or reflection. I might have missed or don't remember it, but I > don't think I saw a discussion about that use-case. Value object, DTO's - > all have a big use case for it. >
This feature has no implementation yet so won't speak out about performance and optimisation now. Declaring classes with private/protected fields doesn't mean you cannot to object initialization using object initializer, you can do this from class inner scope exactly the same way as you're able to access them from class inner scope now. This feature may not be a cure for all diseases, but I believe is the right to reduce noise and boilerplate when instantiating and initializing simple objects with type restrictions and ensures valid object state every time it's used. BR, Michał Brzuchalski
  107394
October 7, 2019 08:00 michal.brzuchalski@gmail.com (=?UTF-8?Q?Micha=C5=82_Brzuchalski?=)
Hi all,

the discussion period was long and discussion I think quite comprehensive.

I'd like to open the RFC up for a voting today at 11:00 UTC. The voting
will take from 11:00 UTC 7th till 11:00 UTC 21st of October 2019.

BR,
Michał Brzuchalski

czw., 12 wrz 2019 o 16:00 Michał Brzuchalski brzuchalski@gmail..com>
napisał(a):

> Hi internals, > > I'd like to open discussion about RFC: Object Initializer. > > This proposal reduces boilerplate of object instantiation and properties > initialization in case of classes without required constructor arguments as > a single expression with initializer block. > > https://wiki.php.net/rfc/object-initializer > > I appreciate any feedback you all can provide. > > Thanks, > -- > Michał Brzuchalski > brzuchal@php.net >