Primary Constructors

php.internals

Rob Landers

75 days ago
Hello internals, I was reminded of my records RFC today, and one of the features of the RFC was "short constructors" generally called "primary constructors" in C#/Kotlin. They would look like this: class Point(public int $x, int $id = 0) extends Base($id); Which is just sugar for this: class Point extends Base { public function __construct(public int $x, int $id = 0) { parent::__construct($id); } } or this: class Point extends Base { public int $x; public function __construct(int $x, int $id = 0) { $this->x = $x; parent::__construct($id); } } A class with a primary constructor *may not* have a defined `__construct` function. Any special initialization must be done with hooks: class Temperature( public float $celsius { set { if ($value < -273.15) { throw new ValueError('below absolute zero'); } $this->celsius = $value; } } ) {} new Temperature(20.0); // ok new Temperature(-300.0); // ValueError: below absolute zero I'm sending this email to the list to gather additional feedback before pursuing a formal RFC proposal. — Rob

Rob Landers

75 days ago
On Wed, Jun 17, 2026, at 23:32, Rob Landers wrote:
> Hello internals, > > I was reminded of my records RFC today, and one of the features of the RFC was "short constructors" generally called "primary constructors" in C#/Kotlin. > > They would look like this: > > class Point(public int $x, int $id = 0) extends Base($id); > > Which is just sugar for this: > > class Point extends Base { > public function __construct(public int $x, int $id = 0) { > parent::__construct($id); > } > } > > or this: > > class Point extends Base { > public int $x; > public function __construct(int $x, int $id = 0) { > $this->x = $x; > parent::__construct($id); > } > } > > A class with a primary constructor *may not* have a defined `__construct` function. Any special initialization must be done with hooks: > > class Temperature( > public float $celsius { > set { > if ($value < -273.15) { > throw new ValueError('below absolute zero'); > } > $this->celsius = $value; > } > } > ) {} > > new Temperature(20.0); // ok > new Temperature(-300.0); // ValueError: below absolute zero > > I'm sending this email to the list to gather additional feedback before pursuing a formal RFC proposal. > > — Rob
I just realized that my examples did not demonstrate the classes with a body, but they may also have a body! — Rob

Nick

71 days ago
On 18.06.26 04:32, Rob Landers wrote:
> Hello internals, > > I was reminded of my records RFC today, and one of the features of the > RFC was "short constructors" generally called "primary constructors" > in C#/Kotlin. > > They would look like this: > > class Point(public int $x, int $id = 0) extends Base($id); > > Which is just sugar for this: > > class Point extends Base { > public function __construct(public int $x, int $id = 0) { > parent::__construct($id); > } > } > > or this: > > class Point extends Base { > public int $x; > public function __construct(int $x, int $id = 0) { > $this->x = $x; > parent::__construct($id); > } > } > > A class with a primary constructor *may not* have a defined > |__construct| function. Any special initialization must be done with > hooks: > > class Temperature( > public float $celsius { > set { > if ($value < -273.15) { > throw new ValueError('below absolute zero'); > } > $this->celsius = $value; > } > } > ) {} > > new Temperature(20.0); // ok > new Temperature(-300.0); // ValueError: below absolute zero > > I'm sending this email to the list to gather additional feedback > before pursuing a formal RFC proposal. > > — Rob
Hey Rob, I was excited about the Records RFC when I read it first. The "inline constructors" part was one of the things I loved the most about it. Glad to see you are picking it up as a stand-alone concept here! Having the constructor up there makes a lot of sense to me. Especially now with property hooks, that happen to move the constructor down in the file a lot -- because people are used to define properties before the constructor. Personally, I would love to see class construction to happen at the very top -- as proposed here. One thing I found weird about the "inline constructors" in the Records RFC was that it allowed primary (inline) *and* traditional constructors. Good to see that it is different in this proposal. However, I have one concern... Making this property hooks only has the downside that the classes cannot be `readonly`. Which makes it unusable in many situations where it would be neat to use primary constructors. I already back then wanted to propose the following, to avoid having two constructor types, but I think it also makes sense here because of the `readonly`-issue: ```php readonly class Point(public int $x, int $id = 0) => { // still allows hooks without readonly    // normal constructor body behaviour } extends Base($id) {   // class body } ``` Same as in your proposal it's just sugar. I'd expect it to behave the exact same as a normal `__construct` body, and the ` => {}` to be optional. As mentioned above, the sole benefit for me would be to have class construction at the very top, co-located with the class definition itself. You open a class -> you know what it consumes, and how it is constructed. Would be awesome! Would you be open to something like this?
-- Cheers Nick

