Hello internals,
I'd like to put forward Primary Constructors https://wiki.php.net/rfc/primary-constructors for comment.
An implementation PR will be opened later today (UTC), and the RFC updated with this discussion thread.
— Rob
Hello internals,
I'd like to put forward Primary Constructors
https://wiki.php.net/rfc/primary-constructors for comment.An implementation PR will be opened later today (UTC), and the RFC
updated with this discussion thread.— Rob
Hey Rob,
thanks again for the work you did put into this!
Unfortunately you are not allowing bodies for primary constructor. This
means there will be no way to work around the "readonly classes don't
allow property hooks" problematic; except using conventional
constructors (private(set) is not the same; hence my previous
"readonly hooks" RFC).
If "use conventional constructors, if you need readonly classes"
stands, then the same can be argued for everything else in the RFC.
Why would we introduce a whole new syntax, but then only make it work
for limited use-cases such as "trivial validation or normalization"?
Your example:
class Money(
public readonly int$amount,
public readonly string$currency = 'USD',
) {}
class Money{
public function __construct(
public readonly int$amount,
public readonly string$currency = 'USD',
) {}
}
Pro:
- two lines less
- construction at top of class (love it!)
Con:
- primary constructor body not supported
- new syntax with gotchas to keep in mind
- for readonly classes it adds two property
readonlyin exchange to
writing two lines less - requires refactoring to conventional constructor as soon as it gets
"non-trivial" (I'd argue a lot of things that do not work are actually
still trivial).
Given the limitations, is this alone adding enough value? What is the
actual gain?
Adding a new syntax to the language but then lock it down as proposed
here does not feel right.
As is, I feel the proposal is too thin, it would unfortunately be yet
another feature that feels mid, incomplete, and introduces new gotchas.
Sorry, IMO, not worth it.
If we would say we want new and neat syntax sugar to write constructors
at the top of the class, I am all for it!
But then they would need to work like actual constructors (otherwise
it's not just sugar), without accepting yet another set of limitations
and weirdness's in a different place.
What I roughly have in mind:
readonly class Point(public int $x, int $id = 0) extends Base($id)
implements Foo {
// normal constructor body behaviour
} => {
// class body
}
--
Cheers
Nick
If "use conventional constructors, if you need
readonlyclasses"
stands, then the same can be argued for everything else in the RFC.
For the record, my view is completely the opposite: if the new syntax
allows everything a normal constructor does, just in a slightly
different position, it will lead to endless style discussions of which
to use.
In fact, I don't think even property hooks belong in a primary
constructor; they're ugly enough in a constructor promoted property.
I think it's perfectly fine to have short-hands that only cover specific
use cases, and longer forms that let you express more complex use
cases. It's one of the reasons I voted against the recent RFC to allow
an extra reassignment of properties in the constructor
[https://wiki.php.net/rfc/promoted_readonly_constructor_reassign].
--
Rowan Tommins
[IMSoP]
Hey Rowan,
If "use conventional constructors, if you need
readonlyclasses"
stands, then the same can be argued for everything else in the RFC.For the record, my view is completely the opposite: if the new syntax
allows everything a normal constructor does, just in a slightly
different position, it will lead to endless style discussions of which
to use.
That's a fair opinion, and not unexpected. Personally, I oppose
introducing more inconsistencies to PHP. Looks like that we need to have
these discussions then?
In fact, I don't think even property hooks belong in a primary
constructor; they're ugly enough in a constructor promoted property.
It's a feature that was accepted to PHP and exists already. So it
doesn't really matter whether we (I agree -- shouldn't be used for
anything more than eg a neat callback) find it ugly and we have to deal
with its existence. You are basically making a point for allowing
primary constructor bodies. Having them allows to have a readonly
classes with primary constructors, and assign properties in their body.
Neat.
I think it's perfectly fine to have short-hands that only cover
specific use cases, and longer forms that let you express more complex
use cases. It's one of the reasons I voted against the recent RFC to
allow an extra reassignment of properties in the constructor
[https://wiki.php.net/rfc/promoted_readonly_constructor_reassign].
For the record, my view is completely the opposite: without allowing
primary constructor bodies the added value of adding the new syntax is
nearly non-existent.
Quoting from your message in the first thread to this:
I don't think we should add extra syntax to the language just to
change people's habits. If you want a constructor body as the first
thing in the class, you can do that right now.
...because the exact argument can be made from either point of view. To
quote you again: "all or nothing"; bodies should be allowed.
--
Cheers
Nick
Hi, Nick,
Hey Rowan,
If "use conventional constructors, if you need
readonlyclasses"
stands, then the same can be argued for everything else in the RFC.For the record, my view is completely the opposite: if the new syntax
allows everything a normal constructor does, just in a slightly
different position, it will lead to endless style discussions of which
to use.That's a fair opinion, and not unexpected. Personally, I oppose
introducing more inconsistencies to PHP. Looks like that we need to have
these discussions then?
There's no inconsistency here. This syntax doesn't prevent you from using readonly classes. It only prevents you from initialization/validation in a body because there are no bodies. If you need a body, use a constructor -- that's what they're for. The RFC makes it plain that bodies are rejected and why. If you can provide a concrete reason why they should be allowed with a completely new syntax, that would be a good follow-up RFC; or even a competing one. If you disagree with the reasoning, I'd love to hear it.
— Rob
Hey Rob,
Hi, Nick,
Hey Rowan,
If "use conventional constructors, if you need
readonlyclasses"
stands, then the same can be argued for everything else in the RFC.For the record, my view is completely the opposite: if the new syntax
allows everything a normal constructor does, just in a slightly
different position, it will lead to endless style discussions of which
to use.That's a fair opinion, and not unexpected. Personally, I oppose
introducing more inconsistencies to PHP. Looks like that we need to have
these discussions then?There's no inconsistency here.
The inconsistency is that we will have two kind of constructors. One
that has a body, one has not. It's a completely new concept, but you
talk about syntax sugar.
It only prevents you from initialization/validation in a body because
there are no bodies.
Right. Which makes them useless in endless situations.
If you need a body, use a constructor -- that's what they're for.
But then I cannot benefit from the position (at the top of the class) of
the primary constructors, which I really would like. Shouldn't we try to
make each new feature as useful as possible? What are the actual
blockers to not support primary constructor bodies?
The RFC makes it plain that bodies are rejected and why.
It's possible that I missed where the "why" was explained, but I don't
see explanations apart from "expressed in property hooks"; which I am
here pointing out will not work in readonly classes. Could you please
make the "why" more clear in the RFC?
If you can provide a concrete reason why they should be allowed with a
completely new syntax, that would be a good follow-up RFC; or even a
competing one. If you disagree with the reasoning, I'd love to hear it.
The concrete reason is what I already mentioned: it will not work with
readonly classes and private(set) is not the same.
It's not that readonly for value projects is a rare thing.
— Rob
--
Cheers
Nick
Hey Rob,
Hi, Nick,
Hey Rowan,
If "use conventional constructors, if you need
readonlyclasses"
stands, then the same can be argued for everything else in the RFC.For the record, my view is completely the opposite: if the new syntax
allows everything a normal constructor does, just in a slightly
different position, it will lead to endless style discussions of which
to use.That's a fair opinion, and not unexpected. Personally, I oppose
introducing more inconsistencies to PHP. Looks like that we need to have
these discussions then?There's no inconsistency here.
The inconsistency is that we will have two kind of constructors. One that has a body, one has not. It's a completely new concept, but you talk about syntax sugar.
There's only one constructor; PHP doesn't allow you to have different constructors. You choose the spelling that makes the most sense for your class and objectives. This is just declaration, not logic.
It only prevents you from initialization/validation in a body because there are no bodies.
Right. Which makes them useless in endless situations.
Of all the PHP code on my machine (production application code, frameworks like symfony/laravel/doctrine) ... it's about 30-40% of all constructors that are completely empty. I also sent an LLM to look further at things that could benefit from this (ie, promote properties on older code instead of just empty constructors). It came back with 71% of Laravel and 61% of Symfony that could use Primary Constructors. I'm not saying they should go rewrite their code or anything like that, but that basically means at least 1-2 out of 3 classes written in a given codebase could use primary constructors to simplify their construction and make it easier to skim.
My 2¢: I don't think this is useless.
If you need a body, use a constructor -- that's what they're for.
But then I cannot benefit from the position (at the top of the class) of the primary constructors, which I really would like. Shouldn't we try to make each new feature as useful as possible? What are the actual blockers to not support primary constructor bodies?
There is nothing preventing a follow-up RFC from being proposed, but as well as anonymous classes, it deserves its own RFC. There's a lot of syntax to bike-shed, as well as questions like "if I give a body, can I still use a parent constructor shortcut?" It is not straightforward and nor should it be. I fully explored this in the Records RFC, and many people pushed back on the logic, even pointed out unsoundness in what seemed like an otherwise sound design.
The RFC makes it plain that bodies are rejected and why.
It's possible that I missed where the "why" was explained, but I don't see explanations apart from "expressed in property hooks"; which I am here pointing out will not work in readonly classes. Could you please make the "why" more clear in the RFC?
Will do.
If you can provide a concrete reason why they should be allowed with a completely new syntax, that would be a good follow-up RFC; or even a competing one. If you disagree with the reasoning, I'd love to hear it.
The concrete reason is what I already mentioned: it will not work with readonly classes and
private(set)is not the same.
It's not that readonly for value projects is a rare thing.
Where do you see in the RFC that it won't work with readonly classes, because that would be an error on my part? It explicitly does not prevent you from using any type of class except enums. If you're referring to the usage of primary constructors in combination with hooks and readonly, that's an orthogonal issue that belongs to the interaction between hooks and readonly and is far out of scope from this RFC. That is a limitation that exists today and this RFC does not strive to address it.
— Rob
--Cheers
Nick
— Rob
Hello internals,
I'd like to put forward Primary Constructors
https://wiki.php.net/rfc/primary-constructors for comment.An implementation PR will be opened later today (UTC), and the RFC updated
with this discussion thread.— Rob
I don't see any mention of anonymous classes, I assume they aren't
compatible syntax wise?
__
Hello internals,I'd like to put forward Primary Constructors https://wiki.php.net/rfc/primary-constructors for comment.
An implementation PR will be opened later today (UTC), and the RFC updated with this discussion thread.
— Rob
I don't see any mention of anonymous classes, I assume they aren't compatible syntax wise?
Hi Lynn,
That's a good observation! Originally, I had a whole section and decided to strip it, then forgot to add it back as non-supported and future scope. FWIW, I was originally going to support them with something like:
$arg = 'foo';
$class = new class(public int $x = $arg) {}
Which desugars to:
$arg = 'foo';
$class = new class($arg) {
public function __construct(public int $x) {}
}
There are a lot of caveats, which is why I dropped it for future scope:
- it would require types on the input parameters to disambiguate between arguments and properties
- it gets weird if you don't specify a default value for the property and makes it ambiguous as a constructor
- which begs the question of always having a default
In essence, it deserves its own RFC because there is a lot of subtlety and only makes sense in the context that primary constructors exist in the first place.
I'll address it in the RFC, thanks!
— Rob
Hello internals,
I'd like to put forward Primary Constructors
https://wiki.php.net/rfc/primary-constructors for comment.An implementation PR will be opened later today (UTC), and the RFC updated
with this discussion thread.— Rob
I don't see any mention of anonymous classes, I assume they aren't
compatible syntax wise?Hi Lynn,
That's a good observation! Originally, I had a whole section and decided
to strip it, then forgot to add it back as non-supported and future scope.
FWIW, I was originally going to support them with something like:$arg = 'foo';
$class = new class(public int $x = $arg) {}Which desugars to:
$arg = 'foo';
$class = new class($arg) {
public function __construct(public int $x) {}
}There are a lot of caveats, which is why I dropped it for future scope:
- it would require types on the input parameters to disambiguate
between arguments and properties- it gets weird if you don't specify a default value for the property
and makes it ambiguous as a constructor- which begs the question of always having a default
In essence, it deserves its own RFC because there is a lot of subtlety and
only makes sense in the context that primary constructors exist in the
first place.I'll address it in the RFC, thanks!
— Rob
I think the only thing that needs to be solved for anonymous classes would
be the syntax. Seeing there's always a visibility/type in property
promotion, I think it should be clear it's going to be style (A) or (B)
based on the syntax as you can't mix. I can't see a scenario where you
would write:
$class = new class(public int $x) {}
Where as the following would be just fine:
$class = new class(public int $x = 0) {}; // construct with
property $x being 0
$class = new class(public int|null $x = null) {}; // construct with
property $x being null
$class = new class(public int $x = $x) {}; // construct with
$x from local scope into property $x
$class = new class(public int $x = $this->x) {}; // construct with
$this->x from the instance scope
$class = new class(public int $x = self::$x) {}; // construct with
$this->x from the static self scope
Bonus points if the body becomes optional:
$class = new class(public int $x = 0, public int $y = 0);
This would be the existing style and remains unchanged
$class = new class($localScope) {};
Mixing would error as you can't pick both:
$class = new class(public int $x = 0, $y) {}; // Nothing handles $y as
no body is allowed
$class = new class($x, public int $y = 0) {}; // Can't allow the
latter as $x requires a body to do something
Perhaps this isn't as simple as I think it is, but this would be a nice +
to have in this RFC. I think it's fine to keep the initial implementation
rather restrictive, it can always be improved upon in the future.
FWIW, I was originally going to support them with something like:
$arg = 'foo';
$class = new class(public int $x = $arg) {}Which desugars to:
$arg = 'foo';
$class = new class($arg) {
public function __construct(public int $x) {}
}
I actually have a draft RFC kicking around for this, but using a different syntax inspired by closures:
$arg = 'foo';
$class = new class() use (public int $arg) {}
This allows a very concise version for very simple cases:
$class = new class() use ($arg) {}
I even had a working implementation, but paused it to work out if a constructor could be allowed as well. If the syntax allowed for a parent constructor call, though, that might be enough for most use cases.
Although it's not obvious, anonymous classes are actually compiled once, and then constructed multiple times, which means the syntax is doing quite a lot more than constructor promotion.
I agree that it should be left as future scope to pin down exactly how it should look and work.
Rowan Tommins
[IMSoP]
Hello internals,
I'd like to put forward Primary Constructors https://wiki.php.net/rfc/primary-constructors for comment.
An implementation PR will be opened later today (UTC), and the RFC updated with this discussion thread.
— Rob
I've updated the RFC with an implementation URL, the link to the mailing list thread, and information regarding anonymous classes.
— Rob