Skip to content
Merged
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
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ PHP NEWS
. Changed run-tests.php to run in parallel by default, using up to 10
automatically detected workers. Pass -j1 for sequential execution.
(NickSdot)
. Allowed readonly properties to declare default values. (NickSdot)
. Changed run-tests.php to run test subprocesses without a shell where
possible. (NickSdot)
. Fixed GH-23083 (SEGV build_trace_args in zend_exceptions.c with
Expand Down
2 changes: 2 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,8 @@ PHP 8.6 UPGRADE NOTES
========================================

- Core:
. Readonly properties may now declare default values.
RFC: https://wiki.php.net/rfc/readonly_property_defaults
. It is now possible to use reference assign on WeakMap without the key
needing to be present beforehand.
. It is now possible to define the __debugInfo() magic method on enums.
Expand Down
36 changes: 36 additions & 0 deletions Zend/tests/readonly_classes/readonly_with_property_default.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
--TEST--
Properties of a readonly class may have default values
--FILE--
<?php

readonly class Foo
{
public int $bar = 1;
public ?string $nullable = null;

public function __construct()
{
try {
$this->bar = 2;
} catch (Error $e) {
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
}
}
}

$foo = new Foo();
var_dump($foo->bar);
var_dump($foo->nullable);

try {
$foo->bar = 3;
} catch (Error $e) {
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
}

?>
--EXPECT--
Error: Cannot modify readonly property Foo::$bar
int(1)
NULL
Error: Cannot modify readonly property Foo::$bar
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
--TEST--
Readonly class may use readonly trait property with default value
--FILE--
<?php

trait TDefault {
public readonly int $prop = 2;
}

readonly class A {
use TDefault;
}

var_dump(new A()->prop);

class B {
use TDefault;
}

var_dump(new B()->prop);

?>
--EXPECT--
int(2)
int(2)
59 changes: 59 additions & 0 deletions Zend/tests/readonly_props/readonly_clone_success1.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,41 @@ var_dump($foo2);

var_dump(clone $foo2);

class FooWithDefault {
public readonly int $bar = 1;

public function __clone()
{
$this->bar++;
}
}

$fooWithDefault = new FooWithDefault();

var_dump(clone $fooWithDefault);

$fooWithDefault2 = clone $fooWithDefault;
var_dump($fooWithDefault2);

var_dump(clone $fooWithDefault2);

class FooWithDefaultCloneWith {
public readonly int $bar = 1;

public function withBar(int $bar)
{
return clone($this, ['bar' => $bar]);
}
}

$clone = new FooWithDefaultCloneWith();
var_dump($clone);

$clone2 = $clone->withBar(2);
var_dump($clone2);

var_dump($clone2->withBar(0));

?>
--EXPECTF--
object(Foo)#%d (%d) {
Expand All @@ -37,3 +72,27 @@ object(Foo)#%d (%d) {
["bar"]=>
int(3)
}
object(FooWithDefault)#%d (%d) {
["bar"]=>
int(2)
}
object(FooWithDefault)#%d (%d) {
["bar"]=>
int(2)
}
object(FooWithDefault)#%d (%d) {
["bar"]=>
int(3)
}
object(FooWithDefaultCloneWith)#%d (%d) {
["bar"]=>
int(1)
}
object(FooWithDefaultCloneWith)#%d (%d) {
["bar"]=>
int(2)
}
object(FooWithDefaultCloneWith)#%d (%d) {
["bar"]=>
int(0)
}
13 changes: 13 additions & 0 deletions Zend/tests/readonly_props/readonly_trait_match.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,20 @@ class C {
use T1, T2;
}

trait TDefault1 {
public readonly int $prop = 1;
}
trait TDefault2 {
public readonly int $prop = 1;
}
class CDefault {
use TDefault1, TDefault2;
}

var_dump(new CDefault()->prop);

?>
===DONE===
--EXPECT--
int(1)
===DONE===
32 changes: 29 additions & 3 deletions Zend/tests/readonly_props/readonly_with_default.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,43 @@ Readonly property with default value
--FILE--
<?php

enum E {
case Case;
}

class Test {
public readonly int $prop = 1;
public readonly string $className = self::class;
public readonly ?string $nullable = null;
public readonly array $array = [1, "two" => 2];
public readonly E $enum = E::Case;
public readonly string $enumString = E::Case->name;
}

