トレイト
PHP 5.4.0 以降では、コードを再利用するための「トレイト」という仕組みが導入されました。
トレイトは、PHP のような単一継承言語でコードを再利用するための仕組みのひとつです。 トレイトは、単一継承の制約を減らすために作られたもので、 いくつかのメソッド群を異なるクラス階層にある独立したクラスで再利用できるようにします。 トレイトとクラスを組み合わせた構文は複雑さを軽減させてくれ、 多重継承や Mixin に関連するありがちな問題を回避することもできます。
トレイトはクラスと似ていますが、トレイトは単にいくつかの機能をまとめるためだけのものです。 トレイト自身のインスタンスを作成することはできません。 昔ながらの継承に機能を加えて、振る舞いを水平方向で構成できるようになります。 つまり、継承しなくてもクラスのメンバーに追加できるようになります。
例1 トレイトの例
<?phptrait ezcReflectionReturnInfo { function getReturnType() { /*1*/ } function getReturnDescription() { /*2*/ }}class ezcReflectionMethod extends ReflectionMethod { use ezcReflectionReturnInfo; /* ... */}class ezcReflectionFunction extends ReflectionFunction { use ezcReflectionReturnInfo; /* ... */}?>
優先順位
基底クラスから継承したメンバーよりも、トレイトで追加したメンバーのほうが優先されます。 優先順位は現在のクラスのメンバーが最高で、その次がトレイトのメソッド、 そしてその次にくるのが継承したメソッドとなります。
例2 優先順位の例
基底クラスから継承したメソッドは、MyHelloWorld に SayWorld トレイトから追加されたメソッドで上書きされます。 この挙動は、MyHelloWorld クラスで定義したメソッドでも同じです。 優先順位は現在のクラスのメンバーが最高で、その次がトレイトのメソッド、 そしてその次にくるのが継承したメソッドとなります。
<?phpclass 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 もうひとつの優先順位の例
<?phptrait 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 複数のトレイトの使用例
<?phptrait 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
演算子を使うと、
メソッドのいずれかにエイリアスを追加できます。
as
演算子はメソッドをリネームするわけではないので、
その他のメソッドにも何も影響を及ぼさないことに注意しましょう。
例5 衝突の解決
この例では、Talker がトレイト A と B を使います。 A と B には同じ名前のメソッドがあるので、 smallTalk はトレイト B を使って bigTalk はトレイト A を使うように定義します。
Aliased_Talker は、as
演算子を使って B の bigTalk の実装に
talk
というエイリアスを指定して使います。
<?phptrait 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; }}?>
注意:
PHP 7.0 より前のバージョンでは、 トレイトに存在する名前と同じ名前をクラスのプロパティに定義すると、 クラス定義に互換性(公開範囲と初期値が同じ)があっても、
E_STRICT
レベルの警告が発生していました。
メソッドの可視性の変更
as
構文を使うと、
クラス内でのメソッドの可視性も変更することができます。
例6 メソッドの可視性の変更
<?phptrait 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 トレイトを組み合わせたトレイト
<?phptrait 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!
トレイトのメンバーの抽象化
トレイトでは、抽象メソッドを使ってクラスの要件を指定できます。
警告 具象クラスでは、 具象メソッドを同じ名前で定義することによって、要件を満たせます。 つまり、シグナチャは異なっていても構いません。
例8 抽象メソッドによる、要件の明示
<?phptrait 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 静的な変数
<?phptrait 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 静的なメソッド
<?phptrait StaticExample { public static function doSomething() { return 'Doing something'; }}class Example { use StaticExample;}Example::doSomething();?>
プロパティ
トレイトにはプロパティも定義できます。
例11 プロパティの定義
<?phptrait PropertiesTrait { public $x = 1;}class PropertiesExample { use PropertiesTrait;}$example = new PropertiesExample;$example->x;?>
トレイトでプロパティを定義したときは、
クラスではそれと互換性(公開範囲と初期値が同じ)がない同じ名前のプロパティを定義できません。
互換性がない名前を定義すると、致命的なエラーが発生します。
PHP 7.0.0 より前のバージョンでは、
トレイトに存在する名前と同じ名前をクラスのプロパティに定義すると、
クラス定義に互換性(公開範囲と初期値が同じ)があっても、
E_STRICT
レベルの警告が発生していました。
例12 衝突の解決
<?phptrait 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}?>