Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ PHP NEWS
- Core:
. Fixed bug GH-15375 (Nested "yield from" skips items after a valid() or
next() call on the inner generator). (iliaal)
. Implemented GH-10497 (Allowed writing to object properties held in
constants). (Khaled Alam)

- PDO_PGSQL:
. Fixed several lazy fetch (PDO::ATTR_PREFETCH => 0) defects: an infinite
Expand Down
22 changes: 22 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ PHP 8.6 UPGRADE NOTES
. ??/empty() on a magic property no longer call __get() when __isset()
has materialised the property by writing into the property table.
The freshly-written value is returned directly. isset() is unaffected.
. Writing to a property of a constant is no longer rejected at compile time
with "Cannot use temporary expression in write context". Such code now
compiles, and behaves as it does for a plain variable holding the same
value: the write succeeds if the constant holds an object, and otherwise
raises an Error at runtime (unset() on a non-object is a silent no-op).
Code that previously failed to compile therefore now runs up to the point
of the write. Enum cases are class constants, so writing to or unsetting
an enum case property now raises the usual readonly property Error at
runtime; enum case properties remain immutable.
RFC: https://wiki.php.net/rfc/const_object_property_write

- Curl:
. The callback registered with CURLOPT_READFUNCTION now throws a ValueError
Expand Down Expand Up @@ -309,6 +319,18 @@ PHP 8.6 UPGRADE NOTES
RFC: https://wiki.php.net/rfc/override_constants
. Implemented partial function application
RFC: https://wiki.php.net/rfc/partial_function_application_v2
. Object properties may now be written through a constant holding the
object, for both global and class constants:

const OBJ = new stdClass;
OBJ->prop = 42;
Cls::OBJ->prop = 42;

This includes compound assignment, increment/decrement, isset()/unset(),
passing by reference, destructuring assignment, by-reference foreach and
nested property chains. The constant binding itself remains immutable, and
dimension writes on constants (e.g. CONST[0] = 1) continue to be rejected.
RFC: https://wiki.php.net/rfc/const_object_property_write

- Curl:
. curl_getinfo() return array now includes a new size_delivered key, which
Expand Down
17 changes: 13 additions & 4 deletions Zend/Optimizer/zend_optimizer.c
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,19 @@ bool zend_optimizer_update_op1_const(zend_op_array *op_array,
case ZEND_SEPARATE:
case ZEND_SEND_VAR_NO_REF:
case ZEND_SEND_VAR_NO_REF_EX:
case ZEND_ASSIGN_OP:
case ZEND_ASSIGN_DIM_OP:
case ZEND_ASSIGN_OBJ:
case ZEND_ASSIGN_OBJ_OP:
case ZEND_ASSIGN_OBJ_REF:
case ZEND_UNSET_OBJ:
case ZEND_FETCH_OBJ_W:
case ZEND_FETCH_OBJ_RW:
case ZEND_FETCH_OBJ_UNSET:
case ZEND_PRE_INC_OBJ:
case ZEND_PRE_DEC_OBJ:
case ZEND_POST_INC_OBJ:
case ZEND_POST_DEC_OBJ:
return false;
case ZEND_CATCH:
REQUIRES_STRING(val);
Expand Down Expand Up @@ -320,10 +333,6 @@ bool zend_optimizer_update_op1_const(zend_op_array *op_array,
}
zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val)));
break;
case ZEND_ASSIGN_OP:
case ZEND_ASSIGN_DIM_OP:
case ZEND_ASSIGN_OBJ_OP:
break;
case ZEND_ASSIGN_STATIC_PROP_OP:
case ZEND_ASSIGN_STATIC_PROP:
case ZEND_ASSIGN_STATIC_PROP_REF:
Expand Down
5 changes: 4 additions & 1 deletion Zend/tests/enum/no-unsed-value.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@ unset(Foo::Bar->value);

?>
--EXPECTF--
Fatal error: Cannot use temporary expression in write context in %s on line %d
Fatal error: Uncaught Error: Cannot unset readonly property Foo::$value in %s:%d
Stack trace:
#0 {main}
thrown in %s on line %d
84 changes: 84 additions & 0 deletions Zend/tests/gh10497.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
--TEST--
GH-10497: Allow direct modification of object properties on constants
--FILE--
<?php

const OBJ = new stdClass;
OBJ->prop = 123;
var_dump(OBJ->prop);

OBJ->foo = 'bar';
OBJ->baz = 456;
var_dump(OBJ->foo, OBJ->baz);

OBJ->prop = 'overwritten';
var_dump(OBJ->prop);

OBJ->inner = new stdClass;
OBJ->inner->value = 999;
var_dump(OBJ->inner->value);

OBJ->counter = 0;
OBJ->counter++;
OBJ->counter++;
OBJ->counter--;
var_dump(OBJ->counter);