Rowan Tommins [IMSoP]

71 days ago
On 22 June 2026 08:27:46 BST, Nick Sdot <php@nicksdot.dev> wrote:
> >```php >readonly class Point(public int $x, int $id = 0) => { // still allows hooks without readonly >   // normal constructor body behaviour >} extends Base($id) { >  // class body >} >``` > >Same as in your proposal it's just sugar. I'd expect it to behave the exact same as a normal `__construct` body, and the ` => {}` to be optional. >As mentioned above, the sole benefit for me would be to have class construction at the very top, co-located with the class definition itself. >You open a class -> you know what it consumes, and how it is constructed. Would be awesome!
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. In your proposed syntax, it puts an arbitrary amount of code between the class name and the "extends" and "implements" clauses, which seems to go against the aim. We could just let the "extends" clause include parameters from a separate constructor: ```php readonly class Point extends Base($id) { public function __construct(public int $x, int $id = 0) { // normal constructor body behaviour }   // class body } ``` That does raise a question of sequencing though: it implies the parent constructor is run automatically, but is that *before* or *after* the child constructor? On the whole, just making the new syntax "all or nothing" does seem a safer option. Regards, Rowan Tommins [IMSoP]

Nick

71 days ago
On 22.06.26 17:38, Rowan Tommins [IMSoP] wrote:
> 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. > > In your proposed syntax, it puts an arbitrary amount of code between the class name and the "extends" and "implements" clauses, which seems to go against the aim. > > We could just let the "extends" clause include parameters from a separate constructor: > > > ```php > readonly class Point extends Base($id) { > public function __construct(public int $x, int $id = 0) { > // normal constructor body behaviour > } >   // class body > } > ``` > > That does raise a question of sequencing though: it implies the parent constructor is run automatically, but is that *before* or *after* the child constructor? > > On the whole, just making the new syntax "all or nothing" does seem a safer option. > > Regards, > > Rowan Tommins > [IMSoP]
Hey Rowan,
> In your proposed syntax, it puts an arbitrary amount of code between the class name and the "extends" and "implements" clauses, which seems to go against the aim.
I do not have any strong feelings on how it should look like. Let's say it would be: ``` readonly class Point(public int $x, int $id = 0) extends Base($id) implements Foo {    // normal constructor body behaviour } => {   // class body } ``` Also fine. Beautiful, in fact.
> ```php > readonly class Point extends Base($id) { > public function __construct(public int $x, int $id = 0) { > // normal constructor body behaviour > } >   // class body > } > ``` > > That does raise a question of sequencing though: it implies the parent constructor is run automatically, but is that *before* or *after* the child constructor?
According to Robs proposal `__construct` is disallowed. The before/after question is general and not exclusive to what I asked, I believe? But yes, I also thought about it. There are multiple options, I have two for now: A) `Base($id)` will not work in combination with `=> {}` (or ` {} =>`); parent controller would need to be called as in `__construct` B) Control it via `Base($id)` and `Base($this->id)` But I guess we would first need to hear from Rob how it is supposed to work without my addition.
> On the whole, just making the new syntax "all or nothing" does seem a safer option.
But how to solve the `readonly` issue? I think if we have a chance for some new neat syntax -- and solve a problem with it, why not? --- Cheers Nick

Larry Garfield

