ここまで、トレイトの特徴、使い方を一通り解説してきました。これまでのサンプルは少し概念的なものです。実際にこのトレイトはどのように活用できるのでしょうか。
そこそこの規模のWebシステムではデータベースの利用は必須です。そういったシステムをPHPで作成する場合(PHPに限りませんが)、各役割に合わせてクラス群を作成するのが普通です。
例としては、HTTPのリクエストを処理して画面を表示するクラス群、データベースに接続してデータ処理するクラス群といったものが挙げられます。通常、リクエストを処理するクラス群を「コントローラークラス」、データ処理をするクラス群を「モデルクラス」と言います。これらはさらに継承を利用し、親コントローラークラスを作成し、全コントローラーに共通の処理をそこに記述します。モデルも同じです(図4)。
ところで実システムでは、障害発生時などの情報収集のためにログへの記述は必須です。一方で、ログへの書き出し処理コードはほぼ定型です。これらの処理を「どこに記述するのか」が1つの設計上の課題です。なぜなら、コントローラーだけではなく、モデルでもログを書き出す可能性があるからです。
そのため、親コントローラーと親モデルのさらに共通の祖先となるクラスを作成して、そこに記述するのも1つの方法です(図5)。
一方で、今回紹介したトレイトを使うのも1つの方法です(図6)。
ログ書き出し処理を記述したトレイトを作成しておき、必要なクラスにそのトレイトを組み込むものです。
このように、モデルクラスの拡張やコントローラークラスの拡張といった同種のクラスを拡張するには垂直方向にクラスを拡張する継承を使います。一方、「モデルクラスとコントローラークラスの両方に対して拡張する」といった異種のクラスに対して水平方向に拡張するには、トレイトを使います。この垂直方向拡張の継承と、水平方向拡張のトレイトを組み合わせると、柔軟なシステム設計が可能となります。
最後に、親クラスとトレイトの両方に同じ名前のメソッドがあった場合を扱っておきましょう。
リスト6のSportsPlayerクラスにはgetName()メソッドがあります。これと同じ名前で処理が違うメソッドを記述したトレイトとしてGetNameTraitトレイトを作成します。
<?php trait GetNameTrait { public function getName():string { return "名前を乗っ取りました"; } }
次に、SportsPlayerクラスを継承し、GetNameTraitトレイトを組み込んだDummyPlayerクラスを作成します。
<?php class DummyPlayer extends SportsPlayer { use GetNameTrait; }
最後にこのDummyPlayerを利用するcallDummyPlayer.phpファイルを作成します。
<?php require_once("GetNameTrait.php"); require_once("SportsPlayer.php"); require_once("DummyPlayer.php"); $namikawa = new DummyPlayer("波川茂雄"); $name = $namikawa->getName(); // (1) print("お名前は? ".$name);
(1)でgetName()メソッドを呼び出していますが、これは、親クラスであるSportsPlayerのgetName()でしょうか、それとも、組み込んだトレイトであるGetNameTraitトレイトのgetName()でしょうか。さあ、実行結果を予想しながら実行してください。実行結果は以下のようになります。
お名前は? 名前を乗っ取りました
答は、トレイトの方が優先されます。自クラス、親クラス、トレイトに同名メソッドがある場合の優先順位は以下の通りです。
自クラス > トレイト > 親クラス
もし、DummyPlayerクラスに以下のようにgetName()を追記したとします。
public function getName():string { return "私は他でもない私だ!"; }
もし、この状態でリスト10を実行すると、以下の結果となります。
お名前は? 私は他でもない私だ!
自クラスのメソッドが優先されていることが分かります。
トレイトと自クラスに同じメンバがある場合は自クラスが優先されることは上で解説しました。では、useするトレイト間で同じメンバがある場合はどうなるのでしょうか。
例えば、CameraTraitトレイトにメソッドtake()があり、VideoTraitトレイトに同じくメソッドtake()があり、この両方のトレイトをCameramanクラスでuseした場合、take()メソッドが重なります。これを「衝突」と言います。この場合は、以下のように「insteadof」を使ってどちらかを選択することになります。
use CameraTrait, VideoTrait { CameraTrait::take insteadof VideoTrait; }
こうすることで、CameraTraitのtake()が優先されます。あるいは、以下のように「as」を使って、それぞれのメソッドに別名を付けます。
use CameraTrait, VideoTrait { CameraTrait::take as takeCamera; VideoTrait::take as takeVideo; }
どちらかの対応をしておかなければfatalエラーが発生するので、注意してください。
次回は、いよいよ本連載の最終回です。PHPの「名前空間」とその利用方法、「オートロード」を扱います。
Copyright © ITmedia, Inc. All Rights Reserved.