본문 바로가기
PHP

[PHP] Traits (트레이트) - 코드를 재사용하는 방법

by teamnova 2025. 11. 17.
728x90

개념설명

트레이트는 클래스 기능(메소드 묶음)을 주입하기 위한 코드 재사용 메커니즘 입니다.

PHP는 객체지향 언어처럼 단일 상속만 지원합니다

즉, 하나의 클래스는 오직 하나의 부모 클래스만 extends할 수 있습니다.

 

서로 상속관계가 없는 여러 클래스에서 공통된 기능을 사용하고 싶을때가 있는데 예를들어 로그기록(logger), 파일업로드(uploader) 등 같은 기능들은 User, Product 다양한 클래스에서 필요할수있음..

 

이때 트레이트를 사용하면 상속 관계와 무관하게 원하는 클래스에서 사용 할 수 있습니다.

음.. 클래스에 필요한 기능들을 플러그인을 하는 것과 비슷합니다.

 

extends(상속) vs use(트레이트)
extends (상속): is a 관계 (자식 is a 부모). 클래스 간의 수직적 계층 관계를 형성합니다. (예: Cat is an Animal)
use (트레이트): can do 관계 (클래스 can do 기능). 클래스에 특정 행동/능력을 수평적으로 추가합니다. (예: User can do Logging)

 

1. 트레이트 사용

트레이트는 tarit 키워드로 정의하고 클래스에서는 user 키워드로 가져와 사용합니다.

<?php
// 1. 재사용할 기능을 담은 트레이트를 정의합니다.
trait Sharable
{
    public function share(string $item): string
    {
        return "Sharing item: '{$item}' to social media.";
    }
}

// 2. 클래스에서 'use' 키워드로 트레이트를 포함시킵니다.
class Post
{
    use Sharable; // Sharable 트레이트의 모든 메소드를 Post 클래스에 주입
    public string $title;
}

class Photo
{
    use Sharable;
    public string $url;
}

// 3. 이제 트레이트의 메소드를 클래스 자신의 메소드처럼 사용할 수 있습니다.
$post = new Post();
echo $post->share("My new blog post") . PHP_EOL;

$photo = new Photo();
echo $photo->share("A picture from my vacation") . PHP_EOL;
?>

 

2. 여러 트레이트를 사용하거나 클래스간에 메소드 이름이 충돌 할 수 있습니다. PHP는 해결 하기 위해 규칙과 방법을 제공

<?php
trait Logger
{
    public function log(string $message): void
    {
        echo "[로그] {$message}" . PHP_EOL;
    }
}

trait Notifiable
{
    public function notify(string $message): void
    {
        echo "[NOTIFICATION] 이메일 전송: {$message}" . PHP_EOL;
    }
}

class Order
{
    // 여러 트레이트를 한 번에 사용
    use Logger, Notifiable;

    public function placeOrder(): void
    {
        $this->log("새로운 주문이 접수되었습니다.");
        // ... 주문 처리 로직 ...
        $this->notify("주문이 확인 되었습니다.");
    }
}

$order = new Order();
$order->placeOrder();
?>

3. 메소드 충돌 해결

서로 다른 트레이트가 같은 이름의 메소드를 가지고 있을 때 그대로 use하면 오류가 발생합니다.

isteadof와 as키워드를 사용해 어떤 메서드를 사용할지 명시적으로 지정.

 

<?php
trait TraitA
{
    public function doSomething(): void
    {
        echo "특성 A에서 무언가를 수행 \ " . PHP_EOL;
    }
}

trait TraitB
{
    public function doSomething(): void
    {
        echo "특성 B에서 무언가를 수행 \ " . PHP_EOL;
    }
}

class MyClass
{
    use TraitA, TraitB {
        // TraitA의 doSomething을 사용하고, TraitB의 것은 무시함
        TraitA::doSomething insteadof TraitB;

        // TraitB의 doSomething 메소드는 'doSomethingFromB'라는 새 이름(별칭)으로 사용함
        TraitB::doSomething as doSomethingFromB;
    }
}

$obj = new MyClass();
$obj->doSomething();       // TraitA의 메소드 호출
$obj->doSomethingFromB();  // TraitB의 메소드를 새 이름으로 호출
?>

4. 가시성 변경

트레이트에서 가져온 메서드의 가시성(public, protected, private) 를 as 키워드로 변경 할 수 있습니다.

<?php
trait Utility
{
    public function process(): void
    {
        // ...
        echo "데이터 처리 중..." . PHP_EOL;
    }
}

class Task
{
    use Utility {
        // Utility 트레이트의 public 메소드인 process를
        // Task 클래스 내에서는 private으로 변경함
        process as private processInternal;
    }

    public function run(): void
    {
        // 이제 내부에서만 접근 가능
        $this->processInternal();
    }
}

$task = new Task();
$task->run(); // 정상 실행
// $task->processInternal(); // 치명적 오류! (private 메소드 호출 시도)
?>

5. 우선순위 규칙

메소드 이름이 충돌 할 경우, 다음과 같은 우선순위를 따릅니다


현재의 클래스 메소드 > 트레이드의 메소드 > 부모 클래스의 메소드

 

<?php
class BaseClass
{
    public function sayHello()
    {
        echo "안녕하세요 BaseClass 입니다" . PHP_EOL;
    }
}

trait MyTrait
{
    public function sayHello()
    {
        echo "안녕하세요 MyTrait 입니다" . PHP_EOL;
    }
}

class MyChild extends BaseClass
{
    use MyTrait;

    // 현재 클래스에 정의된 메소드가 가장 높은 우선순위를 가짐
    public function sayHello()
    {
        echo "안녕하세요 MyChild 입니다" . PHP_EOL;
    }
}

$obj = new MyChild();
$obj->sayHello(); // "안녕하세요 MyChild 입니다"가 출력됨
   
?>