克隆和通过引用的问题 [英] Issue with cloning and pass-by-reference

查看:71
本文介绍了克隆和通过引用的问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

因此,在过去的几天里,我一直在扯头发,试图让一堂课能够正确地克隆.问题在于,克隆不会删除/重做任何传递引用.结果是,仍将主要数据对象作为参考传递,从而完全否定了克隆的效果.

So for the past few days I have been tearing my hair out trying to get a class to clone properly. The problem is that cloning doesn't remove/redo any of the pass-by-reference. The result is, that the main data object is still passed as a reference, thus completely negating the effect of the clone.

这是问题的简化版本:

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

See it in action here: http://3v4l.org/nm6NW

我的问题:如何使用__clone()将输出从"d"更正为"c"?

My Question: How can I use __clone() to correct the output from "d" to "c"?

请以任何方式提供帮助!?

Please help in any way you can!?

我最终使__clone()触发了一个错误,并创建了一个名为make_clone()的函数来标准化克隆.

I ended up making __clone() trigger an error and creating a function called make_clone() to standardize cloning.

__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;
}

推荐答案

TL; DR

这是PHP SNAFU的经典案例.我将解释这种情况的发生方式和原因,但是很不幸的是,据我所知,没有可能的解决方案令人满意.

TL;DR

This is a classic case of PHP SNAFU. I will explain how and why it happens, but unfortunately as far as I can tell there is no possible solution that is satisfactory.

如果您可以在PHP浅克隆对象之前运行代码(例如,通过编写自己的克隆方法),但是如果代码随后运行,则易碎解决方案存在,这就是__clone的工作方式.但是,由于其他无法控制的原因,该解决方案仍然会失败.

A brittle solution exists if you can run code before PHP shallow clones the object (e.g. by writing your own cloning method), but not if the code runs afterwards, which is how __clone works. However, this solution can still fail for other reasons outside your control.

还有一个安全的选项,它涉及一个众所周知的克隆"技巧,但它也有缺点:它仅适用于可序列化的数据,并且不允许您在其中保留任何引用数据,即使您愿意.

There is also another option that is safe which involves a well-known "cloning" trick, but it also has drawbacks: it only works on data that is serializable, and it doesn't allow you to keep any references inside that data around even if you want to.

最终,如果您想保持理智,则必须放弃将属性$this->varN用作引用.

At the end of the day if you want to keep your sanity you will have to move away from implementing the properties $this->varN as references.

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

Normally you would have to deep clone everything that needs to be cloned inside __clone. Then you would also have to reassign any references that still point to the instances that were just deep cloned.

您会认为这两个步骤就足够了,例如:

You would think these two steps should be enough, for example:

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;        
}

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

However, this does not work. How can that be?

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

If you var_dump($this->data) before and after assignReferences() in the constructor you will see that assigning those references causes the contents of $this->data to become references themselves.

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

This is an artifact of how references are internally implemented in PHP and there is nothing you can do about it directly. What you can do is convert them back to normal values first by losing all other references to them, after which cloning as above would 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 appears to work, but it's immediately not very satisfactory because you have to know that the way to clone this object is $obj->makeClone() instead of clone $obj -- the natural approach will fail.

但是,这里还有一个更隐蔽的错误在等待您的叮咬:要取消引用$this->data中的值,您必须在程序中丢失对它们的所有引用.上面的代码针对$this->varN中的引用执行了此操作,但是其他代码可能已经创建的引用又如何呢?

However, there is also a more insidious bug here waiting to bite you: to un-reference the values inside $this->data you have to lose all references to them in the program. The code above does so for the references in $this->varN, but what about references other code might have created?

比较:

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

echo $original->var3; // works, "c"

对此:

$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!

我们现在回到正题 .更糟糕的是,无法阻止某人这样做并弄乱您的程序.

We are now back to square one. And worse, there is no way to prevent someone from doing this and messing up your program.

有一种保证使$this->data中的引用消失的方法,无论是什么:序列化.

There is a guaranteed way to make the references inside $this->data go away no matter what: serialization.

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 works with the values in question, but it also has drawbacks:

  1. $this->data中不能有任何(不可递归的)不可序列化的值.
  2. 它将任意杀掉$this->data中的所有引用-甚至是您可能想要故意保留的引用.
  3. 表现不佳(公平地说,是理论上的观点).
  1. You cannot have any values (recursively) inside $this->data that are not serializable.
  2. It will indiscriminately kill all references inside $this->data -- even those you might want to preserve on purpose.
  3. It's less performant (a theoretical point, to be fair).

那该怎么办?

在强制执行PHP之后,请遵循经典医生的建议:如果您在做某事时感到疼痛,请不要这样做.

So what to do?

After the obligatory bashing of PHP, follow the classic doctor's advice: if it hurts when you do something, then don't do it.

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

In this case this means that you just can't expose the contents of $this->data through public properties (references) on the object. Instead of this use getter functions or possibly implement the magic __get.

这篇关于克隆和通过引用的问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