クラスを水平方向に拡張できるPHPの「トレイト」:Web業界で働くためのPHP入門(19)(3/3 ページ)
オープンソースのWeb開発向けスクリプト言語「PHP」の文法を一から学ぶための入門連載。今回は、クラスに対して横断的に機能を追加できる「トレイト」について解説します。
トレイトの使いどころ
ここまで、トレイトの特徴、使い方を一通り解説してきました。これまでのサンプルは少し概念的なものです。実際にこのトレイトはどのように活用できるのでしょうか。
そこそこの規模の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.
関連記事
- WordPress活用に欠かせない、PHPをWindowsにダウンロードしてインストール、アンインストールする
本連載では、さまざまなソフトウェアのインストール、実行するためのセットアップ設定、実行確認、アンインストールの手順を解説する。今回は、PHPとは何か、PHPで動くツールの紹介、PHPのインストールとアンインストールについて解説。PHPプログラミングを始める参考にしてほしい。 - PHPにおけるクラスの書き方と呼び出し方――インスタンス、メソッド、プロパティ
「PHPで、どのようにオブジェクト指向プログラミングをしていくか」を解説する連載。初回は、「クラス」の書き方と簡単な使い方、メソッド/クラス定義内関数、プロパティ/クラス定義内変数、マルチプルインスタンスについて紹介します。 - PHP(スクリプト言語)
PHPは、HTMLへの埋め込みが可能なスクリプト言語(およびその処理系)。主としてWebアプリ開発に使用される。