克隆和传递引用的问题

Nic*_*ers 9 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;
Run Code Online (Sandbox Code Playgroud)

请在此处查看: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);
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

Jon*_*Jon 12

TL; DR

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

如果你可以在PHP浅克隆对象之前运行代码(例如通过编写自己的克隆方法),那么就会存在一个脆弱的解决方案,但如果代码在之后运行则不行,这就是__clone有效的方法.但是,由于您无法控制的其他原因,此解决方案仍然可能失败.

还有一个安全的选项涉及一个众所周知的"克隆"技巧,但它也有缺点:它只适用于可序列化的数据,并且它不允许你在这些数据中保留任何引用如果你想.

在一天结束时,如果你想保持理智,你将不得不放弃实现属性$this->varN作为参考.

可怜的PHP开发人员的困境

通常,您必须深入克隆需要克隆的所有内容__clone.然后,您还必须重新分配仍指向刚刚克隆的实例的任何引用.

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

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;        
}
Run Code Online (Sandbox Code Playgroud)

但是,这不起作用.怎么可能?

Zend引擎参考

如果在构造函数var_dump($this->data)之前和之后assignReferences(),您将看到分配这些引用会导致内容$this->data成为引用本身.

这是一个关于如何在PHP内部实现引用的工件,您无法直接对其进行任何操作.您可以做的是首先通过丢失对它们的所有其他引用将它们转换回正常值,之后克隆如上所述.

在代码中:

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;        
}
Run Code Online (Sandbox Code Playgroud)

这似乎有效,但它立即不是很令人满意,因为您必须知道克隆此对象的方法$obj->makeClone()不是clone $obj- 自然方法将失败.

然而,在这里还有一个更加阴险的错误等着咬你:要取消引用$this->data你内部的值必须丢失程序中对它们的所有引用.上面的代码是针对引用的$this->varN,但是其他代码可能创建的引用呢?

比较一下:

$original = new my_class;
$new = $original->makeClone();
$new->var3 = 'd'; 

echo $original->var3; // works, "c"
Run Code Online (Sandbox Code Playgroud)

对此:

$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!
Run Code Online (Sandbox Code Playgroud)

我们现在回到原点.更糟糕的是,没有办法阻止某人这样做并弄乱你的程序.

用火杀死参考文献

无论如何,有一种保证使内部引用$this->data消失的方法:序列化.

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();
}
Run Code Online (Sandbox Code Playgroud)

这适用于有问题的值,但它也有缺点:

  1. 你不能在里面有任何$this->data不可序列化的值(递归).
  2. 它将不分青红皂白地杀死内部的所有引用$this->data- 甚至是那些你可能想故意保留的引用.
  3. 它的性能较差(理论上讲,公平).

那么该怎么办?

在强制性抨击PHP之后,请遵循经典医生的建议:如果在做某事时疼痛,则不要这样做.

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