PHP 트레이트는 재사용 가능한 메서드와 속성 묶음입니다. trait Name { ... }으로 선언하고 클래스 본문 안에서 use Name;으로 어느 클래스에든 가져옵니다. PHP가 트레이트의 멤버를 클래스에 복사하므로 그곳에 직접 쓴 것과 정확히 같이 동작합니다.
User와 Robot에는 공통 부모가 없지만 이제 둘 다 hello()를 가집니다. 트레이트는 이런 경우를 위해 존재합니다. PHP 클래스는 클래스를 하나만 상속할 수 있고, 트레이트는 관련 없는 클래스 사이에서 옆으로 코드를 공유합니다.
속성이 있는 트레이트
트레이트는 메서드와 함께 속성도 가져올 수 있습니다. 트레이트가 복사되면 그 메서드는 클래스의 메서드이므로 다른 메서드처럼 $this를 씁니다.
클래스는 트레이트 속성과 같은 이름의 속성을 선언할 수 없습니다. 두 선언이 동일하면(같은 접근 제어, 타입, 기본값) 예외이며, 그렇지 않으면 치명적 에러입니다.
여러 트레이트 쓰기
use 문 하나에 쉼표로 구분해 여러 트레이트를 나열하거나 use 줄을 여러 개 쓰세요. 클래스는 모든 트레이트의 멤버를 받습니다.
insteadof와 as로 충돌 해결하기
두 트레이트가 같은 이름의 메서드를 정의하면 PHP는 추측하지 않고 치명적 에러와 함께 멈춥니다. use 블록 안에서 insteadof는 이길 쪽을 고르고, as는 진 메서드를 다른 이름으로 남깁니다.
블록 없이 그냥 use EnglishGreeting, FrenchGreeting;을 쓰면 스크립트는 아무것도 출력하기 전에 멈춥니다.
PHP Fatal error: Trait method FrenchGreeting::greet has not been applied as Host::greet, because of collision with EnglishGreeting::greet in /home/index.php on line 12
as는 접근 제어도 바꿀 수 있습니다. use Loggable { log as protected; }는 이 클래스에서만 트레이트의 log()를 protected로 만들고, use Loggable { log as private writeLog; }는 private 별칭을 추가합니다.
트레이트의 추상 멤버와 정적 멤버
트레이트는 추상 메서드를 선언해 자신을 쓰는 클래스에 무언가를 요구할 수 있습니다. 정적 메서드와 정적 속성도 가질 수 있습니다. 트레이트를 쓰는 각 클래스는 정적 속성의 자기 복사본을 받습니다.
정적 속성이 각 클래스에 복사되므로 Car와 Bike는 따로 셉니다. 모든 자식이 공유하는, 공통 부모 클래스에 선언한 정적 속성과는 다릅니다. 그 경우는 static 페이지에서 보여 줍니다.
어느 메서드가 이기나: 클래스, 트레이트, 부모
같은 메서드 이름이 클래스 자신, 클래스가 쓰는 트레이트, 부모 클래스에 모두 있으면 PHP는 클래스 자신의 메서드, 트레이트의 것, 부모의 것 순서로 고릅니다. 트레이트 안에서 parent::는 여전히 부모의 버전에 닿습니다.
트레이트는 타입이 아니다
instanceof는 트레이트와 절대 일치하지 않으며, 트레이트 이름으로 타입을 지정한 매개변수는 모든 객체를 TypeError로 거부합니다. "이 클래스에 그 동작이 있는가"를 묻고 싶다면 트레이트를 인터페이스와 짝짓거나 class_uses()로 클래스를 살펴보세요.
위의 패턴이 일반적입니다. 인터페이스 Exportable은 함수가 타입 힌트로 쓰고 instanceof가 확인하는 것이고, 트레이트 ExportsProperties는 클래스가 쓰거나 대체할 수 있는 기성 구현입니다. 트레이트만으로는 계약 없는 공유 코드입니다. 비즈니스 로직에서 class_uses()를 확인하고 있다면 그 트레이트 옆에 인터페이스가 필요했던 것입니다. 관련된 클래스 가족이 공유하는 코드라면 추상 클래스가 더 간단한 선택일 수 있습니다.
자주 묻는 질문
PHP의 트레이트는 무엇인가요?
트레이트는 trait Name { }으로 선언한 이름 있는 메서드(와 속성) 묶음이며, 클래스가 use Name;으로 가져옵니다. PHP가 트레이트의 멤버를 클래스에 복사하므로 그곳에 직접 쓴 것처럼 동작합니다. 공통 부모가 없는 클래스 사이에 코드를 공유하는 방법입니다.
PHP 클래스가 여러 트레이트를 쓸 수 있나요?
네: use Timestamps, SoftDeletes; 또는 use 줄 여러 개. 두 트레이트가 같은 이름의 메서드를 정의하면 insteadof로 하나를 골라야 합니다. 예: use A, B { A::hello insteadof B; }. 다른 하나는 B::hello as helloB;로 새 이름 아래 남길 수 있습니다.
PHP에서 트레이트와 인터페이스의 차이는 무엇인가요?
인터페이스는 코드가 없는 타입입니다. 클래스가 어떤 메서드를 가져야 하는지 말하며 instanceof와 함께 동작합니다. 트레이트는 타입이 없는 코드입니다. 메서드 본문을 클래스에 붙여 넣지만 $obj instanceof SomeTrait는 항상 거짓입니다. 흔히 함께 씁니다. 타입에는 인터페이스를, 기본 구현에는 트레이트를 씁니다.
트레이트에 생성자나 추상 메서드가 있을 수 있나요?
둘 다 가능합니다. 트레이트는 abstract public function name(): string;을 선언해 사용하는 클래스가 그 메서드를 제공하도록 요구할 수 있고, __construct도 정의할 수 있습니다. 다만 트레이트의 생성자는 클래스 자신의 생성자와 충돌하기 쉬워 좋은 생각인 경우가 드뭅니다.
클래스와 트레이트가 같은 메서드를 정의하면 어느 것이 이기나요?
클래스 자신의 메서드가 트레이트의 것을 이기고, 트레이트의 메서드는 부모 클래스에서 상속한 메서드를 이깁니다. 그래서 순서는 클래스, 트레이트, 부모입니다.