71 days ago
On Wed, Jun 17, 2026, at 4:32 PM, Rob Landers wrote:
> Hello internals, > > I was reminded of my records RFC today, and one of the features of the > RFC was "short constructors" generally called "primary constructors" in > C#/Kotlin. > > They would look like this: > > class Point(public int $x, int $id = 0) extends Base($id); > > Which is just sugar for this: > > class Point extends Base { > public function __construct(public int $x, int $id = 0) { > parent::__construct($id); > } > } > > or this: > > class Point extends Base { > public int $x; > public function __construct(int $x, int $id = 0) { > $this->x = $x; > parent::__construct($id); > } > } > > A class with a primary constructor *may not* have a defined > `__construct` function. Any special initialization must be done with > hooks: > > class Temperature( > public float $celsius { > set { > if ($value < -273.15) { > throw new ValueError('below absolute zero'); > } > $this->celsius = $value; > } > } > ) {} > > new Temperature(20.0); // ok > new Temperature(-300.0); // ValueError: below absolute zero > > I'm sending this email to the list to gather additional feedback before > pursuing a formal RFC proposal. > > — Rob
I would support this, and I agree that KISS is the best way to go for now. I don't know if it makes sense to include in the initial design, but for reference here's how Kotlin handles construction: class Person(val name: String, val age: Int) { // Primary constructor, equivalent to promotion and what's proposed here. init { // Argument-less code block/method that runs after the primary constructor is assigned. // This could be useful to include for more complex cases, like where hooks are useful. } // Secondary constructor constructor(name: String): this(name, 0) { // This constructor gets called first, but is forced to call the primary constructor syntactically. // Then it can do other stuff if it wants. } } cf: https://kotlinlang.org/docs/classes.html#constructors-and-initializer-blocks I don't think we should include the "secondary constructor calls primary constructor and you have to make sure it works" logic. However, I do think we can/should consider an init block. Though how that interacts with inheritance I'm not sure yet. Putting non-trivial property hooks inside a constructor promotion is already strongly discouraged, and I would recommend continuing to discourage them even with this syntax. However, that does raise a concern that we would really benefit from some way to allow property hooks to be defined "on their own" on a property, AND link the property to a promoted constructor argument. I'm not sure off hand how we would do that, and it's somewhat tangential to this syntax, but I am pointing it out for completeness. As for the readonly/hooks incompatibility, honestly, I don't care. :-) Readonly is frequently problematic, Internals has already rejected making readonly and hooks compatible, and `protected(set)` gets you almost the same rules with none of the conflicts. So I'm good there. --Larry Garfield

Tim Düsterhus

71 days ago
Hi Am 2026-06-22 17:15, schrieb Larry Garfield:
> I don't think we should include the "secondary constructor calls > primary constructor and you have to make sure it works" logic.
PHP doesn't (and cannot easily) support method overloading, thus there cannot be such a thing as “primary and secondary” constructor. It is unclear to me what you are getting at here.
> However, I do think we can/should consider an init block.
At the point where extra logic is required maybe just write a plain old regular __construct() method instead of inventing yet another “method-like” construct. Best regards Tim Düsterhus

Larry Garfield

71 days ago
On Mon, Jun 22, 2026, at 1:40 PM, Tim Düsterhus wrote:
> Hi > > Am 2026-06-22 17:15, schrieb Larry Garfield: >> I don't think we should include the "secondary constructor calls >> primary constructor and you have to make sure it works" logic. > > PHP doesn't (and cannot easily) support method overloading, thus there > cannot be such a thing as “primary and secondary” constructor. It is > unclear to me what you are getting at here.
That we shouldn't do that part. :-) I was showing what Kotlin does, and calling out the parts we should and should not consider stealing.
>> However, I do think we can/should consider an init block. > > At the point where extra logic is required maybe just write a plain old > regular __construct() method instead of inventing yet another > “method-like” construct. > > Best regards > Tim Düsterhus
Allowing a __construct() method with no arguments when a "primary constructor" is in use would be fine with me, if that's the way we decide to go. --Larry Garfield