| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 |
- mcp server
- 어댑터패턴
- ai agent
- docker network
- dhgrp
- Agent Loop
- SQL
- docker
- linux 권한
- 객체지향
- LLM
- Tool Calling
- MySQL
- SQL문법
- 객체복제
- 라라벨
- PHP객체지향
- PHP
- SQL기초
- MCP
- ai 에이전트
- mcp client
- Laravel
- 인덱스
- 데코레이터패턴
- 데이터베이스
- 소프트웨어설계
- 매직메서드
- OOP
- 디자인패턴
- Today
- Total
개발블로그
[객체지향] PHP Trait 본문
개념
필요성
서로 다른 클래스에서 같은 기능이 필요한 경우가 있음.
ex) 사용자 관리 클래스와 파일 처리 클래스가 모두 로그를 기록한다고 할 때,
각 클래스에 같은 메서드를 작성하면 중복이 생기고, 기능이 바뀔 때 여러 곳을 수정해야 함
개념
여러 클래스에 포함하여 재사용할 수 있는 메서드와 속성의 묶음
- Trait을 포함한 클래스에서는 해당 메서드를 자신의 메서드처럼 사용할 수 있음.
문법
기본 문법
trait TraitName
{
// 속성 정의
private string $propertyName = "초기값";
// 메서드 정의
public function methodName(): string
{
return $this->propertyName;
}
}
class ClassName
{
use TraitName;
}
$object = new ClassName();
echo $object->methodName(); // 초기값
ex)
trait LoggerTrait
{
private string $prefix = "[LOG]";
public function log(string $message): void
{
echo "{$this->prefix} {$message}\n";
}
}
class UserService
{
use LoggerTrait;
}
class FileService
{
use LoggerTrait;
}
$userService = new UserService();
$fileService = new FileService();
$userService->log("사용자 생성");
$fileService->log("파일 저장");
/*
[LOG] 사용자 생성
[LOG] 파일 저장
*/
여러 클래스에서 여러 Trait을 함께 사용할 수 있음
trait FirstTrait
{
public function firstMethod(): void
{
// 첫 번째 기능
}
}
trait SecondTrait
{
public function secondMethod(): void
{
// 두 번째 기능
}
}
class ClassName
{
use FirstTrait, SecondTrait;
}
ex)
trait LoggerTrait
{
public function log(string $message): void
{
echo "[LOG] {$message}\n";
}
}
trait NotifierTrait
{
public function notify(string $message): void
{
echo "[알림] {$message}\n";
}
}
class UserService
{
use LoggerTrait, NotifierTrait;
public function register(): void
{
$this->log("사용자 등록");
$this->notify("가입 완료");
}
}
$service = new UserService();
$service->register();
메소드 충돌 해결
여러 Trait에 같은 이름의 메서드가 있으면, 어떤 구현을 사용할지 명시해야 함.
| 키워드 | 역할 |
| insteadof | 충돌하는 메서드 중 사용할 구현 선택 |
| as | 메서드에 별칭 추가 |
class ClassName
{
use FirstTrait, SecondTrait {
FirstTrait::methodName insteadof SecondTrait;
// methodName을 호출하면, SecondTrait대신 FirstTrait의 구현을 사용하도록 지정
SecondTrait::methodName as anotherMethod;
// SecondTrait의 methodName를 anotherMethod의 이름으로 사용할 수 있도록 별칭 추가
}
}
ex)
trait ConsoleLoggerTrait
{
public function log(string $message): void
{
echo "[일반] {$message}\n";
}
}
trait JsonLoggerTrait
{
public function log(string $message): void
{
echo json_encode(["message" => $message]) . "\n";
}
}
class UserService
{
use ConsoleLoggerTrait, JsonLoggerTrait {
ConsoleLoggerTrait::log insteadof JsonLoggerTrait;
JsonLoggerTrait::log as logJson;
}
}
$service = new UserService();
$service->log("created");
$service->logJson("created");
/*
[일반] created
{"message":"created"}
*/
접근 제어자와 별칭 지정
Trait을 클래스에 포함할 때 as를 사용하면 메서드의 접근 범위를 변경하거나, 호출할 이름을 추가할 수 있음
use TraitName {
methodName as private;
// 기존 메서드의 접근 범위 변경
}
use TraitName {
methodName as aliasName;
// 기존 메서드에 별칭 추가
}
use TraitName {
methodName as private aliasName;
// private 접근 범위를 가진 별칭 추가
}
ex)
trait LoggerTrait
{
public function log(string $message): void
{
echo "[LOG] {$message}\n";
}
}
/*
접근 범위만 변경
*/
class UserService
{
// 기존 log()가 private가 됨. 이름은 그대로이며 외부 호출은 제한 됨
use LoggerTrait {
log as private;
}
}
/*
별칭만 추가
*/
class UserService
{
//기존 log()를 유지하면서 writeLog()라는 호출 이름을 추가한다. 둘 다 public이며 같은 구현을 실행한다.
use LoggerTrait {
log as writeLog;
}
}
/*
별칭의 접근 범위까지 지정
*/
class UserService
{
use LoggerTrait {
log as private writeLog;
}
}
Trait의 추상메서드
Trait에 추상 메서드를 선언하면, Trait을 사용하는 클래스가 제공해야 할 메서드를 지정할 수 있다.
=> Trait은 해당 메서드를 호출하는 공통 흐름을 구현하고, 구체적인 동작은 사용하는 클래스에 맡긴다
trait TraitName
{
// abstract = 메서드 구현이 필요함을 선언하는 키워드
abstract protected function requiredMethod(): string;
}
class ClassName
{
use TraitName;
protected function requiredMethod(): string
{
return "구현 결과";
}
}
ex) 로그 출력 방식은 공통으로 사용하고, 로그에 표시할 서비스 이름은 각 클래스에서 정의하도록 함
trait LoggerTrait
{
abstract protected function getLogName(): string;
public function log(string $message): void
{
echo "[{$this->getLogName()}] {$message}\n";
}
}
class UserService
{
use LoggerTrait;
protected function getLogName(): string
{
return "USER";
}
}
class FileService
{
use LoggerTrait;
protected function getLogName(): string
{
return "FILE";
}
}
$userService = new UserService();
$fileService = new FileService();
$userService->log("사용자 생성");
$fileService->log("파일 저장");
/*
[USER] 사용자 생성
[FILE] 파일 저장
*/
메서드 우선순위
클래스에 직접 정의한 메서드
↓ 우선
Trait에서 포함한 메서드
↓ 우선
부모 클래스에서 상속받은 메서드
※ 위 순서는 정상적으로 재정의할 수 있는 메서드를 기준으로 함.
부모 메서드가 final이거나 선언이 호환되지 않는 경우에는 우선순위로 해결되지 않고 오류가 발생할 수 있음
주의점
이름충돌
여러 Trait을 포함하면 같은 이름의 메서드나 속성이 겹칠 수 있음
=> Trait을 추가할 때는 현재 클래스와 다른 Trait에 이미 같은 이름이 있는지 확인해야 함
숨겨진 의존 관계
Trait이 사용하는 클래스의 특정 속성이나 메서드를 당연히 존재한다고 가정하면,
필요한 조건을 파악하기 어려워짐
=> 필요한 속성을 Trait에 선언하거나, 클래스별 값이 필요하다면 추상 메서드로 요구사항을 명시할 수 있음
과도한 기능 포함
클래스에 Trait을 많이 넣으면 실제로 어떤 속성과 메서드를 가지는지 확인하기 어려워질 수 있음
| 확인 항목 | 판단 기준 |
| 기능의 관련성 | Trait 내부 기능들이 같은 역할에 속하는가? |
| 사용 범위 | 포함한 클래스에서 실제로 필요한 기능인가? |
| 코드 추적 | 메서드의 정의 위치를 쉽게 찾을 수 있는가? |
| 변경 영향 | Trait 수정이 사용하는 클래스들에 어떤 영향을 주는가? |
객체 조합과의 구분
Trait은 클래스 정의에 구현을 포함하는 방식이다.
실행 중에 Trait을 다른 Trait으로 교체하는 방식으로 사용하지 않음.
=> 실행 시점에 구현을 선택하거나 교체해야 한다면, 별도의 객체를 전달받아 사용하는 구조가 더 합할 수 있음
'STUDY > PHP' 카테고리의 다른 글
| [객체지향] PHP readonly 속성과 클래스 (0) | 2026.09.06 |
|---|---|
| [객체지향] PHP final 키워드 (0) | 2026.09.05 |
| PHP 변수 대입과 객체 복제 (1) | 2026.09.05 |
| [객체지향] PHP 매직 메서드 (0) | 2026.09.05 |
| [객체지향] PHP static과 스코프 해석 연산자 (0) | 2026.09.05 |