Computer >> 컴퓨터 >  >> 프로그래밍 >> PHP

PHP 객체 복제 완벽 가이드: clone 키워드부터 __clone() 메서드까지

소개

PHP에서 단순 대입(assignment)으로 객체를 복사하면 새로운 객체가 생성되는 것이 아니라 기존 메모리상의 객체를 가리키는 또 다른 참조(reference)만 만들어집니다. 따라서 한쪽 객체의 속성 값을 변경하면 원본과 복사본 양쪽 모두에 변경 사항이 반영됩니다.

PHP는 이러한 문제를 해결하기 위해 clone 키워드를 제공하며, 이를 통해 객체의 얕은 복사(shallow copy)를 수행할 수 있습니다. 하지만 clone 키워드에도 한계가 있습니다. 원본 객체가 다른 객체를 속성으로 포함하고 있다면, 복제된 객체의 내부 객체 속성은 여전히 동일한 객체를 참조하게 됩니다. 내부 객체까지 완전히 독립적으로 복사하는 깊은 복사(deep copy)를 구현하려면 클래스 안에 매직 메서드인 __clone()을 직접 정의해야 합니다.

이 글에서는 세 가지 방식, 즉 ① 단순 대입, ② clone 키워드, ③ __clone() 메서드를 통한 객체 복제의 차이점을 예제 코드와 실행 결과를 통해 살펴보겠습니다.

대입을 통한 객체 복사

다음 예제에서 myclass는 address 클래스의 객체를 속성 중 하나로 가지고 있습니다. myclass 객체를 단순 대입으로 복사한 뒤 속성 값을 변경하면, 그 변경 사항이 두 객체 모두에 반영되는 것을 확인할 수 있습니다.

예제

<?php
class address{
   var $city="Nanded";
   var $pin="431601";
   function setaddr($arg1, $arg2){
      $this->city=$arg1;
      $this->pin=$arg2;
   }
}
class myclass{
   var $name="Raja";
   var $obj;
   function setname($arg){
      $this->name=$arg;
   }
}
$obj1=new myclass();
$obj1->obj=new address();
echo "original object\n";
print_r($obj1);
$obj2=$obj1;
$obj1->setname("Ravi");
echo "after change:\n";
print_r($obj1);
print_r($obj2);
?>

출력 결과

위 코드를 실행하면 다음과 같은 결과가 출력됩니다.

original object
myclass Object(
   [name] => Raja
   [obj] => address Object(
      [city] => Nanded
      [pin] => 431601
   )
)
after change:
original object
myclass Object(
   [name] => Ravi
   [obj] => address Object(
      [city] => Nanded
      [pin] => 431601
   )
)
copied object
myclass Object(
   [name] => Ravi
   [obj] => address Object(
      [city] => Nanded
      [pin] => 431601
   )

)

실행 결과를 보면 $obj2 = $obj1; 로 복사한 후 원본 객체($obj1)의 name 속성을 "Ravi"로 변경했을 때, 복사본($obj2)의 name 역시 "Ravi"로 바뀐 것을 알 수 있습니다. 이는 두 변수가 같은 객체 인스턴스를 공유하고 있기 때문입니다.

clone 키워드 사용하기

clone 키워드를 사용하면 객체의 얕은 복사가 수행됩니다. 이 경우 최상위 속성 값의 변경은 복제된 객체에 영향을 주지 않습니다. 그러나 객체 내부에 포함된(embedded) 객체가 수정되면, 그 변경 사항은 원본 객체와 복제된 객체 양쪽 모두에 반영됩니다. 내부 객체는 여전히 동일한 인스턴스를 참조하고 있기 때문입니다.

예제

<?php
class address{
   var $city="Nanded";
   var $pin="431601";
   function setaddr($arg1, $arg2){
      $this->city=$arg1;
      $this->pin=$arg2;
   }
}
class myclass{
   var $name="Raja";
   var $obj;
   function setname($arg){
      $this->name=$arg;
   }
}
$obj1=new myclass();
$obj1->obj=new address();
echo "original object:\n";
print_r($obj1);
$obj2=clone $obj1;
$obj1->setname("Ravi");
$obj1->obj->setaddr("Mumbai", "400001");
echo "after change:\n";
echo "original object:\n";
print_r($obj1);
echo "cloned object:\n";
print_r($obj2);
?>

