메아리 저널

php5의 visibility 구현

php4에서 php5로 넘어 가면서 자바스러운 객체지향 기능들이 대거 추가되었는데, protected/private 변수의 추가도 그 중에 하나이다. 그런데 이 protected/private 변수가 내부적으로 추가된 방법이 참 기가 막힌데...

  • private 변수는 \0<클래스명>\0<변수명>라는 내부 이름을 가지고, (왜냐하면 클래스마다 별도로 관리되어야 하기 때문에)
  • protected 변수는 \0*\0<변수명>라는 내부 이름을 가진다. (왜냐하면 모든 종류의 클래스에서 내부적으로는 공유되어야 하기 때문에)

이런 괴악한 구성은 php의 클래스라는 게 순전히 클래스 이름이 추가된 배열과 똑같다는 데서 유래한다. zend engine 개발자들이 하위 호환성을 깨뜨리지 않으면서 기능을 추가하기 위해 이런 mangling 방법을 도입한 걸로 보이는데, 덕분에 다음과 같은 (별로 쓸 일 없는) 코드는 php4에서는 동작하지만 php5에서는 에러가 난다.

$obj = new SomeClass;
$obj->{"the answer"} = 42; // 이건 잘 되지만
$obj->{"\x00SomeClass\x00blah"} = 54; // 이건 \0로 시작한다는 에러가 난다.

물론 꽁수가 있긴 하다. php의 serialize 함수는 __sleep/__wakeup 같은 특별한 메소드가 없다면 이런 private/protected 변수의 내부 표현을 그대로 저장해 버린다. 따라서 다음과 같은 괴악하지만 돌아 가긴 하는 코드가 가능하다.

function toarray($obj) {
    $s = serialize($obj);
    list($type, $nbytes, $remained) = explode(':', $s, 3);
    if ($type != 'O') return null;
    $classname = substr($remained, 1, $nbytes);
    $arr = 'a:' . substr($remained, $nbytes + 3);
    return array($classname, unserialize($arr));
}

function fromarray($classname, $arr) {
    if (!is_array($arr)) return null;
    $s = 'O:' . strlen($classname) . ':"' . $classname .
         '":' . substr(serialize($arr), 2);
    return unserialize($s);
}

다음 코드로 테스트하면 private 변수가 바뀐다는 걸 알 수 있다. 물론 실제로 이런 걸 쓰면 ㅂㅌ 소리 골백번 듣고도 넘쳐서 흐를 것이다 -_-;

class SomeClass {
    private $value;
    function SomeClass() { $this->value = 42; }
    function show() { echo "SomeClass($this->value)\n"; }
}

$obj = new SomeClass;
$obj->show(); // SomeClass(42)
list($objclass, $objarr) = toarray($obj);
$objarr["\x00SomeClass\x00value"] = 54;
$obj = fromarray($objclass, $objarr);
$obj->show(); // SomeClass(54)

결론: private와 protected를 신뢰하지 말 것. 어디서나 뚫릴 방법은 있게 마련이다. 그리고... php 배열/클래스는 존내 무겁다.

이 글은 본래 http://tokigun.net/blog/entry.php?blogid=105에 썼던 것을 옮겨 온 것입니다.


(rev 1d46270eb038)