mirror of
https://github.com/php/php-src.git
synced 2024-09-21 18:07:23 +00:00
Merge branch 'master' of github.com:php/php-src
* 'master' of github.com:php/php-src: Implement readonly properties
This commit is contained in:
commit
27bb57356c
@ -206,6 +206,8 @@ PHP 8.1 UPGRADE NOTES
|
||||
RFC: https://wiki.php.net/rfc/pure-intersection-types
|
||||
. Added support for the final modifier for class constants.
|
||||
RFC: https://wiki.php.net/rfc/final_class_const
|
||||
. Added support for readonly properties.
|
||||
RFC: https://wiki.php.net/rfc/readonly_properties_v2
|
||||
|
||||
- Curl:
|
||||
. Added CURLOPT_DOH_URL option.
|
||||
|
@ -45,6 +45,7 @@ class Obj
|
||||
function array(){ echo __METHOD__, PHP_EOL; }
|
||||
function print(){ echo __METHOD__, PHP_EOL; }
|
||||
function echo(){ echo __METHOD__, PHP_EOL; }
|
||||
function readonly(){ echo __METHOD__, PHP_EOL; }
|
||||
function require(){ echo __METHOD__, PHP_EOL; }
|
||||
function require_once(){ echo __METHOD__, PHP_EOL; }
|
||||
function return(){ echo __METHOD__, PHP_EOL; }
|
||||
@ -125,6 +126,7 @@ $obj->throw();
|
||||
$obj->array();
|
||||
$obj->print();
|
||||
$obj->echo();
|
||||
$obj->readonly();
|
||||
$obj->require();
|
||||
$obj->require_once();
|
||||
$obj->return();
|
||||
@ -205,6 +207,7 @@ Obj::throw
|
||||
Obj::array
|
||||
Obj::print
|
||||
Obj::echo
|
||||
Obj::readonly
|
||||
Obj::require
|
||||
Obj::require_once
|
||||
Obj::return
|
||||
|
29
Zend/tests/readonly_props/by_ref_foreach.phpt
Normal file
29
Zend/tests/readonly_props/by_ref_foreach.phpt
Normal file
@ -0,0 +1,29 @@
|
||||
--TEST--
|
||||
By-ref foreach over readonly property
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public readonly int $prop;
|
||||
|
||||
public function init() {
|
||||
$this->prop = 1;
|
||||
}
|
||||
}
|
||||
|
||||
$test = new Test;
|
||||
|
||||
// Okay, as foreach skips over uninitialized properties.
|
||||
foreach ($test as &$prop) {}
|
||||
|
||||
$test->init();
|
||||
|
||||
try {
|
||||
foreach ($test as &$prop) {}
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Cannot acquire reference to readonly property Test::$prop
|
122
Zend/tests/readonly_props/cache_slot.phpt
Normal file
122
Zend/tests/readonly_props/cache_slot.phpt
Normal file
@ -0,0 +1,122 @@
|
||||
--TEST--
|
||||
Test interaction with cache slots
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public readonly string $prop;
|
||||
public readonly array $prop2;
|
||||
public readonly object $prop3;
|
||||
public function setProp(string $prop) {
|
||||
$this->prop = $prop;
|
||||
}
|
||||
public function initAndAppendProp2() {
|
||||
$this->prop2 = [];
|
||||
$this->prop2[] = 1;
|
||||
}
|
||||
public function initProp3() {
|
||||
$this->prop3 = new stdClass;
|
||||
$this->prop3->foo = 1;
|
||||
}
|
||||
public function replaceProp3() {
|
||||
$ref =& $this->prop3;
|
||||
$ref = new stdClass;
|
||||
}
|
||||
}
|
||||
|
||||
$test = new Test;
|
||||
$test->setProp("a");
|
||||
var_dump($test->prop);
|
||||
try {
|
||||
$test->setProp("b");
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
var_dump($test->prop);
|
||||
echo "\n";
|
||||
|
||||
$test = new Test;
|
||||
try {
|
||||
$test->initAndAppendProp2();
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
$test->initAndAppendProp2();
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
var_dump($test->prop2);
|
||||
echo "\n";
|
||||
|
||||
$test = new Test;
|
||||
$test->initProp3();
|
||||
$test->replaceProp3();
|
||||
var_dump($test->prop3);
|
||||
$test->replaceProp3();
|
||||
var_dump($test->prop3);
|
||||
echo "\n";
|
||||
|
||||
// Test variations using closure rebinding, so we have unknown property_info in JIT.
|
||||
$test = new Test;
|
||||
(function() { $this->prop2 = []; })->call($test);
|
||||
$appendProp2 = (function() {
|
||||
$this->prop2[] = 1;
|
||||
})->bindTo($test, Test::class);
|
||||
try {
|
||||
$appendProp2();
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
$appendProp2();
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
var_dump($test->prop2);
|
||||
echo "\n";
|
||||
|
||||
$test = new Test;
|
||||
$replaceProp3 = (function() {
|
||||
$ref =& $this->prop3;
|
||||
$ref = new stdClass;
|
||||
})->bindTo($test, Test::class);
|
||||
$test->initProp3();
|
||||
$replaceProp3();
|
||||
var_dump($test->prop3);
|
||||
$replaceProp3();
|
||||
var_dump($test->prop3);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
string(1) "a"
|
||||
Cannot modify readonly property Test::$prop
|
||||
string(1) "a"
|
||||
|
||||
Cannot modify readonly property Test::$prop2
|
||||
Cannot modify readonly property Test::$prop2
|
||||
array(0) {
|
||||
}
|
||||
|
||||
object(stdClass)#3 (1) {
|
||||
["foo"]=>
|
||||
int(1)
|
||||
}
|
||||
object(stdClass)#3 (1) {
|
||||
["foo"]=>
|
||||
int(1)
|
||||
}
|
||||
|
||||
Cannot modify readonly property Test::$prop2
|
||||
Cannot modify readonly property Test::$prop2
|
||||
array(0) {
|
||||
}
|
||||
|
||||
object(stdClass)#5 (1) {
|
||||
["foo"]=>
|
||||
int(1)
|
||||
}
|
||||
object(stdClass)#5 (1) {
|
||||
["foo"]=>
|
||||
int(1)
|
||||
}
|
72
Zend/tests/readonly_props/initialization_scope.phpt
Normal file
72
Zend/tests/readonly_props/initialization_scope.phpt
Normal file
@ -0,0 +1,72 @@
|
||||
--TEST--
|
||||
Initialization can only happen from private scope
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A {
|
||||
public readonly int $prop;
|
||||
|
||||
public function initPrivate() {
|
||||
$this->prop = 3;
|
||||
}
|
||||
}
|
||||
class B extends A {
|
||||
public function initProtected() {
|
||||
$this->prop = 2;
|
||||
}
|
||||
}
|
||||
|
||||
$test = new B;
|
||||
try {
|
||||
$test->prop = 1;
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
$test->initProtected();
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
$test->initPrivate();
|
||||
var_dump($test->prop);
|
||||
|
||||
// Rebinding bypass works.
|
||||
$test = new B;
|
||||
(function() {
|
||||
$this->prop = 1;
|
||||
})->bindTo($test, A::class)();
|
||||
var_dump($test->prop);
|
||||
|
||||
class C extends A {
|
||||
public readonly int $prop;
|
||||
}
|
||||
|
||||
$test = new C;
|
||||
$test->initPrivate();
|
||||
var_dump($test->prop);
|
||||
|
||||
class X {
|
||||
public function initFromParent() {
|
||||
$this->prop = 1;
|
||||
}
|
||||
}
|
||||
class Y extends X {
|
||||
public readonly int $prop;
|
||||
}
|
||||
|
||||
$test = new Y;
|
||||
try {
|
||||
$test->initFromParent();
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Cannot initialize readonly property A::$prop from global scope
|
||||
Cannot initialize readonly property A::$prop from scope B
|
||||
int(3)
|
||||
int(1)
|
||||
int(3)
|
||||
Cannot initialize readonly property Y::$prop from scope X
|
74
Zend/tests/readonly_props/magic_get_set.phpt
Normal file
74
Zend/tests/readonly_props/magic_get_set.phpt
Normal file
@ -0,0 +1,74 @@
|
||||
--TEST--
|
||||
Interaction with magic get/set
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public readonly int $prop;
|
||||
|
||||
public function unsetProp() {
|
||||
unset($this->prop);
|
||||
}
|
||||
|
||||
public function __get($name) {
|
||||
echo __METHOD__, "($name)\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
public function __set($name, $value) {
|
||||
echo __METHOD__, "($name, $value)\n";
|
||||
}
|
||||
|
||||
public function __unset($name) {
|
||||
echo __METHOD__, "($name)\n";
|
||||
}
|
||||
|
||||
public function __isset($name) {
|
||||
echo __METHOD__, "($name)\n";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$test = new Test;
|
||||
|
||||
// The property is in uninitialized state, no magic methods should be invoked.
|
||||
var_dump(isset($test->prop));
|
||||
try {
|
||||
var_dump($test->prop);
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
$test->prop = 1;
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
unset($test->prop);
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
$test->unsetProp();
|
||||
|
||||
var_dump(isset($test->prop));
|
||||
var_dump($test->prop);
|
||||
$test->prop = 2;
|
||||
try {
|
||||
unset($test->prop);
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
bool(false)
|
||||
Typed property Test::$prop must not be accessed before initialization
|
||||
Cannot initialize readonly property Test::$prop from global scope
|
||||
Cannot unset readonly property Test::$prop from global scope
|
||||
Test::__isset(prop)
|
||||
bool(true)
|
||||
Test::__get(prop)
|
||||
int(1)
|
||||
Test::__set(prop, 2)
|
||||
Test::__unset(prop)
|
26
Zend/tests/readonly_props/override_with_attributes.phpt
Normal file
26
Zend/tests/readonly_props/override_with_attributes.phpt
Normal file
@ -0,0 +1,26 @@
|
||||
--TEST--
|
||||
Can override readonly property with attributes
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
#[Attribute]
|
||||
class FooAttribute {}
|
||||
|
||||
class A {
|
||||
public readonly int $prop;
|
||||
|
||||
public function __construct() {
|
||||
$this->prop = 42;
|
||||
}
|
||||
}
|
||||
class B extends A {
|
||||
#[FooAttribute]
|
||||
public readonly int $prop;
|
||||
}
|
||||
|
||||
var_dump((new ReflectionProperty(B::class, 'prop'))->getAttributes()[0]->newInstance());
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
object(FooAttribute)#1 (0) {
|
||||
}
|
41
Zend/tests/readonly_props/promotion.phpt
Normal file
41
Zend/tests/readonly_props/promotion.phpt
Normal file
@ -0,0 +1,41 @@
|
||||
--TEST--
|
||||
Promoted readonly property
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Point {
|
||||
public function __construct(
|
||||
public readonly float $x = 0.0,
|
||||
public readonly float $y = 0.0,
|
||||
public readonly float $z = 0.0,
|
||||
) {}
|
||||
}
|
||||
|
||||
var_dump(new Point);
|
||||
$point = new Point(1.0, 2.0, 3.0);
|
||||
try {
|
||||
$point->x = 4.0;
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
var_dump($point);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
object(Point)#1 (3) {
|
||||
["x"]=>
|
||||
float(0)
|
||||
["y"]=>
|
||||
float(0)
|
||||
["z"]=>
|
||||
float(0)
|
||||
}
|
||||
Cannot modify readonly property Point::$x
|
||||
object(Point)#1 (3) {
|
||||
["x"]=>
|
||||
float(1)
|
||||
["y"]=>
|
||||
float(2)
|
||||
["z"]=>
|
||||
float(3)
|
||||
}
|
12
Zend/tests/readonly_props/readonly_const.phpt
Normal file
12
Zend/tests/readonly_props/readonly_const.phpt
Normal file
@ -0,0 +1,12 @@
|
||||
--TEST--
|
||||
Class constants cannot be readonly
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
readonly const X = 1;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Cannot use 'readonly' as constant modifier in %s on line %d
|
67
Zend/tests/readonly_props/readonly_containing_object.phpt
Normal file
67
Zend/tests/readonly_props/readonly_containing_object.phpt
Normal file
@ -0,0 +1,67 @@
|
||||
--TEST--
|
||||
Not-modifying a readonly property holding an object
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public readonly object $prop;
|
||||
|
||||
public function __construct(object $prop) {
|
||||
$this->prop = $prop;
|
||||
}
|
||||
}
|
||||
|
||||
$test = new Test(new stdClass);
|
||||
$test->prop->foo = 1;
|
||||
$test->prop->foo += 1;
|
||||
$test->prop->foo++;
|
||||
try {
|
||||
$test->prop += 1;
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
$test->prop++;
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
--$test->prop;
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
var_dump($test->prop);
|
||||
|
||||
// Unfortunately this is allowed, but does not modify $test->prop.
|
||||
$ref =& $test->prop;
|
||||
$ref = new stdClass;
|
||||
var_dump($test->prop);
|
||||
|
||||
$test = new Test(new ArrayObject());
|
||||
$test->prop[] = [];
|
||||
$test->prop[0][] = 1;
|
||||
var_dump($test->prop);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Unsupported operand types: stdClass + int
|
||||
Cannot modify readonly property Test::$prop
|
||||
Cannot modify readonly property Test::$prop
|
||||
object(stdClass)#2 (1) {
|
||||
["foo"]=>
|
||||
int(3)
|
||||
}
|
||||
object(stdClass)#2 (1) {
|
||||
["foo"]=>
|
||||
int(3)
|
||||
}
|
||||
object(ArrayObject)#7 (1) {
|
||||
["storage":"ArrayObject":private]=>
|
||||
array(1) {
|
||||
[0]=>
|
||||
array(1) {
|
||||
[0]=>
|
||||
int(1)
|
||||
}
|
||||
}
|
||||
}
|
12
Zend/tests/readonly_props/readonly_method.phpt
Normal file
12
Zend/tests/readonly_props/readonly_method.phpt
Normal file
@ -0,0 +1,12 @@
|
||||
--TEST--
|
||||
Method cannot be readonly
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
readonly function test() {}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Cannot use 'readonly' as method modifier in %s on line %d
|
12
Zend/tests/readonly_props/readonly_method_trait.phpt
Normal file
12
Zend/tests/readonly_props/readonly_method_trait.phpt
Normal file
@ -0,0 +1,12 @@
|
||||
--TEST--
|
||||
Method cannot be readonly in trait alias
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
use T { foo as readonly; }
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Cannot use 'readonly' as method modifier in %s on line %d
|
82
Zend/tests/readonly_props/readonly_modification.phpt
Normal file
82
Zend/tests/readonly_props/readonly_modification.phpt
Normal file
@ -0,0 +1,82 @@
|
||||
--TEST--
|
||||
Modifying a readonly property
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
readonly public int $prop;
|
||||
readonly public array $prop2;
|
||||
|
||||
public function __construct() {
|
||||
// Initializing assignments.
|
||||
$this->prop = 1;
|
||||
$this->prop2 = [];
|
||||
}
|
||||
}
|
||||
|
||||
function byRef(&$ref) {}
|
||||
|
||||
$test = new Test;
|
||||
var_dump($test->prop); // Read.
|
||||
try {
|
||||
$test->prop = 2;
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
$test->prop += 1;
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
$test->prop++;
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
++$test->prop;
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
$ref =& $test->prop;
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
$test->prop =& $ref;
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
byRef($test->prop);
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
var_dump($test->prop2); // Read.
|
||||
try {
|
||||
$test->prop2[] = 1;
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
$test->prop2[0][] = 1;
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
int(1)
|
||||
Cannot modify readonly property Test::$prop
|
||||
Cannot modify readonly property Test::$prop
|
||||
Cannot modify readonly property Test::$prop
|
||||
Cannot modify readonly property Test::$prop
|
||||
Cannot modify readonly property Test::$prop
|
||||
Cannot modify readonly property Test::$prop
|
||||
Cannot modify readonly property Test::$prop
|
||||
array(0) {
|
||||
}
|
||||
Cannot modify readonly property Test::$prop2
|
||||
Cannot modify readonly property Test::$prop2
|
15
Zend/tests/readonly_props/readonly_to_readwrite.phpt
Normal file
15
Zend/tests/readonly_props/readonly_to_readwrite.phpt
Normal file
@ -0,0 +1,15 @@
|
||||
--TEST--
|
||||
Cannot replace readonly with readwrite
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A {
|
||||
public readonly int $prop;
|
||||
}
|
||||
class B extends A {
|
||||
public int $prop;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Cannot redeclare readonly property A::$prop as non-readonly B::$prop in %s on line %d
|
19
Zend/tests/readonly_props/readonly_trait_match.phpt
Normal file
19
Zend/tests/readonly_props/readonly_trait_match.phpt
Normal file
@ -0,0 +1,19 @@
|
||||
--TEST--
|
||||
Readonly match of imported trait properties (valid)
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
trait T1 {
|
||||
public readonly int $prop;
|
||||
}
|
||||
trait T2 {
|
||||
public readonly int $prop;
|
||||
}
|
||||
class C {
|
||||
use T1, T2;
|
||||
}
|
||||
|
||||
?>
|
||||
===DONE===
|
||||
--EXPECT--
|
||||
===DONE===
|
18
Zend/tests/readonly_props/readonly_trait_mismatch.phpt
Normal file
18
Zend/tests/readonly_props/readonly_trait_mismatch.phpt
Normal file
@ -0,0 +1,18 @@
|
||||
--TEST--
|
||||
Readonly mismatch of imported trait properties
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
trait T1 {
|
||||
public int $prop;
|
||||
}
|
||||
trait T2 {
|
||||
public readonly int $prop;
|
||||
}
|
||||
class C {
|
||||
use T1, T2;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: T1 and T2 define the same property ($prop) in the composition of C. However, the definition differs and is considered incompatible. Class was composed in %s on line %d
|
19
Zend/tests/readonly_props/readonly_with_default.phpt
Normal file
19
Zend/tests/readonly_props/readonly_with_default.phpt
Normal file
@ -0,0 +1,19 @@
|
||||
--TEST--
|
||||
Readonly property with default value
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public readonly int $prop = 1;
|
||||
}
|
||||
|
||||
$test = new Test;
|
||||
try {
|
||||
$test->prop = 2;
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Readonly property Test::$prop cannot have default value in %s on line %d
|
12
Zend/tests/readonly_props/readonly_without_type.phpt
Normal file
12
Zend/tests/readonly_props/readonly_without_type.phpt
Normal file
@ -0,0 +1,12 @@
|
||||
--TEST--
|
||||
Readonly property without type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
protected readonly $prop;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Readonly property Test::$prop must have type in %s on line %d
|
15
Zend/tests/readonly_props/readwrite_to_readonly.phpt
Normal file
15
Zend/tests/readonly_props/readwrite_to_readonly.phpt
Normal file
@ -0,0 +1,15 @@
|
||||
--TEST--
|
||||
Cannot replace readwrite with readonly
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A {
|
||||
public int $prop;
|
||||
}
|
||||
class B extends A {
|
||||
public readonly int $prop;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Cannot redeclare non-readonly property A::$prop as readonly B::$prop in %s on line %d
|
34
Zend/tests/readonly_props/serialization.phpt
Normal file
34
Zend/tests/readonly_props/serialization.phpt
Normal file
@ -0,0 +1,34 @@
|
||||
--TEST--
|
||||
Serialization of readonly properties
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public function __construct(
|
||||
public readonly int $prop = 1,
|
||||
) {}
|
||||
}
|
||||
|
||||
var_dump($s = serialize(new Test));
|
||||
var_dump(unserialize($s));
|
||||
|
||||
// Readonly properties receive no special handling.
|
||||
// What happens during unserialization stays in unserialization.
|
||||
var_dump(unserialize("O:4:\"Test\":1:{s:4:\"prop\";i:2;}"));
|
||||
var_dump(unserialize("O:4:\"Test\":2:{s:4:\"prop\";i:2;s:4:\"prop\";i:3;}"));
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
string(30) "O:4:"Test":1:{s:4:"prop";i:1;}"
|
||||
object(Test)#1 (1) {
|
||||
["prop"]=>
|
||||
int(1)
|
||||
}
|
||||
object(Test)#1 (1) {
|
||||
["prop"]=>
|
||||
int(2)
|
||||
}
|
||||
object(Test)#1 (1) {
|
||||
["prop"]=>
|
||||
int(3)
|
||||
}
|
12
Zend/tests/readonly_props/static.phpt
Normal file
12
Zend/tests/readonly_props/static.phpt
Normal file
@ -0,0 +1,12 @@
|
||||
--TEST--
|
||||
Readonly static property
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public static readonly int $prop;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Static property Test::$prop cannot be readonly in %s on line %d
|
64
Zend/tests/readonly_props/unset.phpt
Normal file
64
Zend/tests/readonly_props/unset.phpt
Normal file
@ -0,0 +1,64 @@
|
||||
--TEST--
|
||||
Unset readonly property
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public readonly int $prop;
|
||||
|
||||
public function __construct(int $prop) {
|
||||
$this->prop = $prop;
|
||||
}
|
||||
}
|
||||
|
||||
$test = new Test(1);
|
||||
try {
|
||||
unset($test->prop);
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
class Test2 {
|
||||
public readonly int $prop;
|
||||
|
||||
public function __construct() {
|
||||
unset($this->prop); // Unset uninitialized.
|
||||
unset($this->prop); // Unset unset.
|
||||
}
|
||||
|
||||
public function __get($name) {
|
||||
// Lazy init.
|
||||
echo __METHOD__, "\n";
|
||||
$this->prop = 1;
|
||||
return $this->prop;
|
||||
}
|
||||
}
|
||||
|
||||
$test = new Test2;
|
||||
var_dump($test->prop); // Call __get.
|
||||
var_dump($test->prop); // Don't call __get.
|
||||
try {
|
||||
unset($test->prop); // Unset initialized, illegal.
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
class Test3 {
|
||||
public readonly int $prop;
|
||||
}
|
||||
|
||||
$test = new Test3;
|
||||
try {
|
||||
unset($test->prop);
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Cannot unset readonly property Test::$prop
|
||||
Test2::__get
|
||||
int(1)
|
||||
int(1)
|
||||
Cannot unset readonly property Test2::$prop
|
||||
Cannot unset readonly property Test3::$prop from global scope
|
30
Zend/tests/readonly_props/visibility_change.phpt
Normal file
30
Zend/tests/readonly_props/visibility_change.phpt
Normal file
@ -0,0 +1,30 @@
|
||||
--TEST--
|
||||
Visibility can change in readonly property
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A {
|
||||
protected readonly int $prop;
|
||||
|
||||
public function __construct() {
|
||||
$this->prop = 42;
|
||||
}
|
||||
}
|
||||
class B extends A {
|
||||
public readonly int $prop;
|
||||
}
|
||||
|
||||
$a = new A();
|
||||
try {
|
||||
var_dump($a->prop);
|
||||
} catch (Error $error) {
|
||||
echo $error->getMessage() . "\n";
|
||||
}
|
||||
|
||||
$b = new B();
|
||||
var_dump($b->prop);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Cannot access protected property A::$prop
|
||||
int(42)
|
@ -1757,6 +1757,9 @@ simple_list:
|
||||
if (ast->attr & ZEND_ACC_STATIC) {
|
||||
smart_str_appends(str, "static ");
|
||||
}
|
||||
if (ast->attr & ZEND_ACC_READONLY) {
|
||||
smart_str_appends(str, "readonly ");
|
||||
}
|
||||
|
||||
if (type_ast) {
|
||||
zend_ast_export_type(str, type_ast, indent);
|
||||
|
@ -6598,8 +6598,7 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall
|
||||
zend_string *name = zval_make_interned_string(zend_ast_get_zval(var_ast));
|
||||
bool is_ref = (param_ast->attr & ZEND_PARAM_REF) != 0;
|
||||
bool is_variadic = (param_ast->attr & ZEND_PARAM_VARIADIC) != 0;
|
||||
uint32_t visibility =
|
||||
param_ast->attr & (ZEND_ACC_PUBLIC|ZEND_ACC_PROTECTED|ZEND_ACC_PRIVATE);
|
||||
uint32_t property_flags = param_ast->attr & (ZEND_ACC_PPP_MASK | ZEND_ACC_READONLY);
|
||||
|
||||
znode var_node, default_node;
|
||||
zend_uchar opcode;
|
||||
@ -6681,7 +6680,7 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall
|
||||
|
||||
if (type_ast) {
|
||||
uint32_t default_type = *default_ast_ptr ? Z_TYPE(default_node.u.constant) : IS_UNDEF;
|
||||
bool force_nullable = default_type == IS_NULL && !visibility;
|
||||
bool force_nullable = default_type == IS_NULL && !property_flags;
|
||||
|
||||
op_array->fn_flags |= ZEND_ACC_HAS_TYPE_HINTS;
|
||||
arg_info->type = zend_compile_typename(type_ast, force_nullable);
|
||||
@ -6724,14 +6723,14 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall
|
||||
}
|
||||
|
||||
uint32_t arg_info_flags = _ZEND_ARG_INFO_FLAGS(is_ref, is_variadic, /* is_tentative */ 0)
|
||||
| (visibility ? _ZEND_IS_PROMOTED_BIT : 0);
|
||||
| (property_flags ? _ZEND_IS_PROMOTED_BIT : 0);
|
||||
ZEND_TYPE_FULL_MASK(arg_info->type) |= arg_info_flags;
|
||||
if (opcode == ZEND_RECV) {
|
||||
opline->op2.num = type_ast ?
|
||||
ZEND_TYPE_FULL_MASK(arg_info->type) : MAY_BE_ANY;
|
||||
}
|
||||
|
||||
if (visibility) {
|
||||
if (property_flags) {
|
||||
zend_op_array *op_array = CG(active_op_array);
|
||||
zend_class_entry *scope = op_array->scope;
|
||||
bool is_ctor =
|
||||
@ -6778,7 +6777,7 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall
|
||||
zend_string *doc_comment =
|
||||
doc_comment_ast ? zend_string_copy(zend_ast_get_str(doc_comment_ast)) : NULL;
|
||||
zend_property_info *prop = zend_declare_typed_property(
|
||||
scope, name, &default_value, visibility | ZEND_ACC_PROMOTED, doc_comment, type);
|
||||
scope, name, &default_value, property_flags | ZEND_ACC_PROMOTED, doc_comment, type);
|
||||
if (attributes_ast) {
|
||||
zend_compile_attributes(
|
||||
&prop->attributes, attributes_ast, 0, ZEND_ATTRIBUTE_TARGET_PROPERTY);
|
||||
@ -6799,9 +6798,8 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall
|
||||
for (i = 0; i < list->children; i++) {
|
||||
zend_ast *param_ast = list->child[i];
|
||||
bool is_ref = (param_ast->attr & ZEND_PARAM_REF) != 0;
|
||||
uint32_t visibility =
|
||||
param_ast->attr & (ZEND_ACC_PUBLIC|ZEND_ACC_PROTECTED|ZEND_ACC_PRIVATE);
|
||||
if (!visibility) {
|
||||
uint32_t flags = param_ast->attr & (ZEND_ACC_PPP_MASK | ZEND_ACC_READONLY);
|
||||
if (!flags) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -7037,6 +7035,10 @@ zend_string *zend_begin_method_decl(zend_op_array *op_array, zend_string *name,
|
||||
|
||||
zend_string *lcname;
|
||||
|
||||
if (fn_flags & ZEND_ACC_READONLY) {
|
||||
zend_error(E_COMPILE_ERROR, "Cannot use 'readonly' as method modifier");
|
||||
}
|
||||
|
||||
if ((fn_flags & ZEND_ACC_PRIVATE) && (fn_flags & ZEND_ACC_FINAL) && !zend_is_constructor(name)) {
|
||||
zend_error(E_COMPILE_WARNING, "Private methods cannot be final as they are never overridden by other classes");
|
||||
}
|
||||
@ -7354,6 +7356,23 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags, z
|
||||
ZVAL_UNDEF(&value_zv);
|
||||
}
|
||||
|
||||
if (flags & ZEND_ACC_READONLY) {
|
||||
if (!ZEND_TYPE_IS_SET(type)) {
|
||||
zend_error_noreturn(E_COMPILE_ERROR, "Readonly property %s::$%s must have type",
|
||||
ZSTR_VAL(ce->name), ZSTR_VAL(name));
|
||||
}
|
||||
if (!Z_ISUNDEF(value_zv)) {
|
||||
zend_error_noreturn(E_COMPILE_ERROR,
|
||||
"Readonly property %s::$%s cannot have default value",
|
||||
ZSTR_VAL(ce->name), ZSTR_VAL(name));
|
||||
}
|
||||
if (flags & ZEND_ACC_STATIC) {
|
||||
zend_error_noreturn(E_COMPILE_ERROR,
|
||||
"Static property %s::$%s cannot be readonly",
|
||||
ZSTR_VAL(ce->name), ZSTR_VAL(name));
|
||||
}
|
||||
}
|
||||
|
||||
info = zend_declare_typed_property(ce, name, &value_zv, flags, doc_comment, type);
|
||||
|
||||
if (attr_ast) {
|
||||
@ -7381,6 +7400,8 @@ static void zend_check_const_and_trait_alias_attr(uint32_t attr, const char* ent
|
||||
zend_error_noreturn(E_COMPILE_ERROR, "Cannot use 'abstract' as %s modifier", entity);
|
||||
} else if (attr & ZEND_ACC_FINAL) {
|
||||
zend_error_noreturn(E_COMPILE_ERROR, "Cannot use 'final' as %s modifier", entity);
|
||||
} else if (attr & ZEND_ACC_READONLY) {
|
||||
zend_error_noreturn(E_COMPILE_ERROR, "Cannot use 'readonly' as %s modifier", entity);
|
||||
}
|
||||
}
|
||||
/* }}} */
|
||||
@ -7406,7 +7427,7 @@ void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_ast *attr
|
||||
zend_string *doc_comment = doc_comment_ast ? zend_string_copy(zend_ast_get_str(doc_comment_ast)) : NULL;
|
||||
zval value_zv;
|
||||
|
||||
if (UNEXPECTED(flags & (ZEND_ACC_STATIC|ZEND_ACC_ABSTRACT))) {
|
||||
if (UNEXPECTED(flags & (ZEND_ACC_STATIC|ZEND_ACC_ABSTRACT|ZEND_ACC_READONLY))) {
|
||||
zend_check_const_and_trait_alias_attr(flags, "constant");
|
||||
}
|
||||
|
||||
|
@ -220,6 +220,9 @@ typedef struct _zend_oparray_context {
|
||||
#define ZEND_ACC_ABSTRACT (1 << 6) /* X | X | | */
|
||||
#define ZEND_ACC_EXPLICIT_ABSTRACT_CLASS (1 << 6) /* X | | | */
|
||||
/* | | | */
|
||||
/* Readonly property | | | */
|
||||
#define ZEND_ACC_READONLY (1 << 7) /* | | X | */
|
||||
/* | | | */
|
||||
/* Immutable op_array and class_entries | | | */
|
||||
/* (implemented only for lazy loading of op_arrays) | | | */
|
||||
#define ZEND_ACC_IMMUTABLE (1 << 7) /* X | X | | */
|
||||
|
@ -823,6 +823,12 @@ ZEND_COLD zend_never_inline void zend_verify_property_type_error(zend_property_i
|
||||
zend_string_release(type_str);
|
||||
}
|
||||
|
||||
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_readonly_property_modification_error(
|
||||
zend_property_info *info) {
|
||||
zend_throw_error(NULL, "Cannot modify readonly property %s::$%s",
|
||||
ZSTR_VAL(info->ce->name), zend_get_unmangled_property_name(info->name));
|
||||
}
|
||||
|
||||
static zend_class_entry *resolve_single_class_type(zend_string *name, zend_class_entry *self_ce) {
|
||||
if (zend_string_equals_literal_ci(name, "self")) {
|
||||
/* We need to explicitly check for this here, to avoid updating the type in the trait and
|
||||
@ -927,6 +933,11 @@ static zend_never_inline zval* zend_assign_to_typed_prop(zend_property_info *inf
|
||||
{
|
||||
zval tmp;
|
||||
|
||||
if (UNEXPECTED(info->flags & ZEND_ACC_READONLY)) {
|
||||
zend_readonly_property_modification_error(info);
|
||||
return &EG(uninitialized_zval);
|
||||
}
|
||||
|
||||
ZVAL_DEREF(value);
|
||||
ZVAL_COPY(&tmp, value);
|
||||
|
||||
@ -1884,7 +1895,9 @@ static zend_never_inline void zend_post_incdec_overloaded_property(zend_object *
|
||||
object->handlers->write_property(object, name, &z_copy, cache_slot);
|
||||
OBJ_RELEASE(object);
|
||||
zval_ptr_dtor(&z_copy);
|
||||
zval_ptr_dtor(z);
|
||||
if (z == &rv) {
|
||||
zval_ptr_dtor(z);
|
||||
}
|
||||
}
|
||||
|
||||
static zend_never_inline void zend_pre_incdec_overloaded_property(zend_object *object, zend_string *name, void **cache_slot OPLINE_DC EXECUTE_DATA_DC)
|
||||
@ -1915,7 +1928,9 @@ static zend_never_inline void zend_pre_incdec_overloaded_property(zend_object *o
|
||||
object->handlers->write_property(object, name, &z_copy, cache_slot);
|
||||
OBJ_RELEASE(object);
|
||||
zval_ptr_dtor(&z_copy);
|
||||
zval_ptr_dtor(z);
|
||||
if (z == &rv) {
|
||||
zval_ptr_dtor(z);
|
||||
}
|
||||
}
|
||||
|
||||
static zend_never_inline void zend_assign_op_overloaded_property(zend_object *object, zend_string *name, void **cache_slot, zval *value OPLINE_DC EXECUTE_DATA_DC)
|
||||
@ -1938,7 +1953,9 @@ static zend_never_inline void zend_assign_op_overloaded_property(zend_object *ob
|
||||
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
|
||||
ZVAL_COPY(EX_VAR(opline->result.var), &res);
|
||||
}
|
||||
zval_ptr_dtor(z);
|
||||
if (z == &rv) {
|
||||
zval_ptr_dtor(z);
|
||||
}
|
||||
zval_ptr_dtor(&res);
|
||||
OBJ_RELEASE(object);
|
||||
}
|
||||
@ -2821,9 +2838,22 @@ static zend_always_inline void zend_fetch_property_address(zval *result, zval *c
|
||||
ptr = OBJ_PROP(zobj, prop_offset);
|
||||
if (EXPECTED(Z_TYPE_P(ptr) != IS_UNDEF)) {
|
||||
ZVAL_INDIRECT(result, ptr);
|
||||
if (flags) {
|
||||
zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2);
|
||||
if (prop_info) {
|
||||
zend_property_info *prop_info = CACHED_PTR_EX(cache_slot + 2);
|
||||
if (prop_info) {
|
||||
if (UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY)) {
|
||||
/* For objects, R/RW/UNSET fetch modes might not actually modify object.
|
||||
* Similar as with magic __get() allow them, but return the value as a copy
|
||||
* to make sure no actual modification is possible. */
|
||||
ZEND_ASSERT(type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET);
|
||||
if (Z_TYPE_P(ptr) == IS_OBJECT) {
|
||||
ZVAL_COPY(result, ptr);
|
||||
} else {
|
||||
zend_readonly_property_modification_error(prop_info);
|
||||
ZVAL_ERROR(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (flags) {
|
||||
zend_handle_fetch_obj_flags(result, ptr, NULL, prop_info, flags);
|
||||
}
|
||||
}
|
||||
|
@ -69,6 +69,8 @@ ZEND_API ZEND_COLD void zend_throw_ref_type_error_type(zend_property_info *prop1
|
||||
ZEND_API ZEND_COLD zval* ZEND_FASTCALL zend_undefined_offset_write(HashTable *ht, zend_long lval);
|
||||
ZEND_API ZEND_COLD zval* ZEND_FASTCALL zend_undefined_index_write(HashTable *ht, zend_string *offset);
|
||||
|
||||
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_readonly_property_modification_error(zend_property_info *info);
|
||||
|
||||
ZEND_API bool zend_verify_scalar_type_hint(uint32_t type_mask, zval *arg, bool strict, bool is_internal_arg);
|
||||
ZEND_API ZEND_COLD void zend_verify_arg_error(
|
||||
const zend_function *zf, const zend_arg_info *arg_info, uint32_t arg_num, zval *value);
|
||||
|
@ -1249,6 +1249,14 @@ static void do_inherit_property(zend_property_info *parent_info, zend_string *ke
|
||||
(parent_info->flags & ZEND_ACC_STATIC) ? "static " : "non static ", ZSTR_VAL(parent_info->ce->name), ZSTR_VAL(key),
|
||||
(child_info->flags & ZEND_ACC_STATIC) ? "static " : "non static ", ZSTR_VAL(ce->name), ZSTR_VAL(key));
|
||||
}
|
||||
if (UNEXPECTED((child_info->flags & ZEND_ACC_READONLY) != (parent_info->flags & ZEND_ACC_READONLY))) {
|
||||
zend_error_noreturn(E_COMPILE_ERROR,
|
||||
"Cannot redeclare %s property %s::$%s as %s %s::$%s",
|
||||
parent_info->flags & ZEND_ACC_READONLY ? "readonly" : "non-readonly",
|
||||
ZSTR_VAL(parent_info->ce->name), ZSTR_VAL(key),
|
||||
child_info->flags & ZEND_ACC_READONLY ? "readonly" : "non-readonly",
|
||||
ZSTR_VAL(ce->name), ZSTR_VAL(key));
|
||||
}
|
||||
|
||||
if (UNEXPECTED((child_info->flags & ZEND_ACC_PPP_MASK) > (parent_info->flags & ZEND_ACC_PPP_MASK))) {
|
||||
zend_error_noreturn(E_COMPILE_ERROR, "Access level to %s::$%s must be %s (as in class %s)%s", ZSTR_VAL(ce->name), ZSTR_VAL(key), zend_visibility_string(parent_info->flags), ZSTR_VAL(parent_info->ce->name), (parent_info->flags&ZEND_ACC_PUBLIC) ? "" : " or weaker");
|
||||
@ -2218,10 +2226,10 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent
|
||||
zend_hash_del(&ce->properties_info, prop_name);
|
||||
flags |= ZEND_ACC_CHANGED;
|
||||
} else {
|
||||
uint32_t flags_mask = ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC | ZEND_ACC_READONLY;
|
||||
not_compatible = 1;
|
||||
|
||||
if ((colliding_prop->flags & (ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC))
|
||||
== (flags & (ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC)) &&
|
||||
if ((colliding_prop->flags & flags_mask) == (flags & flags_mask) &&
|
||||
property_types_compatible(property_info, colliding_prop) == INHERITANCE_SUCCESS
|
||||
) {
|
||||
/* the flags are identical, thus, the properties may be compatible */
|
||||
|
@ -154,6 +154,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
|
||||
%token <ident> T_PRIVATE "'private'"
|
||||
%token <ident> T_PROTECTED "'protected'"
|
||||
%token <ident> T_PUBLIC "'public'"
|
||||
%token <ident> T_READONLY "'readonly'"
|
||||
%token <ident> T_VAR "'var'"
|
||||
%token <ident> T_UNSET "'unset'"
|
||||
%token <ident> T_ISSET "'isset'"
|
||||
@ -279,7 +280,8 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
|
||||
%type <ast> enum_declaration_statement enum_backing_type enum_case enum_case_expr
|
||||
|
||||
%type <num> returns_ref function fn is_reference is_variadic variable_modifiers
|
||||
%type <num> method_modifiers non_empty_member_modifiers member_modifier optional_visibility_modifier
|
||||
%type <num> method_modifiers non_empty_member_modifiers member_modifier
|
||||
%type <num> optional_property_modifiers property_modifier
|
||||
%type <num> class_modifiers class_modifier use_type backup_fn_flags
|
||||
|
||||
%type <ptr> backup_lex_pos
|
||||
@ -305,7 +307,7 @@ reserved_non_modifiers:
|
||||
|
||||
semi_reserved:
|
||||
reserved_non_modifiers
|
||||
| T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC
|
||||
| T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC | T_READONLY
|
||||
;
|
||||
|
||||
ampersand:
|
||||
@ -769,19 +771,24 @@ attributed_parameter:
|
||||
| parameter { $$ = $1; }
|
||||
;
|
||||
|
||||
optional_visibility_modifier:
|
||||
optional_property_modifiers:
|
||||
%empty { $$ = 0; }
|
||||
| T_PUBLIC { $$ = ZEND_ACC_PUBLIC; }
|
||||
| optional_property_modifiers property_modifier
|
||||
{ $$ = zend_add_member_modifier($1, $2); if (!$$) { YYERROR; } }
|
||||
|
||||
property_modifier:
|
||||
T_PUBLIC { $$ = ZEND_ACC_PUBLIC; }
|
||||
| T_PROTECTED { $$ = ZEND_ACC_PROTECTED; }
|
||||
| T_PRIVATE { $$ = ZEND_ACC_PRIVATE; }
|
||||
| T_READONLY { $$ = ZEND_ACC_READONLY; }
|
||||
;
|
||||
|
||||
parameter:
|
||||
optional_visibility_modifier optional_type_without_static
|
||||
optional_property_modifiers optional_type_without_static
|
||||
is_reference is_variadic T_VARIABLE backup_doc_comment
|
||||
{ $$ = zend_ast_create_ex(ZEND_AST_PARAM, $1 | $3 | $4, $2, $5, NULL,
|
||||
NULL, $6 ? zend_ast_create_zval_from_str($6) : NULL); }
|
||||
| optional_visibility_modifier optional_type_without_static
|
||||
| optional_property_modifiers optional_type_without_static
|
||||
is_reference is_variadic T_VARIABLE backup_doc_comment '=' expr
|
||||
{ $$ = zend_ast_create_ex(ZEND_AST_PARAM, $1 | $3 | $4, $2, $5, $8,
|
||||
NULL, $6 ? zend_ast_create_zval_from_str($6) : NULL); }
|
||||
@ -1001,6 +1008,7 @@ member_modifier:
|
||||
| T_STATIC { $$ = ZEND_ACC_STATIC; }
|
||||
| T_ABSTRACT { $$ = ZEND_ACC_ABSTRACT; }
|
||||
| T_FINAL { $$ = ZEND_ACC_FINAL; }
|
||||
| T_READONLY { $$ = ZEND_ACC_READONLY; }
|
||||
;
|
||||
|
||||
property_list:
|
||||
|
@ -1714,6 +1714,10 @@ NEWLINE ("\r"|"\n"|"\r\n")
|
||||
RETURN_TOKEN_WITH_IDENT(T_PUBLIC);
|
||||
}
|
||||
|
||||
<ST_IN_SCRIPTING>"readonly" {
|
||||
RETURN_TOKEN_WITH_IDENT(T_READONLY);
|
||||
}
|
||||
|
||||
<ST_IN_SCRIPTING>"unset" {
|
||||
RETURN_TOKEN_WITH_IDENT(T_UNSET);
|
||||
}
|
||||
|
@ -277,6 +277,19 @@ static ZEND_COLD zend_never_inline void zend_forbidden_dynamic_property(
|
||||
ZSTR_VAL(ce->name), ZSTR_VAL(member));
|
||||
}
|
||||
|
||||
static ZEND_COLD zend_never_inline void zend_readonly_property_modification_scope_error(
|
||||
zend_class_entry *ce, zend_string *member, zend_class_entry *scope, const char *operation) {
|
||||
zend_throw_error(NULL, "Cannot %s readonly property %s::$%s from %s%s",
|
||||
operation, ZSTR_VAL(ce->name), ZSTR_VAL(member),
|
||||
scope ? "scope " : "global scope", scope ? ZSTR_VAL(scope->name) : "");
|
||||
}
|
||||
|
||||
static ZEND_COLD zend_never_inline void zend_readonly_property_unset_error(
|
||||
zend_class_entry *ce, zend_string *member) {
|
||||
zend_throw_error(NULL, "Cannot unset readonly property %s::$%s",
|
||||
ZSTR_VAL(ce->name), ZSTR_VAL(member));
|
||||
}
|
||||
|
||||
static zend_always_inline uintptr_t zend_get_property_offset(zend_class_entry *ce, zend_string *member, int silent, void **cache_slot, zend_property_info **info_ptr) /* {{{ */
|
||||
{
|
||||
zval *zv;
|
||||
@ -573,6 +586,19 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int
|
||||
if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) {
|
||||
retval = OBJ_PROP(zobj, property_offset);
|
||||
if (EXPECTED(Z_TYPE_P(retval) != IS_UNDEF)) {
|
||||
if (prop_info && UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY)
|
||||
&& (type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET)) {
|
||||
if (Z_TYPE_P(retval) == IS_OBJECT) {
|
||||
/* For objects, R/RW/UNSET fetch modes might not actually modify object.
|
||||
* Similar as with magic __get() allow them, but return the value as a copy
|
||||
* to make sure no actual modification is possible. */
|
||||
ZVAL_COPY(rv, retval);
|
||||
retval = rv;
|
||||
} else {
|
||||
zend_readonly_property_modification_error(prop_info);
|
||||
retval = &EG(uninitialized_zval);
|
||||
}
|
||||
}
|
||||
goto exit;
|
||||
}
|
||||
if (UNEXPECTED(Z_PROP_FLAG_P(retval) == IS_PROP_UNINIT)) {
|
||||
@ -708,6 +734,36 @@ static zend_always_inline bool property_uses_strict_types() {
|
||||
&& ZEND_CALL_USES_STRICT_TYPES(EG(current_execute_data));
|
||||
}
|
||||
|
||||
static bool verify_readonly_initialization_access(
|
||||
zend_property_info *prop_info, zend_class_entry *ce,
|
||||
zend_string *name, const char *operation) {
|
||||
zend_class_entry *scope;
|
||||
if (UNEXPECTED(EG(fake_scope))) {
|
||||
scope = EG(fake_scope);
|
||||
} else {
|
||||
scope = zend_get_executed_scope();
|
||||
}
|
||||
if (prop_info->ce == scope) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* We may have redeclared a parent property. In that case the parent should still be
|
||||
* allowed to initialize it. */
|
||||
if (scope && is_derived_class(ce, scope)) {
|
||||
zend_property_info *prop_info = zend_hash_find_ptr(&scope->properties_info, name);
|
||||
if (prop_info) {
|
||||
/* This should be ensured by inheritance. */
|
||||
ZEND_ASSERT(prop_info->flags & ZEND_ACC_READONLY);
|
||||
if (prop_info->ce == scope) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zend_readonly_property_modification_scope_error(prop_info->ce, name, scope, operation);
|
||||
return false;
|
||||
}
|
||||
|
||||
ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zval *value, void **cache_slot) /* {{{ */
|
||||
{
|
||||
zval *variable_ptr, tmp;
|
||||
@ -723,6 +779,13 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva
|
||||
Z_TRY_ADDREF_P(value);
|
||||
|
||||
if (UNEXPECTED(prop_info)) {
|
||||
if (UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY)) {
|
||||
Z_TRY_DELREF_P(value);
|
||||
zend_readonly_property_modification_error(prop_info);
|
||||
variable_ptr = &EG(error_zval);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ZVAL_COPY_VALUE(&tmp, value);
|
||||
if (UNEXPECTED(!zend_verify_property_type(prop_info, &tmp, property_uses_strict_types()))) {
|
||||
Z_TRY_DELREF_P(value);
|
||||
@ -787,6 +850,13 @@ write_std_property:
|
||||
|
||||
Z_TRY_ADDREF_P(value);
|
||||
if (UNEXPECTED(prop_info)) {
|
||||
if (UNEXPECTED((prop_info->flags & ZEND_ACC_READONLY)
|
||||
&& !verify_readonly_initialization_access(prop_info, zobj->ce, name, "initialize"))) {
|
||||
Z_TRY_DELREF_P(value);
|
||||
variable_ptr = &EG(error_zval);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ZVAL_COPY_VALUE(&tmp, value);
|
||||
if (UNEXPECTED(!zend_verify_property_type(prop_info, &tmp, property_uses_strict_types()))) {
|
||||
zval_ptr_dtor(value);
|
||||
@ -955,6 +1025,9 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam
|
||||
/* we do have getter - fail and let it try again with usual get/set */
|
||||
retval = NULL;
|
||||
}
|
||||
} else if (prop_info && UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY)) {
|
||||
/* Readonly property, delegate to read_property + write_property. */
|
||||
retval = NULL;
|
||||
}
|
||||
} else if (EXPECTED(IS_DYNAMIC_PROPERTY_OFFSET(property_offset))) {
|
||||
if (EXPECTED(zobj->properties)) {
|
||||
@ -1003,6 +1076,10 @@ ZEND_API void zend_std_unset_property(zend_object *zobj, zend_string *name, void
|
||||
zval *slot = OBJ_PROP(zobj, property_offset);
|
||||
|
||||
if (Z_TYPE_P(slot) != IS_UNDEF) {
|
||||
if (UNEXPECTED(prop_info && (prop_info->flags & ZEND_ACC_READONLY))) {
|
||||
zend_readonly_property_unset_error(prop_info->ce, name);
|
||||
return;
|
||||
}
|
||||
if (UNEXPECTED(Z_ISREF_P(slot)) &&
|
||||
(ZEND_DEBUG || ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(slot)))) {
|
||||
if (prop_info) {
|
||||
@ -1019,6 +1096,11 @@ ZEND_API void zend_std_unset_property(zend_object *zobj, zend_string *name, void
|
||||
return;
|
||||
}
|
||||
if (UNEXPECTED(Z_PROP_FLAG_P(slot) == IS_PROP_UNINIT)) {
|
||||
if (UNEXPECTED(prop_info && (prop_info->flags & ZEND_ACC_READONLY)
|
||||
&& !verify_readonly_initialization_access(prop_info, zobj->ce, name, "unset"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Reset the IS_PROP_UNINIT flag, if it exists and bypass __unset(). */
|
||||
Z_PROP_FLAG_P(slot) = 0;
|
||||
return;
|
||||
|
@ -6926,11 +6926,20 @@ ZEND_VM_HANDLER(126, ZEND_FE_FETCH_RW, VAR, ANY, JMP_ADDR)
|
||||
&& EXPECTED(zend_check_property_access(Z_OBJ_P(array), p->key, 0) == SUCCESS)) {
|
||||
if ((value_type & Z_TYPE_MASK) != IS_REFERENCE) {
|
||||
zend_property_info *prop_info =
|
||||
zend_get_typed_property_info_for_slot(Z_OBJ_P(array), value);
|
||||
zend_get_property_info_for_slot(Z_OBJ_P(array), value);
|
||||
if (UNEXPECTED(prop_info)) {
|
||||
ZVAL_NEW_REF(value, value);
|
||||
ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(value), prop_info);
|
||||
value_type = IS_REFERENCE_EX;
|
||||
if (UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY)) {
|
||||
zend_throw_error(NULL,
|
||||
"Cannot acquire reference to readonly property %s::$%s",
|
||||
ZSTR_VAL(prop_info->ce->name), ZSTR_VAL(p->key));
|
||||
UNDEF_RESULT();
|
||||
HANDLE_EXCEPTION();
|
||||
}
|
||||
if (ZEND_TYPE_IS_SET(prop_info->type)) {
|
||||
ZVAL_NEW_REF(value, value);
|
||||
ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(value), prop_info);
|
||||
value_type = IS_REFERENCE_EX;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -21952,11 +21952,20 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FE_FETCH_RW_SPEC_VAR_HANDLER(Z
|
||||
&& EXPECTED(zend_check_property_access(Z_OBJ_P(array), p->key, 0) == SUCCESS)) {
|
||||
if ((value_type & Z_TYPE_MASK) != IS_REFERENCE) {
|
||||
zend_property_info *prop_info =
|
||||
zend_get_typed_property_info_for_slot(Z_OBJ_P(array), value);
|
||||
zend_get_property_info_for_slot(Z_OBJ_P(array), value);
|
||||
if (UNEXPECTED(prop_info)) {
|
||||
ZVAL_NEW_REF(value, value);
|
||||
ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(value), prop_info);
|
||||
value_type = IS_REFERENCE_EX;
|
||||
if (UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY)) {
|
||||
zend_throw_error(NULL,
|
||||
"Cannot acquire reference to readonly property %s::$%s",
|
||||
ZSTR_VAL(prop_info->ce->name), ZSTR_VAL(p->key));
|
||||
UNDEF_RESULT();
|
||||
HANDLE_EXCEPTION();
|
||||
}
|
||||
if (ZEND_TYPE_IS_SET(prop_info->type)) {
|
||||
ZVAL_NEW_REF(value, value);
|
||||
ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(value), prop_info);
|
||||
value_type = IS_REFERENCE_EX;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -11830,7 +11830,6 @@ static int zend_jit_fetch_obj(dasm_State **Dst,
|
||||
| mov FCARG1x, TMP1
|
||||
prop_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1x, 0);
|
||||
if (opline->opcode == ZEND_FETCH_OBJ_W
|
||||
&& (opline->extended_value & ZEND_FETCH_OBJ_FLAGS)
|
||||
&& (!ce || ce_is_instanceof || (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS))) {
|
||||
uint32_t flags = opline->extended_value & ZEND_FETCH_OBJ_FLAGS;
|
||||
|
||||
@ -11839,6 +11838,22 @@ static int zend_jit_fetch_obj(dasm_State **Dst,
|
||||
| cbnz FCARG2x, >1
|
||||
|.cold_code
|
||||
|1:
|
||||
| ldr TMP1w, [FCARG2x, #offsetof(zend_property_info, flags)]
|
||||
| tst TMP1w, #ZEND_ACC_READONLY
|
||||
| beq >3
|
||||
| IF_NOT_TYPE REG2w, IS_OBJECT_EX, >2
|
||||
| GET_Z_PTR REG2, FCARG1x
|
||||
| GC_ADDREF REG2, TMP1w
|
||||
| SET_ZVAL_PTR res_addr, REG2, TMP1
|
||||
| SET_ZVAL_TYPE_INFO res_addr, IS_OBJECT_EX, TMP1w, TMP2
|
||||
| b >9
|
||||
|2:
|
||||
| mov FCARG1x, FCARG2x
|
||||
| SET_EX_OPLINE opline, REG0
|
||||
| EXT_CALL zend_readonly_property_modification_error, REG0
|
||||
| SET_ZVAL_TYPE_INFO res_addr, _IS_ERROR, TMP1w, TMP2
|
||||
| b >9
|
||||
|3:
|
||||
if (flags == ZEND_FETCH_DIM_WRITE) {
|
||||
| SET_EX_OPLINE opline, REG0
|
||||
| EXT_CALL zend_jit_check_array_promotion, REG0
|
||||
@ -11848,7 +11863,7 @@ static int zend_jit_fetch_obj(dasm_State **Dst,
|
||||
| EXT_CALL zend_jit_create_typed_ref, REG0
|
||||
| b >9
|
||||
} else {
|
||||
ZEND_UNREACHABLE();
|
||||
ZEND_ASSERT(flags == 0);
|
||||
}
|
||||
|.code
|
||||
}
|
||||
@ -11869,6 +11884,22 @@ static int zend_jit_fetch_obj(dasm_State **Dst,
|
||||
} else {
|
||||
| IF_UNDEF REG2w, >5
|
||||
}
|
||||
if (opline->opcode == ZEND_FETCH_OBJ_W && (prop_info->flags & ZEND_ACC_READONLY)) {
|
||||
| IF_NOT_TYPE REG2w, IS_OBJECT_EX, >4
|
||||
| GET_ZVAL_PTR REG2, prop_addr, TMP1
|
||||
| GC_ADDREF REG2, TMP1w
|
||||
| SET_ZVAL_PTR res_addr, REG2, TMP1
|
||||
| SET_ZVAL_TYPE_INFO res_addr, IS_OBJECT_EX, TMP1w, TMP2
|
||||
| b >9
|
||||
|.cold_code
|
||||
|4:
|
||||
| LOAD_ADDR FCARG1x, prop_info
|
||||
| SET_EX_OPLINE opline, REG0
|
||||
| EXT_CALL zend_readonly_property_modification_error, REG0
|
||||
| SET_ZVAL_TYPE_INFO res_addr, _IS_ERROR, TMP1w, TMP2
|
||||
| b >9
|
||||
|.code
|
||||
}
|
||||
if (opline->opcode == ZEND_FETCH_OBJ_W
|
||||
&& (opline->extended_value & ZEND_FETCH_OBJ_FLAGS)
|
||||
&& ZEND_TYPE_IS_SET(prop_info->type)) {
|
||||
@ -12895,7 +12926,7 @@ static int zend_jit_assign_obj(dasm_State **Dst,
|
||||
}
|
||||
} else {
|
||||
prop_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1x, prop_info->offset);
|
||||
if (!ce || ce_is_instanceof || !(ce->ce_flags & ZEND_ACC_IMMUTABLE) || ce->__get || ce->__set) {
|
||||
if (!ce || ce_is_instanceof || !(ce->ce_flags & ZEND_ACC_IMMUTABLE) || ce->__get || ce->__set || (prop_info->flags & ZEND_ACC_READONLY)) {
|
||||
// Undefined property with magic __get()/__set()
|
||||
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
|
||||
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
|
||||
|
@ -2006,6 +2006,14 @@ static void ZEND_FASTCALL zend_jit_assign_to_typed_prop(zval *property_val, zend
|
||||
zend_execute_data *execute_data = EG(current_execute_data);
|
||||
zval tmp;
|
||||
|
||||
if (UNEXPECTED(info->flags & ZEND_ACC_READONLY)) {
|
||||
zend_readonly_property_modification_error(info);
|
||||
if (result) {
|
||||
ZVAL_UNDEF(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ZVAL_DEREF(value);
|
||||
ZVAL_COPY(&tmp, value);
|
||||
|
||||
|
@ -12511,7 +12511,6 @@ static int zend_jit_fetch_obj(dasm_State **Dst,
|
||||
| add FCARG1a, r0
|
||||
prop_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0);
|
||||
if (opline->opcode == ZEND_FETCH_OBJ_W
|
||||
&& (opline->extended_value & ZEND_FETCH_OBJ_FLAGS)
|
||||
&& (!ce || ce_is_instanceof || (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS))) {
|
||||
uint32_t flags = opline->extended_value & ZEND_FETCH_OBJ_FLAGS;
|
||||
|
||||
@ -12521,6 +12520,21 @@ static int zend_jit_fetch_obj(dasm_State **Dst,
|
||||
| jnz >1
|
||||
|.cold_code
|
||||
|1:
|
||||
| test dword [FCARG2a + offsetof(zend_property_info, flags)], ZEND_ACC_READONLY
|
||||
| jz >3
|
||||
| IF_NOT_Z_TYPE FCARG1a, IS_OBJECT, >2
|
||||
| GET_Z_PTR r0, FCARG1a
|
||||
| GC_ADDREF r0
|
||||
| SET_ZVAL_PTR res_addr, r0
|
||||
| SET_ZVAL_TYPE_INFO res_addr, IS_OBJECT_EX
|
||||
| jmp >9
|
||||
|2:
|
||||
| mov FCARG1a, FCARG2a
|
||||
| SET_EX_OPLINE opline, r0
|
||||
| EXT_CALL zend_readonly_property_modification_error, r0
|
||||
| SET_ZVAL_TYPE_INFO res_addr, _IS_ERROR
|
||||
| jmp >9
|
||||
|3:
|
||||
if (flags == ZEND_FETCH_DIM_WRITE) {
|
||||
| SET_EX_OPLINE opline, r0
|
||||
| EXT_CALL zend_jit_check_array_promotion, r0
|
||||
@ -12538,7 +12552,7 @@ static int zend_jit_fetch_obj(dasm_State **Dst,
|
||||
|.endif
|
||||
| jmp >9
|
||||
} else {
|
||||
ZEND_UNREACHABLE();
|
||||
ZEND_ASSERT(flags == 0);
|
||||
}
|
||||
|.code
|
||||
}
|
||||
@ -12559,6 +12573,22 @@ static int zend_jit_fetch_obj(dasm_State **Dst,
|
||||
} else {
|
||||
| IF_UNDEF dl, >5
|
||||
}
|
||||
if (opline->opcode == ZEND_FETCH_OBJ_W && (prop_info->flags & ZEND_ACC_READONLY)) {
|
||||
| IF_NOT_TYPE dl, IS_OBJECT, >4
|
||||
| GET_ZVAL_PTR r0, prop_addr
|
||||
| GC_ADDREF r0
|
||||
| SET_ZVAL_PTR res_addr, r0
|
||||
| SET_ZVAL_TYPE_INFO res_addr, IS_OBJECT_EX
|
||||
| jmp >9
|
||||
|.cold_code
|
||||
|4:
|
||||
| LOAD_ADDR FCARG1a, prop_info
|
||||
| SET_EX_OPLINE opline, r0
|
||||
| EXT_CALL zend_readonly_property_modification_error, r0
|
||||
| SET_ZVAL_TYPE_INFO res_addr, _IS_ERROR
|
||||
| jmp >9
|
||||
|.code
|
||||
}
|
||||
if (opline->opcode == ZEND_FETCH_OBJ_W
|
||||
&& (opline->extended_value & ZEND_FETCH_OBJ_FLAGS)
|
||||
&& ZEND_TYPE_IS_SET(prop_info->type)) {
|
||||
@ -13677,7 +13707,7 @@ static int zend_jit_assign_obj(dasm_State **Dst,
|
||||
}
|
||||
} else {
|
||||
prop_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, prop_info->offset);
|
||||
if (!ce || ce_is_instanceof || !(ce->ce_flags & ZEND_ACC_IMMUTABLE) || ce->__get || ce->__set) {
|
||||
if (!ce || ce_is_instanceof || !(ce->ce_flags & ZEND_ACC_IMMUTABLE) || ce->__get || ce->__set || (prop_info->flags & ZEND_ACC_READONLY)) {
|
||||
// Undefined property with magic __get()/__set()
|
||||
if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE) {
|
||||
int32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM);
|
||||
|
@ -5509,6 +5509,11 @@ ZEND_METHOD(ReflectionProperty, isStatic)
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
ZEND_METHOD(ReflectionProperty, isReadOnly)
|
||||
{
|
||||
_property_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_READONLY);
|
||||
}
|
||||
|
||||
/* {{{ Returns whether this property is default (declared at compilation time). */
|
||||
ZEND_METHOD(ReflectionProperty, isDefault)
|
||||
{
|
||||
@ -5535,7 +5540,7 @@ ZEND_METHOD(ReflectionProperty, getModifiers)
|
||||
{
|
||||
reflection_object *intern;
|
||||
property_reference *ref;
|
||||
uint32_t keep_flags = ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC;
|
||||
uint32_t keep_flags = ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC | ZEND_ACC_READONLY;
|
||||
|
||||
if (zend_parse_parameters_none() == FAILURE) {
|
||||
RETURN_THROWS();
|
||||
@ -7102,6 +7107,7 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */
|
||||
reflection_property_ptr->create_object = reflection_objects_new;
|
||||
|
||||
REGISTER_REFLECTION_CLASS_CONST_LONG(property, "IS_STATIC", ZEND_ACC_STATIC);
|
||||
REGISTER_REFLECTION_CLASS_CONST_LONG(property, "IS_READONLY", ZEND_ACC_READONLY);
|
||||
REGISTER_REFLECTION_CLASS_CONST_LONG(property, "IS_PUBLIC", ZEND_ACC_PUBLIC);
|
||||
REGISTER_REFLECTION_CLASS_CONST_LONG(property, "IS_PROTECTED", ZEND_ACC_PROTECTED);
|
||||
REGISTER_REFLECTION_CLASS_CONST_LONG(property, "IS_PRIVATE", ZEND_ACC_PRIVATE);
|
||||
|
@ -428,6 +428,8 @@ class ReflectionProperty implements Reflector
|
||||
/** @tentative-return-type */
|
||||
public function isStatic(): bool {}
|
||||
|
||||
public function isReadOnly(): bool {}
|
||||
|
||||
/** @tentative-return-type */
|
||||
public function isDefault(): bool {}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* This is a generated file, edit the .stub.php file instead.
|
||||
* Stub hash: 49f7b49b187721a143fc54ea1eb4414805603dba */
|
||||
* Stub hash: 74eb6f065b43ebd0ccff2510a49ac5c6fe716910 */
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0)
|
||||
@ -354,6 +354,8 @@ ZEND_END_ARG_INFO()
|
||||
|
||||
#define arginfo_class_ReflectionProperty_isStatic arginfo_class_ReflectionFunctionAbstract_inNamespace
|
||||
|
||||
#define arginfo_class_ReflectionProperty_isReadOnly arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType
|
||||
|
||||
#define arginfo_class_ReflectionProperty_isDefault arginfo_class_ReflectionFunctionAbstract_inNamespace
|
||||
|
||||
#define arginfo_class_ReflectionProperty_isPromoted arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType
|
||||
@ -722,6 +724,7 @@ ZEND_METHOD(ReflectionProperty, isPublic);
|
||||
ZEND_METHOD(ReflectionProperty, isPrivate);
|
||||
ZEND_METHOD(ReflectionProperty, isProtected);
|
||||
ZEND_METHOD(ReflectionProperty, isStatic);
|
||||
ZEND_METHOD(ReflectionProperty, isReadOnly);
|
||||
ZEND_METHOD(ReflectionProperty, isDefault);
|
||||
ZEND_METHOD(ReflectionProperty, isPromoted);
|
||||
ZEND_METHOD(ReflectionProperty, getModifiers);
|
||||
@ -997,6 +1000,7 @@ static const zend_function_entry class_ReflectionProperty_methods[] = {
|
||||
ZEND_ME(ReflectionProperty, isPrivate, arginfo_class_ReflectionProperty_isPrivate, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(ReflectionProperty, isProtected, arginfo_class_ReflectionProperty_isProtected, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(ReflectionProperty, isStatic, arginfo_class_ReflectionProperty_isStatic, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(ReflectionProperty, isReadOnly, arginfo_class_ReflectionProperty_isReadOnly, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(ReflectionProperty, isDefault, arginfo_class_ReflectionProperty_isDefault, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(ReflectionProperty, isPromoted, arginfo_class_ReflectionProperty_isPromoted, ZEND_ACC_PUBLIC)
|
||||
ZEND_ME(ReflectionProperty, getModifiers, arginfo_class_ReflectionProperty_getModifiers, ZEND_ACC_PUBLIC)
|
||||
|
24
ext/reflection/tests/readonly_properties.phpt
Normal file
24
ext/reflection/tests/readonly_properties.phpt
Normal file
@ -0,0 +1,24 @@
|
||||
--TEST--
|
||||
Readonly property reflection
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public int $rw;
|
||||
public readonly int $ro;
|
||||
}
|
||||
|
||||
$rp = new ReflectionProperty(Test::class, 'rw');
|
||||
var_dump($rp->isReadOnly());
|
||||
var_dump(($rp->getModifiers() & ReflectionProperty::IS_READONLY) != 0);
|
||||
|
||||
$rp = new ReflectionProperty(Test::class, 'ro');
|
||||
var_dump($rp->isReadOnly());
|
||||
var_dump(($rp->getModifiers() & ReflectionProperty::IS_READONLY) != 0);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
bool(false)
|
||||
bool(false)
|
||||
bool(true)
|
||||
bool(true)
|
@ -92,6 +92,7 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) {
|
||||
REGISTER_LONG_CONSTANT("T_PRIVATE", T_PRIVATE, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("T_PROTECTED", T_PROTECTED, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("T_PUBLIC", T_PUBLIC, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("T_READONLY", T_READONLY, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("T_VAR", T_VAR, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("T_UNSET", T_UNSET, CONST_CS | CONST_PERSISTENT);
|
||||
REGISTER_LONG_CONSTANT("T_ISSET", T_ISSET, CONST_CS | CONST_PERSISTENT);
|
||||
@ -244,6 +245,7 @@ char *get_token_type_name(int token_type)
|
||||
case T_PRIVATE: return "T_PRIVATE";
|
||||
case T_PROTECTED: return "T_PROTECTED";
|
||||
case T_PUBLIC: return "T_PUBLIC";
|
||||
case T_READONLY: return "T_READONLY";
|
||||
case T_VAR: return "T_VAR";
|
||||
case T_UNSET: return "T_UNSET";
|
||||
case T_ISSET: return "T_ISSET";
|
||||
|
Loading…
Reference in New Issue
Block a user