OBJ->str = 'hello';
OBJ->str .= ' world';
var_dump(OBJ->str);

OBJ->temp = 'remove me';
var_dump(isset(OBJ->temp));
unset(OBJ->temp);
var_dump(isset(OBJ->temp));

var_dump(isset(OBJ->foo));
var_dump(empty(OBJ->foo));
var_dump(isset(OBJ->nonexistent));
var_dump(empty(OBJ->nonexistent));

function incr(&$v) { $v++; }
OBJ->reftest = 10;
incr(OBJ->reftest);
var_dump(OBJ->reftest);

OBJ->arr = [];
OBJ->arr[0] = 42;
OBJ->arr[] = 43;
var_dump(OBJ->arr);

OBJ->coalesce ??= 42;
var_dump(OBJ->coalesce);
OBJ->coalesce ??= 43;
var_dump(OBJ->coalesce);

const OBJS = [new stdClass];
OBJS[0]->prop = 42;
var_dump(OBJS[0]->prop);

?>
--EXPECT--
int(123)
string(3) "bar"
int(456)
string(11) "overwritten"
int(999)
int(1)
string(11) "hello world"
bool(true)
bool(false)
bool(true)
bool(false)
bool(false)
bool(true)
int(11)
array(2) {
[0]=>
int(42)
[1]=>
int(43)
}
int(42)
int(42)
int(42)
76 changes: 76 additions & 0 deletions Zend/tests/gh10497_class_const.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
--TEST--
GH-10497: Allow direct modification of object properties on class constants
--FILE--
<?php

// A class constant cannot use `new` directly, but it may reference a global
// constant that holds an object (see GH-10497 discussion).
const BACKING = new stdClass;

class C {
const O = BACKING;
}

// Access via class name.
C::O->prop = 123;
var_dump(C::O->prop);

// Access via instance (static-property-like syntax on an object).
$c = new C;
$c::O->prop = 'overwritten';
var_dump(C::O->prop);

// Compound assignment, increment/decrement and concatenation.
C::O->counter = 0;
C::O->counter++;
C::O->counter++;
C::O->counter--;
var_dump(C::O->counter);

C::O->str = 'hello';
C::O->str .= ' world';
var_dump(C::O->str);

// Nested property chains.
C::O->inner = new stdClass;
C::O->inner->value = 999;
var_dump(C::O->inner->value);

// isset()/unset().
C::O->temp = 'remove me';
var_dump(isset(C::O->temp));
unset(C::O->temp);
var_dump(isset(C::O->temp));

// Passing by reference.
function incr(&$v) { $v++; }
C::O->reftest = 10;
incr(C::O->reftest);
var_dump(C::O->reftest);

// self:: from within a method. The same backing object is reachable through
// the class constant and the global constant, so the mutation is visible.
class D {
const O = BACKING;

public static function set(): void {
self::O->fromSelf = 'yes';
}
}

D::set();
var_dump(D::O->fromSelf);
var_dump(BACKING->fromSelf);

?>
--EXPECT--
int(123)
string(11) "overwritten"
int(1)
string(11) "hello world"
int(999)
bool(true)
bool(false)
int(11)
string(3) "yes"
string(3) "yes"
19 changes: 19 additions & 0 deletions Zend/tests/gh10497_func_arg.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
GH-10497: Passing constant object property by reference via FUNC_ARG
--FILE--
<?php

// Forward-reference: function declared after call site, so the compiler
// uses BP_VAR_FUNC_ARG rather than BP_VAR_W for the property fetch.
const OBJ = new stdClass;
OBJ->val = 10;
modify(OBJ->val);
var_dump(OBJ->val);

function modify(&$v) {
$v = 42;
}

?>
--EXPECT--
int(42)
9 changes: 9 additions & 0 deletions Zend/tests/gh10497_guardrails.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
--TEST--
GH-10497: Guardrail - array dim write on constant still fails
--FILE--
<?php
const ARR = [1, 2, 3];
ARR[0] = 9;
?>
--EXPECTF--
Fatal error: Cannot use temporary expression in write context in %s on line %d
9 changes: 9 additions & 0 deletions Zend/tests/gh10497_guardrails_dim_obj.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
--TEST--
GH-10497: Guardrail - dim write on constant object still fails
--FILE--
<?php
const OBJ = new stdClass;
OBJ["x"] = 1;
?>
--EXPECTF--
Fatal error: Cannot use temporary expression in write context in %s on line %d
9 changes: 9 additions & 0 deletions Zend/tests/gh10497_guardrails_rebind.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
--TEST--
GH-10497: Guardrail - constant rebinding still fails
--FILE--
<?php
const OBJ = new stdClass;
OBJ = new stdClass;
?>
--EXPECTF--
Parse error: syntax error, unexpected token "=" in %s on line %d
Loading
Loading