본문 바로가기

비개발자의 개발 일지

현장 팀장에서 수출 ERP까지 — AI와 함께 시스템을 만든 기록

시리즈 보기
php

PHP trait 완전 정리 — 수평상속으로 다중 기능을 조합하는 방법

by 왕진 2026. 7. 12.
반응형

 

 

Web Development · PHP OOP

PHP trait 완전 정리

수평상속(trait)으로 단일 상속의 한계를 넘어 여러 기능을 자유롭게 조합하는 방법
시작 오늘 분석할 코드

SayMorning이라는 trait을 만들고, User 부모 클래스를 수직상속받은 Guest 자식 클래스가 이 trait도 함께 사용하는 패턴이다. 수직상속과 수평상속이 동시에 적용된다.

trait SayMorning { // 수평상속 — trait 선언 public function print_morning() { print $this->name . "님, 안녕하세요!<BR>"; } } class User { // 수직상속 — 부모 클래스 private $name = null; public function print_hello() { print $this->name . "님, 반갑습니다!!<BR>"; } } class Guset extends User { // 수직상속: extends User use SayMorning; // 수평상속: use trait private $name = "게스트"; public function print_hello() { // 오버라이딩 print $this->name . "님, 처음 뵙겠습니다!<BR>"; } } $newuser = new Guset(); $newuser->print_hello(); // 오버라이딩된 자식 메서드 실행 $newuser->print_morning(); // trait 메서드 실행
실제 출력 미리보기
게스트님, 처음 뵙겠습니다!
게스트님, 안녕하세요!

→ print_hello는 오버라이딩된 Guest 버전, print_morning은 SayMorning trait에서 가져온 메서드

용어 핵심 용어 정리
traitPHP의 수평상속 단위. 클래스가 아니지만 메서드 묶음을 여러 클래스에 끼워 넣을 수 있다
수직상속extends 키워드로 부모-자식 계층을 만드는 상속. 부모 클래스는 1개만 가능
수평상속use 키워드로 trait을 끼워 넣는 방식. 계층 관계 없이 기능을 조합
use클래스 안에서 trait을 사용할 때 쓰는 키워드. use SayMorning;처럼 선언
믹스인(Mixin)trait과 유사한 개념. 기능 조각을 여러 클래스에 섞어 넣는 설계 패턴
단일 상속PHP는 extends로 부모 클래스를 1개만 지정 가능. C++과 달리 다중 상속 불가
인터페이스메서드 목록만 정의하는 계약 구조. Java의 수평 확장 수단. PHP에서는 trait과 역할이 다름
오버라이딩자식 클래스에서 부모와 같은 이름의 메서드를 재정의. 런타임에 자식 버전이 선택됨
insteadof두 trait에 같은 이름의 메서드가 충돌할 때 어떤 trait 버전을 쓸지 지정. A::method insteadof B;
as충돌을 피하기 위해 trait 메서드에 별칭을 부여. B::method as methodB;처럼 다른 이름으로 접근 가능

1 수직상속의 한계 — 왜 trait이 필요한가
부모 클래스는 1개만 지정할 수 있다

PHP(그리고 Java)는 안정성을 위해 extends로 지정할 수 있는 부모 클래스를 딱 1개로 제한한다. C++처럼 여러 부모를 동시에 상속받는 "다중 상속"을 허용하지 않는다. 그런데 실제 개발에서는 로그 기능, 알림 기능, 인증 기능처럼 여러 클래스에서 공통으로 쓰고 싶은 기능이 생긴다. 이때 trait을 사용한다.

extends (수직상속, 1개 제한)
class Guest extends User { }

부모 클래스는 딱 1개. 계층 구조를 만든다. 부모의 모든 메서드·변수를 상속받는다.
use trait (수평상속, 여러 개 가능)
use SayMorning, SayEvening;

trait은 여러 개를 동시에 끼워 넣을 수 있다. 계층 없이 기능 조합.
※ trait은 "부모"가 아니라 "도구 상자"다. 클래스가 필요한 기능을 골라서 가져오는 방식이다.
class Guest
extends User
↑ 수직
class User
← 수평
trait SayMorning

2 trait 선언과 use 키워드
trait은 class처럼 선언하고 use로 끼워 넣는다

trait은 trait 이름 { } 형식으로 선언한다. 클래스와 모양이 비슷하지만 직접 객체를 만들 수 없다. 클래스 안에서 use 트레이트명;을 쓰면 그 클래스에 trait의 메서드들이 복사된 것처럼 동작한다. $this도 그대로 사용 가능하다.

trait SayMorning { public function print_morning() { print $this->name . "님, 안녕하세요!"; // trait은 $this를 쓸 수 있다 — 사용되는 클래스의 $this를 가리킴 } } class Guset extends User { use SayMorning; // trait 삽입 — print_morning() 메서드를 갖게 됨 private $name = "게스트"; } $g = new Guset(); $g->print_morning(); // trait 메서드 호출 가능
※ trait 안의 $this->name은 Guest 객체의 $name = "게스트"를 가리킨다. trait은 삽입된 클래스의 컨텍스트에서 실행된다.

3 수직 + 수평 동시 적용
extends와 use를 함께 쓰는 패턴

PHP에서는 extends로 부모 클래스를 수직 상속받으면서, use로 trait을 수평 상속받는 것을 동시에 할 수 있다. Guest 클래스는 User의 메서드를 물려받으면서도 SayMorning trait의 메서드까지 사용할 수 있다.

이 예제의 Guset 클래스(오타이지만 원본 코드 그대로)는 User를 extends로, SayMorning을 use로 — 두 방향의 상속을 동시에 적용하는 전형적인 패턴이다. extends는 클래스명 뒤에, use는 클래스 body 첫 줄에 위치한다.

class Guset extends User { // 수직상속: User의 메서드 물려받음 use SayMorning; // 수평상속: SayMorning 메서드 끼워 넣음 private $name = "게스트"; public function print_hello() { // User::print_hello 오버라이딩 print $this->name . "님, 처음 뵙겠습니다!"; } } // Guest 객체가 사용 가능한 메서드: // - print_hello(): 오버라이딩한 자기 버전 // - print_morning(): SayMorning trait에서 가져온 것

 

반응형

 

4 trait vs 인터페이스 — 비슷해 보이지만 다르다
trait은 구현 포함, interface는 선언만

Java의 인터페이스는 메서드 목록(시그니처)만 정의하고 실제 구현은 각 클래스가 직접 작성해야 한다. PHP의 trait은 메서드 구현 코드 자체를 담고 있어서 클래스에 use하면 그 구현이 바로 사용 가능하다. "계약 vs 재사용 코드 묶음" 차이다.

비교 항목 trait (PHP) interface (Java/PHP)
메서드 구현 포함 ✅ 포함 — use하면 바로 사용 ❌ 선언만 — 각 클래스가 직접 구현
여러 개 동시 적용 ✅ use A, B, C; 가능 ✅ implements A, B, C; 가능
직접 객체 생성 ❌ 불가 ❌ 불가
주요 목적 코드 재사용 (공통 기능 배포) 타입 계약 (구현 강제)
PHP에서 사용 키워드 trait / use interface / implements

5 trait 여러 개 동시 사용
use에 쉼표로 나열해서 여러 trait을 한 번에 적용

trait의 가장 큰 장점 중 하나는 여러 개를 동시에 끼워 넣을 수 있다는 점이다. use A, B, C; 형식으로 나열하면 모든 trait의 메서드를 한 클래스에서 사용할 수 있다. 이를 통해 부모 클래스 제한 없이 필요한 기능을 자유롭게 조합할 수 있다.

실무에서 trait은 주로 횡단 관심사(Cross-cutting Concern) — 로깅, 인증 확인, 캐싱, 이벤트 발송처럼 여러 클래스에 흩어지면서도 핵심 비즈니스 로직과는 분리되는 기능 — 을 묶을 때 활용된다.

trait Logging { public function log($msg) { /* 로그 기록 */ } } trait Notification { public function notify($msg) { /* 알림 발송 */ } } class OrderService extends Service { use Logging, Notification; // 두 trait 동시 적용 // log()와 notify() 모두 사용 가능 }

6 trait 충돌 해결 — insteadof와 as 키워드
두 trait에 같은 이름의 메서드가 있을 때

여러 trait을 동시에 사용할 때 같은 이름의 메서드가 충돌하면 PHP는 Fatal error를 낸다. insteadof로 어떤 trait의 버전을 사용할지 지정하고, as로 충돌된 쪽 메서드에 별칭을 부여해 두 버전 모두 접근할 수 있다.

trait A { public function hello() { print "A의 hello"; } } trait B { public function hello() { print "B의 hello"; } } class MyClass { use A, B { A::hello insteadof B; // A의 hello를 기본으로 사용 B::hello as helloB; // B의 hello는 helloB라는 이름으로 접근 } } $obj = new MyClass(); $obj->hello(); // A의 hello $obj->helloB(); // B의 hello
※ insteadof·as 블록 없이 같은 이름을 그냥 쓰면 Fatal error가 난다. 충돌을 명시적으로 해결해 줘야 한다. 이름을 처음부터 겹치지 않게 설계하는 것이 가장 간단한 해결책이다.

7 자주 하는 실수
실수 증상 해결
new SayMorning() 로 trait 직접 객체화 시도 Cannot instantiate trait 오류 trait은 독립 객체 생성 불가. 반드시 클래스 안에서 use로 사용
trait 메서드와 클래스 메서드 이름이 충돌 Fatal error: trait method conflict insteadof / as 키워드로 충돌 해결 또는 메서드명을 다르게 지정
trait 안에서 $this->변수가 null 출력이 비어 있음 $this는 trait이 삽입된 클래스의 멤버변수를 가리킴. 클래스에 해당 변수가 선언돼 있는지 확인
extends 와 use 순서를 바꿔 씀 Parse error extends가 먼저, use는 클래스 body 안에서 선언해야 함
trait에 생성자(__construct)를 넣고 클래스 생성자와 충돌 예상치 못한 초기화 순서 trait에는 생성자 대신 init 메서드를 만들고 클래스에서 명시적으로 호출 권장

핵심 한 줄 요약

traitPHP의 수평상속 단위 — 클래스가 아니지만 메서드 구현을 담아 여러 클래스에 배포
extends vs useextends=수직상속(1개 부모), use=수평상속(여러 trait 동시 적용)
trait의 $this삽입된 클래스의 컨텍스트에서 동작 — 클래스의 멤버변수에 접근 가능
trait vs interfacetrait=구현 포함 재사용 코드, interface=선언만 있는 타입 계약
핵심 패턴class A extends B { use C, D; } — 수직+수평 동시 적용 가능
충돌 해결insteadof로 우선 버전 지정, as로 충돌 메서드에 별칭 부여 — 두 버전 모두 사용 가능

Tags

#PHP trait #수평상속 #수직상속 #PHP 다중상속 #use 키워드 #extends #PHP interface #PHP OOP #믹스인 #PHP 클래스 #객체지향 프로그래밍 #웹개발 #티스토리
▼ 티스토리 태그 입력란 복사용
PHP trait, 수평상속, 수직상속, PHP 다중상속, use 키워드, extends, PHP interface, PHP OOP, 믹스인, PHP 클래스
반응형

댓글