mirror of
https://github.com/php/php-src.git
synced 2024-11-26 19:33:55 +08:00
[RFC] Property hooks (#13455)
RFC: https://wiki.php.net/rfc/property-hooks Co-authored-by: Nikita Popov <nikita.ppv@gmail.com>
This commit is contained in:
parent
09d61b6368
commit
780a8280d2
@ -215,6 +215,8 @@ PHP 8.4 UPGRADE NOTES
|
||||
RFC: https://wiki.php.net/rfc/new_without_parentheses
|
||||
. Added the #[\Deprecated] attribute.
|
||||
RFC: https://wiki.php.net/rfc/deprecated_attribute
|
||||
. Implemented property hooks.
|
||||
RFC: https://wiki.php.net/rfc/property-hooks
|
||||
|
||||
- Curl:
|
||||
. curl_version() returns an additional feature_list value, which is an
|
||||
|
@ -197,6 +197,9 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
|
||||
LITERAL_INFO(opline->op2.constant, 2);
|
||||
}
|
||||
break;
|
||||
case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL:
|
||||
LITERAL_INFO(opline->op1.constant, 1);
|
||||
break;
|
||||
case ZEND_CATCH:
|
||||
LITERAL_INFO(opline->op1.constant, 2);
|
||||
break;
|
||||
|
@ -48,6 +48,7 @@ static void zend_delete_call_instructions(zend_op_array *op_array, zend_op *opli
|
||||
case ZEND_INIT_STATIC_METHOD_CALL:
|
||||
case ZEND_INIT_METHOD_CALL:
|
||||
case ZEND_INIT_FCALL:
|
||||
case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL:
|
||||
if (call == 0) {
|
||||
MAKE_NOP(opline);
|
||||
return;
|
||||
@ -169,12 +170,15 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
|
||||
case ZEND_INIT_METHOD_CALL:
|
||||
case ZEND_INIT_FCALL:
|
||||
case ZEND_NEW:
|
||||
case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL:
|
||||
/* The argument passing optimizations are valid for prototypes as well,
|
||||
* as inheritance cannot change between ref <-> non-ref arguments. */
|
||||
call_stack[call].func = zend_optimizer_get_called_func(
|
||||
ctx->script, op_array, opline, &call_stack[call].is_prototype);
|
||||
call_stack[call].try_inline =
|
||||
!call_stack[call].is_prototype && opline->opcode != ZEND_NEW;
|
||||
!call_stack[call].is_prototype
|
||||
&& opline->opcode != ZEND_NEW
|
||||
&& opline->opcode != ZEND_INIT_PARENT_PROPERTY_HOOK_CALL;
|
||||
ZEND_FALLTHROUGH;
|
||||
case ZEND_INIT_DYNAMIC_CALL:
|
||||
case ZEND_INIT_USER_CALL:
|
||||
@ -212,6 +216,7 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
|
||||
}
|
||||
} else if (fcall->opcode == ZEND_INIT_STATIC_METHOD_CALL
|
||||
|| fcall->opcode == ZEND_INIT_METHOD_CALL
|
||||
|| fcall->opcode == ZEND_INIT_PARENT_PROPERTY_HOOK_CALL
|
||||
|| fcall->opcode == ZEND_NEW) {
|
||||
/* We don't have specialized opcodes for this, do nothing */
|
||||
} else {
|
||||
|
@ -61,6 +61,7 @@ ZEND_API void zend_analyze_calls(zend_arena **arena, zend_script *script, uint32
|
||||
case ZEND_INIT_FCALL:
|
||||
case ZEND_INIT_METHOD_CALL:
|
||||
case ZEND_INIT_STATIC_METHOD_CALL:
|
||||
case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL:
|
||||
call_stack[call] = call_info;
|
||||
func = zend_optimizer_get_called_func(
|
||||
script, op_array, opline, &is_prototype);
|
||||
|
@ -974,6 +974,28 @@ zend_function *zend_optimizer_get_called_func(
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: {
|
||||
zend_class_entry *scope = op_array->scope;
|
||||
ZEND_ASSERT(scope != NULL);
|
||||
if ((scope->ce_flags & ZEND_ACC_LINKED) && scope->parent) {
|
||||
zend_class_entry *parent_scope = scope->parent;
|
||||
zend_string *prop_name = Z_STR_P(CRT_CONSTANT(opline->op1));
|
||||
zend_property_hook_kind hook_kind = opline->op2.num;
|
||||
zend_property_info *prop_info = zend_get_property_info(parent_scope, prop_name, /* silent */ true);
|
||||
|
||||
if (prop_info
|
||||
&& prop_info != ZEND_WRONG_PROPERTY_INFO
|
||||
&& !(prop_info->flags & ZEND_ACC_PRIVATE)
|
||||
&& prop_info->hooks) {
|
||||
zend_function *fbc = prop_info->hooks[hook_kind];
|
||||
if (fbc) {
|
||||
*is_prototype = false;
|
||||
return fbc;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ZEND_NEW:
|
||||
{
|
||||
zend_class_entry *ce = zend_optimizer_get_class_entry_from_op1(
|
||||
@ -1531,6 +1553,19 @@ void zend_foreach_op_array(zend_script *script, zend_op_array_func_t func, void
|
||||
zend_foreach_op_array_helper(op_array, func, context);
|
||||
}
|
||||
} ZEND_HASH_FOREACH_END();
|
||||
|
||||
zend_property_info *property;
|
||||
ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, property) {
|
||||
zend_function **hooks = property->hooks;
|
||||
if (property->ce == ce && property->hooks) {
|
||||
for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) {
|
||||
zend_function *hook = hooks[i];
|
||||
if (hook && hook->common.scope == ce) {
|
||||
zend_foreach_op_array_helper(&hooks[i]->op_array, func, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
} ZEND_HASH_FOREACH_END();
|
||||
} ZEND_HASH_FOREACH_END();
|
||||
}
|
||||
|
||||
|
@ -6,4 +6,4 @@ const FOO_COMPILE_ERROR = "BAR"{0};
|
||||
var_dump(FOO_COMPILE_ERROR);
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Array and string offset access syntax with curly braces is no longer supported in %s on line 2
|
||||
Parse error: syntax error, unexpected token "{", expecting "," or ";" in %s on line %d
|
||||
|
@ -6,4 +6,4 @@ $foo = 'BAR';
|
||||
var_dump($foo{0});
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Array and string offset access syntax with curly braces is no longer supported in %s on line 3
|
||||
Parse error: syntax error, unexpected token "{", expecting ")" in %s on line %d
|
||||
|
@ -3,4 +3,4 @@ Alternative offset syntax should emit only E_COMPILE_ERROR in string interpolati
|
||||
--FILE--
|
||||
<?php "{$g{'h'}}"; ?>
|
||||
--EXPECTF--
|
||||
Fatal error: Array and string offset access syntax with curly braces is no longer supported in %s on line 1
|
||||
Parse error: syntax error, unexpected token "{", expecting "->" or "?->" or "[" in %s on line %d
|
||||
|
@ -10,4 +10,4 @@ class test {
|
||||
echo "Done\n";
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Cannot use the abstract modifier on a property in %s on line %d
|
||||
Fatal error: Only hooked properties may be declared abstract in %s on line %d
|
||||
|
@ -1,13 +0,0 @@
|
||||
--TEST--
|
||||
errmsg: properties cannot be final
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class test {
|
||||
final $var = 1;
|
||||
}
|
||||
|
||||
echo "Done\n";
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Cannot use the final modifier on a property in %s on line %d
|
@ -7,4 +7,4 @@ unset(new ArrayObject());
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Parse error: syntax error, unexpected token ")", expecting "->" or "?->" or "{" or "[" in %s on line %d
|
||||
Parse error: syntax error, unexpected token ")", expecting "->" or "?->" or "[" in %s on line %d
|
||||
|
22
Zend/tests/property_hooks/abstract_hook.phpt
Normal file
22
Zend/tests/property_hooks/abstract_hook.phpt
Normal file
@ -0,0 +1,22 @@
|
||||
--TEST--
|
||||
Abstract hooks compile successfully
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
abstract class A {
|
||||
public abstract $prop {
|
||||
get;
|
||||
set {}
|
||||
}
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
public $prop {
|
||||
get {}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
===DONE===
|
||||
--EXPECT--
|
||||
===DONE===
|
@ -0,0 +1,15 @@
|
||||
--TEST--
|
||||
Abstract hooks in non-abstract class gives an error
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public abstract $prop {
|
||||
get;
|
||||
set {}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Class Test contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Test::$prop::get) in %s on line %d
|
17
Zend/tests/property_hooks/abstract_hook_not_implemented.phpt
Normal file
17
Zend/tests/property_hooks/abstract_hook_not_implemented.phpt
Normal file
@ -0,0 +1,17 @@
|
||||
--TEST--
|
||||
Abstract hooks that are not implemented throw an error
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
abstract class A {
|
||||
public abstract $prop {
|
||||
get;
|
||||
set {}
|
||||
}
|
||||
}
|
||||
|
||||
class B extends A {}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Class B contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (A::$prop::get) in %s on line %d
|
17
Zend/tests/property_hooks/abstract_prop_hooks.phpt
Normal file
17
Zend/tests/property_hooks/abstract_prop_hooks.phpt
Normal file
@ -0,0 +1,17 @@
|
||||
--TEST--
|
||||
Explicit hooked property satisfies abstract property
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
abstract class A {
|
||||
abstract public $prop { get; set; }
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
public $prop { get {} set {} }
|
||||
}
|
||||
|
||||
?>
|
||||
===DONE===
|
||||
--EXPECT--
|
||||
===DONE===
|
14
Zend/tests/property_hooks/abstract_prop_not_implemented.phpt
Normal file
14
Zend/tests/property_hooks/abstract_prop_not_implemented.phpt
Normal file
@ -0,0 +1,14 @@
|
||||
--TEST--
|
||||
Abstract property not implemented throws an error
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A {
|
||||
abstract public $prop { get; set; }
|
||||
}
|
||||
|
||||
class B extends A {}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Class A contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (A::$prop::get, A::$prop::set) in %s on line %d
|
17
Zend/tests/property_hooks/abstract_prop_plain.phpt
Normal file
17
Zend/tests/property_hooks/abstract_prop_plain.phpt
Normal file
@ -0,0 +1,17 @@
|
||||
--TEST--
|
||||
Plain property satisfies abstract property
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
abstract class A {
|
||||
abstract public $prop { get; set; }
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
public $prop;
|
||||
}
|
||||
|
||||
?>
|
||||
===DONE===
|
||||
--EXPECT--
|
||||
===DONE===
|
12
Zend/tests/property_hooks/abstract_prop_without_hooks.phpt
Normal file
12
Zend/tests/property_hooks/abstract_prop_without_hooks.phpt
Normal file
@ -0,0 +1,12 @@
|
||||
--TEST--
|
||||
Abstract property without hook is illegal
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class C {
|
||||
abstract public $prop;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Only hooked properties may be declared abstract in %s on line %d
|
49
Zend/tests/property_hooks/array_access.phpt
Normal file
49
Zend/tests/property_hooks/array_access.phpt
Normal file
@ -0,0 +1,49 @@
|
||||
--TEST--
|
||||
Array offset on ArrayAccess object in virtual property is allowed
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Collection implements ArrayAccess {
|
||||
public function offsetExists(mixed $offset): bool {
|
||||
echo __METHOD__ . "\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
public function offsetGet(mixed $offset): mixed {
|
||||
echo __METHOD__ . "\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
public function offsetSet(mixed $offset, mixed $value): void {
|
||||
echo __METHOD__ . "\n";
|
||||
}
|
||||
|
||||
public function offsetUnset(mixed $offset): void {
|
||||
echo __METHOD__ . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
class C {
|
||||
public function __construct(
|
||||
public Collection $collection = new Collection(),
|
||||
) {}
|
||||
public $prop {
|
||||
get => $this->collection;
|
||||
}
|
||||
}
|
||||
|
||||
$c = new C();
|
||||
var_dump($c->prop['foo']);
|
||||
var_dump($c->prop[] = 'foo');
|
||||
var_dump(isset($c->prop['foo']));
|
||||
unset($c->prop['foo']);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Collection::offsetGet
|
||||
bool(true)
|
||||
Collection::offsetSet
|
||||
string(3) "foo"
|
||||
Collection::offsetExists
|
||||
bool(true)
|
||||
Collection::offsetUnset
|
45
Zend/tests/property_hooks/ast_printing.phpt
Normal file
45
Zend/tests/property_hooks/ast_printing.phpt
Normal file
@ -0,0 +1,45 @@
|
||||
--TEST--
|
||||
Hook AST printing
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
try {
|
||||
assert(false && new class {
|
||||
public $prop1 { get; set; }
|
||||
public $prop2 {
|
||||
get {
|
||||
return parent::$prop1::get();
|
||||
}
|
||||
final set {
|
||||
echo 'Foo';
|
||||
$this->prop1 = 42;
|
||||
}
|
||||
}
|
||||
public $prop3 = 1 {
|
||||
get => 42;
|
||||
}
|
||||
});
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
assert(false && new class {
|
||||
public $prop1 {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public $prop2 {
|
||||
get {
|
||||
return parent::$prop1::get();
|
||||
}
|
||||
final set {
|
||||
echo 'Foo';
|
||||
$this->prop1 = 42;
|
||||
}
|
||||
}
|
||||
public $prop3 = 1 {
|
||||
get => 42;
|
||||
}
|
||||
})
|
40
Zend/tests/property_hooks/attributes.phpt
Normal file
40
Zend/tests/property_hooks/attributes.phpt
Normal file
@ -0,0 +1,40 @@
|
||||
--TEST--
|
||||
Hooks accept method-targeted attributes
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
#[Attribute]
|
||||
class A {}
|
||||
|
||||
#[Attribute(Attribute::TARGET_METHOD)]
|
||||
class B {}
|
||||
|
||||
class C {
|
||||
public $prop {
|
||||
#[A] get {}
|
||||
#[B] set {}
|
||||
}
|
||||
}
|
||||
|
||||
$getAttr = (new ReflectionProperty(C::class, 'prop'))->getHook(PropertyHookType::Get)->getAttributes()[0];
|
||||
var_dump($getAttr->getName());
|
||||
var_dump($getAttr->getArguments());
|
||||
var_dump($getAttr->newInstance());
|
||||
|
||||
$setAttr = (new ReflectionProperty(C::class, 'prop'))->getHook(PropertyHookType::Set)->getAttributes()[0];
|
||||
var_dump($setAttr->getName());
|
||||
var_dump($setAttr->getArguments());
|
||||
var_dump($setAttr->newInstance());
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
string(1) "A"
|
||||
array(0) {
|
||||
}
|
||||
object(A)#3 (0) {
|
||||
}
|
||||
string(1) "B"
|
||||
array(0) {
|
||||
}
|
||||
object(B)#5 (0) {
|
||||
}
|
66
Zend/tests/property_hooks/backed_delegated_read_wirte.phpt
Normal file
66
Zend/tests/property_hooks/backed_delegated_read_wirte.phpt
Normal file
@ -0,0 +1,66 @@
|
||||
--TEST--
|
||||
Attempted read/write of backing value in a delegated method throws
|
||||
--SKIPIF--
|
||||
<?php
|
||||
if (!function_exists('zend_test_zend_call_stack_get')) die("skip zend_test_zend_call_stack_get() is not available");
|
||||
?>
|
||||
--EXTENSIONS--
|
||||
zend_test
|
||||
--INI--
|
||||
; The test may use a large amount of memory on systems with a large stack limit
|
||||
memory_limit=2G
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public $prop = 42 {
|
||||
get => $this->getProp($this->prop);
|
||||
set {
|
||||
$this->setProp($this->prop, $value);
|
||||
}
|
||||
}
|
||||
|
||||
private function getProp($prop) {
|
||||
return $this->prop;
|
||||
}
|
||||
|
||||
private function setProp($prop, $value) {
|
||||
$this->prop = $value;
|
||||
}
|
||||
|
||||
public $prop2 = 42 {
|
||||
get => $this->prop2;
|
||||
set => $value;
|
||||
}
|
||||
}
|
||||
|
||||
class Child extends Test {
|
||||
public $prop2 = 42 {
|
||||
get => parent::$prop2::get();
|
||||
set { parent::$prop2::set($value); }
|
||||
}
|
||||
}
|
||||
|
||||
$test = new Test;
|
||||
|
||||
try {
|
||||
$test->prop = 0;
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
try {
|
||||
var_dump($test->prop);
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
$child = new Child();
|
||||
$child->prop2 = 43;
|
||||
var_dump($child->prop2);
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion?
|
||||
Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached. Infinite recursion?
|
||||
int(43)
|
22
Zend/tests/property_hooks/backed_implicit_get.phpt
Normal file
22
Zend/tests/property_hooks/backed_implicit_get.phpt
Normal file
@ -0,0 +1,22 @@
|
||||
--TEST--
|
||||
Backed property with implicit get
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class C {
|
||||
public $prop {
|
||||
set {
|
||||
echo __METHOD__, "\n";
|
||||
$this->prop = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$c = new C();
|
||||
$c->prop = 42;
|
||||
var_dump($c->prop);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
C::$prop::set
|
||||
int(42)
|
22
Zend/tests/property_hooks/backed_implicit_set.phpt
Normal file
22
Zend/tests/property_hooks/backed_implicit_set.phpt
Normal file
@ -0,0 +1,22 @@
|
||||
--TEST--
|
||||
Backed property with implicit set
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class C {
|
||||
public $prop {
|
||||
get {
|
||||
echo __METHOD__, "\n";
|
||||
return $this->prop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$c = new C();
|
||||
$c->prop = 42;
|
||||
var_dump($c->prop);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
C::$prop::get
|
||||
int(42)
|
16
Zend/tests/property_hooks/backed_invariant.phpt
Normal file
16
Zend/tests/property_hooks/backed_invariant.phpt
Normal file
@ -0,0 +1,16 @@
|
||||
--TEST--
|
||||
Backed property is invariant
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A {
|
||||
public string|int $prop { get => $this->prop; }
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
public string $prop { get => 'foo'; }
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Type of B::$prop must be string|int (as in class A) in %s on line %d
|
45
Zend/tests/property_hooks/backing_value_simple.phpt
Normal file
45
Zend/tests/property_hooks/backing_value_simple.phpt
Normal file
@ -0,0 +1,45 @@
|
||||
--TEST--
|
||||
Reads and writes from backing store are only cached for $this
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class C {
|
||||
public $switch;
|
||||
|
||||
public $prop = 42 {
|
||||
get {
|
||||
echo __METHOD__, "\n";
|
||||
if ($this->switch) {
|
||||
$other = new C();
|
||||
$other->switch = false;
|
||||
} else {
|
||||
$other = $this;
|
||||
}
|
||||
var_dump($other->prop);
|
||||
return 1;
|
||||
}
|
||||
set => $this->prop;
|
||||
}
|
||||
}
|
||||
|
||||
function test() {
|
||||
$c = new C();
|
||||
$c->switch = true;
|
||||
var_dump($c->prop);
|
||||
}
|
||||
|
||||
test();
|
||||
test();
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
C::$prop::get
|
||||
C::$prop::get
|
||||
int(42)
|
||||
int(1)
|
||||
int(1)
|
||||
C::$prop::get
|
||||
C::$prop::get
|
||||
int(42)
|
||||
int(1)
|
||||
int(1)
|
30
Zend/tests/property_hooks/bug001.phpt
Normal file
30
Zend/tests/property_hooks/bug001.phpt
Normal file
@ -0,0 +1,30 @@
|
||||
--TEST--
|
||||
Bug 001
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
abstract class A {
|
||||
abstract public $x { get; }
|
||||
}
|
||||
|
||||
class C extends A {
|
||||
private $_x;
|
||||
public $x {
|
||||
get => $this->_x;
|
||||
}
|
||||
}
|
||||
|
||||
var_dump((new ReflectionProperty(C::class, 'x'))->isVirtual());
|
||||
|
||||
$c = new C;
|
||||
|
||||
try {
|
||||
$c->x = 3;
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
bool(true)
|
||||
Property C::$x is read-only
|
32
Zend/tests/property_hooks/bug002.phpt
Normal file
32
Zend/tests/property_hooks/bug002.phpt
Normal file
@ -0,0 +1,32 @@
|
||||
--TEST--
|
||||
Bug 002
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
trait Foo {
|
||||
public string $bar {
|
||||
get => $this->getBar();
|
||||
}
|
||||
|
||||
protected function getBar() {
|
||||
return 'bar';
|
||||
}
|
||||
}
|
||||
|
||||
class A {
|
||||
use Foo;
|
||||
}
|
||||
|
||||
class B {
|
||||
use Foo;
|
||||
}
|
||||
|
||||
$a = new A();
|
||||
$b = new B();
|
||||
var_dump($a->bar);
|
||||
var_dump($b->bar);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
string(3) "bar"
|
||||
string(3) "bar"
|
23
Zend/tests/property_hooks/bug003.phpt
Normal file
23
Zend/tests/property_hooks/bug003.phpt
Normal file
@ -0,0 +1,23 @@
|
||||
--TEST--
|
||||
Callable conversion of parent hook call
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class B {
|
||||
public mixed $x;
|
||||
}
|
||||
class C extends B {
|
||||
public mixed $x {
|
||||
set {
|
||||
$f = parent::$x::set(...);
|
||||
$f($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$c = new C();
|
||||
$c->x = 0;
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Cannot create Closure for parent property hook call in %s on line %d
|
16
Zend/tests/property_hooks/bug004.phpt
Normal file
16
Zend/tests/property_hooks/bug004.phpt
Normal file
@ -0,0 +1,16 @@
|
||||
--TEST--
|
||||
Set hook value param must not have a default value
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class B {
|
||||
public $x {
|
||||
set($value = null) {
|
||||
$field = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Parameter $value of set hook B::$x must not have a default value in %s on line %d
|
21
Zend/tests/property_hooks/bug005.phpt
Normal file
21
Zend/tests/property_hooks/bug005.phpt
Normal file
@ -0,0 +1,21 @@
|
||||
--TEST--
|
||||
Parent hook call restriction may fail due to mangled name
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class B {
|
||||
protected mixed $x;
|
||||
}
|
||||
|
||||
class C extends B {
|
||||
protected mixed $x {
|
||||
set {
|
||||
parent::$x::set(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
===DONE===
|
||||
--EXPECT--
|
||||
===DONE===
|
25
Zend/tests/property_hooks/bug006.phpt
Normal file
25
Zend/tests/property_hooks/bug006.phpt
Normal file
@ -0,0 +1,25 @@
|
||||
--TEST--
|
||||
Abstract properties correctly track virtualness
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
abstract class Y {
|
||||
abstract public string $prop {
|
||||
get;
|
||||
set => "foo";
|
||||
}
|
||||
}
|
||||
|
||||
class X extends Y {
|
||||
public string $prop {
|
||||
get => "bar";
|
||||
}
|
||||
}
|
||||
|
||||
$x = new X;
|
||||
$x->prop = 1;
|
||||
var_dump($x->prop);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
string(3) "bar"
|
25
Zend/tests/property_hooks/bug007.phpt
Normal file
25
Zend/tests/property_hooks/bug007.phpt
Normal file
@ -0,0 +1,25 @@
|
||||
--TEST--
|
||||
Assign by reference to backed property is forbidden for &get-only
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public $prop = 0 {
|
||||
&get {
|
||||
echo __METHOD__, "\n";
|
||||
return $this->prop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$test = new Test();
|
||||
$test->prop = &$ref;
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Test::$prop::get
|
||||
|
||||
Fatal error: Uncaught Error: Cannot assign by reference to overloaded object in %s:%d
|
||||
Stack trace:
|
||||
#0 {main}
|
||||
thrown in %s on line %d
|
24
Zend/tests/property_hooks/bug008.phpt
Normal file
24
Zend/tests/property_hooks/bug008.phpt
Normal file
@ -0,0 +1,24 @@
|
||||
--TEST--
|
||||
Assign by reference to backed property is forbidden for &get-only
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Foo {
|
||||
private $_bar;
|
||||
public $bar {
|
||||
&get {
|
||||
echo __METHOD__, PHP_EOL;
|
||||
return $this->_bar;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$foo = new Foo;
|
||||
$foo->bar = 'bar';
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Uncaught Error: Property Foo::$bar is read-only in %s:%d
|
||||
Stack trace:
|
||||
#0 {main}
|
||||
thrown in %s on line %d
|
28
Zend/tests/property_hooks/bug009.phpt
Normal file
28
Zend/tests/property_hooks/bug009.phpt
Normal file
@ -0,0 +1,28 @@
|
||||
--TEST--
|
||||
Assign by reference to backed property is allowed for &get-only
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Foo {
|
||||
public $bar = [] {
|
||||
&get {
|
||||
echo __METHOD__ . "\n";
|
||||
return $this->bar;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$foo = new Foo;
|
||||
$foo->bar[] = 'bar';
|
||||
var_dump($foo);
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Foo::$bar::get
|
||||
object(Foo)#%d (1) {
|
||||
["bar"]=>
|
||||
array(1) {
|
||||
[0]=>
|
||||
string(3) "bar"
|
||||
}
|
||||
}
|
64
Zend/tests/property_hooks/cache.phpt
Normal file
64
Zend/tests/property_hooks/cache.phpt
Normal file
@ -0,0 +1,64 @@
|
||||
--TEST--
|
||||
Test caching of hooked property
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public $prop {
|
||||
get { echo __METHOD__, "\n"; return $this->prop; }
|
||||
set { echo __METHOD__, "\n"; $this->prop = $value; }
|
||||
}
|
||||
}
|
||||
|
||||
function doTest(Test $test) {
|
||||
$test->prop = null;
|
||||
$test->prop;
|
||||
$test->prop = 1;
|
||||
$test->prop += 1;
|
||||
$test->prop = [];
|
||||
try {
|
||||
$test->prop[] = 1;
|
||||
} catch (\Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
isset($test->prop);
|
||||
isset($test->prop[0]);
|
||||
try {
|
||||
unset($test->prop);
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$test = new Test;
|
||||
$test->dyn = 1;
|
||||
doTest($test);
|
||||
echo "\n";
|
||||
doTest($test);
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Deprecated: Creation of dynamic property Test::$dyn is deprecated in %s on line %d
|
||||
Test::$prop::set
|
||||
Test::$prop::get
|
||||
Test::$prop::set
|
||||
Test::$prop::get
|
||||
Test::$prop::set
|
||||
Test::$prop::set
|
||||
Test::$prop::get
|
||||
Indirect modification of Test::$prop is not allowed
|
||||
Test::$prop::get
|
||||
Test::$prop::get
|
||||
Cannot unset hooked property Test::$prop
|
||||
|
||||
Test::$prop::set
|
||||
Test::$prop::get
|
||||
Test::$prop::set
|
||||
Test::$prop::get
|
||||
Test::$prop::set
|
||||
Test::$prop::set
|
||||
Test::$prop::get
|
||||
Indirect modification of Test::$prop is not allowed
|
||||
Test::$prop::get
|
||||
Test::$prop::get
|
||||
Cannot unset hooked property Test::$prop
|
34
Zend/tests/property_hooks/cpp.phpt
Normal file
34
Zend/tests/property_hooks/cpp.phpt
Normal file
@ -0,0 +1,34 @@
|
||||
--TEST--
|
||||
Constructor property promotion
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public function __construct(
|
||||
public $prop = 42 {
|
||||
get => print("Getting\n");
|
||||
set { print("Setting\n"); }
|
||||
}
|
||||
) {
|
||||
echo "Constructor\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo "Pre-test\n";
|
||||
$test = new Test;
|
||||
$test->prop;
|
||||
$test->prop = 42;
|
||||
|
||||
$r = (new ReflectionProperty(Test::class, 'prop'));
|
||||
var_dump($r->hasDefaultValue());
|
||||
var_dump($r->getDefaultValue());
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Pre-test
|
||||
Setting
|
||||
Constructor
|
||||
Getting
|
||||
Setting
|
||||
bool(false)
|
||||
NULL
|
35
Zend/tests/property_hooks/default_on_hooks.phpt
Normal file
35
Zend/tests/property_hooks/default_on_hooks.phpt
Normal file
@ -0,0 +1,35 @@
|
||||
--TEST--
|
||||
Backed property may have default value
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A {
|
||||
public $prop = 42 {
|
||||
get {
|
||||
echo __METHOD__, "\n";
|
||||
return $this->prop;
|
||||
}
|
||||
set {
|
||||
echo __METHOD__, "\n";
|
||||
$this->prop = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$a = new A();
|
||||
var_dump($a);
|
||||
var_dump($a->prop);
|
||||
$a->prop = 43;
|
||||
var_dump($a->prop);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
object(A)#1 (1) {
|
||||
["prop"]=>
|
||||
int(42)
|
||||
}
|
||||
A::$prop::get
|
||||
int(42)
|
||||
A::$prop::set
|
||||
A::$prop::get
|
||||
int(43)
|
15
Zend/tests/property_hooks/default_on_virtual.phpt
Normal file
15
Zend/tests/property_hooks/default_on_virtual.phpt
Normal file
@ -0,0 +1,15 @@
|
||||
--TEST--
|
||||
Virtual properties cannot have default value
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public $prop = 0 {
|
||||
get {}
|
||||
set {}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Cannot specify default value for virtual hooked property Test::$prop in %s on line %d
|
@ -0,0 +1,19 @@
|
||||
--TEST--
|
||||
Virtual property cannot have default value
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A {
|
||||
public $prop { get {} set {} }
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
public $prop = 0 {
|
||||
get {}
|
||||
set {}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Cannot specify default value for virtual hooked property B::$prop in %s on line %d
|
28
Zend/tests/property_hooks/direct_hook_call.phpt
Normal file
28
Zend/tests/property_hooks/direct_hook_call.phpt
Normal file
@ -0,0 +1,28 @@
|
||||
--TEST--
|
||||
Call property hooks by name
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public $prop {
|
||||
get { echo "get called\n"; }
|
||||
set { echo "set called with $value\n"; }
|
||||
}
|
||||
}
|
||||
|
||||
$test = new Test;
|
||||
try {
|
||||
$test->{'$prop::get'}();
|
||||
} catch (\Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
$test->{'$prop::set'}('foo');
|
||||
} catch (\Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Call to undefined method Test::$prop::get()
|
||||
Call to undefined method Test::$prop::set()
|
245
Zend/tests/property_hooks/dump.phpt
Normal file
245
Zend/tests/property_hooks/dump.phpt
Normal file
@ -0,0 +1,245 @@
|
||||
--TEST--
|
||||
Dumping object with property hooks
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public $addedHooks = 'addedHooks';
|
||||
public $virtual {
|
||||
get { return strtoupper('virtual'); }
|
||||
}
|
||||
public $backed = 'backed' {
|
||||
get { return strtoupper($this->backed); }
|
||||
set { $this->backed = $value; }
|
||||
}
|
||||
public $writeOnly {
|
||||
set {}
|
||||
}
|
||||
private $private = 'private' {
|
||||
get { return strtoupper($this->private); }
|
||||
set { $this->private = $value; }
|
||||
}
|
||||
private $changed = 'changed Test' {
|
||||
get { return strtoupper($this->changed); }
|
||||
}
|
||||
public function dumpTest() {
|
||||
var_dump($this);
|
||||
var_dump(get_object_vars($this));
|
||||
var_dump(get_mangled_object_vars($this));
|
||||
var_export($this);
|
||||
echo "\n";
|
||||
echo json_encode($this), "\n";
|
||||
var_dump((array) $this);
|
||||
}
|
||||
}
|
||||
|
||||
class Child extends Test {
|
||||
public $addedHooks {
|
||||
get { return strtoupper(parent::$addedHooks::get()); }
|
||||
}
|
||||
private $changed = 'changed Child' {
|
||||
get { return strtoupper($this->changed); }
|
||||
}
|
||||
public function dumpChild() {
|
||||
var_dump($this);
|
||||
var_dump(get_object_vars($this));
|
||||
var_export($this);
|
||||
echo "\n";
|
||||
echo json_encode($this), "\n";
|
||||
var_dump((array) $this);
|
||||
}
|
||||
}
|
||||
|
||||
function dump($test) {
|
||||
var_dump($test);
|
||||
var_dump(get_object_vars($test));
|
||||
var_export($test);
|
||||
echo "\n";
|
||||
echo json_encode($test), "\n";
|
||||
var_dump((array) $test);
|
||||
}
|
||||
|
||||
dump(new Test);
|
||||
dump(new Child);
|
||||
(new Child)->dumpTest();
|
||||
(new Child)->dumpChild();
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
object(Test)#%d (4) {
|
||||
["addedHooks"]=>
|
||||
string(10) "addedHooks"
|
||||
["backed"]=>
|
||||
string(6) "backed"
|
||||
["private":"Test":private]=>
|
||||
string(7) "private"
|
||||
["changed":"Test":private]=>
|
||||
string(12) "changed Test"
|
||||
}
|
||||
array(3) {
|
||||
["addedHooks"]=>
|
||||
string(10) "addedHooks"
|
||||
["virtual"]=>
|
||||
string(7) "VIRTUAL"
|
||||
["backed"]=>
|
||||
string(6) "BACKED"
|
||||
}
|
||||
\Test::__set_state(array(
|
||||
'addedHooks' => 'addedHooks',
|
||||
'virtual' => 'VIRTUAL',
|
||||
'backed' => 'BACKED',
|
||||
'private' => 'PRIVATE',
|
||||
'changed' => 'CHANGED TEST',
|
||||
))
|
||||
{"addedHooks":"addedHooks","virtual":"VIRTUAL","backed":"BACKED"}
|
||||
array(4) {
|
||||
["addedHooks"]=>
|
||||
string(10) "addedHooks"
|
||||
["backed"]=>
|
||||
string(6) "backed"
|
||||
["%0Test%0private"]=>
|
||||
string(7) "private"
|
||||
["%0Test%0changed"]=>
|
||||
string(12) "changed Test"
|
||||
}
|
||||
object(Child)#%d (5) {
|
||||
["addedHooks"]=>
|
||||
string(10) "addedHooks"
|
||||
["backed"]=>
|
||||
string(6) "backed"
|
||||
["private":"Test":private]=>
|
||||
string(7) "private"
|
||||
["changed":"Test":private]=>
|
||||
string(12) "changed Test"
|
||||
["changed":"Child":private]=>
|
||||
string(13) "changed Child"
|
||||
}
|
||||
array(3) {
|
||||
["addedHooks"]=>
|
||||
string(10) "ADDEDHOOKS"
|
||||
["virtual"]=>
|
||||
string(7) "VIRTUAL"
|
||||
["backed"]=>
|
||||
string(6) "BACKED"
|
||||
}
|
||||
\Child::__set_state(array(
|
||||
'addedHooks' => 'ADDEDHOOKS',
|
||||
'changed' => 'CHANGED CHILD',
|
||||
'virtual' => 'VIRTUAL',
|
||||
'backed' => 'BACKED',
|
||||
'private' => 'PRIVATE',
|
||||
'changed' => 'changed Child',
|
||||
))
|
||||
{"addedHooks":"ADDEDHOOKS","virtual":"VIRTUAL","backed":"BACKED"}
|
||||
array(5) {
|
||||
["addedHooks"]=>
|
||||
string(10) "addedHooks"
|
||||
["backed"]=>
|
||||
string(6) "backed"
|
||||
["%0Test%0private"]=>
|
||||
string(7) "private"
|
||||
["%0Test%0changed"]=>
|
||||
string(12) "changed Test"
|
||||
["%0Child%0changed"]=>
|
||||
string(13) "changed Child"
|
||||
}
|
||||
object(Child)#%d (5) {
|
||||
["addedHooks"]=>
|
||||
string(10) "addedHooks"
|
||||
["backed"]=>
|
||||
string(6) "backed"
|
||||
["private":"Test":private]=>
|
||||
string(7) "private"
|
||||
["changed":"Test":private]=>
|
||||
string(12) "changed Test"
|
||||
["changed":"Child":private]=>
|
||||
string(13) "changed Child"
|
||||
}
|
||||
array(4) {
|
||||
["addedHooks"]=>
|
||||
string(10) "ADDEDHOOKS"
|
||||
["virtual"]=>
|
||||
string(7) "VIRTUAL"
|
||||
["backed"]=>
|
||||
string(6) "BACKED"
|
||||
["private"]=>
|
||||
string(7) "PRIVATE"
|
||||
}
|
||||
array(5) {
|
||||
["addedHooks"]=>
|
||||
string(10) "addedHooks"
|
||||
["backed"]=>
|
||||
string(6) "backed"
|
||||
["%0Test%0private"]=>
|
||||
string(7) "private"
|
||||
["%0Test%0changed"]=>
|
||||
string(12) "changed Test"
|
||||
["%0Child%0changed"]=>
|
||||
string(13) "changed Child"
|
||||
}
|
||||
\Child::__set_state(array(
|
||||
'addedHooks' => 'ADDEDHOOKS',
|
||||
'changed' => 'CHANGED CHILD',
|
||||
'virtual' => 'VIRTUAL',
|
||||
'backed' => 'BACKED',
|
||||
'private' => 'PRIVATE',
|
||||
'changed' => 'changed Child',
|
||||
))
|
||||
{"addedHooks":"ADDEDHOOKS","virtual":"VIRTUAL","backed":"BACKED"}
|
||||
array(5) {
|
||||
["addedHooks"]=>
|
||||
string(10) "addedHooks"
|
||||
["backed"]=>
|
||||
string(6) "backed"
|
||||
["%0Test%0private"]=>
|
||||
string(7) "private"
|
||||
["%0Test%0changed"]=>
|
||||
string(12) "changed Test"
|
||||
["%0Child%0changed"]=>
|
||||
string(13) "changed Child"
|
||||
}
|
||||
object(Child)#%d (5) {
|
||||
["addedHooks"]=>
|
||||
string(10) "addedHooks"
|
||||
["backed"]=>
|
||||
string(6) "backed"
|
||||
["private":"Test":private]=>
|
||||
string(7) "private"
|
||||
["changed":"Test":private]=>
|
||||
string(12) "changed Test"
|
||||
["changed":"Child":private]=>
|
||||
string(13) "changed Child"
|
||||
}
|
||||
array(5) {
|
||||
["addedHooks"]=>
|
||||
string(10) "ADDEDHOOKS"
|
||||
["changed"]=>
|
||||
string(13) "CHANGED CHILD"
|
||||
["virtual"]=>
|
||||
string(7) "VIRTUAL"
|
||||
["backed"]=>
|
||||
string(6) "BACKED"
|
||||
["changed"]=>
|
||||
string(13) "changed Child"
|
||||
}
|
||||
\Child::__set_state(array(
|
||||
'addedHooks' => 'ADDEDHOOKS',
|
||||
'changed' => 'CHANGED CHILD',
|
||||
'virtual' => 'VIRTUAL',
|
||||
'backed' => 'BACKED',
|
||||
'private' => 'PRIVATE',
|
||||
'changed' => 'changed Child',
|
||||
))
|
||||
{"addedHooks":"ADDEDHOOKS","virtual":"VIRTUAL","backed":"BACKED"}
|
||||
array(5) {
|
||||
["addedHooks"]=>
|
||||
string(10) "addedHooks"
|
||||
["backed"]=>
|
||||
string(6) "backed"
|
||||
["%0Test%0private"]=>
|
||||
string(7) "private"
|
||||
["%0Test%0changed"]=>
|
||||
string(12) "changed Test"
|
||||
["%0Child%0changed"]=>
|
||||
string(13) "changed Child"
|
||||
}
|
15
Zend/tests/property_hooks/duplicate_hook.phpt
Normal file
15
Zend/tests/property_hooks/duplicate_hook.phpt
Normal file
@ -0,0 +1,15 @@
|
||||
--TEST--
|
||||
Cannot declare same property hook twice
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public $prop {
|
||||
get {}
|
||||
get {}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Cannot redeclare property hook "get" in %s on line %d
|
48
Zend/tests/property_hooks/explicit_iter.phpt
Normal file
48
Zend/tests/property_hooks/explicit_iter.phpt
Normal file
@ -0,0 +1,48 @@
|
||||
--TEST--
|
||||
Explicit iterator implementation
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Foo implements Iterator {
|
||||
public string $hook {
|
||||
get => 'this is not the correct value';
|
||||
}
|
||||
|
||||
private $x = ['foo', 'BAR'];
|
||||
private $cursor = 0;
|
||||
|
||||
public function current(): string { return $this->x[$this->cursor]; }
|
||||
public function key(): int { return $this->cursor; }
|
||||
public function next(): void { ++$this->cursor; }
|
||||
public function rewind(): void { $this->cursor = 0; }
|
||||
public function valid(): bool { return isset($this->x[$this->cursor]); }
|
||||
}
|
||||
|
||||
class Bar implements IteratorAggregate {
|
||||
public string $hook {
|
||||
get => 'this is not the correct value';
|
||||
}
|
||||
|
||||
public function getIterator(): Traversable {
|
||||
yield 1;
|
||||
yield 2;
|
||||
}
|
||||
}
|
||||
|
||||
var_dump(iterator_to_array(new Foo()));
|
||||
var_dump(iterator_to_array(new Bar()));
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
array(2) {
|
||||
[0]=>
|
||||
string(3) "foo"
|
||||
[1]=>
|
||||
string(3) "BAR"
|
||||
}
|
||||
array(2) {
|
||||
[0]=>
|
||||
int(1)
|
||||
[1]=>
|
||||
int(2)
|
||||
}
|
19
Zend/tests/property_hooks/explicit_set_value_parameter.phpt
Normal file
19
Zend/tests/property_hooks/explicit_set_value_parameter.phpt
Normal file
@ -0,0 +1,19 @@
|
||||
--TEST--
|
||||
Explicit set property hook $value parameter
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public $prop {
|
||||
set($customName) {
|
||||
var_dump($customName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$test = new Test();
|
||||
$test->prop = 42;
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
int(42)
|
@ -0,0 +1,28 @@
|
||||
--TEST--
|
||||
Explicit set property hook $value parameter
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public string $prop {
|
||||
set(string|array $prop) {
|
||||
$this->prop = is_array($prop) ? join(', ', $prop) : $prop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$test = new Test();
|
||||
var_dump($test->prop = 'prop');
|
||||
var_dump($test->prop = ['prop1', 'prop2']);
|
||||
var_dump($test->prop);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
string(4) "prop"
|
||||
array(2) {
|
||||
[0]=>
|
||||
string(5) "prop1"
|
||||
[1]=>
|
||||
string(5) "prop2"
|
||||
}
|
||||
string(12) "prop1, prop2"
|
35
Zend/tests/property_hooks/field_assign.phpt
Normal file
35
Zend/tests/property_hooks/field_assign.phpt
Normal file
@ -0,0 +1,35 @@
|
||||
--TEST--
|
||||
$field in different assignments
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public $prop {
|
||||
set {
|
||||
$field ??= 42;
|
||||
var_dump($field);
|
||||
$field += 1;
|
||||
var_dump($field);
|
||||
$field -= 2;
|
||||
var_dump($field);
|
||||
$field *= 3;
|
||||
var_dump($field);
|
||||
$field++;
|
||||
var_dump($field);
|
||||
--$field;
|
||||
var_dump($field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$test = new Test;
|
||||
$test->prop = null;
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
int(42)
|
||||
int(43)
|
||||
int(41)
|
||||
int(123)
|
||||
int(124)
|
||||
int(123)
|
27
Zend/tests/property_hooks/field_guard.phpt
Normal file
27
Zend/tests/property_hooks/field_guard.phpt
Normal file
@ -0,0 +1,27 @@
|
||||
--TEST--
|
||||
$this->prop refers to backing store from either hook
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public $prop {
|
||||
get {
|
||||
echo "get\n";
|
||||
$this->prop = 'prop';
|
||||
}
|
||||
set {
|
||||
echo "set\n";
|
||||
var_dump($this->prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$test = new Test;
|
||||
$test->prop;
|
||||
$test->prop = 42;
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
get
|
||||
set
|
||||
string(4) "prop"
|
20
Zend/tests/property_hooks/final.phpt
Normal file
20
Zend/tests/property_hooks/final.phpt
Normal file
@ -0,0 +1,20 @@
|
||||
--TEST--
|
||||
Final hooks
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A {
|
||||
public $prop {
|
||||
final get { return 42; }
|
||||
}
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
public $prop {
|
||||
get { return 24; }
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Cannot override final property hook A::$prop::get() in %s on line %d
|
12
Zend/tests/property_hooks/final_private_prop.phpt
Normal file
12
Zend/tests/property_hooks/final_private_prop.phpt
Normal file
@ -0,0 +1,12 @@
|
||||
--TEST--
|
||||
Property cannot be both final and private
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
final private $prop;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Property cannot be both final and private in %s on line %d
|
16
Zend/tests/property_hooks/final_prop.phpt
Normal file
16
Zend/tests/property_hooks/final_prop.phpt
Normal file
@ -0,0 +1,16 @@
|
||||
--TEST--
|
||||
Property itself may be marked final (hook)
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A {
|
||||
public final $prop { get {} set {} }
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
public $prop { get {} set {} }
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Cannot override final property A::$prop in %s on line %d
|
16
Zend/tests/property_hooks/final_prop_2.phpt
Normal file
16
Zend/tests/property_hooks/final_prop_2.phpt
Normal file
@ -0,0 +1,16 @@
|
||||
--TEST--
|
||||
Property itself may be marked final (normal)
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A {
|
||||
public final $prop;
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
public $prop { get {} set {} }
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Cannot override final property A::$prop in %s on line %d
|
15
Zend/tests/property_hooks/final_prop_final_hook.phpt
Normal file
15
Zend/tests/property_hooks/final_prop_final_hook.phpt
Normal file
@ -0,0 +1,15 @@
|
||||
--TEST--
|
||||
Cannot make hook explicitly final in final property
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
final public $prop {
|
||||
final get => $field;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
===DONE===
|
||||
--EXPECT--
|
||||
===DONE===
|
28
Zend/tests/property_hooks/find_property_usage.phpt
Normal file
28
Zend/tests/property_hooks/find_property_usage.phpt
Normal file
@ -0,0 +1,28 @@
|
||||
--TEST--
|
||||
Usage of property inside hook adds backing store
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public $prop1 { get => $this->prop1; }
|
||||
public $prop2 { get => fn () => $this->prop2; }
|
||||
public $prop3 { get => function () { return $this->prop3; }; }
|
||||
public $prop4 { get => $this->prop1; }
|
||||
public $prop5 { get {} }
|
||||
public $prop6 { get { var_dump($this->prop6); } }
|
||||
public $prop7 { get => new class { function test() { $this->prop7; } }; }
|
||||
}
|
||||
|
||||
foreach ((new ReflectionClass(Test::class))->getProperties() as $prop) {
|
||||
var_dump($prop->isVirtual());
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
bool(false)
|
||||
bool(true)
|
||||
bool(true)
|
||||
bool(true)
|
||||
bool(true)
|
||||
bool(false)
|
||||
bool(true)
|
136
Zend/tests/property_hooks/foreach.phpt
Normal file
136
Zend/tests/property_hooks/foreach.phpt
Normal file
@ -0,0 +1,136 @@
|
||||
--TEST--
|
||||
foreach over hooked properties
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
#[AllowDynamicProperties]
|
||||
class ByRef {
|
||||
public $plain = 'plain';
|
||||
private $_virtualByRef = 'virtualByRef';
|
||||
public $virtualByRef {
|
||||
&get {
|
||||
echo __METHOD__, "\n";
|
||||
return $this->_virtualByRef;
|
||||
}
|
||||
set {
|
||||
echo __METHOD__, "\n";
|
||||
$this->_virtualByRef = $value;
|
||||
}
|
||||
}
|
||||
public function __construct() {
|
||||
$this->dynamic = 'dynamic';
|
||||
}
|
||||
}
|
||||
|
||||
#[AllowDynamicProperties]
|
||||
class ByVal extends ByRef {
|
||||
private $_virtualByVal = 'virtualByVal';
|
||||
public $virtualByVal {
|
||||
get {
|
||||
echo __METHOD__, "\n";
|
||||
return $this->_virtualByVal;
|
||||
}
|
||||
set {
|
||||
echo __METHOD__, "\n";
|
||||
$this->_virtualByVal = $value;
|
||||
}
|
||||
}
|
||||
public $backed = 'backed' {
|
||||
get {
|
||||
echo __METHOD__, "\n";
|
||||
return $this->backed;
|
||||
}
|
||||
set {
|
||||
echo __METHOD__, "\n";
|
||||
$this->backed = $value;
|
||||
}
|
||||
}
|
||||
public string $backedUninitialized {
|
||||
get {
|
||||
echo __METHOD__, "\n";
|
||||
$this->backedUninitialized ??= 'backedUninitialized';
|
||||
return $this->backedUninitialized;
|
||||
}
|
||||
set {
|
||||
echo __METHOD__, "\n";
|
||||
$this->backedUninitialized = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function testByRef($object) {
|
||||
foreach ($object as $prop => &$value) {
|
||||
echo "$prop => $value\n";
|
||||
$value = strtoupper($value);
|
||||
}
|
||||
unset($value);
|
||||
var_dump($object);
|
||||
}
|
||||
|
||||
function testByVal($object) {
|
||||
foreach ($object as $prop => $value) {
|
||||
echo "$prop => $value\n";
|
||||
$object->{$prop} = strtoupper($value);
|
||||
}
|
||||
var_dump($object);
|
||||
}
|
||||
|
||||
testByVal(new ByVal);
|
||||
testByVal(new ByRef);
|
||||
testByRef(new ByRef);
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
ByVal::$virtualByVal::get
|
||||
virtualByVal => virtualByVal
|
||||
ByVal::$virtualByVal::set
|
||||
ByVal::$backed::get
|
||||
backed => backed
|
||||
ByVal::$backed::set
|
||||
ByVal::$backedUninitialized::get
|
||||
backedUninitialized => backedUninitialized
|
||||
ByVal::$backedUninitialized::set
|
||||
plain => plain
|
||||
ByRef::$virtualByRef::get
|
||||
virtualByRef => virtualByRef
|
||||
ByRef::$virtualByRef::set
|
||||
dynamic => dynamic
|
||||
object(ByVal)#%d (6) {
|
||||
["plain"]=>
|
||||
string(5) "PLAIN"
|
||||
["_virtualByRef":"ByRef":private]=>
|
||||
string(12) "VIRTUALBYREF"
|
||||
["_virtualByVal":"ByVal":private]=>
|
||||
string(12) "VIRTUALBYVAL"
|
||||
["backed"]=>
|
||||
string(6) "BACKED"
|
||||
["backedUninitialized"]=>
|
||||
string(19) "BACKEDUNINITIALIZED"
|
||||
["dynamic"]=>
|
||||
string(7) "DYNAMIC"
|
||||
}
|
||||
plain => plain
|
||||
ByRef::$virtualByRef::get
|
||||
virtualByRef => virtualByRef
|
||||
ByRef::$virtualByRef::set
|
||||
dynamic => dynamic
|
||||
object(ByRef)#%d (3) {
|
||||
["plain"]=>
|
||||
string(5) "PLAIN"
|
||||
["_virtualByRef":"ByRef":private]=>
|
||||
string(12) "VIRTUALBYREF"
|
||||
["dynamic"]=>
|
||||
string(7) "DYNAMIC"
|
||||
}
|
||||
plain => plain
|
||||
ByRef::$virtualByRef::get
|
||||
virtualByRef => virtualByRef
|
||||
dynamic => dynamic
|
||||
object(ByRef)#%d (3) {
|
||||
["plain"]=>
|
||||
string(5) "PLAIN"
|
||||
["_virtualByRef":"ByRef":private]=>
|
||||
string(12) "VIRTUALBYREF"
|
||||
["dynamic"]=>
|
||||
string(7) "DYNAMIC"
|
||||
}
|
23
Zend/tests/property_hooks/foreach_002.phpt
Normal file
23
Zend/tests/property_hooks/foreach_002.phpt
Normal file
@ -0,0 +1,23 @@
|
||||
--TEST--
|
||||
foreach over hooked properties
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A extends stdClass {
|
||||
public $foo {
|
||||
&get => $this->foo;
|
||||
}
|
||||
}
|
||||
|
||||
$a = new A;
|
||||
foreach ($a as $k => &$v) {
|
||||
if ($k == "foo") {
|
||||
$a->bar = "baz";
|
||||
}
|
||||
var_dump($k);
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
string(3) "foo"
|
||||
string(3) "bar"
|
34
Zend/tests/property_hooks/foreach_val_to_ref.phpt
Normal file
34
Zend/tests/property_hooks/foreach_val_to_ref.phpt
Normal file
@ -0,0 +1,34 @@
|
||||
--TEST--
|
||||
foreach by-ref on object with by-val hooked property
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class ByVal {
|
||||
public $byRef {
|
||||
&get {
|
||||
$x = 42;
|
||||
return $x;
|
||||
}
|
||||
}
|
||||
public $byVal = 'byValue' {
|
||||
get => $this->byVal;
|
||||
set => $value;
|
||||
}
|
||||
}
|
||||
|
||||
function test($object) {
|
||||
foreach ($object as $prop => &$value) {
|
||||
var_dump($value);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
test(new ByVal);
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
int(42)
|
||||
Cannot create reference to property ByVal::$byVal
|
45
Zend/tests/property_hooks/generator_hook.phpt
Normal file
45
Zend/tests/property_hooks/generator_hook.phpt
Normal file
@ -0,0 +1,45 @@
|
||||
--TEST--
|
||||
Generator hook
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A {
|
||||
public $backed = 2 {
|
||||
get {
|
||||
yield 1;
|
||||
yield $this->backed;
|
||||
yield 3;
|
||||
}
|
||||
}
|
||||
|
||||
public $virtual {
|
||||
get {
|
||||
yield 1;
|
||||
yield 2;
|
||||
yield 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$a = new A();
|
||||
var_dump(iterator_to_array($a->backed));
|
||||
var_dump(iterator_to_array($a->virtual));
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
array(3) {
|
||||
[0]=>
|
||||
int(1)
|
||||
[1]=>
|
||||
int(2)
|
||||
[2]=>
|
||||
int(3)
|
||||
}
|
||||
array(3) {
|
||||
[0]=>
|
||||
int(1)
|
||||
[1]=>
|
||||
int(2)
|
||||
[2]=>
|
||||
int(3)
|
||||
}
|
29
Zend/tests/property_hooks/generator_hook_002.phpt
Normal file
29
Zend/tests/property_hooks/generator_hook_002.phpt
Normal file
@ -0,0 +1,29 @@
|
||||
--TEST--
|
||||
Allow calling parent get in generator property hooks
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A {
|
||||
public $prop = 42;
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
public $prop {
|
||||
get {
|
||||
yield parent::$prop::get() + 1;
|
||||
yield parent::$prop::get() + 2;
|
||||
yield parent::$prop::get() + 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$b = new B();
|
||||
foreach ($b->prop as $value) {
|
||||
var_dump($value);
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
int(43)
|
||||
int(44)
|
||||
int(45)
|
24
Zend/tests/property_hooks/get.phpt
Normal file
24
Zend/tests/property_hooks/get.phpt
Normal file
@ -0,0 +1,24 @@
|
||||
--TEST--
|
||||
Basic get only property hook
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public $prop {
|
||||
get { return 42; }
|
||||
}
|
||||
}
|
||||
|
||||
$test = new Test;
|
||||
var_dump($test->prop);
|
||||
|
||||
try {
|
||||
$test->prop = 0;
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
int(42)
|
||||
Property Test::$prop is read-only
|
34
Zend/tests/property_hooks/get_by_ref.phpt
Normal file
34
Zend/tests/property_hooks/get_by_ref.phpt
Normal file
@ -0,0 +1,34 @@
|
||||
--TEST--
|
||||
Get property hook by ref and indirect modification
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public $byVal {
|
||||
get { return $this->byVal; }
|
||||
set { $this->byVal = $value; }
|
||||
}
|
||||
}
|
||||
|
||||
$test = new Test;
|
||||
|
||||
try {
|
||||
$test->byVal = [];
|
||||
$test->byVal[] = 42;
|
||||
} catch (\Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
var_dump($test->byVal);
|
||||
|
||||
try {
|
||||
$test->byVal =& $ref;
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Indirect modification of Test::$byVal is not allowed
|
||||
array(0) {
|
||||
}
|
||||
Cannot assign by reference to overloaded object
|
30
Zend/tests/property_hooks/get_by_ref_auto.phpt
Normal file
30
Zend/tests/property_hooks/get_by_ref_auto.phpt
Normal file
@ -0,0 +1,30 @@
|
||||
--TEST--
|
||||
Get by reference with hooked property
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public $byVal { get { return []; } }
|
||||
}
|
||||
|
||||
$test = new Test;
|
||||
|
||||
try {
|
||||
$test->byVal[] = 42;
|
||||
} catch (\Error $e) {
|
||||
echo get_class($e) . ': ' . $e->getMessage() . "\n";
|
||||
}
|
||||
var_dump($test->byVal);
|
||||
|
||||
try {
|
||||
$test->byVal =& $ref;
|
||||
} catch (Error $e) {
|
||||
echo get_class($e) . ': ' . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Error: Indirect modification of Test::$byVal is not allowed
|
||||
array(0) {
|
||||
}
|
||||
Error: Cannot assign by reference to overloaded object
|
20
Zend/tests/property_hooks/get_by_ref_backed.phpt
Normal file
20
Zend/tests/property_hooks/get_by_ref_backed.phpt
Normal file
@ -0,0 +1,20 @@
|
||||
--TEST--
|
||||
Virtual get hook allows returning by reference
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A {
|
||||
public $prop;
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
private $_prop;
|
||||
public $prop {
|
||||
&get => $this->_prop;
|
||||
set { $this->_prop = $value; }
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Get hook of backed property B::prop with set hook may not return by reference in %s on line %d
|
@ -0,0 +1,28 @@
|
||||
--TEST--
|
||||
By-reference get may be implemented as plain
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface I {
|
||||
public $prop { &get; }
|
||||
}
|
||||
|
||||
class A implements I {
|
||||
public $prop;
|
||||
}
|
||||
|
||||
function test(I $i) {
|
||||
$ref = &$i->prop;
|
||||
$ref = 42;
|
||||
}
|
||||
|
||||
$a = new A();
|
||||
test($a);
|
||||
var_dump($a);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
object(A)#1 (1) {
|
||||
["prop"]=>
|
||||
int(42)
|
||||
}
|
18
Zend/tests/property_hooks/get_by_ref_implemented_by_val.phpt
Normal file
18
Zend/tests/property_hooks/get_by_ref_implemented_by_val.phpt
Normal file
@ -0,0 +1,18 @@
|
||||
--TEST--
|
||||
By-reference get may be not implemented as by-value
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface I {
|
||||
public $prop { &get; }
|
||||
}
|
||||
|
||||
class A implements I {
|
||||
public $prop {
|
||||
get => $this->prop;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Declaration of A::$prop::get() must be compatible with & I::$prop::get() in %s on line %d
|
42
Zend/tests/property_hooks/get_by_ref_virtual.phpt
Normal file
42
Zend/tests/property_hooks/get_by_ref_virtual.phpt
Normal file
@ -0,0 +1,42 @@
|
||||
--TEST--
|
||||
Virtual get hook allows returning by reference
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
private $_prop;
|
||||
public $prop {
|
||||
&get => $this->_prop;
|
||||
set { $this->_prop = $value; }
|
||||
}
|
||||
}
|
||||
|
||||
function inc(&$ref) {
|
||||
$ref++;
|
||||
}
|
||||
|
||||
$test = new Test();
|
||||
$test->prop = 42;
|
||||
|
||||
$prop = &$test->prop;
|
||||
$prop++;
|
||||
var_dump($test);
|
||||
var_dump($test->prop);
|
||||
unset($prop);
|
||||
|
||||
inc($test->prop);
|
||||
var_dump($test);
|
||||
var_dump($test->prop);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
object(Test)#1 (1) {
|
||||
["_prop":"Test":private]=>
|
||||
&int(43)
|
||||
}
|
||||
int(43)
|
||||
object(Test)#1 (1) {
|
||||
["_prop":"Test":private]=>
|
||||
int(44)
|
||||
}
|
||||
int(44)
|
26
Zend/tests/property_hooks/get_type_check.phpt
Normal file
26
Zend/tests/property_hooks/get_type_check.phpt
Normal file
@ -0,0 +1,26 @@
|
||||
--TEST--
|
||||
Get property hook must respect property type
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public int $prop1 {
|
||||
get { return "foobar"; }
|
||||
}
|
||||
public int $prop2 {
|
||||
get { return "42"; }
|
||||
}
|
||||
}
|
||||
|
||||
$test = new Test;
|
||||
try {
|
||||
var_dump($test->prop1);
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
var_dump($test->prop2);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Test::$prop1::get(): Return value must be of type int, string returned
|
||||
int(42)
|
28
Zend/tests/property_hooks/hooked_with_magic_method.phpt
Normal file
28
Zend/tests/property_hooks/hooked_with_magic_method.phpt
Normal file
@ -0,0 +1,28 @@
|
||||
--TEST--
|
||||
Access hooked property from magic method
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
private $prop {
|
||||
get { echo __METHOD__, "\n"; return 42; }
|
||||
set { echo __METHOD__, "\n"; }
|
||||
}
|
||||
|
||||
public function __get($name) {
|
||||
return $this->{$name};
|
||||
}
|
||||
|
||||
public function __set($name, $value) {
|
||||
$this->{$name} = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$test = new Test;
|
||||
$test->prop;
|
||||
$test->prop = 42;
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Test::$prop::get
|
||||
Test::$prop::set
|
51
Zend/tests/property_hooks/indirect_modification.phpt
Normal file
51
Zend/tests/property_hooks/indirect_modification.phpt
Normal file
@ -0,0 +1,51 @@
|
||||
--TEST--
|
||||
Different kinds of indirect modification with by-val and by-ref getters
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
|
||||
class Test {
|
||||
public $byVal {
|
||||
set {
|
||||
echo __METHOD__, "\n";
|
||||
$this->byVal = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$test = new Test;
|
||||
|
||||
$test->byVal = 0;
|
||||
$test->byVal++;
|
||||
++$test->byVal;
|
||||
$test->byVal += 1;
|
||||
var_dump($test->byVal);
|
||||
$test->byVal = [];
|
||||
try {
|
||||
$test->byVal[] = 1;
|
||||
} catch (\Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
var_dump($test->byVal);
|
||||
try {
|
||||
$ref =& $test->byVal;
|
||||
} catch (\Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
$ref = 42;
|
||||
var_dump($test->byVal);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Test::$byVal::set
|
||||
Test::$byVal::set
|
||||
Test::$byVal::set
|
||||
Test::$byVal::set
|
||||
int(3)
|
||||
Test::$byVal::set
|
||||
Indirect modification of Test::$byVal is not allowed
|
||||
array(0) {
|
||||
}
|
||||
Indirect modification of Test::$byVal is not allowed
|
||||
array(0) {
|
||||
}
|
32
Zend/tests/property_hooks/inheritance.phpt
Normal file
32
Zend/tests/property_hooks/inheritance.phpt
Normal file
@ -0,0 +1,32 @@
|
||||
--TEST--
|
||||
Basic property hook inheritance
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A {
|
||||
public $prop {
|
||||
get { return "A"; }
|
||||
set { echo __METHOD__, "\n"; }
|
||||
}
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
public $prop {
|
||||
get { return "B"; }
|
||||
}
|
||||
}
|
||||
|
||||
$a = new A;
|
||||
var_dump($a->prop);
|
||||
$a->prop = 1;
|
||||
|
||||
$b = new B;
|
||||
var_dump($b->prop);
|
||||
$b->prop = 1;
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
string(1) "A"
|
||||
A::$prop::set
|
||||
string(1) "B"
|
||||
A::$prop::set
|
15
Zend/tests/property_hooks/interface.phpt
Normal file
15
Zend/tests/property_hooks/interface.phpt
Normal file
@ -0,0 +1,15 @@
|
||||
--TEST--
|
||||
Property hooks in interfaces
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface I {
|
||||
public $prop { get; set; }
|
||||
}
|
||||
|
||||
class C implements I {
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Class C contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (I::$prop::get, I::$prop::set) in %s on line %d
|
12
Zend/tests/property_hooks/interface_explicit_abstract.phpt
Normal file
12
Zend/tests/property_hooks/interface_explicit_abstract.phpt
Normal file
@ -0,0 +1,12 @@
|
||||
--TEST--
|
||||
Cannot have explicitly abstract property in interface
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface I {
|
||||
abstract public $prop { get; set; }
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Property in interface cannot be explicitly abstract. All interface members are implicitly abstract in %s on line %d
|
10
Zend/tests/property_hooks/interface_final_hook.phpt
Normal file
10
Zend/tests/property_hooks/interface_final_hook.phpt
Normal file
@ -0,0 +1,10 @@
|
||||
--TEST--
|
||||
Cannot declare final hook in interface
|
||||
--FILE--
|
||||
<?php
|
||||
interface I {
|
||||
public $prop { final get; }
|
||||
}
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Property hook cannot be both abstract and final in %s on line %d
|
10
Zend/tests/property_hooks/interface_final_prop.phpt
Normal file
10
Zend/tests/property_hooks/interface_final_prop.phpt
Normal file
@ -0,0 +1,10 @@
|
||||
--TEST--
|
||||
Cannot declare final property in interface
|
||||
--FILE--
|
||||
<?php
|
||||
interface I {
|
||||
final public $prop { get; set; }
|
||||
}
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Property in interface cannot be final in %s on line %d
|
24
Zend/tests/property_hooks/interface_get_by_ref_backed.phpt
Normal file
24
Zend/tests/property_hooks/interface_get_by_ref_backed.phpt
Normal file
@ -0,0 +1,24 @@
|
||||
--TEST--
|
||||
Backed prop satisfies interface get hook by-reference
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface I {
|
||||
public $prop { get; }
|
||||
}
|
||||
|
||||
class A implements I {
|
||||
public $prop = 42 {
|
||||
get => $this->prop;
|
||||
}
|
||||
}
|
||||
|
||||
$a = new A();
|
||||
var_dump($a);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
object(A)#1 (1) {
|
||||
["prop"]=>
|
||||
int(42)
|
||||
}
|
24
Zend/tests/property_hooks/interface_get_by_ref_plain.phpt
Normal file
24
Zend/tests/property_hooks/interface_get_by_ref_plain.phpt
Normal file
@ -0,0 +1,24 @@
|
||||
--TEST--
|
||||
Plain prop satisfies interface get hook by-reference
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface I {
|
||||
public $prop { get; }
|
||||
}
|
||||
|
||||
class A implements I {
|
||||
public $prop = 42 {
|
||||
get => $this->prop;
|
||||
}
|
||||
}
|
||||
|
||||
$a = new A();
|
||||
var_dump($a);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
object(A)#1 (1) {
|
||||
["prop"]=>
|
||||
int(42)
|
||||
}
|
31
Zend/tests/property_hooks/interface_get_by_ref_virtual.phpt
Normal file
31
Zend/tests/property_hooks/interface_get_by_ref_virtual.phpt
Normal file
@ -0,0 +1,31 @@
|
||||
--TEST--
|
||||
Virtual prop satisfies interface get hook by-reference
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface I {
|
||||
public $prop { &get; }
|
||||
}
|
||||
|
||||
class A implements I {
|
||||
private $_prop;
|
||||
public $prop {
|
||||
&get => $this->_prop;
|
||||
}
|
||||
}
|
||||
|
||||
function test(I $i) {
|
||||
$ref = &$i->prop;
|
||||
$ref = 42;
|
||||
}
|
||||
|
||||
$a = new A();
|
||||
test($a);
|
||||
var_dump($a);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
object(A)#1 (1) {
|
||||
["_prop":"A":private]=>
|
||||
int(42)
|
||||
}
|
15
Zend/tests/property_hooks/interface_get_only.phpt
Normal file
15
Zend/tests/property_hooks/interface_get_only.phpt
Normal file
@ -0,0 +1,15 @@
|
||||
--TEST--
|
||||
Interface may contain only set with no implementation
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface I {
|
||||
public $prop { set; }
|
||||
}
|
||||
|
||||
class A implements I {
|
||||
public $prop;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
15
Zend/tests/property_hooks/interface_get_only_readonly.phpt
Normal file
15
Zend/tests/property_hooks/interface_get_only_readonly.phpt
Normal file
@ -0,0 +1,15 @@
|
||||
--TEST--
|
||||
readonly property satisfies get only interface property
|
||||
--FILE--
|
||||
<?php
|
||||
interface I {
|
||||
public int $prop { get; }
|
||||
}
|
||||
class C implements I {
|
||||
public function __construct(public readonly int $prop) {}
|
||||
}
|
||||
$c = new C(42);
|
||||
var_dump($c->prop);
|
||||
?>
|
||||
--EXPECT--
|
||||
int(42)
|
13
Zend/tests/property_hooks/interface_get_set_readonly.phpt
Normal file
13
Zend/tests/property_hooks/interface_get_set_readonly.phpt
Normal file
@ -0,0 +1,13 @@
|
||||
--TEST--
|
||||
readonly property does not satisfy get/set interface property
|
||||
--FILE--
|
||||
<?php
|
||||
interface I {
|
||||
public int $prop { get; set; }
|
||||
}
|
||||
class C implements I {
|
||||
public function __construct(public readonly int $prop) {}
|
||||
}
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Readonly property C::$prop does not satisfy abstract read-write property I::$prop in %s on line %d
|
31
Zend/tests/property_hooks/interface_get_value_as_ref.phpt
Normal file
31
Zend/tests/property_hooks/interface_get_value_as_ref.phpt
Normal file
@ -0,0 +1,31 @@
|
||||
--TEST--
|
||||
By-value get may be implemented as by-reference
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface I {
|
||||
public $prop { get; }
|
||||
}
|
||||
|
||||
class A implements I {
|
||||
private $_prop;
|
||||
public $prop {
|
||||
&get => $this->_prop;
|
||||
}
|
||||
}
|
||||
|
||||
function test(I $i) {
|
||||
$ref = &$i->prop;
|
||||
$ref = 42;
|
||||
}
|
||||
|
||||
$a = new A();
|
||||
test($a);
|
||||
var_dump($a);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
object(A)#1 (1) {
|
||||
["_prop":"A":private]=>
|
||||
int(42)
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
--TEST--
|
||||
Property hooks in interfaces cannot be explicitly abstract
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface I {
|
||||
public abstract $prop { get; }
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Property in interface cannot be explicitly abstract. All interface members are implicitly abstract in %s on line %d
|
15
Zend/tests/property_hooks/interface_not_implemented.phpt
Normal file
15
Zend/tests/property_hooks/interface_not_implemented.phpt
Normal file
@ -0,0 +1,15 @@
|
||||
--TEST--
|
||||
Property hook in interfaces not implemented
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface I {
|
||||
public $prop { get; set; }
|
||||
}
|
||||
|
||||
class C implements I {
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Class C contains 2 abstract methods and must therefore be declared abstract or implement the remaining methods (I::$prop::get, I::$prop::set) in %s on line %d
|
12
Zend/tests/property_hooks/interface_not_public.phpt
Normal file
12
Zend/tests/property_hooks/interface_not_public.phpt
Normal file
@ -0,0 +1,12 @@
|
||||
--TEST--
|
||||
Cannot use non-public property hook in interface (whole property)
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface I {
|
||||
protected $prop { get; set; }
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Property in interface cannot be protected or private in %s on line %d
|
15
Zend/tests/property_hooks/interface_set_only.phpt
Normal file
15
Zend/tests/property_hooks/interface_set_only.phpt
Normal file
@ -0,0 +1,15 @@
|
||||
--TEST--
|
||||
Interface may contain only get with no implementation
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
interface I {
|
||||
public $prop { get; }
|
||||
}
|
||||
|
||||
class A implements I {
|
||||
public $prop { get {} }
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
41
Zend/tests/property_hooks/invalid_abstract.phpt
Normal file
41
Zend/tests/property_hooks/invalid_abstract.phpt
Normal file
@ -0,0 +1,41 @@
|
||||
--TEST--
|
||||
Implementing abstract property hooks
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
abstract class A {
|
||||
public abstract $prop1 {
|
||||
get;
|
||||
set { echo __METHOD__, "\n"; }
|
||||
}
|
||||
public abstract $prop2 {
|
||||
get { echo __METHOD__, "\n"; }
|
||||
set;
|
||||
}
|
||||
public abstract $prop3 { get; set; }
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
public $prop1 {
|
||||
get { echo __METHOD__, "\n"; }
|
||||
}
|
||||
public $prop2 {
|
||||
set { echo __METHOD__, "\n"; }
|
||||
}
|
||||
public $prop3;
|
||||
}
|
||||
|
||||
$b = new B;
|
||||
$b->prop1;
|
||||
$b->prop1 = 1;
|
||||
$b->prop2;
|
||||
$b->prop2 = 1;
|
||||
$b->prop3;
|
||||
$b->prop3 = 1;
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
B::$prop1::get
|
||||
A::$prop1::set
|
||||
A::$prop2::get
|
||||
B::$prop2::set
|
14
Zend/tests/property_hooks/invalid_abstract_body.phpt
Normal file
14
Zend/tests/property_hooks/invalid_abstract_body.phpt
Normal file
@ -0,0 +1,14 @@
|
||||
--TEST--
|
||||
Abstract property hook cannot have body
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public abstract $prop {
|
||||
get {}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Abstract property Test::$prop must specify at least one abstract hook in %s on line %d
|
12
Zend/tests/property_hooks/invalid_abstract_final.phpt
Normal file
12
Zend/tests/property_hooks/invalid_abstract_final.phpt
Normal file
@ -0,0 +1,12 @@
|
||||
--TEST--
|
||||
Property hook cannot be both abstract and final
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public abstract $prop { final get; }
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Property hook cannot be both abstract and final in %s on line %d
|
16
Zend/tests/property_hooks/invalid_abstract_indirect.phpt
Normal file
16
Zend/tests/property_hooks/invalid_abstract_indirect.phpt
Normal file
@ -0,0 +1,16 @@
|
||||
--TEST--
|
||||
Class with abstract property hook not declared abstract (inherited 1)
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
abstract class A {
|
||||
public abstract $prop { get; }
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
public $prop { set {} }
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Class B contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (A::$prop::get) in %s on line %d
|
15
Zend/tests/property_hooks/invalid_abstract_indirect_2.phpt
Normal file
15
Zend/tests/property_hooks/invalid_abstract_indirect_2.phpt
Normal file
@ -0,0 +1,15 @@
|
||||
--TEST--
|
||||
Class with abstract property hook not declared abstract (inherited 2)
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
abstract class A {
|
||||
public abstract $prop { get; }
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Class B contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (A::$prop::get) in %s on line %d
|
12
Zend/tests/property_hooks/invalid_abstract_private.phpt
Normal file
12
Zend/tests/property_hooks/invalid_abstract_private.phpt
Normal file
@ -0,0 +1,12 @@
|
||||
--TEST--
|
||||
Property hook cannot be both abstract and private
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
private abstract $prop { get; }
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Property hook cannot be both abstract and private in %s on line %d
|
12
Zend/tests/property_hooks/invalid_empty_hooks.phpt
Normal file
12
Zend/tests/property_hooks/invalid_empty_hooks.phpt
Normal file
@ -0,0 +1,12 @@
|
||||
--TEST--
|
||||
Property hook list cannot be empty
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public $prop {}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Property hook list cannot be empty in %s on line %d
|
12
Zend/tests/property_hooks/invalid_final_private.phpt
Normal file
12
Zend/tests/property_hooks/invalid_final_private.phpt
Normal file
@ -0,0 +1,12 @@
|
||||
--TEST--
|
||||
Property hook cannot be both final and private
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
private $prop { final get; }
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Property hook cannot be both final and private in %s on line %d
|
14
Zend/tests/property_hooks/invalid_hook_visibility.phpt
Normal file
14
Zend/tests/property_hooks/invalid_hook_visibility.phpt
Normal file
@ -0,0 +1,14 @@
|
||||
--TEST--
|
||||
Property hooks cannot have explicity visibility
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
private $prop {
|
||||
public get;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Cannot use the public modifier on a property hook in %s on line %d
|
14
Zend/tests/property_hooks/invalid_static.phpt
Normal file
14
Zend/tests/property_hooks/invalid_static.phpt
Normal file
@ -0,0 +1,14 @@
|
||||
--TEST--
|
||||
Property hook cannot be static
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public $prop {
|
||||
static get {}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Cannot use the static modifier on a property hook in %s on line %d
|
12
Zend/tests/property_hooks/invalid_static_prop.phpt
Normal file
12
Zend/tests/property_hooks/invalid_static_prop.phpt
Normal file
@ -0,0 +1,12 @@
|
||||
--TEST--
|
||||
Cannot use hooks for static property (for now)
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public static $prop { get; set; }
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Cannot declare hooks for static property in %s on line %d
|
37
Zend/tests/property_hooks/isset.phpt
Normal file
37
Zend/tests/property_hooks/isset.phpt
Normal file
@ -0,0 +1,37 @@
|
||||
--TEST--
|
||||
isset() and empty() call get property hook
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public $prop1 {
|
||||
get { return $this->prop1; }
|
||||
set { $this->prop1 = $value; }
|
||||
}
|
||||
}
|
||||
|
||||
$test = new Test;
|
||||
|
||||
$test->prop1 = true;
|
||||
var_dump(isset($test->prop1));
|
||||
var_dump(!empty($test->prop1));
|
||||
echo "\n",
|
||||
$test->prop1 = false;
|
||||
var_dump(isset($test->prop1));
|
||||
var_dump(!empty($test->prop1));
|
||||
echo "\n",
|
||||
$test->prop1 = null;
|
||||
var_dump(isset($test->prop1));
|
||||
var_dump(!empty($test->prop1));
|
||||
echo "\n";
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
bool(true)
|
||||
bool(true)
|
||||
|
||||
bool(true)
|
||||
bool(false)
|
||||
|
||||
bool(false)
|
||||
bool(false)
|
24
Zend/tests/property_hooks/magic_consts.phpt
Normal file
24
Zend/tests/property_hooks/magic_consts.phpt
Normal file
@ -0,0 +1,24 @@
|
||||
--TEST--
|
||||
Magic constants in property hooks
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public $prop {
|
||||
get {
|
||||
var_dump(__FUNCTION__);
|
||||
var_dump(__METHOD__);
|
||||
var_dump(__CLASS__);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$test = new Test;
|
||||
$test->prop;
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
string(10) "$prop::get"
|
||||
string(16) "Test::$prop::get"
|
||||
string(4) "Test"
|
62
Zend/tests/property_hooks/magic_interaction.phpt
Normal file
62
Zend/tests/property_hooks/magic_interaction.phpt
Normal file
@ -0,0 +1,62 @@
|
||||
--TEST--
|
||||
Interaction of inaccessible property hooks with magic methods
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class A {
|
||||
public $prop {
|
||||
get {
|
||||
echo __METHOD__, "\n";
|
||||
return 'prop';
|
||||
}
|
||||
set { echo __METHOD__, "\n"; }
|
||||
}
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
public function __get($name) {
|
||||
echo __METHOD__, "($name)\n";
|
||||
try {
|
||||
$this->$name;
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
}
|
||||
public function __set($name, $value) {
|
||||
echo __METHOD__, "($name, $value)\n";
|
||||
try {
|
||||
$this->$name = $value;
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
}
|
||||
public function __isset($name) {
|
||||
echo __METHOD__, "($name)\n";
|
||||
try {
|
||||
var_dump(isset($this->$name));
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
}
|
||||
public function __unset($name) {
|
||||
echo "Never reached\n";
|
||||
}
|
||||
}
|
||||
|
||||
$b = new B;
|
||||
$b->prop;
|
||||
var_dump(isset($b->prop));
|
||||
$b->prop = 1;
|
||||
try {
|
||||
unset($b->prop);
|
||||
} catch (Error $e) {
|
||||
echo $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
A::$prop::get
|
||||
A::$prop::get
|
||||
bool(true)
|
||||
A::$prop::set
|
||||
Cannot unset hooked property B::$prop
|
30
Zend/tests/property_hooks/magic_method_from_hooked.phpt
Normal file
30
Zend/tests/property_hooks/magic_method_from_hooked.phpt
Normal file
@ -0,0 +1,30 @@
|
||||
--TEST--
|
||||
Accessing property from hook does not call magic method
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public $prop {
|
||||
get => $this->prop;
|
||||
set => $value;
|
||||
}
|
||||
|
||||
public function __get($name) {
|
||||
echo __METHOD__, "\n";
|
||||
return 42;
|
||||
}
|
||||
|
||||
public function __set($name, $value) {
|
||||
echo __METHOD__, "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$test = new Test;
|
||||
var_dump($test->prop);
|
||||
$test->prop = 42;
|
||||
var_dump($test->prop);
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
NULL
|
||||
int(42)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user