Take the State of PHP 2026 Survey →

Allow Object Property Writes on Objects Referenced by Constants View as Markdown

Implemented (PHP 8.6)
Authors
Khaled Alam, khaledalam.net@gmail.com
Date
2026-06-28
Version
0.4
Implementation
https://github.com/php/php-src/pull/20903
Source
wiki.php.net/rfc/const_object_property_write

Vote

Accept the RFC Allow Object Property Writes on Objects Referenced by Constants? closed
Yes
17
No
2
Abstain
6
25 voters
alcaeus Abstain 2026-07-31 09:27
alexandredaubois Yes 2026-07-29 06:56
bmajdak Yes 2026-08-05 22:33
bwoebi Yes 2026-07-25 14:19
crell Yes 2026-07-25 17:41
daniels Yes 2026-07-30 21:09
derick Abstain 2026-07-26 08:09
duncan3dc Yes 2026-07-25 16:16
ericmann No 2026-07-27 17:01
galvao Abstain 2026-07-27 01:56
jordikroon Yes 2026-07-26 16:30
kalle Yes 2026-07-30 11:16
kguest Yes 2026-08-05 14:50
mbeccati Abstain 2026-07-27 07:40
nicolasgrekas Yes 2026-07-25 19:42
ocramius No 2026-07-26 12:25
ramsey Yes 2026-07-25 19:58
sebastian Abstain 2026-07-26 14:53
sergey Yes 2026-07-27 11:03
shivam Yes 2026-07-31 14:30
svpernova09 Abstain 2026-07-28 14:33
thekid Yes 2026-08-08 08:34
theodorejb Yes 2026-07-26 21:46
timwolla Yes 2026-07-25 20:38
weilindu Yes 2026-07-28 16:01

Discussions on the mailing list

RFC: Namespace-Scoped Visibility for Methods and Properties
by Rob Landers · 32 messages

RFC text

Copied from wiki.php.net, last synchronized Oct 13, 2026 11:50 UTC

Introduction

This RFC allows writing to object properties (and isset() / unset() on properties) when the object instance is referenced by a constant.

PHP constants are immutable in the sense that the constant binding cannot be changed. However, objects in PHP are mutable by design: mutating an object property does not rebind the constant, it only updates the state of the referenced object.

Today, PHP rejects CONST_OBJ->prop = ... with a fatal error (“Cannot use temporary expression in write context”). This RFC proposes permitting property write operations in this specific scenario, aligning behavior with user expectations while keeping the constant binding immutable.

<?php
const OBJ = new stdClass();
 
OBJ->value = 123;
var_dump(OBJ->value); // int(123)
 
unset(OBJ->value);
var_dump(isset(OBJ->value)); // bool(false)
?>

This applies both to global constants (const / define()) and to class constants (Cls::CONST).

Proposal

This RFC proposes to allow property write operations where the base expression is a constant that evaluates to an object.

The change is intentionally narrow:

  • It permits object property writes (->) and related operations (isset, unset, compound assignment, increment/decrement, string concatenation) on objects referenced by constants.
  • It permits passing constant object properties by reference to functions (including the “BP_VAR_FUNC_ARG” path when the function is declared after the call site).
  • It does not make constants mutable (rebinding remains illegal).
  • It does not change array/dimension write semantics ([]), nor does it change “write-to-temporary” behavior for dims.
  • It applies to both global constants (CONST->prop = ...) and class constants (Cls::CONST->prop = ... and $obj::CONST->prop = ...).

