とぎぷろべい

包丁研ぎとプログラミングと米国株投資についてのなんやかんや

traitを使った複数クラスでの共通処理について(PHP)

f:id:feci:20210727001932j:plain

PHP7

 

PHP7.4.1におけるtraitを使った複数クラスでの共通処理について

<?php
declare(strict_types=1);

//トレイトその①
trait Ccc {                      //trait trait{}名でtraitを作成
  public function getA(): int {  //メソッド
    return 12345;
  }
}

//トレイトその②
trait Ddd {
  public function getA(): int {  //Cccトレイトとメソッド名が被っている(Fatal Error)
    return 12345;
  }
  public function getB(): int {  //メソッド
    return 6789;
  }
}

//クラスその①
class Aaa {
  use Ccc;                   //use trait名でそのtraitを呼び出せる
  use Ddd {                  //1つのクラスで複数のtraitを呼び出すことも可能
    Ccc::getA insteadof Ddd//getAメソッドはCccクラスのものを使えという宣言
  }
  protected int $aaa;        //プロパティ
}

//クラスその②
class Bbb {
  use CccDdd {             //traitが複数ある場合はこの書き方でもOK
    Ddd::getA insteadof Ccc//getAメソッドはDddクラスのものを使えという宣言
    Ccc::getA as getC;       //CccクラスのgetAメソッドの名前をgetCに変更
  }
  protected int $aaa;        //プロパティ
}

//処理
$aaa = new Aaa;              //Aaaクラスのインスタンス作成
echo $aaa->getA(), "\n";     //12345
echo $aaa->getB(), "\n";     //6789

$bbb = new Bbb;                        //Bbbクラスのインスタンス作成
echo $bbb->getA(), $bbb->getB(), "\n"//123456789
echo $bbb->getc(), "\n";               //12345

トレイトは、各クラスで使える共通のプロパティやメソッドを定義できる機能です。

 

 

トレイト定義の定形は以下のとおりです。

 

trait 〇〇 {
    アクセス修飾子 データ型 プロパティ名;
    アクセス修飾子 function メソッド名() {
        実行したい処理;
    }
}

 

 

○○の部分にはトレイト名が入ります。

トレイトは単体として存在するものなので、トレイトをクラス継承などの対象にすることはできません。

 

トレイトのメソッドやプロパティを呼び出すには、呼び出したいクラス内に use 呼び出したいtrait名, ...; と記述します。

上記スクリプトのとおり、1つのクラス内でも複数のトレイトを呼び出すことも可能です。

 

 

また、もし複数の異なるトレイト内で同名のメソッドがあった場合は、そのメソッドを呼び出した瞬間にコンピュータくんがFatal Errorをだします。

この場合は、可能ならば被っているメソッド名のどちらか片方を違う名前に変えるのが一番楽な対処法です。

 

しかしそのような改名が許されない場合は、insteadofまたはasを使っためんどうくさい対処法を使うしかありません。

 

 

〇insteadofを使った対処法

上記スクリプトでは、トレイトCccとDddでgetAというメソッドの名前が被っています。

この重複をinsteadofを使って解決するには、use文を以下のように改変しなくてはいけません。

 

use Ccc, Ddd {
    Ccc::getA insteadof Ddd;
}

 

または、

 

use Ccc, Ddd {
    Ddd::getA insteadof Ccc;
}

 

と記述します。

 

上のパターンでは、「getAメソッドはCccトレイトのものを使いますよ」という宣言になります。

下のパターンでは、「getAメソッドはDddトレイトのものを使いますよ」という宣言になります。

 

 

〇asを使った対処法

insteadofはどちらのトレイトのメソッドを使うのかという宣言でした。

それに対してasは、指定したメソッド名を解明して使うための文になります。

 

お気づきのように、insteadofでは使う宣言をしなかった方のメソッドを呼び出すことは不可能となります。

今回のように同名で同じ処理をしている場合はどっちでもいいのですが、同名でもまったく違う処理をおこなうメソッドだった場合は、insteadofでは問題がでてきます。

 

しかしas文ならば片方のメソッド名を改名するだけなので、改名後の名前を使えばそのメソッドを以前と同じように自由に呼び出して使うことができます。

 

as文を使って上記スクリプトgetAメソッドの重複を解決するには、当該クラス内に以下のような記述が必要になります。

 

use Ccc, Ddd {
    Ccc::getA as getc;
}

 

または、

 

use Ccc, Ddd {
    Ddd::getA as getc;
}

 

と記述します。

 

上のパターンでは、CccトレイトのgetAメソッドはgetCという名前に改名されます。

下のパターンでは、DddトレイトのgetAメソッドはgetCという名前に改名されます。

 

もしも複数のトレイトがあり、その中のいくつかのトレイトで重複した名前のメソッドがあり、なおかつその重複した名前をソースコードとして変更できない場合は、上記のようなめんどうくさい方法で対処する必要があります。

 

 

以上です。