2015-08-19
php 5.4버전부터 생긴 trait라는 지시어가 있다.
우선 예제를 보자
trait AA
{
    var $a = "b1ix ";
    var $b = "is ";
    var $c = "best";

    function aa()
    {
        echo $this->a.$this->b.$this->c;
    }
}

class BB
{
    use AA, CC;

    var $d = "great";

    function bbb()
    {
        echo $this->a.$this->b.$this->d,$this->e;
    }

    function cc()
    {
        echo $this->a.$this->e; 
    }
}

trait CC
{
    var $e = "!!";

    function cc()
    {
        echo $this->e;
    }
}

$aa = new BB;
$aa->bbb();
echo "<br>";
$aa->aa();
echo "<br>";
$aa->cc();
b1ix is great!!
b1ix is best
b1ix !!
간단히 설명하자면, class에 trait들을 쓰게 되면 해당 trait에 있는 변수와 함수를 자신의 것처럼 사용이 가능하다.
즉, 상속이라기 보다는, 해당 class에 trait에 있는 것들을 끼워넣어서 자기것처럼 사용 할 수 있는 것이다.

물론 주의할 점이 있는데, trait와 class 또는 한 class에 use되는 trait사이에 같은 이름의 변수가 있으면 오류가 나게 되고
함수이름이 같을 경우에는 위와 같이 class에서 나중에 선언된 함수가 trait에 있는 함수를 오버라이드 하게 된다.
(단, 위의 문제점은 아래 예제에 나와 있듯이 insteadof연산자로 해결이 가능하다)

하지만, 이런 구조가 가능하게 되면서 php에서는 다중상속php는 원래 다중상속을 지원하지 않지만..이나 디자인 패턴이 무의미 하게 되어버린다.
class의 가장큰 낭비는, 상속으로 몇가지 기능을 편하게 재사용하지만, 필요한 기능 외에는 전부 낭비가 되버린다는 점인데,
위와 같은 구조가 되버린다면, 원하는 trait들을 여러개 만들어 놓고 자신이 사용하는 class에서 필요한것들만 가져다 쓸 수 있게 되기 때문이다.

아래는 겹치는 trait간의 사용 여부를 표현한 예제이다.
사용할 함수와 trait를 표시하고 사용 하지 않을 trait를 insteadof뒤에 나열 하면 된다.
trait A {
    public function smallTalk() {
        echo 'a';
    }
    public function bigTalk() {
        echo 'A';
    }
}

trait B {
    public function smallTalk() {
        echo 'b';
    }
    public function bigTalk() {
        echo 'B';
    }
}

trait C {
    public function smallTalk() {
        echo 'c';
    }
    public function bigTalk() {
        echo 'C';
    }
}

class Talker {
    use A, B, C {
        B::smallTalk insteadof A, C;
        A::bigTalk insteadof B, C;
        A::bigTalk as talk;
    }
}

$a = new Talker();
$a->smallTalk();
$a->talk();
bA

좀더 자세한 내용은 아래 링크에 가면 알 수 있다.
링크 : http://php.net/manual/kr/language.oop5.traits.php


삭제 대비용 원본글 복사

트레이트

PHP 5.4.0 부터, 트레이트 라는 코드 재사용 방법을 구현했습니다.

트레이트는 PHP와 같은 단일상속 언어를 위한 코드 재사용 기법입니다. 트레이트는 단일상속의 몇가지 한계를 줄이기 위해 고안되었습니다. 개발자들은 서로 다른 클래스 계층구조를 가지는 독립 클래스들에서 자유롭게 메서드셋을 재사용할수 있습니다. 트레이트와 클래스의 조합에 대한 의의는 복잡성을 줄이고, 다중상속과 믹신(Mixin)과 연관된 통상적인 문제를 줄이는데 있습니다.

트레이트는 클래스와 비슷하지만, 매끄럽고 일관된 방법으로 기능을 그룹화하기 위해 고안되었습니다. 트레이트는 자신을 인스턴스화할 수 없습니다. 트레이트는 기존의 상속을 확장하고, 수평적으로 동작을 내재화 할수 있도록 합니다. 이말은, 상속없이 클래스 멤버에 추가할수 있음을 의미합니다.

Example #1 트레이트 예제

trait ezcReflectionReturnInfo {
    function getReturnType() { /*1*/ }
    function getReturnDescription() { /*2*/ }
}

class ezcReflectionMethod extends ReflectionMethod {
    use ezcReflectionReturnInfo;
    /* ... */
}

class ezcReflectionFunction extends ReflectionFunction {
    use ezcReflectionReturnInfo;
    /* ... */
}

우선순위

기본 클래스로부터 상속된 멤버는 트레이트의 의해 추가된 멤버에 의한 방법이 우선 됩니다. 우선순위는 현재 클래스의 멤버를 트레이트의 메서드로 재정의하고, 그 다음이 상속된 메서드를 재정의하는 순서입니다.

Example #2 우선순위 예제

기본 클래스로부터 상속된 메서드는 MyHelloWorld 에 추가된 SayWorld 트레이트의 메서드의해 재정의됩니다. MyHelloWord 클래스에 메서드가 정의된것과 같이 동작합니다. 우선순위는 현재클래스의 메서드를 트레이트 메서드로 재정의하고, 그 다음으로 기본 클래스의 메서드를 재정의하는 순서입니다.

