====== PHP RFC: Readonly Property Defaults ====== * Version: 0.9 * Date: 2026-07-04 * Author: Nick Sdot, php@nicksdot.dev * Status: Under Discussion * Implementation: https://github.com/php/php-src/pull/22588 * Discussion thread: https://news-web.php.net/php.internals/131760 * Voting thread: tbd * First Published at: https://wiki.php.net/rfc/readonly_property_defaults ===== Introduction ===== This RFC seeks to allow default values for instance readonly properties. Historically, default values on readonly properties were not disallowed for technical reasons, but because they were considered “not particularly useful”. With interfaces properties, introduced in PHP 8.4, this changed. Allowing default values on readonly class properties enables creating a strict contract for implementation property values that will not change at runtime -- as in, constant-like behaviour with contract. ===== Proposal ===== This RFC allows instance readonly properties to declare default values. The same applies to properties of readonly classes, because properties declared in a readonly class are implicitly readonly. No new syntax is introduced. This RFC only removes the current compile-time restriction that rejects an explicit default value on a readonly property. The default value follows the same rules as default values for non-readonly properties. Type checks, constant expression restrictions, inheritance checks, and trait composition are unchanged. ==== Why now? ==== The reason for not allowing readonly properties was usefulness, not technical. Quote from the original "Readonly Properties" RFC:
As the default value counts as an initialising assignment, a readonly property with a default value is essentially the same as a constant, and thus not particularly useful. The notion could become more useful in the future if new expressions are allowed as property default values.
This means the original RFC actively highlighted to look out for future language additions as a reason that would make allowing readonly property default values worthwhile. The in PHP 8.4 landed interface properties are such a reason. ==== General ==== The readonly defaults are useful for classes that implement get-only interface properties. Such classes can provide contractable, fixed metadata or blueprint values without boilerplate constructor assignments or getter methods. This is intentionally not equivalent to a { get; set; } interface property. A readonly property, with or without a default value, satisfies a ''{ get; }'' requirement but not a ''{ get; set; }'' requirement. A default value counts as the initialising assignment of the readonly property. This means the property is already initialised when the object is created, before the constructor body runs. Any later assignment, including from the constructor, is a modification and fails. className = $className; // Error: Cannot modify readonly property Rule::$className } } ?> ==== Interaction with inheritance ==== Readonly properties with defaults follow the existing inheritance rules for properties. A child class may inherit the default value. If a property is redeclared, the normal property compatibility checks apply. priority); // int(1) var_dump(new ChildRule()->priority); // int(2) ?> ==== Interaction with magic methods and unset ==== Readonly properties without default values keep the existing behaviour: They may be unset before initialisation from the declaring scope. This can make the property visible to magic methods and enables the usual lazy initialisation pattern through __get(). Readonly properties with default values are already initialised when the object is created -- therefore, unset($this->prop) is treated as unsetting an initialised readonly property and throws an error. value); // Error: Cannot unset readonly property LazyValue::$value } public function __get(string $name): mixed { echo "__get\n"; return 2; } } $object = new LazyValue(); var_dump($object->value); // int(1), __get() is not called ?> ==== Interaction with clone ==== Readonly properties with default values behave the same as other initialised readonly properties during cloning. If a class has a __clone() method, the cloned readonly property may be reinitialised once during that method. value++; } } var_dump((clone new Counter())->value); // int(2) ?> ==== Interaction with serialisation ==== Serialisation follows existing readonly property behaviour. A readonly property with a default value is initialised, so it is included in normal object serialisation. Unserialisation continues to use the existing object hydration semantics and is not changed by this RFC. ==== Interaction with asymmetric visibility ==== Readonly properties may already be combined with asymmetric visibility. This RFC does not change those rules. A default value initialises the property immediately, so later writes fail as readonly modifications regardless of set visibility. id = 2; // Error: Cannot modify readonly property Example::$id ?> ==== Interaction with traits ==== Traits may declare readonly properties with default values. Existing trait property compatibility rules apply. If two imported trait properties define the same readonly property with the same default value, composition is allowed. If the default values differ, the composition is incompatible. trait T1 { public readonly int $prop = 1; } trait T2 { public readonly int $prop = 2; } class Foo { use T1, T2; // Fatal error: T1 and T2 define the same property ($prop) in the composition of Foo. However, the definition differs... } ?> ==== Interaction with reflection ==== Reflection reports readonly properties with default values in the same way as other properties with default values. ReflectionProperty::isReadOnly() returns true, ReflectionProperty::hasDefaultValue() returns true, and ReflectionProperty::getDefaultValue() returns the declared default value. ==== Interaction with property hooks ==== None. Readonly properties cannot declare hooks. This remains unchanged. A readonly property with a default value may satisfy a get-only interface property. It does not satisfy an interface property that requires set access. ==== Non-goals ==== This RFC does not change: * the behaviour of readonly properties without default values * how readonly interacts with property hooks * how readonly interacts with static properties ===== Backward Incompatible Changes ===== None. ===== Proposed PHP Version(s) ===== PHP 8.6 ===== RFC Impact ===== The change only relaxes a compile-time validation rule for property declarations, the internal representation remains unchanged. ==== To the Ecosystem ==== IDEs, language servers, static analysers, and other tools that currently report defaults on readonly properties as disallowed must be updated to allow them. ==== To Extensions & SAPIs ==== None. ===== Future Scope ===== None. ===== Voting Choices ===== Primary Vote requiring a 2/3 majority to accept the RFC: * Yes * No * Abstain ===== Patches and Tests ===== https://github.com/php/php-src/pull/22588 ===== Implementation ===== tbd ===== References ===== * https://wiki.php.net/rfc/readonly_properties_v2 * https://wiki.php.net/rfc/readonly_classes ===== Rejected Features ===== None. ===== Changelog ===== * 2026-07-04: Initial version.