Sunday, 22 June 2025 A Tale of Two RFCs
A few minutes ago, my third RFC,
adding support for promoted final properties, was implemented
after being approved unanimously. This comes after my second RFC,
adding support for never
parameters, was rejected by the
community.
I wrote both of these RFCs because I thought that the feature might be useful
for others in the broader PHP developer community. In neither case was the
feature something I personally was likely to use. While the decisive 3-23
defeat of the never
parameters RFC was annoying, I'm not too beaten up about
the result - never
parameters isn't something that I desperately wanted the
language to include. Likewise, while I am glad that final property promotion was
approved, if it had been rejected I probably wouldn't have dwelled on it too
much.
Since never
-typed parameters were rejected, I'm not going to discuss them
further; if you are interested, the discussion thread
and vote thread on the
PHP internals mailing list covers some of the reasons why the
feature was not accepted. Instead, I'll focus on the RFC that was accepted and
just got implemented - supporting final
on promoted properties.
Background - CPP
Constructor property promotion (CPP) is a feature that was added in PHP 8.0 to reduce boilerplate in the common case of class properties being initialized in the constructor. For example:
<?php
class User {
private int $id;
private string $name;
public function __construct( int $id, string $name ) {
$this->id = $id;
$this->name = $name;
}
}
The pattern of declaring a class property and initializing it in the constructor is so common that a shortcut, constructor property promotion, was added:
<?php
class User {
public function __construct(
private int $id,
private string $name
) {}
}
This second code example has the exact same behavior as the first one; by
virtue of the private
keywords in the parameter declarations, in addition to
declaring parameters for the constructor, the $id
and $name
are "promoted"
to class properties, and the properties are initialized with the values given in
the constructor.
Hooks and final properties
I discovered the lack of support for final
on promoted properties while
working on adding support for final properties in the
PHP CodeSniffer library. As part of the
addition of property hooks, PHP 8.4 added support
for properties to be declared final
:
<?php
class ParentClass {
public final string $myProp;
}
class ChildClass extends ParentClass {
public string $myProp;
}
results in
Fatal error: Cannot override final property ParentClass::$myProp in {file} on line {line}
while in previous versions of PHP there would have been an error about not
being allowed to use final
for properties.
At the same time, hooked properties could also be used with constructor property promotion:
<?php
class AlwaysCapitalized {
public function __construct(
public string $inner { set => strtoupper( $value ); }
) {}
}
$str = new AlwaysCapitalized( "lowercase" );
echo $str->inner . "\n"; // LOWERCASE
$str->inner = "again lowercase";
echo $str->inner . "\n"; // AGAIN LOWERCASE
Since constructor property promotion worked with hooked properties, I expected that it would also work for final properties, for the same reason - reducing boilerplate.
Discovery
However, PHP 8.4 did not support final promoted properties, with or without hooks; the code
<?php
class Demo {
public function __construct(
public final $prop
) {}
}
would trigger an error
Fatal error: Cannot use the final modifier on a parameter in {file} on line {line}
I thought that the missing support for using final
with promoted properties
was a bug; without that support, a developer would need to write out
boilerplate when using final properties, exactly what constructor property
promotion was meant to reduce!
<?php
class User {
private final int $id;
private final string $name;
public function __construct( int $id, string $name ) {
$this->id = $id;
$this->name = $name;
}
}
From bug fix to RFC
When I discovered that final
could not be used on promoted properties, I filed
a bug report explaining the error. However, it turned out that
"fixing" the "bug" was not as complicated as I expected it would be, and I was
able to send a patch later the same day to implement the missing
functionality.
I was then told that the "bug" was not a bug at all, but rather had been a deliberate omission from the property hooks RFC. The "bug" report and associated pull request were both closed.
But, just because it was omitted earlier didn't mean that the community didn't want the feature - it wasn't voted on and explicitly rejected, it was just left out of the enormous property hooks RFC. Accordingly, I converted the bug report to a feature request, and emailed the PHP internals mailing list to see if there were any objections, or if the feature was small enough that it would be accepted without needing an RFC.
As it turns out, one developer (who noted that they did not oppose the feature, and eventually voted in favor of the RFC) insisted that, though small, the feature should indeed go through the RFC process. And so it did, being accepted with a vote of 25-0.
As for what comes next, I've already started discussion on an RFC
to add a new #[\DelayedTargetValidation]
attribute. Hopefully that will also
be accepted for PHP 8.5.