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
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,22 @@ Readonly class can extend a readonly class

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

readonly class Bar extends Foo
{
public int $prop = 2;
}

readonly class Baz extends Foo {}

var_dump(new Foo()->prop);
var_dump(new Bar()->prop);
var_dump(new Baz()->prop);

?>
--EXPECT--
int(1)
int(2)
int(1)
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(), "\n";
}
}
}

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

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

?>
--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,18 @@
--TEST--
Readonly class may use readonly trait property with default value
--FILE--
<?php

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

readonly class C {
use TDefault;
}

var_dump(new C()->prop);

?>
--EXPECT--
int(2)
30 changes: 30 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,24 @@ 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);

?>
--EXPECTF--
object(Foo)#%d (%d) {
Expand All @@ -37,3 +55,15 @@ 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)
}
16 changes: 16 additions & 0 deletions Zend/tests/readonly_props/readonly_modification.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ class Test {
}
}

class TestWithDefault {
public readonly int $prop = 1;

public function __construct() {
try {
$this->prop = 2;
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}
}
}

function byRef(&$ref) {}

$test = new Test;
Expand Down Expand Up @@ -66,6 +78,8 @@ try {
echo $e->getMessage(), "\n";
}

var_dump(new TestWithDefault()->prop);

?>
--EXPECT--
int(1)
Expand All @@ -80,3 +94,5 @@ array(0) {
}
Cannot indirectly modify readonly property Test::$prop2
Cannot indirectly modify readonly property Test::$prop2
Error: Cannot modify readonly property TestWithDefault::$prop
int(1)
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(), "\n";
}

?>
--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,33 @@
--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();
var_dump($test->default, $test->private, $test->protected, $test->public);

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

?>
--EXPECT--
int(1)
int(2)
int(3)
int(4)
Error: Cannot modify readonly property Test::$default
Error: Cannot modify readonly property Test::$private
Error: Cannot modify readonly property Test::$protected
Error: Cannot modify readonly property Test::$public
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)
29 changes: 29 additions & 0 deletions Zend/tests/readonly_props/readonly_with_default_interface.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
--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);

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

// does not satisfy set
class D implements J {
public readonly int $prop = 42;
}

?>
--EXPECTF--
int(42)

Fatal error: Set access level of D::$prop must be omitted (as in class J) in %s on line %d

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the test is testing what you want. $prop should be public(set) in D.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey Tim, thanks for the review!

It's essentially a copy of here:
https://github.com/php/php-src/blob/master/Zend/tests/property_hooks/interface_get_set_readonly.phpt#L11

The intention was to confirm that having a default value does not change anything. You probably think it was not what I wanted to test because of the irrelevant error message? If so please check the comment in the existing test file above -- known issue, it seems.

The case you mean should be tested here:
https://github.com/php/php-src/pull/22588/changes#diff-a5f6cb0065c3c8fe569119380cdb62cb4a1a7b9813fcf3bb97723894cb262667R10

--

Fine?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's essentially a copy of here:

I assume that test predated asymmetric visibility. It should probably be adjusted to include public(set) (or a second test added).

The case you mean should be tested here:

No. That test is not testing the behavior of the interaction between readonly and set; in an interface.

35 changes: 35 additions & 0 deletions Zend/tests/readonly_props/readonly_with_default_reflection.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
--TEST--
Reflection for readonly property with default value
--FILE--
<?php

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

$rp = new ReflectionProperty(Foo::class, 'prop');
var_dump($rp->isReadOnly());
var_dump($rp->hasDefaultValue());
var_dump($rp->getDefaultValue());
var_dump(new ReflectionClass(Foo::class)->getDefaultProperties());

$test = new Foo();
try {
$rp->setValue($test, 2);
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), "\n";
}

?>
--EXPECT--
bool(true)
bool(true)
int(1)
array(2) {
["prop"]=>
int(1)
["nullable"]=>
NULL
}
Error: Cannot modify readonly property Foo::$prop
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