늦은 정적 바인딩
늦은 정적 바인딩 (Late Static Bindings)
PHP는 늦은 정적 바인딩(late static bindings) 이라는 기능을 제공하며, 이를 사용하면 정적 상속 문맥에서 호출된 클래스(called class)를 참조할 수 있다. static:: 키워드를 사용하는 이 기능이 self::와 어떻게 다르게 동작하는지를 예제와 함께 살펴본다.
본문
PHP는 late static bindings 이라는 기능을 구현하는데, 이 기능을 사용하면 정적 상속의 맥락에서 called class(호출된 클래스)를 참조할 수 있습니다.
좀 더 정확히 말하면, late static bindings은 마지막 "non-forwarding call"(전달되지 않은 호출)에서 이름이 지정된 클래스를 저장하는 방식으로 동작합니다. 정적 메서드 호출의 경우에는 명시적으로 이름이 지정된 클래스(보통 :: 연산자의 왼쪽에 있는 클래스)가 그 클래스이고, 비(非)정적 메서드 호출의 경우에는 객체의 클래스가 그 클래스입니다. "forwarding call"(전달 호출)이란 self::, parent::, static:: 에 의해 도입된 정적 호출이거나, 클래스 계층을 타고 올라가는 경우에는 forward_static_call() 입니다.
get_called_class() 함수를 사용하면 called class의 이름을 담은 문자열을 얻을 수 있고, static:: 도 그 스코프를 도입합니다.
이 기능은 내부적 관점에서 "late static bindings" 이라고 이름 붙여졌습니다. "Late binding"이라는 표현은 static:: 이 메서드가 정의된 클래스를 사용해 해석되지 않고, 런타임 정보를 이용해 계산된다는 사실에서 비롯된 것입니다.
또한 정적 메서드 호출에 사용할 수 있기 때문에(그것만을 위한 것은 아니지만) "static binding" 이라고도 불렸습니다.
self:: 의 한계
self:: 나 __CLASS__ 와 같은 현재 클래스에 대한 정적 참조는 함수가 속한 클래스, 즉 함수가 정의된 클래스를 사용해 해석됩니다.
예제 #1 self:: 사용
<?php
class A
{
public static function who()
{
echo __CLASS__;
}
public static function test()
{
self::who();
}
}
class B extends A
{
public static function who()
{
echo __CLASS__;
}
}
B::test();
?>
위 예제의 출력은 다음과 같습니다.
A
Late Static Bindings의 사용
Late static bindings은 런타임에 처음 호출된 클래스를 참조하는 키워드를 도입함으로써 그런 한계를 해결하려고 합니다. 기본적으로, 앞선 예제의 test() 안에서 B 를 참조할 수 있게 해 주는 키워드인 셈입니다. 새로운 키워드를 도입하는 대신 이미 예약되어 있던 static 을 사용하기로 했습니다.
예제 #2 static:: 단순 사용
<?php
class A
{
public static function who()
{
echo __CLASS__;
}
public static function test()
{
static::who(); // Here comes Late Static Bindings
}
}
class B extends A
{
public static function who()
{
echo __CLASS__;
}
}
B::test();
?>
위 예제의 출력은 다음과 같습니다.
B
Note: 비정적(non-static) 문맥에서는 called class가 객체 인스턴스의 클래스가 됩니다.
$this->는 같은 스코프에서 private 메서드를 호출하려고 하므로,static::을 사용하면 다른 결과가 나올 수 있습니다. 또 다른 차이점은static::은 정적 프로퍼티만 참조할 수 있다는 점입니다.
예제 #3 비정적 문맥에서의 static:: 사용
<?php
class A
{
private function foo()
{
echo "Success!\n";
}
public function test()
{
$this->foo();
static::foo();
}
}
class B extends A
{
/* foo() will be copied to B, hence its scope will still be A and
* the call be successful */
}
class C extends A
{
private function foo()
{
/* Original method is replaced; the scope of the new one is C */
}
}
$b = new B();
$b->test();
$c = new C();
try {
$c->test();
} catch (Error $e) {
echo $e->getMessage();
}
?>
위 예제의 출력은 다음과 같습니다.
Success!
Success!
Success!
Call to private method C::foo() from scope A
Note: Late static bindings의 해석은 완전히 해석된 정적 호출(fallback 없음)에서 멈춥니다. 반면
parent::나self::같은 키워드를 사용한 정적 호출은 호출 정보를 계속 전달(forward)합니다.
예제 #4 Forwarding과 non-forwarding 호출
<?php
class A
{
public static function foo()
{
static::who();
}
public static function who()
{
echo __CLASS__ . "\n";
}
}
class B extends A
{
public static function test()
{
A::foo();
parent::foo();
self::foo();
}
public static function who()
{
echo __CLASS__ . "\n";
}
}
class C extends B
{
public static function who()
{
echo __CLASS__ . "\n";
}
}
C::test();
?>
위 예제의 출력은 다음과 같습니다.
A
C
C
더 알아보기
- get_called_class() — 호출된 클래스의 이름을 반환
static::키워드와 정적 멤버의 실제 사용 예시는 위 예제들을 통해 확인할 수 있다.- PHP의 상속(extends)과 메서드 오버라이딩 동작 방식 관련 문서.