1. ホーム 
  2. 備忘録 
  3. PHP

トレイト

トレイトについて

PHP は、コードを再利用するための「トレイト」という仕組みを実装している

PHP は単一継承言語であり多重継承ができないため、このトレイトの仕組みを用いてコードの再利用をしやすくしている

トレイトはクラスと似ているが、単にいくつかの機能をまとめるためだけのものであり、トレイト自身のインスタンスを作ることはできない

感覚としてはトレイトとして括った関数群をまるっと任意の箇所にコピペしていくような使い勝手である

trait Foo
{
  public function foo()
  {
    echo 'Foo!!';
  }
}

trait Bar
{
  public function bar()
  {
    echo 'Bar!!';
  }
}

class Sample
{
  // use でトレイトの処理を使用できるようにする
  // 複数のトレイトを利用することもできる
  use Foo, Bar;
}

$sample = new Sample();
$sample->foo(); // Foo!!
$sample->bar(); // Bar!!

優先順位について

現在のクラスや基底クラスのメンバーと、トレイトのメンバーが被るケースがある

その際は『現在のクラスのメンバー > トレイトのメンバー > 基底クラスのメンバー』の順で優先順位が決まっている

コードの上から下に流れていくので下の方が優先度高いと覚えるとよいかも

↑ use は クラスの冒頭に書くことが多いが最下部に書くこともできて、その際にも『現在のクラスのメンバー > トレイトのメンバー』だったので訂正

class Base
{
  public function Foo()
  {
    echo 'Foo_Base!!';
  }
}

trait Test
{
  public function Foo()
  {
    // 親クラスがあることが前提となるのであまり好ましくないが一応呼び出せる
    parent::Foo();
    echo 'Foo_Trait!!';
  }
}

class Sample extends Base
{
  // トレイトのほうが基底クラスより優先度が高い
  use Test;
}

$sample = new Sample();
$sample->Foo(); // Foo_Base!!Foo_Trait!!

現在のクラスで上書きされるパターンも下記に記載しておく

trait Test
{
  public function Foo()
  {
    echo 'Foo_Trait!!';
  }
}

class Sample extends Base
{
  use Test;

  // トレイトより現在のクラスのほうが優先度が高い
  public function Foo()
  {
    echo 'Foo_Class!!';
  }
}

$sample = new Sample();
$sample->Foo(); // Foo_Class!!

衝突の解決

同一クラス内に複数のトレイトを追加しようとすると、メソッド名などが被ってしまうケースがある。

これを『衝突』と呼び、衝突を解決しておかないと fatal エラーが発生する

衝突を解決するには insteadof 演算子を使用して、衝突が発生しているメンバーに関していずれかひとつを選択する必要がある

衝突が発生しているメンバーに関して、全て使用したい場合は as 演算子を使ってメソッドにエイリアスを追加することで解決できる

衝突の解決例として下記に記載しておく

trait A
{
  public function foo()
  {
    echo 'Foo_A!!';
  }

  public function bar()
  {
    echo 'Bar_A!!';
  }

  public function qux()
  {
    echo 'Qux_A!!';
  }
}

trait B
{
  public function foo()
  {
    echo 'Foo_B!!';
  }

  public function bar()
  {
    echo 'Bar_B!!';
  }

  public function qux()
  {
    echo 'Qux_B!!';
  }
}

class Sample
{
  use A, B {
    A::foo insteadof B; // A の foo を選択する
    B::bar insteadof A; // B の bar を選択する
    A::qux insteadof B; // B にエイリアスを付けるとしても A の qux を選ぶ過程は必要
    B::qux as quxSub; // B の qux を quxSub で使用できるようにする
  }
}

$sample = new Sample();
$sample->foo(); // Foo_A!!
$sample->bar(); // Bar_B!!
$sample->qux(); // Qux_A!!
$sample->quxSub(); // Qux_B!!

メソッドのアクセス権の変更

as 構文を使うと、クラス内でのメソッドのアクセス権も変更することができる

trait HelloWorld
{
  public function sayHello()
  {
    echo 'Hello World!';
  }
}

// sayHello のアクセス権を変更する
class Sample1
{
  use HelloWorld { sayHello as protected; }
}

// アクセス権を変更したエイリアスメソッドを作る
// sayHello 自体のアクセス権は変わらず、privateSayHello として新しくメソッドを増やす
class Sample2
{
  use HelloWorld { sayHello as private privateSayHello; }
}

トレイトのメンバーの抽象化

トレイトでは、抽象メソッドを使ってクラスの要件を指定できる

アクセス権は public, protected, private をサポートしている( PHP 8.0.0 以前では public と protected のみ )

trait A
{
  abstract public function foo();
  abstract public function bar( $baz );
}

class Sample
{
  use A;

  public function foo()
  {
    return 'Foo!!';
  }
  
  public function bar( $baz )
  {
    return "Bar_{$baz}!!";
  }
}

    参考文献

  1. 公式ドキュメント