출력 결과

위 코드를 실행하면 다음과 같은 결과가 출력됩니다.

original object:
myclass Object(
   [name] => Raja
   [obj] => address Object(
      [city] => Nanded
      [pin] => 431601
   )

)
after change:
original object:
myclass Object(
   [name] => Ravi
   [obj] => address Object(
      [city] => Mumbai
      [pin] => 400001
   )

)
cloned object:
myclass Object(
   [name] => Raja
   [obj] => address Object(
      [city] => Mumbai
      [pin] => 400001
   )

)

결과를 분석해 보면 흥미로운 점을 발견할 수 있습니다. clone으로 복제한 객체($obj2)의 name 속성은 여전히 "Raja"로 유지되어, 최상위 속성은 독립적으로 복사되었음을 보여줍니다. 반면 내부의 address 객체 속성(city, pin)은 원본에서 "Mumbai", "400001"로 변경한 값이 복제본에도 그대로 반영되어 있습니다. 이것이 바로 얕은 복사의 특징입니다.

__clone() 메서드 사용하기

__clone() 매직 메서드를 활용하면 내부에 포함된 객체까지 함께 복제하여 깊은 복사(deep copy)를 구현할 수 있습니다. PHP에서 clone 키워드로 객체를 복제할 때, 해당 클래스에 __clone() 메서드가 정의되어 있다면 자동으로 호출됩니다. 따라서 이 메서드 안에서 내부 객체를 명시적으로 clone 처리하면 됩니다.

예제

<?php
class address{
   var $city="Nanded";
   var $pin="431601";
   function setaddr($arg1, $arg2){
      $this->city=$arg1;
      $this->pin=$arg2;
   }
}
class myclass{
   var $name="Raja";
   var $obj;
   function setname($arg){
      $this->name=$arg;
   }
   public function __clone() {
      $this->obj = clone $this->obj ;
   }
}
$obj1=new myclass();
$obj1->obj=new address();
echo "original object:\n";
print_r($obj1);
$obj2=clone $obj1;
$obj1->setname("Ravi");
$obj1->obj->setaddr("Mumbai", "400001");
echo "after change:\n";
echo "original object:\n";
print_r($obj1);
echo "cloned object:\n";
print_r($obj2);
?>

출력 결과

위 코드를 실행하면 다음과 같은 결과가 출력됩니다.

original object:
myclass Object(
   [name] => Raja
   [obj] => address Object(
      [city] => Nanded
      [pin] => 431601
   )

)
after change:
original object:
myclass Object(
   [name] => Ravi
   [obj] => address Object(
      [city] => Mumbai
      [pin] => 400001
   )

)
cloned object:
myclass Object(
   [name] => Raja
   [obj] => address Object(
      [city] => Nanded
      [pin] => 431601
   )

)

이번에는 복제된 객체($obj2)의 내부 address 객체도 초기값("Nanded", "431601")을 그대로 유지하고 있습니다. __clone() 메서드에서 내부 객체를 clone 했기 때문에 원본과 복제본이 완전히 독립적인 객체가 된 것입니다.

정리

PHP 객체 복제 방식을 요약하면 다음과 같습니다.

  • 단순 대입($obj2 = $obj1): 참조만 복사되므로 모든 변경 사항이 양쪽 객체에 함께 반영됩니다.
  • clone 키워드: 얕은 복사를 수행합니다. 최상위 속성은 독립적이지만, 내부 객체는 원본과 공유됩니다.
  • clone + __clone() 메서드: 깊은 복사를 구현합니다. 내부 객체까지 별도로 복제되어 완전히 독립적인 복사본을 만들 수 있습니다.

객체 지향 PHP 프로그래밍에서 의도치 않은 데이터 공유로 인한 버그를 방지하려면, 객체의 구조에 맞게 적절한 복제 전략을 선택하는 것이 중요합니다.