$test = new Test;
var_dump($test->prop);
var_dump($test->className);
var_dump($test->nullable);
var_dump($test->array);
var_dump($test->enum);
var_dump($test->enumString);
try {
$test->prop = 2;
} catch (Error $e) {
echo $e->getMessage(), "\n";
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
}

?>
--EXPECTF--
Fatal error: Readonly property Test::$prop cannot have default value in %s on line %d
--EXPECT--
int(1)
string(4) "Test"
NULL
array(2) {
[0]=>
int(1)
["two"]=>
int(2)
}
enum(E::Case)
string(4) "Case"
Error: Cannot modify readonly property Test::$prop
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
--TEST--
Readonly property with default value has restricted set visibility for get/set abstract property
--DESCRIPTION--
The error message should be improved, the set access level comes from readonly.
--FILE--
<?php

abstract class P {
public abstract int $prop { get; set; }
}

class C extends P {
public readonly int $prop = 42;
}

?>
--EXPECTF--
Fatal error: Set access level of C::$prop must be omitted (as in class P) in %s on line %d
Comment thread
NickSdot marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
--TEST--
Readonly property with default value and asymmetric visibility
--FILE--
<?php

class Test {
public readonly int $default = 1;
public private(set) readonly int $private = 2;
public protected(set) readonly int $protected = 3;
public public(set) readonly int $public = 4;
}

$test = new Test();

foreach (['default', 'private', 'protected', 'public'] as $prop) {
$before = $test->$prop;
try {
$test->$prop = 42;
} catch (Error $e) {
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
}
echo "$$prop before $before, after {$test->$prop}", PHP_EOL;
}

?>
--EXPECT--
Error: Cannot modify readonly property Test::$default
$default before 1, after 1
Error: Cannot modify readonly property Test::$private
$private before 2, after 2
Error: Cannot modify readonly property Test::$protected
$protected before 3, after 3
Error: Cannot modify readonly property Test::$public
$public before 4, after 4
40 changes: 40 additions & 0 deletions Zend/tests/readonly_props/readonly_with_default_inheritance.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
--TEST--
Readonly property with default value and inheritance
--FILE--
<?php

class ParentDefault {
public readonly int $prop = 1;
}

class ChildInherits extends ParentDefault {}

class ChildOverrides extends ParentDefault {
public readonly int $prop = 2;
}

class PrivateParent {
private readonly int $prop = 3;

public function getParentProp(): int {
return $this->prop;
}
}

class PrivateChild extends PrivateParent {
public readonly int $prop = 4;
}

var_dump(new ChildInherits()->prop);
var_dump(new ChildOverrides()->prop);

$privateChild = new PrivateChild();
var_dump($privateChild->getParentProp());
var_dump($privateChild->prop);

?>
--EXPECT--
int(1)
int(2)
int(3)
int(4)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
Readonly property with default value satisfies get-only interface property
--FILE--
<?php

interface I {
public int $prop { get; }
}

class C implements I {
public readonly int $prop = 42;
}

var_dump(new C()->prop);
?>
--EXPECT--
int(42)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
Readonly public(set) property with default value does not satisfy get/set interface property
--FILE--
<?php

interface I {
public int $prop { get; set; }
}

// does not satisfy set
class C implements I {
public public(set) readonly int $prop = 42;
}
?>
--EXPECTF--
Fatal error: Class C contains 1 abstract method and must therefore be declared abstract or implement the remaining method (I::$prop::set) in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
Readonly property with default value has restricted set visibility for get/set interface property
--DESCRIPTION--
The error message should be improved, the set access level comes from readonly. Ref: Zend/tests/property_hooks/interface_get_set_readonly.phpt
--FILE--
<?php

interface I {
public int $prop { get; set; }
}

class C implements I {
public readonly int $prop = 42;
}
?>
--EXPECTF--
Fatal error: Set access level of C::$prop must be omitted (as in class I) in %s on line %d
Comment thread
NickSdot marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--TEST--
Readonly trait property default value mismatch
--FILE--
<?php

trait T1 {
public readonly int $prop = 1;
}

trait T2 {
public readonly int $prop = 2;
}

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
Loading
Loading