IMG-LOGO
공지사항 :

PHP 상속 오버라이드(override)와 parent

lmkfox - 2025-09-09 06:38:55 22 Views 0 Comment

개념 정리

  • 오버라이드(override): 자식 클래스가 부모로부터 상속받은 메서드와 같은 시그니처(이름·정적성·가시성 호환·타입 호환)를 가진 메서드를 재정의하는 것. 재정의하지 않은 멤버는 부모 동작을 그대로 사용한다. 

  • parent 키워드: 자식 클래스의 코드 안에서 부모 클래스의 멤버(메서드·정적 프로퍼티·상수)를 가리킬 때 사용한다. parent::메서드(), parent::__construct(), parent::$staticProp, parent::CONST, parent::class 등이 가능하다. 

메서드 오버라이드 규칙

  1. 가시성(visibility)

    • 부모보다 좁게 만들 수 없다(public → protected/ private 불가). 넓히는 건 가능( protected → public 등).

  2. 정적/비정적 일치

    • 부모가 static이면 자식도 static이어야 하고, 인스턴스 메서드면 그대로 인스턴스 메서드여야 한다. 서로 바꾸면 에러. 근거: 정적 참조 규칙. 

  3. 타입 호환(variance)

    • 반공변(Contravariance): 매개변수 타입은 자식에서 더 넓게 받을 수 있다.

    • 공변(Covariance): 반환 타입은 자식에서 더 구체적으로 좁힐 수 있다.

    • 이 규칙은 PHP 7.4부터 정식 지원. 

  4. final 메서드/클래스는 오버라이드 불가.

  5. private 메서드는 상속되지 않으므로 “오버라이드”가 아니라 새 메서드 정의가 된다.

  6. 시그니처 검사 도구

    • PHP 8.3의 #[\Override] 속성을 오버라이드하는 메서드에 붙이면, 실제로 부모/인터페이스의 동일 메서드를 정확히 재정의하지 않으면 컴파일 에러가 나서 실수를 잡아준다. __construct()에는 사용할 수 없다. 

기본 예시

class Animal {
    public function speak(): string { return "sound"; }
}

class Dog extends Animal {
    #[\Override]                      // PHP 8.3+
    public function speak(): string { // 반환 타입 공변(동일/더 구체적 가능)
        return "woof";
    }
}

가시성 넓히기

class Base {
    protected function run(): void {}
}
class Child extends Base {
    #[\Override]
    public function run(): void {} // OK: protected → public (넓힘)
}

반공변 매개변수, 공변 반환타입

class User {}
class Admin extends User {}

class Repo {
    public function save(User $u): object { /*...*/ return new stdClass; }
}
class AdminRepo extends Repo {
    #[\Override]
    public function save(object $u): Admin { // 매개변수 넓힘(object), 반환 좁힘(Admin)
        // ...
        return new Admin();
    }
}

parent

의 사용

부모 구현 호출(확장)

class Logger {
    public function log(string $msg): void { echo "[base] $msg\n"; }
}
class FileLogger extends Logger {
    #[\Override]
    public function log(string $msg): void {
        parent::log($msg);               // 부모 동작 재사용
        file_put_contents('app.log', $msg.PHP_EOL, FILE_APPEND);
    }
}

생성자 연쇄 호출

  • 자식이 __construct()를 정의하면 부모 생성자는 자동으로 호출되지 않는다. 필요하면 parent::__construct()를 명시적으로 호출해야 한다. 

class Conn {
    public function __construct(protected string $dsn) {}
}

class UserRepo extends Conn {
    public function __construct(string $dsn, private string $table = 'users') {
        parent::__construct($dsn); // 직접 호출
    }
}

정적 맥락/상수/클래스명

class A { public const TAG = 'A'; public static function who(){ return __CLASS__; } }
class B extends A {
    public function demo(): void {
        echo parent::TAG, PHP_EOL;  // 상수
        echo parent::who(), PHP_EOL;// 정적 메서드
        echo parent::class, PHP_EOL;// 부모 FQCN 문자열
    }
}

parent는 현재 클래스의 직접 부모만 가리킨다. “조부모”를 바로 가리키는 parent::parent:: 같은 문법은 없고, 필요하면 조부모 클래스명을 직접 명시해 호출한다. 

프로퍼티(속성)에 관하여

  • PHP에서는 부모와 같은 이름의 프로퍼티를 자식에서 다시 선언할 수 있다. 다만 이는 “덮어쓰기”가 아니라 별도의 저장소가 생기는 것이며, 특히 부모의 private 프로퍼티는 애초에 상속되지 않기 때문에 같은 이름으로 선언해도 서로 독립적이다. 혼동을 피하려면 같은 이름으로 재선언하지 않는 것이 실무 권장.

self::

 / 

static::

 / 

parent::

 차이(정적 맥락)

  • self::는 정의된 클래스를,

  • static::는 호출 시점의 실제 클래스(Late Static Bindings) 를,

  • parent::는 직접 부모 클래스를 가리킨다. 공장 메서드, 싱글톤, ActiveRecord류에서는 보통 static::가 유용하다. 

class Base {
    public static function make(): static { return new static(); } // LSB
}
class Child extends Base {}
var_dump(get_class(Base::make()));  // "Base"
var_dump(get_class(Child::make())); // "Child"

트레이트와의 상호작용(요점)

  • 클래스가 트레이트를 사용할 때 메서드 결정 우선순위는 “클래스 > 트레이트 > 부모 클래스”다. 트레이트 내부에서 parent::method()를 호출하면, 트레이트를 포함한 클래스의 부모 체인에서 해당 메서드를 찾는다. (트레이트 자체에는 부모가 없다는 점만 유의.)

실수/버그 방지 체크리스트

  1. 시그니처가 부모와 호환되는지(가시성·정적성·타입) 확인.

  2. 자식 생성자에서 부모 초기화가 필요하면 parent::__construct()를 꼭 호출

  3. 정적 맥락 분기 필요 시 static::(LSB)와 parent::의 차이를 의도대로 사용. 

  4. 오버라이드 의도를 명확히 하려면 PHP 8.3의 #[\Override] 사용. 

  5. 같은 이름의 프로퍼티 재선언은 피한다(혼동과 디버깅 비용↑).


댓글