PHP中__get魔术方法无法设置引用属性的解决办法求助
PHP中__get魔术方法与引用关联的问题及解决办法
一、常规方式为类属性设置引用
通常我们可以通过类方法直接为属性设置变量引用,代码示例如下:
class TestClass { public $t; public function set(\stdClass &$t) { $this->t = &$t; } } $obj = new \stdClass(); $obj->fromOUTSIDE = 1; $test = new TestClass(); $test->set($obj); var_dump($test);
执行结果符合预期,属性t为引用类型(可见&标识):
object(TestClass)#2 (1) { ["t"]=> &object(stdClass)#1 (1) { ["fromOUTSIDE"]=> int(1) } }
二、__get魔术方法创建属性时的引用失效问题
但当通过__get魔术方法动态创建属性时,即使在方法内为属性赋值引用,最终属性也会丢失引用特性:
class TestClass { public function __get(string $propertyName) { $xx = new \stdClass(); $xx->fromGET = 1; $this->t = &$xx; return $this->t; } } $test = new TestClass(); $test->t; var_dump($test);
执行结果中引用标识消失,属性t不再是引用类型:
object(TestClass)#1 (1) { ["t"]=> object(stdClass)#2 (1) { ["fromGET"]=> int(1) } }
即使声明public function &__get,若处理不当仍无法解决此问题——核心原因是方法内的局部变量$xx在函数执行完毕后被销毁,导致引用关联失效。
三、补充场景:外部变量重新赋值时类内属性无法同步
当类属性需要通过__get动态关联外部变量时,若外部变量重新赋值新对象,类内属性无法同步指向新对象:
class X { public \stdClass $t; public function __construct(\stdClass &$t) { $this->t = &$t; } } $t = new \stdClass(); $t->TTTT = 1; $X = new X($t); var_dump($t);echo "\r\n"; var_dump($X->t);echo "\r\n"; $t = new \stdClass(); $t->TTTT = 2; var_dump($t);echo "\r\n"; var_dump($X->t);echo "\r\n";
执行结果中,外部变量$t重新赋值后,类内属性$X->t仍指向旧对象,无法同步更新。
四、解决方案
1. 修复__get中的引用失效问题
通过类内部的容器存储引用目标,避免局部变量销毁导致的引用失效,同时声明引用型__get方法:
class TestClass { private $_refs = []; public function &__get(string $propertyName) { if ($propertyName === 't' && !isset($this->_refs['t'])) { $this->_refs['t'] = new \stdClass(); $this->_refs['t']->fromGET = 1; } // 将类属性与容器内的引用目标绑定 $this->$propertyName = &$this->_refs[$propertyName]; return $this->$propertyName; } } $test = new TestClass(); $test->t; var_dump($test);
此时执行结果中,属性t会保留引用标识:
object(TestClass)#1 (2) { ["_refs":"TestClass":private]=> array(1) { ["t"]=> &object(stdClass)#2 (1) { ["fromGET"]=> int(1) } } ["t"]=> &object(stdClass)#2 (1) { ["fromGET"]=> int(1) } }
2. 实现外部变量与类内属性的同步更新
使用引用持有者类来保存外部变量的引用,通过__get返回持有者内的引用目标,实现外部变量重新赋值时类内属性同步更新:
// 引用持有者类,用于保存变量引用 class ReferenceHolder { public $ref; public function __construct(&$ref) { $this->ref = &$ref; } } class X { private $_referenceHolders = []; // 绑定外部变量到类属性 public function bindReference(string $propertyName, &$externalVar) { $this->_referenceHolders[$propertyName] = new ReferenceHolder($externalVar); } public function &__get(string $propertyName) { if (isset($this->_referenceHolders[$propertyName])) { return $this->_referenceHolders[$propertyName]->ref; } // 若属性未绑定,可按需初始化默认值 $default = new \stdClass(); $this->bindReference($propertyName, $default); return $default; } } // 使用示例 $t = new \stdClass(); $t->TTTT = 1; $X = new X(); $X->bindReference('t', $t); var_dump($X->t); // 指向初始对象#1 echo "\r\n"; $t = new \stdClass(); $t->TTTT = 2; var_dump($X->t); // 同步指向新对象#2 echo "\r\n";
执行结果中,外部变量$t重新赋值后,$X->t会同步指向新对象,实现了引用关联的需求。
内容的提问来源于stack exchange,提问作者John Smith
相关产品推荐
相关产品推荐