class Base {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait SayWorld {
    public function sayHello() {
        parent::sayHello();
        echo 'World!';
    }
}

class MyHelloWorld extends Base {
    use SayWorld;
}

$o = new MyHelloWorld();
$o->sayHello();

위 예제의 출력:

Hello World!

Example #3 다른 우선순위 예제

trait HelloWorld {
    public function sayHello() {
        echo 'Hello World!';
    }
}

class TheWorldIsNotEnough {
    use HelloWorld;
    public function sayHello() {
        echo 'Hello Universe!';
    }
}

$o = new TheWorldIsNotEnough();
$o->sayHello();

위 예제의 출력:

Hello Universe!

다중 트레이트

다중 트레이트는 콤마로 구분하여 클래스에 삽입될수 있습니다.

Example #4 다중 트레이트 사용법

trait Hello {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait World {
    public function sayWorld() {
        echo 'World';
    }
}

class MyHelloWorld {
    use Hello, World;
    public function sayExclamationMark() {
        echo '!';
    }
}

$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
$o->sayExclamationMark();

위 예제의 출력:

Hello World!

충돌 해결

같은 메서드 이름을 가진 트레이트가 삽입된다면, 충돌이 명시적으로 해결되지 않는한 치명적인(fatal) 에러가 날것입니다.

동일 클래스내에서 트레이트들간의 이름 충돌을 해결하기 위해서는, insteadof 연산자로 확실하게 사용할 메서드를 골라줄 필요가 있습니다.

이 방법은 다른 메서드들을 배제하고 하나믜 메서드만 허용합니다. as 연산자를 사용하면 충돌되는 메서드를 다른 이름을 사용하여 포함할수 있습니다.

Example #5 충돌 해결 예제

이 예제에서는, Talker 는 틀레이트 A와 B를 사용합니다. A와 B의 메서드가 충돌하지만, 트레이트 B의 smallTalk와 트레이트 A의 bigTalk의 사용하도록 정의 합니다.

Aliased_Talker는 as 연산자를 사용하여 B의 bigTalk 구현을 talk 로 별칭을 사용하도록 만듭니다.

trait A {
    public function smallTalk() {
        echo 'a';
    }
    public function bigTalk() {
        echo 'A';
    }
}

trait B {
    public function smallTalk() {
        echo 'b';
    }
    public function bigTalk() {
        echo 'B';
    }
}

class Talker {
    use A, B {
        B::smallTalk insteadof A;
        A::bigTalk insteadof B;
    }
}

class Aliased_Talker {
    use A, B {
        B::smallTalk insteadof A;
        A::bigTalk insteadof B;
        B::bigTalk as talk;
    }
}

메서드 가시성 변경

as 구문을 사용하여, 가시성또한 조정할 수 있습니다.

Example #6 메서드 가시성 변경 예제

trait HelloWorld {
    public function sayHello() {
        echo 'Hello World!';
    }
}

// sayHello 가시성 변경
class MyClass1 {
    use HelloWorld { sayHello as protected; }
}

// 메서드를 별칭을 지정하여 가시성을 변경하면
// sayHello 의 가시성은 변경되지 않습니다.
class MyClass2 {
    use HelloWorld { sayHello as private myPrivateHello; }
}

트레이트로 조합된 트레이트

클래스들이 트레이트를 사용할수 있는것처럼 트레이트도 트레이트를 사용할 수 있습니다. 하나또는 여러 트레이트를 하나의 트레이트로 정의하기 위해서, 부분적 혹은 전체적으로 조합할수 있습니다.

Example #7 Traits Composed from Traits

trait Hello {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait World {
    public function sayWorld() {
        echo 'World!';
    }
}

trait HelloWorld {
    use Hello, World;
}

class MyHelloWorld {
    use HelloWorld;
}

$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();

위 예제의 출력:

Hello World!

추상 트레이트 멤버

트레이트는 클래스의 요구사항을 강요하기위해 추상 메서드의 사용을 지원합니다.

Example #8 추상 메서드에 의햔 요구사항 표현

trait Hello {
    public function sayHelloWorld() {
        echo 'Hello'.$this->getWorld();
    }
    abstract public function getWorld();
}

class MyHelloWorld {
    private $world;
    use Hello;
    public function getWorld() {
        return $this->world;
    }
    public function setWorld($val) {
        $this->world = $val;
    }
}

정적 트레이트 멤버

트레이트는 정적 멤버와 정적 메서드로 정의 가능합니다.

Example #9 정적 변수

trait Counter {
    public function inc() {
        static $c = 0;
        $c = $c + 1;
        echo &quot;$c\n&quot;;
    }
}

class C1 {
    use Counter;
}

class C2 {
    use Counter;
}

$o = new C1(); $o->inc(); // echo 1
$p = new C2(); $p->inc(); // echo 1

Example #10 정적 메서드

trait StaticExample {
    public static function doSomething() {
        return 'Doing something';
    }
}

class Example {
    use StaticExample;
}

Example::doSomething();

프로퍼티

트레이트는 프로퍼티를 정의할 수 있습니다.

Example #11 프로퍼티 정의

trait PropertiesTrait {
    public $x = 1;
}

class PropertiesExample {
    use PropertiesTrait;
}

$example = new PropertiesExample;
$example->x;

트레이트가 프로퍼티를 정의하면 클래스는 같은 이름으로 프로퍼티를 정의할수 없습니다. 그렇지 않을 경우 에러가 발생합니다. E_STRICT 는 클래스 정의가 동일할때(같은 가시성과 초기값을 가진경우) 발생하며, 그렇지 않을경우에는 치명적인 에러가 발생합니다.

Example #12 충돌 해결

trait PropertiesTrait {
    public $same = true;
    public $different = false;
}

class PropertiesExample {
    use PropertiesTrait;
    public $same = true; // Strict Standards
    public $different = true; // Fatal error
}