トレイト

PHP 5.4.0 以降では、コードを再利用するための「トレイト」という仕組みが導入されました。

トレイトは、PHP のような単一継承言語でコードを再利用するための仕組みのひとつです。 トレイトは、単一継承の制約を減らすために作られたもので、 いくつかのメソッド群を異なるクラス階層にある独立したクラスで再利用できるようにします。 トレイトとクラスを組み合わせた構文は複雑さを軽減させてくれ、 多重継承や Mixin に関連するありがちな問題を回避することもできます。

トレイトはクラスと似ていますが、トレイトは単にいくつかの機能をまとめるためだけのものです。 トレイト自身のインスタンスを作成することはできません。 昔ながらの継承に機能を加えて、振る舞いを水平方向で構成できるようになります。 つまり、継承しなくてもクラスのメンバーに追加できるようになります。

例1 トレイトの例

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

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

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

優先順位

基底クラスから継承したメンバーよりも、トレイトで追加したメンバーのほうが優先されます。 優先順位は現在のクラスのメンバーが最高で、その次がトレイトのメソッド、 そしてその次にくるのが継承したメソッドとなります。

例2 優先順位の例

基底クラスから継承したメソッドは、MyHelloWorld に SayWorld トレイトから追加されたメソッドで上書きされます。 この挙動は、MyHelloWorld クラスで定義したメソッドでも同じです。 優先順位は現在のクラスのメンバーが最高で、その次がトレイトのメソッド、 そしてその次にくるのが継承したメソッドとなります。

<?php
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!

例3 もうひとつの優先順位の例

<?php
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!

複数のトレイト

複数のトレイトをひとつのクラスに追加するには、use 文でカンマ区切りで指定します。

例4 複数のトレイトの使用例

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

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

class 
MyHelloWorld {
    use 
HelloWorld;
    public function 
sayExclamationMark() {
        echo 
'!';
    }
}

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

上の例の出力は以下となります。

Hello World!

衝突の解決

同じ名前のメンバーを含む複数のトレイトを追加するときには、 衝突を明示的に解決しておかないと fatal エラーが発生します。

同一クラス内での複数のトレイト間の名前の衝突を解決するには、 insteadof 演算子を使って そのうちのひとつを選ばなければなりません。

この方法はひとつのメソッドだけしか使えませんが、 as 演算子を使うと、 メソッドのいずれかにエイリアスを追加できます。 as 演算子はメソッドをリネームするわけではないので、 その他のメソッドにも何も影響を及ぼさないことに注意しましょう。

例5 衝突の解決

この例では、Talker がトレイト A と B を使います。 A と B には同じ名前のメソッドがあるので、 smallTalk はトレイト B を使って bigTalk はトレイト A を使うように定義します。

Aliased_Talker は、as 演算子を使って B の bigTalk の実装に talk というエイリアスを指定して使います。

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

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

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

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

注意:

Prior to PHP 7.0, defining a property in a class with the same name as in a trait would throw an E_STRICT if the class definition was compatible (same visibility and initial value).

メソッドの可視性の変更

as 構文を使うと、 クラス内でのメソッドの可視性も変更することができます。

例6 メソッドの可視性の変更

<?php
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; }
}
?>

トレイトを組み合わせたトレイト

クラスからトレイトを使えるのと同様に、トレイトからもトレイトを使えます。 トレイトの定義の中でトレイトを使うと、 定義したトレイトのメンバーの全体あるいは一部を組み合わせることができます。

例7 トレイトを組み合わせたトレイト

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

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

trait 
HelloWorld {
    use 
HelloWorld;
}

class 
MyHelloWorld {
    use 
HelloWorld;
}

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

上の例の出力は以下となります。

Hello World!

トレイトのメンバーの抽象化

トレイトでは、抽象メソッドを使ってクラスの要件を指定できます。

例8 抽象メソッドによる、要件の明示

<?php
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;
    }
}
?>

トレイトの静的なメンバー

トレイトでは、静的なメンバーやメソッドを定義できます。

例9 静的な変数

<?php
trait Counter {
    public function 
inc() {
        static 
$c 0;
        
$c $c 1;
        echo 
"$c\n";
    }
}

class 
C1 {
    use 
Counter;
}

class 
C2 {
    use 
Counter;
}

$o = new C1(); $o->inc(); // 1 と表示
$p = new C2(); $p->inc(); // 1 と表示
?>

例10 静的なメソッド

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

class 
Example {
    use 
StaticExample;
}

Example::doSomething();
?>

プロパティ

トレイトにはプロパティも定義できます。

例11 プロパティの定義

<?php
trait PropertiesTrait {
    public 
$x 1;
}

class 
PropertiesExample {
    use 
PropertiesTrait;
}

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

トレイトでプロパティを定義したときは、クラスでは同じ名前のプロパティを定義できません。 unless it is compatible (same visibility and initial value), otherwise a fatal error is issued. Before PHP 7.0.0, defining a property in the class with the same visibility and initial value as in the trait, raised an E_STRICT notice.

例12 衝突の解決

<?php
trait PropertiesTrait {
    public 
$same true;
    public 
$different false;
}

class 
PropertiesExample {
    use 
PropertiesTrait;
    public 
$same true// Allowed as of PHP 7.0.0; E_STRICT notice formerly
    
public $different true// Fatal error
}
?>