- 매직 상수도 있지만 매직 메서드도 있다.
- 매직 메서드는 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);
isset($a->message);
- 이 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->message);
- 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;
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