HomeRésuméOpen SourceWorkBlog
    Table of contents
  1. (top)
  2. Background - CPP
  3. Hooks and final properties
  4. Discovery
  5. From bug fix to RFC

A Tale of Two RFCs

Sunday, 22 June 2025

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.