오버라이드(override): 자식 클래스가 부모로부터 상속받은 메서드와 같은 시그니처(이름·정적성·가시성 호환·타입 호환)를 가진 메서드를 재정의하는 것. 재정의하지 않은 멤버는 부모 동작을 그대로 사용한다.
parent 키워드: 자식 클래스의 코드 안에서 부모 클래스의 멤버(메서드·정적 프로퍼티·상수)를 가리킬 때 사용한다. parent::메서드(), parent::__construct(), parent::$staticProp, parent::CONST, parent::class 등이 가능하다.
가시성(visibility)
부모보다 좁게 만들 수 없다(public → protected/ private 불가). 넓히는 건 가능( protected → public 등).
정적/비정적 일치
부모가 static이면 자식도 static이어야 하고, 인스턴스 메서드면 그대로 인스턴스 메서드여야 한다. 서로 바꾸면 에러. 근거: 정적 참조 규칙.
타입 호환(variance)
반공변(Contravariance): 매개변수 타입은 자식에서 더 넓게 받을 수 있다.
공변(Covariance): 반환 타입은 자식에서 더 구체적으로 좁힐 수 있다.
이 규칙은 PHP 7.4부터 정식 지원.
final 메서드/클래스는 오버라이드 불가.
private 메서드는 상속되지 않으므로 “오버라이드”가 아니라 새 메서드 정의가 된다.
시그니처 검사 도구
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();
}
}
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::는 호출 시점의 실제 클래스(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()를 호출하면, 트레이트를 포함한 클래스의 부모 체인에서 해당 메서드를 찾는다. (트레이트 자체에는 부모가 없다는 점만 유의.)
시그니처가 부모와 호환되는지(가시성·정적성·타입) 확인.
자식 생성자에서 부모 초기화가 필요하면 parent::__construct()를 꼭 호출.
정적 맥락 분기 필요 시 static::(LSB)와 parent::의 차이를 의도대로 사용.
오버라이드 의도를 명확히 하려면 PHP 8.3의 #[\Override] 사용.
같은 이름의 프로퍼티 재선언은 피한다(혼동과 디버깅 비용↑).