I opened PR #20903 to address GH-10497 by allowing writes to properties of objects referenced by consts (const binding stays immutable, referenced objects can be mutable).
class C { public int $x = 0; } const OBJ = new C(); OBJ->x = 1; // currently rejected, PR makes it valid var_dump(OBJ->x); // 1
Notes:
This is a language/semantics change and may overlap with / impact GH-13800.
A potentially surprising case: some newly-valid writes can be a no-op when they target a temporary const value, e.g.
const X = [1,2,3]; X = 4;
const Y = [‘a’ => 1]; Y[‘b’] = 2;
These become valid syntax but have no effect because the write happens on a temporary basis.
Feedback on semantics + expected behavior for the temporary-write case is appreciated.
A potentially surprising case: some newly-valid writes can be a no-op
when they target a temporary const value, e.g.
- const X = [1,2,3]; X[] = 4;
- const Y = ['a' => 1]; Y['b'] = 2;
These become valid syntax but have no effect because the write happens on a
temporary basis.
Feedback on semantics + expected behavior for the temporary-write case is
appreciated.
This is too unexpected / error-prone for me. Trying to write into arrays should remain disallowed / an Error, since this is never (?) useful.
For objects, I don't have a strong opinion, but I expect objects in constants to be so rare that it probably isn't worth it to make a change at all.
On Sun, Jan 18, 2026 at 1:28 PM Tim Düsterhus <tim@bastelstu.be> wrote:
On 1/15/26 07:36, Khaled Alam wrote:
> A potentially surprising case: some newly-valid writes can be a no-op
> when they target a temporary const value, e.g.
> - const X = [1,2,3]; X[] = 4;
> - const Y = ['a' => 1]; Y['b'] = 2;
>
> These become valid syntax but have no effect because the write happens on a
> temporary basis.
>
> Feedback on semantics + expected behavior for the temporary-write case is
> appreciated.
This is too unexpected / error-prone for me. Trying to write into arrays
should remain disallowed / an Error, since this is never (?) useful.
No strong opinions on my part, but I'd like to point out that similar
situations already exist.
const C = [1, 2, 3];
function foo(): array {
return C;
}
foo()[] = 4;
var_dump(C);
PHP will happily duplicate the array returned from foo() to append 4,
without warning. The original array in C remains unmodified. It might
make sense for ASSIGN_DIM to warn/throw if OP1 is not a "pointer"
(indirect, reference or object), as then the operation will never have
an effect. But it seems to me this is a distinct issue.
const C = [1, 2, 3];
function foo(): array {
return C;
}
foo()[] = 4;
var_dump(C);
PHP will happily duplicate the array returned from foo() to append 4,
without warning. The original array in C remains unmodified. It might
I'm aware. However to me this situation is a little different, intuition-wise: A return value already looks and feels like a temporary value to me (unless a by-ref return is used, of course, but those are rare). An access on a const does not.
make sense for ASSIGN_DIM to warn/throw if OP1 is not a "pointer"
(indirect, reference or object), as then the operation will never have
an effect. But it seems to me this is a distinct issue.
That makes sense to me and IMO is a necessary precursor for this.
Summary: This RFC proposes allowing property write operations (->) on objects referenced by constants. Currently,
CONST_OBJ->prop = value produces “Cannot use temporary expression in write context”, even though the operation
mutates the object, not the constant binding.
What changed since the initial discussion (Jan 2026):
Addressed Tim Düsterhus’s and Ilija Tovilo’s feedback regarding array constant dim writes. The RFC now explicitly
scopes out dim/array writes and documents the “silent no-op” concern in a dedicated section. A future RFC can
address whether ASSIGN_DIM on non-pointer OP1 should warn/throw — this is acknowledged as a valid but distinct
issue.
Added by-reference passing support (BP_VAR_FUNC_ARG path), with tests.
Added guardrail tests confirming that dim writes on constants, dim writes on constant objects, and constant
rebinding all remain unchanged.
Updated the gh12102_3.phpt test: passing an array constant element by-ref now correctly throws an Error instead of
silently operating on a temporary copy.
Thank you. The updated RFC makes sense to me and with the removal of the `FOO[123] = 'bar';` support I don't have any further technical concerns.
With regard to the RFC policy, I just added the necessary “Abstain” option to the vote.
I'm also noting some rendering issues in the RFC, particularly in the Proposal section, where the “bullet point list” does not render properly, due to the line-breaks in the markup. I'm also seeing that you try to use the markdown syntax for inline code. That doesn't work in the Wiki, since it doesn't speak markdown. When showcasing PHP code, I recommend using <php>isset()</php> tags (<php> XML tag). For non-PHP-Code ''BP_VAR_FUNC_ARG'' (two single-quotes works). You might want to clean-up both of these, it will make the RFC look much nicer and more approachable.
Please note that your RFC update is considered a major change, so it needs 14 days of “cooldown” for discussion, before the RFC can go to vote.
With regard to the RFC policy, I just added the necessary “Abstain”
option to the vote.
I’m also noting some rendering issues in the RFC, particularly in the
Proposal section, where the “bullet point list” does not render
properly, due to the line-breaks in the markup. I’m also seeing that you
try to use the markdown syntax for inline code. That doesn’t work in the
Wiki, since it doesn’t speak markdown. When showcasing PHP code, I
recommend using isset() tags ( XML tag). For
non-PHP-Code ‘‘BP_VAR_FUNC_ARG’’ (two single-quotes works). You might
want to clean-up both of these, it will make the RFC look much nicer and
more approachable.
Please note that your RFC update is considered a major change, so it
needs 14 days of “cooldown” for discussion, before the RFC can go to
vote.
Thanks for the feedback, I just fixed the RFC rendering issues.