IT
PHP의 클래스 멤버 static
1) 한눈에 요약
-
static 멤버는 클래스 자체에 속하는 값/함수로, 인스턴스 없이 ClassName::$prop 또는 ClassName::method()로 접근합니다. $this는 정적 메서드 내부에서 사용할 수 없습니다.
2) 문법(정의·접근)
class Foo {
public static int $count = 0; // 정적(클래스) 프로퍼티 (타입 선언 가능)
protected static string $name = 'app';
public static function inc(): void {
self::$count++;
}
public static function info(): string {
// $this 불가 (인스턴스 컨텍스트 없음)
return self::$name . '(' . self::$count . ')';
}
}
// 접근
Foo::inc();
echo Foo::$count; // 1
echo Foo::info();
3) 클래스 상수와의 차이
-
const(또는 public const)는 변경 불가능한 상수. static은 변경 가능한 클래스 레벨 값.
class C {
public const VERSION = '1.0';
public static int $counter = 0;
}
4) 타입 선언·초기화 규칙(중요)
-
타입이 붙은 정적 프로퍼티(public static int $x;)는 초기화되지 않은 상태가 될 수 있고, 초기화 전에 읽으면 런타임 오류가 납니다. 생성자 또는 초기화 루틴에서 값을 설정하세요.
5) 인스턴스 vs 정적의 차이(상태 보유 방식)
-
인스턴스 프로퍼티: 객체마다 별도 값.
-
정적 프로퍼티: 클래스(또는 선언된 클래스) 단위로 값 보유. 같은 클래스 내의 모든 인스턴스에서 동일한 정적 값을 참조합니다.
실용 예: 요청당 카운터나 전역 설정(읽기 전용)·유틸성 함수에 자주 사용됩니다.
6) 상속에서의 동작(공유 vs 재선언)
-
부모 클래스에만 정적 프로퍼티가 선언되어 있다면, 부모가 선언한 “한 번의” 값이 공유되는 동작을 보일 수 있습니다(상황에 따라 공유처럼 보임).
-
부모와 자식 모두 같은 이름으로 static을 다시 선언하면 각 클래스는 서로 독립적인 정적 저장소를 가집니다(즉, 자식에 재선언하면 자식 클래스용 별도 정적 값이 됨).
예시(동작 차이):
class A {
public static int $x = 0;
}
class B extends A { /* 재선언 없음 */ }
class C extends A { public static int $x = 100; } // C는 별도 선언
A::$x = 1;
echo B::$x; // 1 (A에 선언된 값 참조)
echo C::$x; // 100 (C에서 재선언했으므로 C 전용)
핵심: “공유”처럼 보이는 경우(부모만 선언)와 “클래스별 고유 저장소”(자식에서 재선언) 동작을 구분해서 설계하세요.
7) 정적 메서드와 Late Static Binding (
self::
vs
static::
)
-
self::는 정의된 클래스의 멤버를 가리키고, static::은 호출한(런타임) 클래스를 가리키는 늦은 정적 바인딩(Late Static Binding)입니다. 팩토리/템플릿 패턴에서 유용합니다.
예:
class Base {
public static function factory() { return new self(); } // self -> 항상 Base
public static function lateFactory() { return new static(); } // static -> 호출 클래스
}
class Child extends Base {}
var_dump(get_class(Base::factory())); // "Base"
var_dump(get_class(Child::factory())); // "Base" (self 바인딩)
var_dump(get_class(Child::lateFactory())); // "Child" (late static binding)
8) static 변수(함수 내부) vs static 프로퍼티
function counter() {
static $i = 0; // 함수 스코프에 영속적(해당 함수 내부 고정)
return ++$i;
}
-
이 static $i(함수 내부)는 그 함수 호출 횟수 등을 기억할 때 유용합니다. 클래스 static 프로퍼티는 클래스 범위의 값입니다. 둘은 개념은 비슷하지만 스코프가 다릅니다.
9) 싱글턴 구현 예제(전통적 패턴)
final class Singleton {
private static ?Singleton $instance = null;
private function __construct() {}
public static function instance(): Singleton {
return self::$instance ??= new self();
}
}
$s = Singleton::instance();
-
싱글턴은 전역 상태를 만드는 쉬운 방법이지만 테스트·유연성·의존성 관리에 문제를 일으킵니다. DI 컨테이너 사용을 권장합니다.
10) 정적 멤버 사용 시 주의사항 (실무 팁)
-
전역 상태는 최소화: 정적 프로퍼티로 전역 상태를 만들면 테스트와 재현성이 어려워집니다.
-
테스트성 저하: 모킹이 어렵고, 실행 순서/사이드 이펙트가 테스트에 영향을 줍니다.
-
장기 실행 프로세스 주의(워커/서버): PHP-FPM/Apache의 경우 프로세스가 재사용될 수 있으므로 정적 값이 요청 간에 남을 수 있습니다(특히 데몬/AMQP 워커 등에서는 주의).
-
초기화 방식: 정적 값이 복잡한 계산(파일 읽기, DB 조회 등)으로 초기화되어야 하면 “명시적 초기화 메서드”나 지연 초기화(lazy init) 패턴을 써서 예측 가능하게 만드세요.
예: getConfig() 내부에서 if (self::$cfg === null) self::$cfg = loadFromFile(); 처럼.
-
대안: 순수 유틸은 네임스페이스 함수(또는 final 클래스로 감싼 정적 메서드)로 만들고, 상태가 필요한 로직은 객체(DI)로 분리하세요.
11) 트레이트에서
static
사용
-
트레이트는 정적 멤버를 가질 수 있습니다. 트레이트를 use한 클래스에 해당 멤버가 포함됩니다. 충돌 해결은 insteadof/as로 해야 합니다.
12) 성능 관련 한마디
-
static 접근은 인스턴스 메서드 호출보다 미세한 차이는 있을 수 있으나, 일반적인 애플리케이션에서는 설계(아키텍처)가 성능보다 훨씬 중요합니다. 미세 최적화 목적만으로 static을 남용하지 마세요.
13) 요약 체크리스트(권장)
-
전역·가변 상태는 static로 만들지 말고 DI로 주입하라.
-
유틸·순수 함수는 static(혹은 namespaced function) 적절.
-
상속 환경에서 클래스별 고유 정적 값을 원하면 자식에서 재선언하라.
-
타입이 있는 static 프로퍼티는 초기화 확인(또는 nullable로 선언)하라.
-
팩토리용 정적 메서드와 static::(late static binding)을 함께 쓰면 하위 클래스에 맞는 인스턴스 생성에 편리하다.
원하시면 다음 중 한 가지를 바로 만들어 드리겠습니다.
-
static 기반의 설정 캐시 클래스(lazy init 포함) 예제
-
싱글턴 대체로 DI 컨테이너와 결합한 구현 예제
-
상속 상황에서 static 프로퍼티 동작을 보여주는 실습 코드 + 실행 결과
댓글
아직 댓글이 없습니다.
로그인 후 댓글을 남길 수 있습니다.