PHP

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

teamnova 2025. 11. 17. 15:24
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 입니다"가 출력됨
   
?>