객체 복제

객체 복제 (Object Cloning)

객체를 복사할 때 모든 프로퍼티를 똑같이 복제하는 게 항상 원하는 동작은 아니에요. 복사 생성자(copy constructor)가 필요한 대표적인 경우로, GTK 창을 나타내는 객체가 있다고 생각해 봐요. 그 객체가 GTK 창의 리소스를 들고 있는데, 이걸 똑같이 복제하면 새 창을 만들고 싶을 때도 기존 창의 리소스를 공유해 버리게 되거든요. 이럴 땐 같은 프로퍼티를 가지되 새 창의 리소스를 새로 들고 있는 객체가 필요해요. 또 객체가 내부에서 쓰는 다른 객체에 대한 참조를 갖고 있을 때도 마찬가지예요. 부모 객체를 복제하면 참조된 그 객체도 새 인스턴스로 만들어서, 복제본이 자기만의 별도 사본을 가지도록 하고 싶을 때가 있죠.

객체의 복사본은 clone 키워드로 만들 수 있어요. 가능하면 이때 객체의 __clone() 메서드가 호출됩니다.

$copy_of_object = clone $object;

객체가 복제되면 PHP는 객체의 모든 프로퍼티를 얕은 복사(shallow copy) 합니다. 다른 변수를 가리키는 참조 프로퍼티들은 여전히 그 참조를 유지해요.

function __clone(): void

복제가 끝난 뒤에 __clone() 메서드가 정의되어 있다면, 새로 만들어진 객체의 __clone() 메서드가 호출되어 필요한 프로퍼티를 바꿀 수 있어요.

예제 #1 — 객체 복제하기

<?php
class SubObject
{
    static $instances = 0;
    public $instance;

    public function __construct() {
        $this->instance = ++self::$instances;
    }

    public function __clone() {
        $this->instance = ++self::$instances;
    }
}

class MyCloneable
{
    public $object1;
    public $object2;

    function __clone()
    {
        // Force a copy of this->object, otherwise
        // it will point to same object.
        $this->object1 = clone $this->object1;
    }
}

$obj = new MyCloneable();

$obj->object1 = new SubObject();
$obj->object2 = new SubObject();

$obj2 = clone $obj;


print "Original Object:\n";
print_r($obj);

print "Cloned Object:\n";
print_r($obj2);

?>

위 예제는 다음을 출력합니다.

Original Object:
MyCloneable Object
(
    [object1] => SubObject Object
        (
            [instance] => 1
        )

    [object2] => SubObject Object
        (
            [instance] => 2
        )

)
Cloned Object:
MyCloneable Object
(
    [object1] => SubObject Object
        (
            [instance] => 3
        )

    [object2] => SubObject Object
        (
            [instance] => 2
        )

)

복제된 직후의 객체 멤버를 하나의 표현식 안에서 바로 접근할 수도 있어요.

예제 #2 — 복제된 객체의 멤버에 접근하기

<?php
$dateTime = new DateTime();
echo (clone $dateTime)->format('Y');
?>

위 예제는 다음과 비슷한 결과를 출력합니다.

2016

프로퍼티 오버라이드와 함께 복제하기

PHP 8.5.0부터 clone은 함수로도 쓸 수 있어요. 두 번째 파라미터인 withProperties는 프로퍼티 이름을 복제본이 받게 될 값에 매핑한 배열을 받습니다. 나열된 프로퍼티만 덮어쓰이고, 다른 프로퍼티는 복제된 그대로의 값을 유지해요.

이 오버라이드들은 __clone()이 실행된 뒤에 적용되고, 모든 프로퍼티의 가시성(visibility)을 존중합니다. readonly 프로퍼티는 원본 객체에서 이미 설정되어 있었더라도 복제본에서 업데이트할 수 있어요. 그래서 이 함수는 수정된 복사본을 반환하는 메서드를 만들기에 잘 맞습니다.

예제 #3 — 프로퍼티 오버라이드와 함께 객체 복제하기

<?php
class Point {
    public function __construct(
        public readonly int $x,
        public readonly int $y,
    ) {}

    public function withX(int $x): static
    {
        return clone($this, ['x' => $x]);
    }
}

$p = new Point(1, 2);
$q = $p->withX(10);

echo $q->x, ' ', $q->y;
?>

위 예제는 다음을 출력합니다.

10 2