目录

PHP 中的浅克隆和深克隆

变量有值传递和引用传递,其实,对象也如此,所以,对象有浅克隆和深克隆这么一说。浅克隆也叫浅复制或浅拷贝,深克隆也叫深复制或深拷贝。唉,名称复名称,名称何其多?

浅克隆

引用的解释,官方说的最靠谱

警告
PHP 5 起,new 运算符自动返回一个引用

浅克隆在赋值时,引用赋值,相当于取了一个别名。对其中一个的修改,会影响到另一个

赋值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php

class Student
{
    private $name;

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function getName(): string
    {
        return $this->name;
    }
}

$jack1 = new Student();
$jack1->setName('Jack 1');

$jack2 = $jack1;
$jack2->setName('Jack 2');

var_dump($jack1);
var_dump($jack2);

// 结果
class Student#1 (1) {
  private $name =>
  string(6) "Jack 2"
}

class Student#1 (1) {
  private $name =>
  string(6) "Jack 2"
}

在以上的场景中,无论你修改哪个 Jack,都会影响到另一个对象,当然我这样说,可能有些人不服,比如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php

class Student
{
    private $name;

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function getName(): string
    {
        return $this->name;
    }
}

$jack1 = new Student();
$jack1->setName('Jack 1');

var_dump($jack1); // 这儿调整了下执行顺序

$jack2 = $jack1;
$jack2->setName('Jack 2');

var_dump($jack2);

// 结果
class Student#1 (1) {
  private $name =>
  string(6) "Jack 1"
}

class Student#1 (1) {
  private $name =>
  string(6) "Jack 2"
}

看到了吗?两个 Jack 的学生名称就是不一样。感觉像是被打脸了,如果你再 $jack2 赋值后再打印一下 $jack1 试试,看谁能笑到最后

克隆

警告
使用 clone 操作复制对象时,当被复制的对象有对其它对象的引用的时候,引用的对象将不会被复制
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?php

class Student
{
    private $name;

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function getName(): string
    {
        return $this->name;
    }
}

class Administration
{
    private $classLeader;

    public function setClassLeader(Student $student)
    {
        $this->classLeader = $student;

        return $this;
    }

    /**
     * @return \Student
     */
    public function getClassLeader(): Student
    {
        return $this->classLeader;
    }
}

$jack = new Student();
$jack->setName('Jack');

$admin1 = new Administration();
$admin1->setClassLeader($jack);
$admin2 = clone $admin1;

$admin2->getClassLeader()->setName('Lucy');

echo $admin1->getClassLeader()->getName(), PHP_EOL, $admin2->getClassLeader()->getName();

// 结果
Lucy
Lucy

操蛋呀,$admin1->getClassLeader()$admin2->getClassLeader() 居然都指向同一个对象。但 $admin1$admin2 都是货真价实的两个独立对象,唯一的遗憾是他们的属性出卖了他们,看来这也是个浅复制

所以,浅克隆时,被赋值对象的所有变量都还与原来对象有相同的值,而所有的对其他对象的引用都仍然指向原来的对象

深克隆

方案一

基于魔术变量 __clone() 的实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?php

class Student
{
    private $name;

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function getName(): string
    {
        return $this->name;
    }
}

class Administration
{
    private $classLeader;

    public function __clone()
    {
        $this->classLeader = clone $this->classLeader;
    }

    public function setClassLeader(Student $student)
    {
        $this->classLeader = $student;

        return $this;
    }

    /**
     * @return \Student
     */
    public function getClassLeader(): Student
    {
        return $this->classLeader;
    }
}

$jack = new Student();
$jack->setName('Jack');

$admin1 = new Administration();
$admin1->setClassLeader($jack);
$admin2 = clone $admin1;

$admin2->getClassLeader()->setName('Lucy');

print $admin1->getClassLeader()->getName();
print PHP_EOL;
print $admin2->getClassLeader()->getName();

// 结果
Jack
Lucy

方案二

利用串行化做深复制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?php

class Student
{
    private $name;

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function getName(): string
    {
        return $this->name;
    }
}

class Administration
{
    private $classLeader;

    public function setClassLeader(Student $student)
    {
        $this->classLeader = $student;

        return $this;
    }

    /**
     * @return \Student
     */
    public function getClassLeader(): Student
    {
        return $this->classLeader;
    }
}

$jack = new Student();
$jack->setName('Jack');

$admin1 = new Administration();
$admin1->setClassLeader($jack);

/** @var \Administration $admin2 */
$admin2 = unserialize(serialize($admin1));

$admin2->getClassLeader()->setName('Lucy');

print_r([
    $admin1->getClassLeader()->getName(),
    $admin2->getClassLeader()->getName(),
]);

// 结果
Array
(
    [0] => Jack
    [1] => Lucy
)

当然,利用串行化的这种方案也可以封装到 __clone() 方法中:

1
2
3
4
5
6
7
8
public function __clone()
{
    foreach($this as $key => $val) {
        if (is_object($val) || (\is_array($val))) {
            $this->{$key} = unserialize(serialize($val));
        }
    }
}

所以,深克隆把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象。