phpのtrait(トレイト)とは、クラスの継承を使わずにコードの再利用を行うための仕組みです。
この記事ではトレイトについて説明します。
トレイトの概要
先に書いたように、 trait(トレイト) とはphpでコードを再利用する仕組みです。
英語のtraitには「特性」「特徴」「遺伝的な形質」といった意味があります。
遺伝的形質のようにメソッドやメンバを定義、各クラスに遺伝させていくようなイメージでしょうか。

クラスの継承と似ていますが、phpは単一継承言語となっています。
継承できるスーパークラスは一つだけなので、それ以外の方法でコードを再利用するためにtrait(トレイト)という仕組みが用意されています。
また、トレイト自身はインスタンスを作る事はできません。
必ずクラスに継承させて使う必要があります。
トレイトの単純な例
単純な例として、TraitAのメソッドをClassAに継承させて使うコードが以下となります。
<?php
trait TraitA {
function funcA() {
echo 'funcA'.PHP_EOL;
}
}
class ClassA {
use TraitA;
}
$a = new ClassA();
$a->funcA();
ーーー
実行結果
funcA
トレイトの定義には「trait」キーワードを使います。
トレイトを使うためには継承先のクラスで「use」キーワードを使い、トレイトの名前を指定します。
スーパークラスとトレイトの優先順位

あるクラスがスーパークラスとトレイトを継承し、それぞれで同じ名前のメソッドが定義された場合、トレイトで定義したメソッドが優先されます。
トレイトからスーパークラスのメソッドを呼び出すためにparentキーワードを使う事もできます。
<?php
class BaseA {
function funcA() {
echo 'BaseA'.PHP_EOL;
}
}
trait TraitA {
function funcA() {
parent::funcA();
echo 'traitA'.PHP_EOL;
}
}
class ClassA extends BaseA{
use TraitA;
}
$a = new ClassA();
$a->funcA();
ーーー
実行結果
BaseA
traitA
子クラスとトレイトの優先順位

子クラスとトレイトでは、子クラスで定義したメソッドが優先されます。
<?php
trait TraitA {
function funcA() {
parent::funcA();
echo 'traitA'.PHP_EOL;
}
}
class ClassA {
use TraitA;
function funcA() {
echo 'classA'.PHP_EOL;
}
}
$a = new ClassA();
$a->funcA();
ーーー
実行結果
classA
複数のトレイトを継承する

複数のトレイトを使うには、use文を一行ずつ書くか、カンマ区切りでトレイト名を指定します。
<?php
trait TraitA {
function funcA() {
echo 'traitA'.PHP_EOL;
}
}
trait TraitB {
function funcB() {
echo 'traitB'.PHP_EOL;
}
}
class ClassA {
use TraitA, TraitB;
function print() {
$this->funcA();
$this->funcB();
}
}
$a = new ClassA();
$a->print();
ーーー
traitA
traitB
複数のトレイトで同じ関数を定義した場合
複数のトレイトで同じ関数名が定義されている場合、それらのトレイトを同時に継承するとFatal errorになります。
<?php
trait TraitA {
function print() {
echo 'traitA'.PHP_EOL;
}
}
trait TraitB {
function print() {
echo 'traitB'.PHP_EOL;
}
}
class ClassA {
use TraitA, TraitB;
}
$a = new ClassA();
$a->print();
ーーー
実行結果
PHP Fatal error: Trait method print has not been applied, because there are collisions with other trait methods on ClassA in sample.php on line 14
複数のトレイトで同じ関数を定義した場合(insteadofでエラーを回避する方法)

複数のトレイトで同名の関数が定義された場合のFatal errorを回避するには、insteadofキーワードを使います。
insteadofは「~の代わりに」という意味です。
衝突する関数名に対して、どのトレイトの関数を使うかを指定します。
<?php
trait TraitA {
function print() {
echo 'traitA'.PHP_EOL;
}
}
trait TraitB {
function print() {
echo 'traitB'.PHP_EOL;
}
}
class ClassA {
use TraitA;
use TraitB {
TraitB::print insteadof TraitA;
}
}
$a = new ClassA();
$a->print();
ーーー
実行結果
traitB
「TraitB::print insteadof TraitA;」と書くことで、「TraitB::print()を、TraitAの代わりに使う」という意味になります。
トレイトのメソッドのスコープを変更する

asキーワードを使うことでメソッドのスコープを変更できます。
<?php
trait TraitA {
private function print() {
echo 'traitA'.PHP_EOL;
}
}
class ClassA {
use TraitA {
print as public; // privateメソッドをpublicに変更
}
}
$a = new ClassA();
$a->print();
ーーー
実行結果
traitA
複数のトレイトを組み合わせる

トレイトからトレイトへ継承させる事もできます。
<?php
trait TraitA {
public function printA() {
echo 'traitA'.PHP_EOL;
}
}
trait TraitB {
public function printB() {
echo 'traitB'.PHP_EOL;
}
}
trait TraitAB {
use TraitA, TraitB;
}
class ClassA {
use TraitAB;
}
$a = new ClassA();
$a->printA();
$a->printB();
ーーー
実行結果
traitA
traitB
トレイトで抽象メソッドを定義する

トレイトに抽象メソッドを定義することも出来ます。
<?php
trait TraitA {
abstract public function printA();
}
class ClassA {
use TraitA;
public function printA() {
echo 'classA'.PHP_EOL;
}
}
$a = new ClassA();
$a->printA();
ーーー
実行結果
traitA
トレイトで静的変数を使う

トレイトで静的変数を使う事ができます。
静的変数は実装されたクラスごとに状態を持つようになります。
<?php
trait TraitA {
public function countA(){
static $counter = 0;
$counter = $counter + 1;
echo $counter.PHP_EOL;
}
}
class ClassA {
use TraitA;
}
class ClassB {
use TraitA;
}
$a = new ClassA();
$a->countA();
$a->countA();
$a->countA();
$b = new ClassB();
$b->countA();
$b->countA();
ーーー
実行結果
1
2
3
1
2
トレイトで静的メソッドを使う

トレイトで静的メソッドを使う事もできます。
<?php
trait TraitA {
public static function printA() {
echo 'static TraitA'.PHP_EOL;
}
}
class ClassA {
use TraitA;
}
ClassA::printA();
ーーー
実行結果
static TraitA
トレイトでプロパティ(メンバ変数)を定義する

トレイトにはプロパティ(メンバ変数)を持たせる事もできます。
<?php
trait TraitA {
public $t = 'traitA';
}
class ClassA {
use TraitA;
}
$a = new ClassA();
echo $a->t.PHP_EOL;
ーーー
実行結果
traitA
子クラスとトレイトで同じ名前のプロパティを定義する

子クラスとトレイトで同じ名前のプロパティを定義する場合、初期値が一致していないとFatal errorになります。
<?php
trait TraitA {
public $t = 'traitA';
}
class ClassA {
use TraitA;
public $t = 'classA';
}
$a = new ClassA();
echo $a->t.PHP_EOL;
ーーー
実行結果
PHP Fatal error: ClassA and TraitA define the same property ($t) in the composition of ClassA. However, the definition differs and is considered incompatible. Class was composed in sample.php on line 6
プロパティの初期値が一致していればエラーは出ません。
<?php
trait TraitA {
public $t = 'traitA';
}
class ClassA {
use TraitA;
public $t = 'traitA';
}
$a = new ClassA();
echo $a->t.PHP_EOL;
ーーー
実行結果
traitA
トレイトの説明は以上です。