[PHP OOP] 4. 매직 메서드 (Magic Methods)

정현섭·2021년 6월 23일
3

PHP OOP

목록 보기
4/6
  • 매직 상수도 있지만 매직 메서드도 있다.
  • 매직 메서드는 class 내부에서 php 자체가 기본적으로 호출하는 method 다.
  • 보통 메서드 이름 앞에 __ 이 붙은 형태다.

Method 관련 magic method

  • php에서는 magic method를 이용하여, 없는 method를 호출하고 처리하는 것이 가능하다.

Example 1 : __call($name, $args)

class A
{

}

$a = new A();
$a->foo();
  • 위 코드는 당연히 정의 하지 않은 method foo() 를 불렀으니 에러가 난다. (Uncaught Error: Call to undefined method A::foo())
class A
{
    public function __call($name, $args) {
        echo "$name $args[0]";
    }
}

$a = new A();
$a->foo('hihihi');
  • 위 코드는 class A에서 method foo를 정의하지 않았음에도 에러가 나지 않는다.
  • 결과로 foo hihihi 가 출력된다.
  • 하지만 여기서 A::foo('hihihi') 와 같은 접근은 안된다.

Example 2 : __callStatic($name, $args)

class A
{
    public static function __callStatic($name, $args) {
        echo "$name $args[0]\n";
    }
}

A::foo('bar');
  • static method는 call 대신 callStatic 이라는 magic method를 써야한다.
  • 위 코드의 결과로 foo bar 이 출력된다.

Example 3 : __invoke(...$args)

class A
{
    public function __invoke(...$args) {
        foreach($args as $a)
            echo $a . ' ';
    }
}

$a = new A();
$a('foo', 'bar');
  • __invoke() 의 경우 객체 자체가 함수처럼 불릴때 불러지는 method다.
  • 위 코드의 결과로 foo bar 이 출력된다.

Property 관련 magic method

Example 1 : __isset($name)

class A
{
    private $foo;

    public function __isset($name) {
        echo 'isset';
        return isset($this->$name);
    }
}

$a = new A();
isset($a);     // 아무것도 출력 X
isset($a->message); // "isset" 출력!
  • 이 magic method의 경우 object의 property에 isset함수를 사용했을때 불리는 method다.
  • 위 예시 코드에 적혀있듯이, object자체에 isset함수를 쓰면 __isset method가 호출이 안되고 object의 property를 대상으로 했을때 호출된다.
  • 즉, 위 코드의 결과로 isset 이 하나만 출력된다.

Example 2 : __unset($name)

class A
{
    private $foo;

    public function __unset($name) {
        echo 'unset';
        return isset($this->$name);
    }
}

$a = new A();
// unset($a);   // 아무것도 출력 X
unset($a->message); // 'unset' 출력.
  • unset도 isset과 마찬가지로 작동한다.

Example 3 : set, get

  • setter와 getter의 역할을 하는 magic method다.
class A
{
    private $foo;

    public function __set($name, $value) {
        $this->$name = $value;
    }
    public function __get($name) {
        return $this->$name;
    }
}

$a = new A();
$a->foo = 'aaa';
echo $a->foo;
  • 위 코드에서 마지막 두 줄을 원래 에러가 나야한다. 왜냐하면 객체의 private property에 직접적으로 접근하고 있기 때문이다.
  • 하지만 set, get 이 정의되어 있다면 위와 같은 접근 시 자동으로 set, get method를 불러준다.
  • property 하나 하나마다 setter, getter 를 만들어주기 힘들때 유용하다. (근데 그럼 property의 visibility를 private으로 할 이유가 있나? 모르겠음..)

Serialize 관련 magic method

  • 직렬화(serialize), 역직렬화(unserialize) 관련 magic method도 있다.
  • 직렬화(Serialize)는 메모리 상에 존재하는 데이터(object)를 db에 저장하거나 외부로 보낼 때 사용하는 것이다. (php 내부 data를 string (byte sequence) 형태로 바꿔 줌)
  • 역직렬화(Deserialize)는 직렬화된 데이터를 다시 프로그램 내부(php) 에서 쓸 수 있게 만들어주는 것이다.

Example 1 : 직렬화 역직렬화 예제

class A
{
    public $foo = 'Hello';
}

$a = new A();
var_dump($a);

$k = serialize($a);
var_dump($k);

$b = unserialize($k);
var_dump($b);

$b->foo = 'Hi';
echo $a->foo . ' ' . $b->foo;
  • output
object(A)#1 (1) {
  ["foo"]=>
  string(5) "Hello"
}