Specifically, the following operations become valid:

  • Assignment to a property:

    <?php
    const OBJ = new stdClass();
    OBJ->prop = "value";
    ?>
    
      <?php
      const BACKING = new stdClass();
     
      class C {
          const O = BACKING;
      }
     
      C::O->prop = 42;        // via class name
      var_dump(C::O->prop);   // int(42)
     
      $c = new C();
      $c::O->prop = 'updated'; // via instance
      var_dump(C::O->prop);    // string(7) "updated"
      ?>
     
    
  • Compound operations on a property:

    <?php
    const OBJ = new stdClass();
    OBJ->counter = 0;
    OBJ->counter++;
    OBJ->counter--;
    OBJ->counter += 10;
    OBJ->str = 'hello';
    OBJ->str .= ' world';
    ?>
    
  • isset() / unset() on a property:

    <?php
    const OBJ = new stdClass();
    OBJ->prop = 1;
    var_dump(isset(OBJ->prop)); // true
    unset(OBJ->prop);
    var_dump(isset(OBJ->prop)); // false
    ?>
    
  • Nested property operations:

    <?php
    const OBJ = new stdClass();
    OBJ->a = new stdClass();
    OBJ->a->b = 7;
    var_dump(OBJ->a->b); // int(7)
    ?>
    
  • Passing properties by reference:

    <?php
    const OBJ = new stdClass();
    OBJ->val = 10;
     
    function modify(&$v) { $v = 42; }
    modify(OBJ->val);
    var_dump(OBJ->val); // int(42)
    ?>
    

Semantics

  • The constant binding remains immutable.
  • The referenced object may be mutated, consistent with PHP's object behavior.
  • If the constant does not evaluate to an object at runtime, existing behavior (type error/fatal in property access) applies as usual.
  • Class constants holding objects behave identically to global constants; only the object state can change, never the constant binding.

Note on Array Constants

A related but distinct scenario exists with array constants:

<?php
const ARR = [1, 2, 3];
$ref = ARR;
$ref[0] = 9;
var_dump(ARR); // [1, 2, 3] — original unchanged
?>

Because arrays use copy-on-write semantics, modifying the copy has no effect on the original constant. Similarly:

<?php
const C = [1, 2, 3];
function foo(): array { return C; }
foo()[] = 4;
var_dump(C); // [1, 2, 3] — unmodified, assignment is on a temporary
?>

These dim-write cases involve assignment to temporaries that silently have no effect. Whether such no-effect writes should produce a warning or error is a valid concern but is out of scope for this RFC and may be addressed separately (e.g. warning/throwing on “ASSIGN_DIM” when OP1 is not a pointer — i.e. not an indirect, reference, or object — as the operation can never have an effect). See the Future Scope section.

Non-goals / Explicitly Unchanged

This RFC does NOT change:

  • Rebinding constants:

    <?php
    const OBJ = new stdClass();
    OBJ = new stdClass(); // still illegal (parse error)
    ?>
    
  • Dimension/array writes or other write-to-temporary semantics:

    <?php
    const ARR = [1,2,3];
    ARR[0] = 9; // unchanged (still rejected as before)
     
    const OBJ = new stdClass();
    OBJ['x'] = 1; // unchanged (still rejected as before)
    ?>
    

Implementation Details

The compiler marks the constant AST node rather than emitting the fetch directly. zend_delayed_compile_prop() in Zend/zend_compile.c calls zend_mark_const_object_fetch(), which walks down through any ZEND_AST_DIM nodes and sets a ZEND_CONST_OBJECT_FETCH attribute on a ZEND_AST_CONST or ZEND_AST_CLASS_CONST base. zend_delayed_compile_var() then handles those two AST kinds when the attribute is set, so the constant is fetched at runtime and the property write targets the real object.

zend_emit_fetch_constant() and zend_compile_class_const() take the fetch type: in read context (BP_VAR_R, BP_VAR_IS) the result stays IS_TMP_VAR as before, and in write context it is IS_VAR. Because a constant may still be substituted at compile time (true, null, persistent constants, same-file class constants), a compile-time-evaluated constant in write context is wrapped in a ZEND_QM_ASSIGN to materialise it into an IS_VAR; the property opcodes have no handler for an IS_CONST container operand.

Two supporting changes were needed. zend_optimizer_update_op1_const() now refuses to substitute a constant into op1 of the object-write opcodes (ZEND_ASSIGN_OBJ, ZEND_FETCH_OBJ_W, ZEND_UNSET_OBJ, ZEND_PRE_INC_OBJ and friends), which would otherwise reintroduce the invalid operand during optimisation. And zend_can_write_to_variable() accepts a constant at the base of a PROP/DIM chain when the chain contains at least one property fetch, so that destructuring assignment and by-reference foreach can target a constant object property; a write to the constant itself, or to a dimension of it, still targets a temporary and remains illegal.

Why This Brings Value

