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
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
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 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.netHeya,
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
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
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
Le 13 sept. 2019 à 07:49, Michał Brzuchalski michal.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
Hi Claude,
pt., 13 wrz 2019 o 08:39 Claude Pache claude.pache@gmail.com napisał(a):
Le 13 sept. 2019 à 07:49, Michał Brzuchalski michal.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
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
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.
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 anstdClass
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 allownew 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
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
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,
- I don't feel strongly about iti either way,
- 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
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,
I don't feel strongly about iti either way,
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
$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
Hi Arnold,
pt., 13 wrz 2019 o 10:51 Arnold Daniels arnold.adaniels.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 anstdClass
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 allownew 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
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 anstdClass
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 allownew 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
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:
-
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.
-
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.
-
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
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
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
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.
Hi Rasmus,
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]
Hi Rasmus,
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
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
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.
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
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:
-
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.
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
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.
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:
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
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.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: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
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 [5w9qi]
Hi all,
czw., 12 wrz 2019 o 16:00 Michał Brzuchalski michal.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.
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
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]
Hi Rowan,
pon., 16 wrz 2019 o 15:47 Rowan Tommins rowan.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
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]
Hi Rowan,
pon., 16 wrz 2019 o 16:57 Rowan Tommins rowan.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
Hi Rowan,
pon., 16 wrz 2019 o 16:57 Rowan Tommins rowan.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
чт, 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
Hi Arvids,
sorry for the late response, I've been busy recently.
wt., 17 wrz 2019 o 13:08 Arvids Godjuks arvids.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 meansprivate/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
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 michal.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