Re: [RFC] Primary Constructors
| From: | Larry Garfield | Date: | Tue, 30 Jun 2026 15:55:16 +0000 |
| Subject: | Re: [RFC] Primary Constructors | ||
| References: | 1 2 3 4 5 6 7 8 9 10 11 12 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-131638@lists.php.net to get a copy of this message | ||
On Mon, Jun 29, 2026, at 3:56 PM, Nick Sdot wrote:
>> final readonly class Foo(Collection|array $value) implements Bar {
>> $this->items = $value instanceof Collection ? $value : new
>> Collection($value);
>> } => {
>> public Collection $items;
>>
>> // class body private Collection $items
>> }
>>
>> final readonly class Foo implements Bar {
>> public Collection $items;
>>
>> public function __construct(Collection|array $value) {
>> $this->items = $value instanceof Collection ? $value : new
>> Collection($value);
>> }
>>
>> // class body private Collection $items
>> }
>>
>> I find the latter *much* clearer.
>
> Great, then you are lucky, because you *can* write it that way. Now
> allow me too writing it how I want writing it and everyone is happy! <3
>> Best regards
>> Tim Düsterhus
>
> --
>
> Cheers
> Nick
(Not sure where in the thread to put this, so just replying to the most recent message.)
It's worth noting that in C# (not sure about the other languages) it's possible to set the
default value of a non-promoted property to an expression that includes the primary constructor
parameters. The example from the C# docs[1]
public struct Distance(double dx, double dy)
{
public readonly double Magnitude => Math.Sqrt(dx * dx + dy * dy);
public readonly double Direction => Math.Atan2(dy, dx);
}
Which in PHP would look something like this:
class Distance(float $dx, float $dy)
{
public readonly float $magnitude = sqrt($dx * $dx + $dy * $dy);
public readonly float $direction = atan2($dy, $dx);
}
I'm not saying we should adopt that approach, necessarily. I'm just noting it as a
possible workaround for "i want a primary constructor and a tiny bit of post-processing",
if an init block isn't an acceptable. (I'm not sure how exactly that overlaps with
property accessors; maybe it's the same thing?)
It sounds like Nick and Tim are fundamentally split on having an init block (however it's
spelled). I'm impartial. I can see the use for it, but I also would be OK if it is punted for
later. I'd also be OK with exploring a C#-style approach as above.
As far as size/scope, RFCs should be "self-complete." They should offer some
functionality that is useful on its own, even if it gets multiplied by some other RFC. Sometimes
that means split it up, sometimes it means combine it. Right-sizing an RFC is not about making it
as small as possible or as far-reaching as possible. So if the result of the discussion is that we
do need to double the length of the RFC to include a constructor init block of some kind, so be it.
That's not a bad thing if the result is a better feature. Nor is leaving it out bad if the
result is a better feature. The goal should be "a complete and robust feature", not big
or small chunks.
[1] https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/primary-constructors#create-mutable-state
--Larry Garfield