PHP RFC: Readonly Variables
- Version: 0.1
- Date: 2026-02-22
- Author: Joshua Rüsweg josh@php.net
- Status: Draft
- Implementation: https://github.com/joshuaruesweg/php-src/pull/1
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 support —
readonly [$a, $b] = [1, 2];is not in scope for this RFC but could be added later. readonlyparameters — marking function parameters as immutable could be a natural extension of this concept.readonlyinforeachloops -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.
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:
- Version merged into
- Link to git commit(s)
- Link to PHP manual entry
References
- JavaScript
constdeclaration: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const
Rejected Features
None yet.
Changelog
- 2026-02-22: Initial draft published.