克隆和传递引用的问题

时间:2014-08-21 07:41:35

标签: php oop clone pass-by-reference

所以在过去的几天里,我一直在试图让一个班级正确克隆。问题是克隆不会删除/重做任何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;
}

1 个答案:

答案 0 :(得分:12)

TL; DR

这是PHP SNAFU的经典案例。我将解释它是如何以及为什么会发生的,但不幸的是,据我所知,没有可能的解决方案令人满意。

如果您可以在PHP浅层克隆对象之前运行代码(例如,通过编写自己的克隆方法),则存在脆弱解决方案,但如果代码在之后运行则不行,这就是{{1}工作。但是,由于您无法控制的其他原因,此解决方案仍然可能失败。

还有一个安全的选项涉及一个众所周知的“克隆”技巧,但它也有缺点:它只适用于可序列化的数据,并且它不允许你在其中保留任何引用即使你想要的数据。

如果你想要保持理智,你将不得不放弃实施属性__clone作为参考。

可怜的PHP开发人员的困境

通常,您必须深入克隆需要在$this->varN内克隆的所有内容。然后,您还必须重新分配仍然指向刚刚克隆的实例的任何引用。

你会认为这两个步骤应该足够了,例如:

__clone

然而, this does not work 。怎么会这样?

Zend引擎参考

如果您在构造函数中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 ,但它也有缺点:

  1. 您不能在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(); } 内有任何不可序列化的值(递归)。
  2. 它会不分青红皂白地杀死$this->data内的所有引用 - 甚至是那些你可能想故意保留的引用。
  3. 性能较差(理论上一点,公平)。
  4. 那该怎么办?

    在强制性抨击PHP之后,请遵循经典医生的建议:如果您在做某事时感到疼痛,那么就不要这样做。

    在这种情况下,这意味着您无法通过对象上的公共属性(引用)公开$this->data的内容。而不是使用getter函数或可能实现魔法$this->data