Hello internals.
I am looking into making the constructor body optional in classes,
essentially allowing you to write
class User {
public function __construct(
private string $name,
)
}
Currently to make this code valid, it would have to be written the
following way
class User {
public function __construct(
public string $name,
) {}
}
With the introduction or constructor property promotion in 8.0, we often
see classes where the constructor has an empty body, and my guess would be
that this will only increase with the introduction of property access hooks
in 8.4 which is allowed to be defined in the constructor also.
This change would only be a cosmetic change and simplify the userland code
by removing two redundant characters.
This would be my first RFC and I am willing to try and implement it myself.
Best regards
Oliver Nybroe (he/him)
Hi Oliver!
I am looking into making the constructor body optional in classes,
essentially allowing you to writeclass User { public function __construct( private string $name, ) }
See https://externals.io/message/114324 for a prior discussion of a very
similar idea.
Cheers,
Christoph
Hello internals.
I am looking into making the constructor body optional in classes, essentially allowing you to write
class User { public function __construct( private string $name, ) }
Currently to make this code valid, it would have to be written the following way
class User { public function __construct( public string $name, ) {} }
With the introduction or constructor property promotion in 8.0, we often see classes where the constructor has an empty body, and my guess would be that this will only increase with the introduction of property access hooks in 8.4 which is allowed to be defined in the constructor also.
This change would only be a cosmetic change and simplify the userland code by removing two redundant characters.
This would be my first RFC and I am willing to try and implement it myself.
Best regards
Oliver Nybroe (he/him)
Hi Oliver,
Some links to previous discussions when this was brought up in the
mailing list before:
I followed those discussions closely back then, and I suppose the
general sentiment (to which I also agree) was that this is more of a
code style consideration rather than a technical one. Although it's a
cosmetic change, the fact that it will cause BC issues on older PHP
versions was a significant concern; now more so after we had
Constructor Properties for 4 years.
I think you can gather more pre-RFC opinions from this mailing thread,
especially now that this is brought up after a while since Constructor
Properties were introduced.
If you do with to go with an RFC, I'd like to see if your proposal
addresses whether this syntax should implicitly call
parent::__construct()
, and if a semi colon is expected or not
(public function __construct(public int $foo);
vs public function __construct(public int $foo)
).
Ayesh.
I don't view this proposition as a breaking change. The way I
understand it, writing an empty body for a constructor would
still work, but we would also get the option to just omit the
body altogether.
I think it would be a very sensible update. I also think it should
require a semicolon, just so we have the same kind of syntax
as interfaces.
I would love to see such a change!
Hello internals.
I am looking into making the constructor body optional in classes, essentially allowing you to write
class User { public function __construct( private string $name, ) }
Currently to make this code valid, it would have to be written the following way
class User { public function __construct( public string $name, ) {} }
With the introduction or constructor property promotion in 8.0, we often see classes where the constructor has an empty body, and my guess would be that this will only increase with the introduction of property access hooks in 8.4 which is allowed to be defined in the constructor also.
This change would only be a cosmetic change and simplify the userland code by removing two redundant characters.
This would be my first RFC and I am willing to try and implement it myself.
Best regards
Oliver Nybroe (he/him)Hi Oliver,
Some links to previous discussions when this was brought up in the
mailing list before:I followed those discussions closely back then, and I suppose the
general sentiment (to which I also agree) was that this is more of a
code style consideration rather than a technical one. Although it's a
cosmetic change, the fact that it will cause BC issues on older PHP
versions was a significant concern; now more so after we had
Constructor Properties for 4 years.I think you can gather more pre-RFC opinions from this mailing thread,
especially now that this is brought up after a while since Constructor
Properties were introduced.If you do with to go with an RFC, I'd like to see if your proposal
addresses whether this syntax should implicitly call
parent::__construct()
, and if a semi colon is expected or not
(public function __construct(public int $foo);
vspublic function __construct(public int $foo)
).Ayesh.
Thanks for sharing previous discussions, I will definitely take a look at
those before writing up the RFC.
If you do with to go with an RFC, I'd like to see if your proposal
addresses whether this syntax should implicitly call
parent::__construct()
, and if a semi colon is expected or not
(public function __construct(public int $foo);
vspublic function __construct(public int $foo)
).
Thank you, these are very valuable points to address in the RFC.
I can definitely feel that there will be some mixed opinions about
semicolon vs no semi colon.
Best regards
Oliver Nybroe (he/him)
On Thu, 18 Jul 2024 at 11:49, Lily Bergonzat lilybergonzat+php@gmail.com
wrote:
I don't view this proposition as a breaking change. The way I
understand it, writing an empty body for a constructor would
still work, but we would also get the option to just omit the
body altogether.I think it would be a very sensible update. I also think it should
require a semicolon, just so we have the same kind of syntax
as interfaces.I would love to see such a change!
On Thu, Jul 18, 2024 at 11:46 AM Ayesh Karunaratne ayesh@php.watch
wrote:Hello internals.
I am looking into making the constructor body optional in classes,
essentially allowing you to writeclass User { public function __construct( private string $name, ) }
Currently to make this code valid, it would have to be written the
following wayclass User { public function __construct( public string $name, ) {} }
With the introduction or constructor property promotion in 8.0, we
often see classes where the constructor has an empty body, and my guess
would be that this will only increase with the introduction of property
access hooks in 8.4 which is allowed to be defined in the constructor also.This change would only be a cosmetic change and simplify the userland
code by removing two redundant characters.This would be my first RFC and I am willing to try and implement it
myself.Best regards
Oliver Nybroe (he/him)Hi Oliver,
Some links to previous discussions when this was brought up in the
mailing list before:I followed those discussions closely back then, and I suppose the
general sentiment (to which I also agree) was that this is more of a
code style consideration rather than a technical one. Although it's a
cosmetic change, the fact that it will cause BC issues on older PHP
versions was a significant concern; now more so after we had
Constructor Properties for 4 years.I think you can gather more pre-RFC opinions from this mailing thread,
especially now that this is brought up after a while since Constructor
Properties were introduced.If you do with to go with an RFC, I'd like to see if your proposal
addresses whether this syntax should implicitly call
parent::__construct()
, and if a semi colon is expected or not
(public function __construct(public int $foo);
vspublic function __construct(public int $foo)
).Ayesh.
Thanks for sharing previous discussions, I will definitely take a look at those before writing up the RFC.
If you do with to go with an RFC, I'd like to see if your proposal
addresses whether this syntax should implicitly call
parent::__construct()
, and if a semi colon is expected or not
(public function __construct(public int $foo);
vspublic function __construct(public int $foo)
).
Thank you, these are very valuable points to address in the RFC.I can definitely feel that there will be some mixed opinions about semicolon vs no semi colon.
Best regards
Oliver Nybroe (he/him)
Don't forget to bottom post :)
I have a feeling a semi-colon is going to be required to disambiguate:
public function __construct(protected string $foo) protected string $bar;
Without semi-colons, that is perfectly valid and confusing.
— Rob
Thanks for sharing previous discussions, I will definitely take a look
at those before writing up the RFC.If you do with to go with an RFC, I'd like to see if your proposal
addresses whether this syntax should implicitly call
parent::__construct()
, and if a semi colon is expected or not
(public function __construct(public int $foo);
vspublic function __construct(public int $foo)
).
Thank you, these are very valuable points to address in the RFC.I can definitely feel that there will be some mixed opinions about
semicolon vs no semi colon.Best regards
Oliver Nybroe (he/him)
Please don't top-post.
Since the last time this came up, PSR-12 has been replaced with PER-CS, which as of 2.0 now says:
If a function or method contains no statements or comments (such as an empty no-op implementation or when using constructor property promotion), then the body SHOULD be abbreviated as {} and placed on the same line as the previous symbol, separated by a space.
cf: https://www.php-fig.org/per/coding-style/#44-methods-and-functions
(I... suppose technically it doesn't mention classes, but I've been doing it for empty classes too.)
So the "coding style" part of the previous issue has been resolved. Whether that changes anyone's mind about whether this should be done or not is up to them to decide.
Personally, I'd probably vote for it if it came up, but I agree it's a pretty minor improvement and unlikely to pass. It would probably only be worth doing if there were other common-pattern-optimizations around the constructor that came with it. Things like auto-forwarding to the parent, or a more compact syntax than a full constructor method, or other things that make writing a "pure data" product type easier rather than just s/{}/;/
I don't know what those could look like. As a data point, in Kotlin (which is what my day job is now), constructor properties are always promoted, essentially.
class Foo(val a: String, val b: String) { // This is the equivalent of PHP's promoted properties.
val c: Int = 5 // A non-constructor-initialized property. These can have hooks, constructor ones I think cannot.
init {
// This is the non-promoted part of a constructor body, and runs after the properties are assigned.
}
}
In case of inheritance, there's dedicated required syntax for forwarding to the parent:
class Foo(val a: String, val b: String) : Bar(b) { // equivalent to parent::__construct($b)
}
You can also make the constructor private (etc.) with more explicitness:
class Foo private constructor(val a: String, val b: String) {}
Of note, if there's no constructor then the parens are omitted, and if there's no body then the {} body is omitted. That means a great many "value objects"/DTOs, etc just look like this:
class Foo(
val a: String,
val b: String,
)
Which would be equivalent to PHP's
class Foo {
public function __construct(
public readonly string $a,
public readonly string $b.
) {}
}
cf: https://kotlinlang.org/docs/classes.html
To be clear, I'm not suggesting PHP just copy Kotlin directly. I'm saying that if we want to improve the constructor syntax for common cases, which I am open to, we should be looking to do something more substantial and ergonomic than just replacing {} with ;, and we could probably get some good inspiration from other languages in our family. (Java, Kotlin, C#, Swift, etc.)
--Larry Garfield
I would love to see those improvements as well, however I am surprised
we seem to be more inclined to push a more substantial change than a
minor one.
I feel like the more substantial one would be more likely to break
stuff, compared to the minor one, and so I don't see why the minor one
would be refused?
That said, I am new here, and I do not yet know how things work, so it
probably is because I lack experience.
Thanks for sharing previous discussions, I will definitely take a look
at those before writing up the RFC.If you do with to go with an RFC, I'd like to see if your proposal
addresses whether this syntax should implicitly call
parent::__construct()
, and if a semi colon is expected or not
(public function __construct(public int $foo);
vspublic function __construct(public int $foo)
).
Thank you, these are very valuable points to address in the RFC.I can definitely feel that there will be some mixed opinions about
semicolon vs no semi colon.Best regards
Oliver Nybroe (he/him)Please don't top-post.
Since the last time this came up, PSR-12 has been replaced with PER-CS, which as of 2.0 now says:
If a function or method contains no statements or comments (such as an empty no-op implementation or when using constructor property promotion), then the body SHOULD be abbreviated as {} and placed on the same line as the previous symbol, separated by a space.
cf: https://www.php-fig.org/per/coding-style/#44-methods-and-functions
(I... suppose technically it doesn't mention classes, but I've been doing it for empty classes too.)
So the "coding style" part of the previous issue has been resolved. Whether that changes anyone's mind about whether this should be done or not is up to them to decide.
Personally, I'd probably vote for it if it came up, but I agree it's a pretty minor improvement and unlikely to pass. It would probably only be worth doing if there were other common-pattern-optimizations around the constructor that came with it. Things like auto-forwarding to the parent, or a more compact syntax than a full constructor method, or other things that make writing a "pure data" product type easier rather than just s/{}/;/
I don't know what those could look like. As a data point, in Kotlin (which is what my day job is now), constructor properties are always promoted, essentially.
class Foo(val a: String, val b: String) { // This is the equivalent of PHP's promoted properties.
val c: Int = 5 // A non-constructor-initialized property. These can have hooks, constructor ones I think cannot.
init {
// This is the non-promoted part of a constructor body, and runs after the properties are assigned.
}
}In case of inheritance, there's dedicated required syntax for forwarding to the parent:
class Foo(val a: String, val b: String) : Bar(b) { // equivalent to parent::__construct($b)
}
You can also make the constructor private (etc.) with more explicitness:
class Foo private constructor(val a: String, val b: String) {}
Of note, if there's no constructor then the parens are omitted, and if there's no body then the {} body is omitted. That means a great many "value objects"/DTOs, etc just look like this:
class Foo(
val a: String,
val b: String,
)Which would be equivalent to PHP's
class Foo {
public function __construct(
public readonly string $a,
public readonly string $b.
) {}
}cf: https://kotlinlang.org/docs/classes.html
To be clear, I'm not suggesting PHP just copy Kotlin directly. I'm saying that if we want to improve the constructor syntax for common cases, which I am open to, we should be looking to do something more substantial and ergonomic than just replacing {} with ;, and we could probably get some good inspiration from other languages in our family. (Java, Kotlin, C#, Swift, etc.)
--Larry Garfield
I would love to see those improvements as well, however I am surprised
we seem to be more inclined to push a more substantial change than a
minor one.I feel like the more substantial one would be more likely to break
stuff, compared to the minor one, and so I don't see why the minor one
would be refused?That said, I am new here, and I do not yet know how things work, so it
probably is because I lack experience.
As you're new, I'll just note, please don't top post. :-)
The optimal size of an RFC is a very squishy topic. There are some that argue for "the smallest possible and no smaller," on the theory that bite-sized changes are easier to discuss. However, they also attract more bikeshedding, and often a feature is not actually useful except in conjunction with other parts of it. On the flipside, a tiny RFC has a hard time justifying its existence, since whatever intended follow-ups that would make it actually useful are never guaranteed to happen (either the authors lose interest or they get voted down later, etc.). Going through an RFC also has a lot of process overhead, and breaking one small RFC into many tiny RFCs can actually take far longer than the one larger RFC.
Conversely, an RFC can be too big to really comprehend, and then no one really understands what it's doing without hours of reading and research, and there's so many moving parts even the authors cannot keep track of them. On the flipside, some parts just don't make any sense on their own unless combined with other aspects of a proposal.
So there's really no "natural" size for RFCs generally. It will vary with the topic. Also, the impact of an RFC often has very little to do with its length or number of features. The RFC text for "don't require ; to end a statement anymore" would likely be pretty short, but would also break, er, everything. :-)
Conversely, offering a new syntax for abbreviated constructors (as being discussed here) would be longer than that, but the impact on existing code would be zero (unless it's done badly).
Also, "minor work" can end up causing problems for future work. See also: readonly, which was intended as a "junior stepping stone" to more complex features, but as we've found, actually makes those future features more complex because it wasn't designed with those future expansions in mind.
My own stance (which I do not claim to be universal) is that an RFC is "right-sized" when it offers notable, meaningful benefit on its own, without "holes" in the functionality, but can and should have natural "extension points" where it will dovetail nicely with other, future features, which can be planned or not. Sometimes that means a very short RFC, other times it means something the size of property hooks. :-) It's really a case-by-case situation.
--Larry Garfield
I would love to see those improvements as well, however I am surprised
we seem to be more inclined to push a more substantial change than a
minor one.I feel like the more substantial one would be more likely to break
stuff, compared to the minor one, and so I don't see why the minor one
would be refused?That said, I am new here, and I do not yet know how things work, so it
probably is because I lack experience.As you're new, I'll just note, please don't top post. :-)
The optimal size of an RFC is a very squishy topic. There are some that argue for "the smallest possible and no smaller," on the theory that bite-sized changes are easier to discuss. However, they also attract more bikeshedding, and often a feature is not actually useful except in conjunction with other parts of it. On the flipside, a tiny RFC has a hard time justifying its existence, since whatever intended follow-ups that would make it actually useful are never guaranteed to happen (either the authors lose interest or they get voted down later, etc.). Going through an RFC also has a lot of process overhead, and breaking one small RFC into many tiny RFCs can actually take far longer than the one larger RFC.
Conversely, an RFC can be too big to really comprehend, and then no one really understands what it's doing without hours of reading and research, and there's so many moving parts even the authors cannot keep track of them. On the flipside, some parts just don't make any sense on their own unless combined with other aspects of a proposal.
So there's really no "natural" size for RFCs generally. It will vary with the topic. Also, the impact of an RFC often has very little to do with its length or number of features. The RFC text for "don't require ; to end a statement anymore" would likely be pretty short, but would also break, er, everything. :-)
Conversely, offering a new syntax for abbreviated constructors (as being discussed here) would be longer than that, but the impact on existing code would be zero (unless it's done badly).
Also, "minor work" can end up causing problems for future work. See also: readonly, which was intended as a "junior stepping stone" to more complex features, but as we've found, actually makes those future features more complex because it wasn't designed with those future expansions in mind.
My own stance (which I do not claim to be universal) is that an RFC is "right-sized" when it offers notable, meaningful benefit on its own, without "holes" in the functionality, but can and should have natural "extension points" where it will dovetail nicely with other, future features, which can be planned or not. Sometimes that means a very short RFC, other times it means something the size of property hooks. :-) It's really a case-by-case situation.
--Larry Garfield
I noted the "don't top-post" earlier, and I was very confused about
what it meant. I thought about it for a while and couldn't figure it
out so I just figured it was not to me. I understand, now, and I think
I am not top-posting right now? Thank you for giving me a nudge.
Also thank you for the in-depth explanation about the RFC size. I
understand your position now, and I think I mostly agree, fwiw.
Hi
I am not top-posting right now? Thank you for giving me a nudge.
To confirm: Yes, this email is not a top post. Top-posting means putting
your reply at the very top of the email, above the quoted bits, instead
of below them. The "gold standard" would be splitting and cutting the
quoted bits as appropriate so the context of what you are replying to is
immediately visible. Just like I am doing in my email right now: I've
removed everything except the specific sentence I'm replying to.
Best regards
Tim Düsterhus
Hi
I feel like the more substantial one would be more likely to break
stuff, compared to the minor one, and so I don't see why the minor one
would be refused?
There is no such thing as a minor syntax change. Any changes to the
syntax has consequences for all the downstream tools trying to
understand PHP. This includes IDEs, static analyzers, code formatting
and linting tools, which would all need to be updated to understand the
new syntax. Furthermore the syntax would need to be documented and
translated within the PHP documentation.
That's quite a bit of effort to save a single character per class
definition. In practice it will be even fewer, because not every class
will have an empty constructor. Also whenever a class gets a non-trivial
constructor in the future, the diff would not just consist of inserting
the new code into the body, but also replacing the semicolon by braces,
making the diff less readable.
I do not think this is worth it.
Best regards
Tim Düsterhus
I feel like the more substantial one would be more likely to break
stuff, compared to the minor one, and so I don't see why the minor one
would be refused?There is no such thing as a minor syntax change. Any changes to the
syntax has consequences for all the downstream tools trying to
understand PHP. This includes IDEs, static analyzers, code formatting
and linting tools, which would all need to be updated to understand the
new syntax. Furthermore the syntax would need to be documented and
translated within the PHP documentation.That's quite a bit of effort to save a single character per class
definition. In practice it will be even fewer, because not every class
will have an empty constructor. Also whenever a class gets a non-trivial
constructor in the future, the diff would not just consist of inserting
the new code into the body, but also replacing the semicolon by braces,
making the diff less readable.I do not think this is worth it.
Best regards
Tim Düsterhus
I am also of the same opinion.
The benefit of this is marginal.
And if this requires a semicolon, the benefit is even lower by effectively just typing one less character.
Best regards,
Gina P. Banyard
Please don't top-post.
Since the last time this came up, PSR-12 has been replaced with PER-CS,
which as of 2.0 now says:If a function or method contains no statements or comments (such as an
empty no-op implementation or when using constructor property promotion),
then the body SHOULD be abbreviated as {} and placed on the same line as
the previous symbol, separated by a space.cf: https://www.php-fig.org/per/coding-style/#44-methods-and-functions
(I... suppose technically it doesn't mention classes, but I've been doing
it for empty classes too.)So the "coding style" part of the previous issue has been resolved.
Whether that changes anyone's mind about whether this should be done or not
is up to them to decide.Personally, I'd probably vote for it if it came up, but I agree it's a
pretty minor improvement and unlikely to pass. It would probably only be
worth doing if there were other common-pattern-optimizations around the
constructor that came with it. Things like auto-forwarding to the parent,
or a more compact syntax than a full constructor method, or other things
that make writing a "pure data" product type easier rather than just s/{}/;/I don't know what those could look like. As a data point, in Kotlin
(which is what my day job is now), constructor properties are always
promoted, essentially.class Foo(val a: String, val b: String) { // This is the equivalent of
PHP's promoted properties.val c: Int = 5 // A non-constructor-initialized property. These can have
hooks, constructor ones I think cannot.init {
// This is the non-promoted part of a constructor body, and runs after
the properties are assigned.
}
}In case of inheritance, there's dedicated required syntax for forwarding
to the parent:class Foo(val a: String, val b: String) : Bar(b) { // equivalent to
parent::__construct($b)}
You can also make the constructor private (etc.) with more explicitness:
class Foo private constructor(val a: String, val b: String) {}
Of note, if there's no constructor then the parens are omitted, and if
there's no body then the {} body is omitted. That means a great many
"value objects"/DTOs, etc just look like this:class Foo(
val a: String,
val b: String,
)Which would be equivalent to PHP's
class Foo {
public function __construct(
public readonly string $a,
public readonly string $b.
) {}
}cf: https://kotlinlang.org/docs/classes.html
To be clear, I'm not suggesting PHP just copy Kotlin directly. I'm saying
that if we want to improve the constructor syntax for common cases, which I
am open to, we should be looking to do something more substantial and
ergonomic than just replacing {} with ;, and we could probably get some
good inspiration from other languages in our family. (Java, Kotlin, C#,
Swift, etc.)--Larry Garfield
Sorry about the top posting and thank you for your feedback. I'll take that
into account.
I have used Kotlin myself for many years and love how concise the syntax is
there. I'll take a look at other languages and see if I can come up with a
more concise syntax that still feels like PHP.
Else I might try out creating this small RFC first as a good introduction
to the flow, even though it is unlikely to pass.
Best regards
Oliver Nybroe (he/him)
On Fri, Jul 19, 2024 at 1:34 AM Oliver Nybroe olivernybroe@gmail.com
wrote:
Hello internals.
I am looking into making the constructor body optional in classes,
essentially allowing you to writeclass User { public function __construct( private string $name, ) }
Currently to make this code valid, it would have to be written the
following wayclass User { public function __construct( public string $name, ) {} }
With the introduction or constructor property promotion in 8.0, we often
see classes where the constructor has an empty body, and my guess would be
that this will only increase with the introduction of property access hooks
in 8.4 which is allowed to be defined in the constructor also.This change would only be a cosmetic change and simplify the userland code
by removing two redundant characters.This would be my first RFC and I am willing to try and implement it
myself.Best regards
Oliver Nybroe (he/him)
FWIW, there is already a PR for it (
https://github.com/php/php-src/issues/8420) which also contains the RFC
draft.
Kind regards,
Faizan