This feature addresses a common surprise: PHP already permits objects in constants (since PHP 8.1), and developers naturally expect to mutate the object's state through the constant reference. The current fatal error is not about constant immutability per se, but rather about an overly broad “temporary write context” restriction in the compiler. Narrowly enabling property writes reduces friction, improves ergonomics, and matches the runtime model of objects without increasing language complexity for other write contexts.

Backward Incompatible Changes

Enum cases are class constants, so this change affects them. Writing to or unsetting a readonly case property — for example unset(Enum::Case->value) or Enum::Case->value = $x — now raises the standard runtime readonly-property “Error” (“Cannot unset/modify readonly property”) instead of the previous compile-time fatal (“Cannot use temporary expression in write context”).

This makes enum-case property access consistent with plain variable access ($x = Enum::Case;unset($x->value);). Enum case properties remain immutable — no value that was previously read-only becomes writable, and dynamic properties on enum cases continue to be rejected.

Apart from this, code that previously terminated with a fatal error for CONST_OBJ->prop = ... will now execute successfully. This is a compatibility improvement — no previously-working code changes behavior.

Proposed PHP Version(s)

PHP 8.6

RFC Impact

To the Ecosystem

  • IDEs / LSPs: Mostly positive; previously-invalid syntax becomes valid.
  • Static analyzers: May need minor updates to remove a false-positive for property writes via constants. Semantics remain straightforward: constant binding is unchanged, object state may change.
  • Formatters / linters: No expected changes beyond accepting the syntax.

To Existing Extensions

No expected impact. This is a core compilation change around property write access, not an extension API change.

To SAPIs

No expected impact.

Open Issues

None currently.

Future Scope

  • No-effect dim writes: A future RFC could explore whether certain no-effect write cases should produce a warning or error. For example, “ASSIGN_DIM” when OP1 is not a “pointer” (indirect, reference, or object) can never have an effect, since the write is to a temporary that is immediately discarded. This is a distinct concern from the property-write issue addressed here.
  • Other write-to-temporary semantics: Cases like foo()[0] = 1 where the return value is a temporary are similarly out of scope but could be revisited.

Voting Choices

Primary Vote requiring a 2/3 majority to accept the RFC:

Vote: Accept the RFC Allow Object Property Writes on Objects Referenced by Constants? — Yes: 17 · No: 2 · Abstain: 6 (closed)

Patches and Tests

Proof of concept implementation:

Implementation

  1. Merged into: PHP 8.6
  2. Git commit: https://github.com/php/php-src/commit/b16cab7da8e5748b173542da42fc8b602ae59dea
  3. PR: https://github.com/php/php-src/pull/20903

References

Rejected Features

  • Extending the change to dimension/array writes (out of scope for this RFC; involves distinct copy-on-write and temporary semantics)
  • Changing “write-to-temporary” warning/error policy for dims (out of scope; may be addressed in a future RFC)

Changelog

  • 2026-02-24: Initial draft created.
  • 2026-04-04: v0.2 — Updated after PR review feedback. Added by-ref passing examples, implementation details section, note on array constant behavior per internals feedback, explicit test inventory, updated voting timeline.
  • 2026-06-28: v0.3 — Extended scope to class constants (Cls::CONST->prop and $obj::CONST->prop) per Claude Pache's feedback. Documented the enum readonly-property backward-incompatible change. Restored the Abstain vote option (added by Tim Düsterhus). Restarted the discussion period for the widened scope.
  • 2026-08-13 (v0.4): Updated Implementation Details to match the merged implementation. Marked implemented in PHP 8.6.

Key changes from v0.1: - Added by-ref passing examples (confirmed working in tests) - Added implementation details section explaining the compiler change - Added “Note on Array Constants” section addressing Ilija's and Tim's feedback about silent no-op dim writes - Expanded Future Scope with Tim's suggestion about warning on ASSIGN_DIM with non-pointer OP1 - Voting options were revised during later RFC updates; the current vote includes Yes, No, and Abstain - Removed the placeholder voting close date until voting opens - Targeted PHP 8.6 explicitly - Listed all test files in the Patches and Tests section - Removed Open Issues (the by-ref question is answered, isset/unset is confirmed) - Added full URLs to References - Added changelog entry for v0.2