트레이트(Trait)란 무엇인가?
PHP는 다중 상속(multiple inheritance)을 지원하지 않습니다. 하나의 클래스는 단 하나의 부모 클래스만 상속받을 수 있기 때문에, 여러 곳에서 코드를 재사용하기 어려운 제약이 있었습니다. 이러한 한계를 어느 정도 극복할 수 있게 해주는 기능이 바로 트레이트(trait)입니다.
트레이트는 코드 재사용을 위한 메커니즘으로, 정의 방식은 클래스와 매우 유사합니다. 하지만 트레이트는 직접 인스턴스화할 수 없으며, 대신 use 키워드를 통해 클래스 안에 그 기능을 불러와 사용합니다. 클래스는 트레이트에 정의된 메서드를 그대로 사용할 수도 있고, 필요하다면 자신만의 로직으로 재정의(오버라이드)할 수도 있습니다. 이 모든 것이 기존에 상속받는 부모 클래스와는 별개로 동작합니다.
트레이트는 인터페이스(interface)와도 비슷한 면이 있습니다. 다만 중요한 차이점이 있습니다. 인터페이스는 메서드의 시그니처만 선언할 뿐 구현체를 포함하지 않아, 반드시 구현 클래스가 내용을 작성해야 합니다. 반면 트레이트의 메서드는 실제 구현 코드를 포함하고 있으며, 이를 사용하는 클래스에서 재정의할 수도 있고 그대로 가져다 쓸 수도 있습니다.
기본 문법
트레이트는 trait 키워드로 선언하며, 클래스 내부에서 use 키워드로 불러옵니다.
<?php
trait testtrait {
public function test1() {
// 메서드 본문
}
}
// 트레이트 사용
class testclass {
use testtrait;
// 클래스의 나머지 멤버들
}
?>기본 예제
다음 예제에서는 두 개의 메서드를 가진 트레이트를 클래스에서 사용하고, 그중 하나의 메서드를 오버라이드합니다.
<?php
// 트레이트 정의
trait testtrait {
public function test1() {
echo "test1 method in trait\n";
}
public function test2() {
echo "test2 method in trait\n";
}
}
// 트레이트 사용
class testclass {
use testtrait;
public function test1() {
echo "test1 method overridden\n";
}
}
$obj = new testclass();
$obj->test1();
$obj->test2();
?>실행 결과
클래스에서 재정의한 test1()이 우선 적용되고, 재정의하지 않은 test2()는 트레이트의 원본 메서드가 그대로 실행됩니다.
test1 method overridden test2 method in trait
자식 클래스에서 트레이트 활용하기
트레이트의 가장 큰 장점은 바로 여기에 있습니다. 부모 클래스를 상속받는 클래스도 동시에 트레이트를 사용할 수 있으며, 필요에 따라 트레이트나 부모 클래스의 메서드를 각각 오버라이드할 수 있습니다. 이를 통해 사실상 다중 상속과 같은 효과를 얻을 수 있습니다.
<?php
// 트레이트 정의
trait testtrait {
public function test1() {
echo "test1 method in trait\n";
}
}
// 부모 클래스
class parentclass {
public function test2() {
echo "test2 method in parent\n";
}
}
// 부모 클래스 상속 + 트레이트 사용
class childclass extends parentclass {
use testtrait;
public function test1() {
echo "parent method overridden\n";
}
public function test2() {
echo "trait method overridden\n";
}
}
$obj = new childclass();
$obj->test1();
$obj->test2();
?>실행 결과
자식 클래스에서 트레이트 메서드와 부모 클래스 메서드를 모두 오버라이드했기 때문에, 두 호출 모두 자식 클래스의 메서드가 실행됩니다.
parent method overridden trait method overridden
트레이트와 인터페이스 함께 사용하기
하나의 클래스가 인터페이스를 구현하고, 다른 부모 클래스를 상속하면서, 동시에 트레이트까지 사용하는 것도 가능합니다. 세 가지 요소를 조합하면 유연하면서도 체계적인 구조 설계가 가능합니다.
<?php
// 트레이트 정의
trait mytrait {
public function test1() {
echo "test1 method in trait1\n";
}
}
// 부모 클래스
class myclass {
public function test2() {
echo "test2 method in parent class\n";
}
}
// 인터페이스 정의
interface myinterface {
public function test3();
}
// 트레이트 + 부모 클래스 + 인터페이스 동시 사용
class testclass extends myclass implements myinterface {
use mytrait;
public function test3() {
echo "implementation of test3 method\n";
}
}
$obj = new testclass();
$obj->test1();
$obj->test2();
$obj->test3();
?>실행 결과
test1 method in trait1 test2 method in parent class implementation of test3 method
메서드 충돌 해결 – insteadof 키워드
하나의 클래스가 이름이 같은 메서드를 가진 두 개의 트레이트를 함께 사용하면 충돌이 발생합니다. 이때는 범위 결정 연산자(::)와 insteadof 키워드를 사용해 어떤 트레이트의 메서드를 우선 사용할지 명시적으로 지정할 수 있습니다.
<?php
trait trait1 {
public function test1() {
echo "test1 method in trait1\n";
}
public function test2() {
echo "test2 method in trait1\n";
}
}
trait trait2 {
public function test1() {
echo "test1 method in trait2\n";
}
public function test2() {
echo "test2 method in trait2\n";
}
}
// 두 트레이트 사용 및 충돌 해결
class testclass {
use trait1, trait2 {
trait1::test1 insteadof trait2;
trait2::test2 insteadof trait1;
}
}
$obj = new testclass();
$obj->test1();
$obj->test2();
?>실행 결과
test1()은 trait1의 메서드가, test2()는 trait2의 메서드가 선택되어 실행됩니다.
test1 method in trait1 test2 method in trait2
정리
트레이트는 PHP에서 다중 상속의 부재를 보완하는 강력한 코드 재사용 도구입니다. 핵심 특징을 요약하면 다음과 같습니다.
• 트레이트는 직접 인스턴스화할 수 없으며, use 키워드로 클래스에 삽입됩니다.
• 트레이트의 메서드는 구현체를 포함하며, 클래스에서 자유롭게 오버라이드할 수 있습니다.
• 부모 클래스 상속과 동시에 사용 가능하여 사실상 다중 상속 효과를 냅니다.
• 인터페이스 구현과도 함께 조합할 수 있습니다.
• 메서드 이름이 충돌할 경우 insteadof 키워드로 우선순위를 지정해 해결합니다.