매직메소드 __get, __set에 대해서는 크게 두가지 의견이 있는데, 하나는 OO를 해치기 때문에 getter, setter를 정의해서 사용하라는 것이고, 또다른 하나는 스크립트 언어의 특성이므로 적극 활용을 해야 한다는 것이다.

한동안 getter, setter를 정의해서 사용하다, PHP가 스크립트 언어라는 특성을 활용하는 것이 맞는 이야기인 것 같아 매직메소드를 쓰기 시작한 것이 얼마 전부터 사용하고 있다.

그러나 매직메소드는 getter, setter를 작성하는 수고스러움 이상으로 문제를 일으키고 있다. 앞으로 __get(), __set() 매직메소드는 특별한 일이 아닌 이상 사용하지 않을 것 같다.

일단 아래 소스 코드를 보자. 전에 올렸던 글의 예제다.

 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
public function __get($name) {
    if ( property_exists($this, $name) ) {
        return $this->{$name};
    }
    $method_name = "get_{$name}";
    if ( method_exists($this, $method_name) ) {
        return $this->{$method_name}();
    }

    trigger_error("Undefined property $name or method $method_name");
}

public function __set($name, $value) {
    if ( property_exists($this, $name) ) {
        $this->{$name} = $value;
        return;
    }
    $method_name = "set_{$name}";
    if ( method_exists($this, $method_name) ) {
        $this->{$method_name}($value);
        return;
    }

    trigger_error("Undefined property $name or method $method_name");
}

이 코드는 하나의 클래스에서 문제를 일으키지 않는다. 그러나 상속을 받은 경우 다르다.

 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
<?php

class Foobar {
    private $name;

    public function __get($name) {
        if ( property_exists($this, $name) ) {
            return $this->{$name};
        }
        $method_name = "get_{$name}";
        if ( method_exists($this, $method_name) ) {
            return $this->{$method_name}();
        }

        trigger_error("Undefined property $name or method $method_name");
    }

    public function __set($name, $value) {
        if ( property_exists($this, $name) ) {
            $this->{$name} = $value;
            return;
        }
        $method_name = "set_{$name}";
        if ( method_exists($this, $method_name) ) {
            $this->{$method_name}($value);
            return;
        }

        trigger_error("Undefined property $name or method $method_name");
    }
}

class MyFoobar extends Foobar {
    private $age;
}

$foobar = new Foobar();

$foobar->name = "Foobar";
echo "{$foobar->name}\n";

$my_foobar = new MyFoobar();
$my_foobar->age = 42;
$my_foobar->name = "My Foobar";
echo "{$my_foobar->name}\n";
echo "{$my_foobar->age}\n";

?>

실행을 시켜보면 __get() 메소드에서 ‘MyFoobar’ 클래스 멤버 변수 ‘$age’를 찾지 못 하는 것을 에러 메시지로 확인 할 수 있다. 이렇게 된 것은 __get() 메소드가 ‘Foobar’ 클래스 내에 존재하기 때문이다. 이를 해결하기 위해서는 __get(), __set() 메소드를 ‘MyFoobar’로 옮기면 되지만, 그런 경우 ‘Foobar’ 클래스에 문제가 생길뿐더라 전혀 아름다운 모습이 아니기도 하다.

그나마 조금 절충을 해 볼 수 있는 방법이 멤버 변수들은 ‘protected’로 바꾸고 ‘ReflectionProperty()’ 함수를 이용하는 것이다.

 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
<?php

class Foobar {
    protected $name;

    public function __get($name) {
        if ( property_exists($this, $name) ) {
            $refprop = new ReflectionProperty($this, $name);
            $refprop->setAccessible($name);
            return $refprop->getValue($this);
        }
        $method_name = "get_{$name}";
        if ( method_exists($this, $method_name) ) {
            return $this->{$method_name}();
        }

        trigger_error("Undefined property $name or method $method_name");
    }

    public function __set($name, $value) {
        if ( property_exists($this, $name) ) {
            $refprop = new ReflectionProperty($this, $name);
            $refprop->setAccessible($name);
            $refprop->setValue($this, $value);
            return;
        }
        $method_name = "set_{$name}";
        if ( method_exists($this, $method_name) ) {
            $this->{$method_name}($value);
            return;
        }

        trigger_error("Undefined property $name or method $method_name");
    }
}

class MyFoobar extends Foobar {
    protected $age;
}

$foobar = new Foobar();

$foobar->name = "Foobar";
echo "{$foobar->name}\n";

$my_foobar = new MyFoobar();
$my_foobar->age = 42;
$my_foobar->name = "My Foobar";
echo "{$my_foobar->name}\n";
echo "{$my_foobar->age}\n";

?>

에러가 발생하지 않고 의도한대로 동작한다. 하지만 private으로 선언한 멤버 변수들을 protected로 선언해야 하는 문제가 있다. 이런 문제를 완전히 해결하기 위해서는 다른 언어들처럼 getter, setter를 제대로 정의하는 것이 나아 보인다.

 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 Foobar {
    private $name;

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

    public function set_name($name) {
        $this->name = $name;
    }
}

class MyFoobar extends Foobar {
    private $age;

    public function age() {
        return $this->age;
    }

    public function set_age($age) {
        $this->age = $age;
    }
}

$foobar = new Foobar();

$foobar->set_name("Foobar");
echo "{$foobar->name()}\n";

$my_foobar = new MyFoobar();
$my_foobar->set_age(42);
$my_foobar->set_name("My Foobar");
echo "{$my_foobar->name()}\n";
echo "{$my_foobar->age()}\n";

?>

처음 의도한대로 멤버 함수를 private으로 유지하면서 자식 클래스에서 부모의 멤버 변수를 접근하는데 아무런 문제가 생기지 않는다.

다만 멤버 변수가 많은 경우 일일히 getter, setter를 입력하는 것이 번거로우므로 snippet을 생성하는 스크립트나 에디터의 snippet 생성 기능을 적극적으로 활용해서 반복적인 작업 시간을 절약하는 것이 좋을 듯 하다. 간단한 클래스 코드를 생성 해주는 페이지도 있으니 이것을 사용해도 괜찮다.