浅复制与深复制

  • 浅复制:变量复制时,只复制变量的引用到新变量,复制前后的变量指向相同的变量地址,改变旧变量的值会同时影响复制后的新变量;

  • 深复制:变量复制过程为完整的复制,复制后的值与旧变量完全独立,改变旧变量值不影响新变量。

PHP 对于普通变量的 “=” 复制全部都是深复制,对于对象的复制,根据 PHP 版本有所有区别,在 PHP 5 之前的版本中,对象间的赋值是深复制,PHP 5 之后的版本中,赋值操作产生的对象复制是浅复制:

$a = 1;
$b = $a;
$a = 2;
var_dump($a, $b); // $a = 2, $b = 1

class Foo
{
    public $bar = 1;
}

$obj1 = new Foo();
$obj2 = $obj1;
$obj1->bar++;
var_dump($obj1->bar, $obj2->bar); // 结果都为2

此外当对象作为函数参数传递时候,也是引用传递,且无论是否有 & 标识,都是如此。

clone 关键字

不难想到,引用复制相对值复制的优势在于复制速度快,节省内存空间,但在实际编程中,确实有不少情况是需要使用值复制获取新对象的,这时候,就可以使用 PHP 中的 clone 关键字:

class Foo
{
    public $bar = 1;
}

$obj1 = new Foo();
$obj2 = clone $obj1;
$obj1->bar++;
var_dump($obj1->bar, $obj2->bar); // 结果:$obj1->bar = 2,$ob2->bar = 1

上面的例子中,使用 clone 关键字复制得到的新对象属性没有和旧对象关联,似乎是实现了深复制过程,再看下面的例子:

class Test
{
    public $option = 1;
}

class Foo
{
    public $bar = 1;
    public $test = new test();
}

$obj1 = new Foo();
$obj2 = clone $obj1;
$obj1->bar++;
$obj1->test->option++;
var_dump($obj1->bar, $obj2->bar, $obj1->test->option, $obj2->test->option); // 结果:$obj1->bar = 2,$ob2->bar = 1,$obj1->test->option = 2,$obj2->test->option = 2

可以发现,使用 clone 关键字拷贝对象时,原对象中的普通属性会按照值复制的方式拷贝到新对象中,而对于对象属性,则仍旧是引用复制的形式进行拷贝的。

在实际项目中,如果 clone 后的对象属性出现例子中的情况,有可能会和复制对象的初衷违背,产生意料之外的 bug,为了避免这种情况,可以使用 __clone 拦截器(魔术方法):

class Test
{
    public $option = 1;
}

class Foo
{
    public $bar = 1;
    public $test = new test();

    // __clone 方法会在使用 clone 时候自动执行
    public function __clone()
    {
        $this->test = clone new test();
    }
}