string(34) "O:1:"A":1:{s:3:"foo";s:5:"Hello";}"

object(A)#2 (1) {
  ["foo"]=>
  string(5) "Hello"
}

Hello Hi
  • 객체 $a를 직렬화한 다음 $b에 다시 역직렬화를 하니 $b에 객체 $a 내용이 복사된 것을 볼 수 있다.

Example 2 : sleep(), wake()

  • serialize() 함수는 class에 sleep()이 정의되어있는지 확인하고 정의되어 있다면 serialization전에 sleep()을 실행 시킨다.
  • __sleep()은 object에서 serialize되어야 하는 property들의 이름을 array로 리턴해주기로 되어있다.
  • __wake()는 unserialize() 함수에 의해 실행되고, 역직렬화가 되기 전에 초기화 되어야 하는 것들을 초기화 해주는 역할로 사용한다.
class A
{
    public $foo;
    public $bar;
    public $foobar;

    public function __construct() {
        $this->foo = 'foo';
        $this->bar = 'bar';
        $this->foobar = 'foobar';
    }

    public function __sleep() {
        return ['bar'];
    }

    public function __wakeup() {
        $this->foobar = 'wakeup!';
    }
}

$a = new A();
var_dump($a);

$serial = serialize($a);

$b = unserialize($serial);
var_dump($b);
  • 결과
object(A)#1 (3) {
  ["foo"]=>
  string(3) "foo"
  ["bar"]=>
  string(3) "bar"
  ["foobar"]=>
  string(6) "foobar"
}

object(A)#2 (3) {
  ["foo"]=>
  NULL
  ["bar"]=>
  string(3) "bar"
  ["foobar"]=>
  string(7) "wakeup!"
}
  • 출력된 결과를 보면,
  • object $a는 생성자(constructor)에 의해 foo, bar, foobar가 모두 초기화되어 문자가 들어갔다.
  • object $b는 foo 는 NULL이고 bar 는 $a와 같이 "bar"로, foobar 는 "wakeup!"으로 초기화 되었다.
  • bar 의 경우 magic function인 __sleep()에서 해당 property를 serialize하라고 지정했기 때문에 serialize되는 과정에서 들어간 것 이고
  • foobar 의 경우 __wakeup()에서 해당 property를 초기화 시켜 줬기 때문에 unserialize 되는 과정에서 들어간 것이다.

Example 3 : Serializable 인터페이스 이용하기

  • magic method를 이용해서 직렬화를 다루는 것 보다,
  • Serializable이라는 인터페이스를 implements하는 방식을 실제로 더 많이 쓴다.
  • 배웠다시피 어떤 인터페이스를 implements하려면 해당 인터페이스에서 선언하는 모든 method를 정의해야한다.
  • 인터페이스 Serializable은 serialize와 unserialize라는 두 method를 선언하고 있다.
  • 근데 magic method를 쓰는것과 직렬화 포맷이 좀 다르다. (근데 뭐 상관없다.)
  • 또한 __sleep()과 Serializeable의 serialize()가 다르게 작동하고
  • __wakeup()과 Serializeable의 serialize()가 다르게 작동한다.
class A implements Serializable
{
    public $foo;
    public $bar;
    public $foobar;

    public function __construct() {
        $this->foo = 'foo';
        $this->bar = 'bar';
        $this->foobar = 'foobar';
    }

    public function serialize() {
        return serialize([$this->bar, $this->foobar]);
    }

    public function unserialize($data) {
        list(
            $this->foo,
            $this->bar
        ) = unserialize($data);
    }
}

$a = new A();
var_dump($a);

$serial = serialize($a);

$b = unserialize($serial);
var_dump($b);
  • 결과
object(A)#1 (3) {
  ["foo"]=>
  string(3) "foo"
  ["bar"]=>
  string(3) "bar"
  ["foobar"]=>
  string(6) "foobar"
}

object(A)#2 (3) {
  ["foo"]=>
  string(3) "bar"
  ["bar"]=>
  string(6) "foobar"
  ["foobar"]=>
  NULL
}
  • 결과를 보면 serialize()에서 리턴한 배열에 저장된 순서대로 unserialize에서 초기화 되는것을 볼 수 있다. (일부러 테스트를 위해 값이 한 칸씩 밀리게 함.)

list() : language construct

  • 참고로 list()는 language construct 로서,
$profile = ['Hyeonseop', 25, 'HGU'];
$name = $profile[0];
$age = $profile[1];
$uni = $profile[2];
  • 위 코드를 list()를 이용하여 아래와 같이 쓸 수 있다.
list($name, $age, $uni) = $profile

0개의 댓글