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
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
__constructfunction. 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 zeroI'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
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 zeroI'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:
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
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
__constructbody, 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:
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]
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:
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.
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__constructis 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 thereadonlyissue? I think if we have a chance for
some new neat syntax -- and solve a problem with it, why not?
Cheers
Nick
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
__constructfunction. 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 zeroI'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
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
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