所以在过去的几天里,我一直在试图让一个班级正确克隆。问题是克隆不会删除/重做任何pass-by-reference。结果是,主数据对象仍然作为引用传递,从而完全否定了克隆的影响。
以下是问题的简化版本:
class my_class {
private $data;
public $var1;
public $var2;
public $var3;
public function __construct() {
$this->data = new stdClass;
$this->data->var1 = 'a';
$this->data->var2 = 'b';
$this->data->var3 = 'c';
$this->var1 = &$this->data->var1;
$this->var2 = &$this->data->var2;
$this->var3 = &$this->data->var3;
}
}
$original = new my_class;
$new = clone $original;
$new->var3 = 'd';
// Output Should Be "c", outputs "d"
echo $original->var3;
在此处查看此行动:http://3v4l.org/nm6NW
我的问题:如何使用__clone()
更正“d”到“c”的输出?
请以任何方式帮助你!?
我最终让__clone()
触发了错误并创建了一个名为make_clone()
的函数来标准化克隆。
__clone()
现在看起来像:
public function __clone() {
$trace = debug_backtrace();
$fmt = 'Invalid clone in <b>%4$s</b> on line <b>%5$s</b>. Because of how cloning works, and how references are configured within the class, extensions of %1$s cannot be cloned. Please use <code>make_clone()</code> instead. Error triggered';
trigger_error(sprintf($fmt, $trace[0]['class'], $trace[0]['function'], 'clone', $trace[0]['file'], $trace[0]['line']), E_USER_NOTICE);
}
和make_clone()
看起来像:
public function make_clone() {
// This line recreates the current instance at its' current state.
$clone = new $this(generate::string($this->object));
// In my class $this->input is a type of history state.
// The history of both the original and the clone should match
$clone->input = $this->input;
return $clone;
}
答案 0 :(得分:12)
这是PHP SNAFU的经典案例。我将解释它是如何以及为什么会发生的,但不幸的是,据我所知,没有可能的解决方案令人满意。
如果您可以在PHP浅层克隆对象之前运行代码(例如,通过编写自己的克隆方法),则存在脆弱解决方案,但如果代码在之后运行则不行,这就是{{1}工作。但是,由于您无法控制的其他原因,此解决方案仍然可能失败。
还有一个安全的选项涉及一个众所周知的“克隆”技巧,但它也有缺点:它只适用于可序列化的数据,并且它不允许你在其中保留任何引用即使你想要的数据。
如果你想要保持理智,你将不得不放弃实施属性__clone
作为参考。
通常,您必须深入克隆需要在$this->varN
内克隆的所有内容。然后,您还必须重新分配仍然指向刚刚克隆的实例的任何引用。
你会认为这两个步骤应该足够了,例如:
__clone
然而, this does not work 。怎么会这样?
如果您在构造函数中public function __construct()
{
$this->data = new stdClass;
$this->data->var1 = 'a';
$this->data->var2 = 'b';
$this->data->var3 = 'c';
$this->assignReferences();
}
public function __clone()
{
$this->data = clone $this->data;
$this->assignReferences();
}
private function assignReferences()
{
$this->var1 = &$this->data->var1;
$this->var2 = &$this->data->var2;
$this->var3 = &$this->data->var3;
}
之前和之后var_dump($this->data)
,您会看到分配这些引用会导致assignReferences()
的内容自己成为引用。< / p>
这是一个关于如何在PHP内部实现引用的工件,您无法直接对其进行任何操作。你可以做的是先丢失对它们的所有其他引用,然后将它们转换回正常值,之后克隆将起作用。
在代码中:
$this->data
This appears to work ,但它立即不太令人满意,因为你必须知道克隆这个对象的方法是public function __construct()
{
$this->data = new stdClass;
$this->data->var1 = 'a';
$this->data->var2 = 'b';
$this->data->var3 = 'c';
$this->assignReferences();
}
public function makeClone()
{
unset($this->var1); // turns $this->data->var1 into non-reference
unset($this->var2); // turns $this->data->var2 into non-reference
unset($this->var3); // turns $this->data->var3 into non-reference
$clone = clone $this; // this code is the same
$clone->data = clone $clone->data; // as what would go into
$clone->assignReferences(); // __clone() normally
$this->assignReferences(); // undo the unset()s
return $clone;
}
private function assignReferences()
{
$this->var1 = &$this->data->var1;
$this->var2 = &$this->data->var2;
$this->var3 = &$this->data->var3;
}
而不是$obj->makeClone()
- 自然的方法会失败。
然而,在这里还有一个更加阴险的错误等着咬你:要取消引用clone $obj
中的值,你必须在程序中丢失对它们的所有引用。上面的代码是针对$this->data
,中的引用执行的,但是其他代码可能创建的引用呢?
比较一下:
$this->varN
对此:
$original = new my_class;
$new = $original->makeClone();
$new->var3 = 'd';
echo $original->var3; // works, "c"
<强> We are now back to square one 即可。更糟糕的是,没有办法阻止某人这样做并弄乱你的程序。
有保证的方法可以使$original = new my_class;
$oops = &$original->var3; // did you think this might be a problem?
$new = $original->makeClone();
$new->var3 = 'd';
echo $original->var3; // doesn't work!
内的引用无论如何都会消失:序列化。
$this->data
带有问题的值This works ,但它也有缺点:
public function __construct()
{
$this->data = new stdClass;
$this->data->var1 = 'a';
$this->data->var2 = 'b';
$this->data->var3 = 'c';
$this->assignReferences();
}
public function __clone()
{
$this->data = unserialize(serialize($this->data)); // instead of clone
$this->assignReferences();
}
内有任何不可序列化的值(递归)。$this->data
内的所有引用 - 甚至是那些你可能想故意保留的引用。在强制性抨击PHP之后,请遵循经典医生的建议:如果您在做某事时感到疼痛,那么就不要这样做。
在这种情况下,这意味着您无法通过对象上的公共属性(引用)公开$this->data
的内容。而不是使用getter函数或可能实现魔法$this->data
。