※当サイトの記事には、広告・プロモーションが含まれます。

PHP5.4以降で導入されたトレイト(trait)というしくみ

PHP5.4以降で『トレイト(trait)』というしくみが導入されたようです。

抽象クラス、インターフェイスときて、トレイトです。

トレイトは、多重継承のできないPHPで、継承することなくクラスにメンバ(メソッドやプロパティ)を追加できるしくみのようです。

抽象クラスやインターフェイスと同じく、トレイト単体では何も出来ないので、必ず何かしらのクラスを必要とします。

 

例のごとく、他サイト様が素晴らしいまとめをしてくれてます。

 

⇩  PHPのトレイトについては下記サイトへ

PHP5.4 の新機能 trait のまとめと実際の利用例 | 株式会社インフィニットループ技術ブログ

PHP5.4のtrait機能を理解する - Hack Your Design!

trait時代のライブラリ設計を予想してみる #php5_4: Architect Note

PHPのtrait(トレイト)の使いドコロ - Alug

⇩  PHPのトレイトの使い方などについては下記サイトへ 

PHP のトレイトに気をつける - tototoshi の日記

PHP5.4のトレイトを使ってみる

PHP5.4のtraitを使ったシングルトンパターン実装によるtrait入門 - id:anatooのブログ

PHP - シングルトンの抽象化クラス&トレイトを作成する - Qiita

 

そして、PHPマニュアル(PHP: トレイト - Manual )によると

トレイト

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();
?>

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

例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();
?>

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

衝突の解決

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

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

この方法はひとつのメソッドだけしか使えませんが、 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();
?>

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

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

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

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

トレイトでプロパティを定義したときは、クラスでは同じ名前のプロパティを定義できません。 定義しようとすると、エラーが発生します。
クラス側での定義がトレイトでの定義と互換性がある (可視性も初期値も同じ) 場合は E_STRICT、 それ以外の場合は fatal error となります。

例12 衝突の解決 

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

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

 

トレイトの特徴

  • インスタンス化できません。
  • キーワードuseでクラスに追加。
  • メソッドとプロパティを持てます。
  • 静的(static)メンバ(メソッド、プロパティ)を定義できます。
  • 抽象(abstract)メソッドを定義できます。
  • クラスにはいくつでもトレイトを追加できます。
    (※複数トレイトの追加は、use文でコンマで区切り指定します。)
  • Trait 同士の優先順位は同じなので,同じクラスにメソッド名の重複する2つの Trait を使用した場合,デフォルトではエラーになります。
    しかし "insteadof" と "as" 演算子を使用してコンフリクトを解決するように記述すれば,このエラーを回避することができます。
  • as構文でメソッドの可視性(アクセス権)を変更できます。
  • トレイトの中でトレイトが使えます。

 

コンフリクトconflict

コンフリクトとは、競合、衝突、対立、葛藤、緊張などの意味を持つ英単語。

ITの世界では、複数の同種の何かが同じ資源を同時に利用しようとして競合状態になってしまうことを意味する場合が多い。

コンピュータシステムの中で共存する複数のソフトウェアやハードウェアが、同じ資源(メモリ領域やI/Oポート名前空間など)を同時に利用しようとして奪い合いになったり、動作が不安定になったりすることをコンフリクトという。

プログラミングなどで、複数のライブラリなどが同じ名前空間クラス名、変数名などを定義していて、両者を同時に利用できない状態になってしまうことをコンフリクトという。

データベースシステムやファイルシステムなどで、同じレコードやファイルなどに同時に更新要求が発生して正常に処理できない状態のことをコンフリクトという。

 

 

トレイトの注意点

インターフェイスを実装したクラスのオブジェクトのインスタンスであるかどうかも調べられる『instanceof演算子(型演算子)』 が使えないようです。

⇩  詳しくは下記サイトへ

PHPのtraitはinstanceofで確認できない - Perl日記

 

⇩  型演算子については下記

PHP: 型演算子 - Manual