IMG-LOGO
공지사항 :

PHP의 final (상속금지)

lmkfox - 2025-09-11 06:18:04 16 Views 0 Comment

1) 문법과 기본 동작

  • final class ClassName { ... } — 이 클래스는 다른 클래스가 extends할 수 없습니다.

  • final public function foo() { ... } — 이 메서드는 자식 클래스에서 재정의(override)할 수 없습니다.

  • 재정의하거나 final 클래스를 확장하려고 하면 **런타임(실행 시) Fatal error**가 발생합니다. (예: Cannot override final method ..., Class Foo may not inherit from final class ...) 

예:

final class A {}
class B extends A {} // Fatal error: Class B may not inherit from final class (A)
class C {
    final public function locked() {}
}
class D extends C {
    public function locked() {} // Fatal error: Cannot override final method C::locked()
}


2) 어디에 적용 가능한가(버전 주의)

  • 클래스: 항상 적용 가능(기본 개념).

  • 메서드: 항상 적용 가능.

  • 상수: PHP 8.1부터 final 클래스 상수 사용 가능(자식이 상수 값을 덮어쓸 수 없음). 

  • 프로퍼티: PHP 8.4부터 final 프로퍼티 선언이 도입되어(예시가 매뉴얼에 있음) 자식이 같은 이름의 프로퍼티로 덮어쓸 수 없게 할 수 있습니다. (버전 확인 필요) 

요약: constants(8.1), properties(8.4) 같은 세부 기능은 PHP 버전별로 추가되었으니 실제로 사용할 때는 런타임 PHP 버전을 확인하세요. 


3) 특수 규칙·예외

  • abstract + final 조합은 모순입니다. 즉, abstract final class … 같은 선언은 허용되지 않으며 문법/런타임 오류를 냅니다(원인: abstract는 확장을 전제로 하고 final은 확장을 금지하기 때문). 

  • private 메서드와 final: PHP 8.0부터는 private 메서드를 final로 선언하는 것이 허용되지 않거나(대응되는 경고/제한이 있음) 특별 취급됩니다 — 생성자(__construct)에 대해서만 예외적으로 final private 관련 동작이 다를 수 있으니 주의하세요. 관련 세부 동작은 버전·릴리스 노트를 참조하세요. 

  • 트레이트( trait )와 final: 트레이트에서 가져온(임포트된) 메서드에 as final 별칭을 붙여 해당 메서드를 클래스의 자식에서 오버라이드하지 못하도록 만들 수 있는 기능이 PHP 8.3부터 도입되었습니다. 또한 트레이트의 상수는 final로 선언할 수 있고, 충돌 규칙이 엄격합니다. 

예(트레이트에서 메서드 final 처리 — PHP ≥ 8.3):

trait T {
    public function hello() { echo "hi"; }
}
class A {
    use T { T::hello as final; }
}
class B extends A {
    public function hello() {} // Fatal error: Cannot override final method A::hello()
}

(위 동작은 as final 별칭을 사용했을 때의 예시입니다.) 


4) 왜 

final

을 쓰는가 — 설계 관점

  • 안정성: 중요한 동작(보안, 내부 상태 처리 등)을 자식이 바꾸지 못하게 고정하고 싶을 때.

  • 의도 표현: 이 클래스/메서드는 확장 의도가 없다는 것을 명확히 함.

  • 성능적 이점: (언어 최적화 관점에서) 일부 상황에서 최적화에 도움될 수 있으나 실제 효과는 미미함.

    단점: 테스트/모킹이 어려워질 수 있고, 프레임워크나 라이브러리 사용자에게 확장 포인트를 빼앗아 버릴 수 있음.

대안: 확장이 필요하면 인터페이스·추상 메서드·의존성 주입(Composition)을 통해 확장 가능 지점을 설계하거나, 필요한 경우 ‘protected’ 훅 메서드만 내놓는 식으로 캡슐화를 유지하세요.


5) 실제로 마주치는 오류 메시지(예시)

  • Fatal error: Cannot override final method BaseClass::method() — final 메서드 재정의 시. 

  • Fatal error: Class Child may not inherit from final class (BaseClass) — final 클래스 확장 시. 

    (오류 텍스트는 PHP 버전에 따라 표현이 조금 달라질 수 있습니다.)


6) 실무 권장사항(체크리스트)

  • 라이브러리·프레임워크 설계 시: **공개 API(확장 지점)**는 문서로 명확히 해두고, 내부 구현은 final로 잠글지 결정.

  • 단위 테스트를 자주 작성한다면, 무턱대고 모든 클래스를 final로 만들지 말 것(모킹/스파이 필요시 불편).

  • 외부 사용자를 위한 확장 포인트는 protected 훅(작은 메서드) 또는 명시적 인터페이스로 제공.

  • 레거시 코드에 final이 많아 테스트가 어려우면, 우선 리팩터(인터페이스 추출·컴포지션) 고려.


댓글