トレイト

PHP は、コードを再利用するための「トレイト」という仕組みを実装しています。

トレイトは、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;
    }
}
?>

メソッドのアクセス権の変更

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!

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

トレイトでは、抽象メソッドを使ってクラスの要件を指定できます。 アクセス権は public, protected, private をサポートしています。 PHP 8.0.0 より前のバージョンでは、 public と protected な抽象メソッドだけがサポートされていました。

警告

具象クラスでは、 具象メソッドを同じ名前で定義することによって、要件を満たせます。 つまり、シグネチャは異なっていても構いません。

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

トレイトの static メンバー

トレイトでは、static 変数、static メソッド、static プロパティを定義できます。

注意:

PHP 8.1.0 以降では、トレイトにある static メソッドや、 static プロパティに直接アクセスすることは、 推奨されなくなりました。 これらは、トレイトを使っているクラスからのみアクセスすべきものです。

例9 static変数

<?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 staticメソッド

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

class 
Example {
    use 
StaticExample;
}

Example::doSomething();
?>

例11 staticプロパティ

<?php
trait StaticExample {
    public static 
$static 'foo';
}
class 
Example {
    use 
StaticExample;
}
echo 
Example::$static;
?>

プロパティ

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

例12 プロパティの定義

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

class 
PropertiesExample {
    use 
PropertiesTrait;
}

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

トレイトでプロパティを定義したときは、 クラスではそれと互換性(公開範囲と初期値が同じ)がない同じ名前のプロパティを定義できません。 互換性がない名前を定義すると、致命的なエラーが発生します。

例13 衝突の解決

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

class 
PropertiesExample {
    use 
PropertiesTrait;
    public 
$same true;
    public 
$different true// Fatal error
}
?>
関連キーワード:  メソッド, クラス, 定義, メンバー, static, 継承, アクセス, 名前, 追加, 複数