【図解】phpのtrait(トレイト)とは何か。シンプルな例で説明します。

PHPプログラミング

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

トレイトの説明は以上です。

タイトルとURLをコピーしました