rfc:readonly-variables

PHP RFC: Readonly Variables

Introduction

PHP has long supported immutable values through define() and the const keyword at the global and class scope. However, there is currently no way to declare a variable as immutable within a local or functional scope — a pattern that has proven valuable in languages such as JavaScript (const), Rust (let), and Swift (let).

This RFC proposes readonly variables: the ability to mark a variable as immutable after its initial assignment using the readonly modifier. Once declared, reassignment results in a fatal error, making developer intent explicit and preventing a class of bugs that are otherwise undetectable without additional tooling.

<?php
 
readonly $connection = new PDO($dsn, $user, $password);
 
$connection = null; // Fatal error: Cannot re-assign readonly variable

Proposal

Immutable variables are declared by prefixing the variable name with the readonly keyword at the point of assignment. After the initial assignment, any attempt to reassign or pass the variable by reference results in a fatal error.

<?php
 
readonly $greeting = "Hello, World!";
 
$greeting = "Goodbye!"; // Fatal error: Cannot re-assign readonly variable
?>

Readonly variables are scoped exactly like regular variables — they follow function, method, and closure boundaries. They are not global constants and are not accessible via constant().

<?php
 
function getConnection(): PDO {
    readonly $pdo = new PDO('sqlite::memory:');
    // $pdo is constant within this function scope only
    return $pdo;
}
 
$connection = getConnection(); 
$connection = null; // Valid reassignment of the variable. 
?>

Pass-by-reference is forbidden:

<?php
 
function modify(string &$value): void {}
 
readonly $name = "PHP";
modify($name); // Fatal error: Cannot pass readonly variable by reference
?>

Compound assignment operators are forbidden:

<?php
 
readonly $count = 1;
$count += 1; // Fatal error: Cannot re-assign readonly variable
$count++;    // Fatal error: Cannot re-assign readonly variable
?>

Unset removes the readonly flag:

<?php
 
readonly $foo = "bar"; 
unset($foo); 
$foo = "test"; // Valid action, because unset removes the readonly flag.
?>

Scope and Conditional Initialization

<?php
 
if (true) {
    readonly $variable = "test"; 
}
 
if (false) {
    readonly $foo = "test"; 
}
 
$foo = "bar"; // Valid action, because variable is not initialized. 
$variable = "Change-me!"; // Fatal error: Cannot re-assign readonly variable
?>

Edge case — Readonly variables inside loops are limited to a single declaration:

Readonly variables are permitted inside loop bodies, but can only be declared once. Since loop variables are exposed to the outer scope after the loop completes, a readonly variable declared inside a loop cannot be re-initialized on subsequent iterations.

<?php
 
foreach ([1, 2, 3] as $item) {
    readonly $doubled = $item * 2; // Fatal error: Cannot re-assign readonly variable
}
?>

Examples

Simple example — immutable configuration value:

<?php
 
readonly $dsn = 'mysql:host=localhost;dbname=myapp';
readonly $pdo = new PDO($dsn, 'root', '');
 
echo $dsn; // mysql:host=localhost;dbname=myapp
?>

Multiple Assignment

Multiple variables can be declared as constant in a single statement by separating them with a comma, following the same syntax as regular variable assignment.

<?php
 
readonly $foo = "bar", $bar = "baz";
 
$foo = "Change-me!"; // Fatal error: Cannot re-assign readonly variable
$bar = "Change-me!"; // Fatal error: Cannot re-assign readonly variable
?>

Edge case — readonly variables in closures:

<?php
 
$multiplier = 3;
 
$fn = function(int $value) use ($multiplier) {
    readonly $factor = 2;
    return $value * $factor * $multiplier;
};
 
echo $fn(5); // 30
?>

Backward Incompatible Changes

This RFC introduces no breaking changes for existing code. The readonly keyword in the context of variable declarations is currently a parse error, meaning no valid existing PHP code uses this syntax.

Proposed PHP Version(s)

Next PHP 8.x

RFC Impact

To the Ecosystem

IDEs, language servers (e.g. Intelephense, PHPStan, Psalm), and static analyzers will need to be updated to recognize the readonly variable modifier and enforce immutability during static analysis. Auto-formatters and linters may also need updates to handle the new syntax. The benefit is that these tools can now guarantee immutability where previously they could only hint at it via docblock annotations such as @readonly on variables.

To Existing Extensions

Extensions that inspect or manipulate variable symbols (e.g. via zend_hash or custom opcache strategies) may need to account for the new Z_EXTRA_USER_READONLY_VAR flag which is stored in (zval).u2.extra. Extensions that do not interact with variable internals are unaffected. Additionally, there is readonly_var_flags in the zend_op_array.

To SAPIs

No impact expected on CLI, the built-in development server, or embedded PHP SAPIs. Readonly variables follow the same lifecycle as regular variables within their respective execution contexts.

Future Scope

  • Destructuring supportreadonly [$a, $b] = [1, 2]; is not in scope for this RFC but could be added later.
  • readonly parameters — marking function parameters as immutable could be a natural extension of this concept.
  • readonly in foreach loops - foreach ($items as readonly $item)
  • Typed variables - Typed variables, which are not immutable (int $var = 1).

Voting Choices

This RFC requires a 2/3 majority to be accepted.

Implement Readonly Variables as outlined in the RFC?
Real name Yes No Abstain
Final result: 0 0 0
This poll has been closed.

Patches and Tests

A proof-of-concept implementation is available at: https://github.com/joshuaruesweg/php-src/pull/1

Implementation

To be filled after merging:

  1. Version merged into
  2. Link to git commit(s)
  3. Link to PHP manual entry

References

Rejected Features

None yet.

Changelog

  • 2026-02-22: Initial draft published.
rfc/readonly-variables.txt · Last modified: by josh