【図解】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メソッドをpublickに変更
    }
}

$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をコピーしました