PHPのトレイトについて調べてみた

最近になってトレイト(trait)使うことが増えてきて、
「クラスの継承に似たようなやつ」レベルの認識だとあまり良くない気がしたので、
今更ながら少し調べました。

traitとは

PHPの5.4.0からサポートされている仕組みのようです。

トレイトにメソッドを定義することで、複数のクラスでそれらを使用することができます。

クラスの継承と似てますが、「単一継承言語でコードを再利用するための仕組み」ということなので、
一つのクラスから複数のトレイトを利用することができます。

トレイトは、PHP のような単一継承言語でコードを再利用するための仕組みのひとつです。
トレイトは、単一継承の制約を減らすために作られたもので、 いくつかのメソッド群を異なるクラス階層にある独立したクラスで再利用できるようにします。
トレイトとクラスを組み合わせた構文は複雑さを軽減させてくれ、 多重継承や Mixin に関連するありがちな問題を回避することもできます。
トレイトはクラスと似ていますが、トレイトは単にいくつかの機能をまとめるためだけのものです。
トレイト自身のインスタンスを作成することはできません。 昔ながらの継承に機能を加えて、振る舞いを水平方向で構成できるようになります。
つまり、継承しなくてもクラスのメンバーに追加できるようになります。

PHP: トレイト – Manual


traitの使い方

トレイトをuseすることで、定義されたメソッドを利用できます。

<?php
trait SampleTraitA {
    public function hoge() {
        echo "hoge!";
    }
}

trait SampleTraitB {
    public function fuga() {
        echo "fuga!";
    }
}

class Sample {
    use SampleTraitA;
    use SampleTraitB;
}

$sample = new Sample();
$sample->hoge();
$sample->fuga();

// 実行結果:hoge!fuga!


優先順位について

親クラスとトレイトに同名のメソッドが定義されている場合は、トレイトが優先されるようです。

<?php
class Base {
    public function hoge() {
        echo "hoge!(Base Class)";
    }
}

trait SampleTraitA {
    // 優先される
    public function hoge() {
        echo "hoge!(SampleTraitA)";
    }
}

class Sample extends Base {
    use SampleTraitA;
}

$sample = new Sample();
$sample->hoge();

// 実行結果:hoge!(SampleTraitA)

子クラスとトレイトに同名のメソッドが定義されている場合は、子クラスが優先されるようです。

<?php
class Base {
    public function hoge() {
        echo "hoge!(Base Class)";
    }
}

trait SampleTraitA {
    public function hoge() {
        echo "hoge!(SampleTraitA)";
    }
}

class Sample extends Base {
    use SampleTraitA;
    
    // 優先される
    public function hoge() {
        echo "hoge!(Sample Class)";
    }
}

$sample = new Sample();
$sample->hoge();

// 実行結果:hoge!(Sample Class)


複数のトレイトに同名のメソッドが定義されている場合(衝突の解決 

複数のトレイトに同名メソッドが定義されている場合は、insteadof演算子を使ってどちらのメソッドを利用するか選択しないとエラーになるようです。

<?php
trait SampleTraitA {
    public function hoge() {
        echo "hoge!(SampleTraitA)";
    }
    public function fuga() {
        echo "fuga!(SampleTraitA)";
    }
}

trait SampleTraitB {
    public function hoge() {
        echo "hoge!(SampleTraitB)";
    }
    public function fuga() {
        echo "fuga!(SampleTraitB)";
    }
}

class Sample {
    use SampleTraitA {
        // SampleTraitAのhoge()を優先させる
        SampleTraitA::hoge insteadof SampleTraitB;
    }
    use SampleTraitB {
        // SampleTraitBのfuga()を優先させる
        SampleTraitB::fuga insteadof SampleTraitA;
    }
}

$sample = new Sample();
$sample->hoge();
$sample->fuga();

// 実行結果:hoge!(SampleTraitA)fuga!(SampleTraitB)