はじめに
PHPには「Trait」という機能があります。
通常のクラスの継承は、1つのクラスしかできませんが、traitを使用すれば、複数のクラスの変数や関数などを使用することができるので、とっても便利です。(説明ざっくり)
が、実際にコーディングしていて気付いたのですが、traitのコンストラクタにはちょっと、気を付けた方が良さそうです。
というのも、traitのコンストラクタは、実装方法によっては呼ばれない可能性が非常に高いです。
検証
実際に検証してみました。今回はLaravelを採用します。
やりたいこと
クラス、抽象クラス、traitそれぞれにコンストラクタを設け、それぞれのコンストラクタをすべて実行する
パターン1:抽象クラスとtraitの同時実装
まずは、抽象クラスとtraitを作成してみます。
<?php
namespace App;
abstract class ModelBase{
public function __construct(){
echo 'call ModelBase ';
}
}
<?php
namespace App;
trait ModelTrait{
public function __construct(){
echo 'call ModelTrait ';
}
}
それぞれ、echoでメッセージを表示するだけのものです。
これらを、Modelクラスで呼び出します。親クラスとtraitで実装したコンストラクタを呼び出したく、parent::__construct();を実行しました。
<?php
namespace App;
class Model extends ModelBase{
use ModelTrait;
public function __construct(){
echo 'call Model ';
parent::__construct();
}
}
そしてページを表示します。
<?php
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {
new \App\Model();
return view('welcome');
});
これを実行すると、以下のように表示されます。
左上に「cal Model」と「call ModelBase」と表示されています。(薄くてごめんね)
しかし、「call ModelTrait」は表示されません。
「parent::__construct();」は親となるModelBaseが実行されますが、ModelTraitは実行されないようですね。
言われてみれば当たり前かもしれないですが、試してみてしっくりしました。
パターン2:traitのみの実装
今度は、ModelBaseの継承をやめて、ModelTraitのみの実装としてみます。
<?php
namespace App;
//class Model extends ModelBase{
class Model{
use ModelTrait;
public function __construct(){
echo 'call Model ';
parent::__construct();
}
}
悲しいことに、エラーが出てしまいました。
エラーの内容は「Cannot access parent:: when current class scope has no parent」ということで、「親がない」というエラーです。
parentはextendsの場合だけ利用可能で、traitの場合はダメみたいですね。残念。
パターン3:parent::__construct();削除
parent::__construct();を消してみます。
<?php
namespace App;
//class Model extends ModelBase{
class Model{
use ModelTrait;
public function __construct(){
echo 'call Model ';
//parent::__construct();
}
}
左上に「call Model」だけ出て、「call ModelTrait」は出ませんでした。
完全に__construct関数をオーバーライドしているようです。なのでtrait側のコンストラクタは呼び出せません。
パターン4:Modelのコンストラクタそのものを削除
Modelのコンストラクタそのものを削除してみます。
<?php
namespace App;
//class Model extends ModelBase{
class Model{
use ModelTrait;
//public function __construct(){
// echo 'call Model ';
//
// parent::__construct();
//}
}
これでようやく、「call ModelTrait」が表示されました。
しかし、そもそものModel側のコンストラクタ側を消しちゃってるので、本末転倒ですね。
(追記)パターン5:エイリアス
@mpyw 様にコメントにて記入いただきました!!本当にありがとうございます。
エイリアスを使用することで解決するそうです。
<?php
namespace App;
class Model extends ModelBase
{
use ModelTrait {
ModelTrait::__construct as traitConstructor;
}
public function __construct()
{
echo 'call Model ';
$this->traitConstructor();
}
}
これでようやく、一番やりたかった「call Model call ModelTrait」が表示されました。
本当にありがとうございます!
(追記)今回学んだこと
元々は、「すでにあるパッケージ内のクラスを継承して、クラスを実装する」という処理を行う際、「手軽に共通処理や共通変数を実装できないかなあ」というところから、traitを使ってみました。
ですが、Twitterなどでいただいたコメントを見てみると、traitはコンストラクタを持たせるのは良くないらしく、ただ単に処理・関数のみを共通化するのに使うのが良いみたいですね。
「Laravelのパッケージでも、traitにコンストラクタを持たせることは絶対にやってない」というコメントもいただきました。
なので今回の用途では、素直にtraitを止めて、元々のクラスでだけ、コンストラクタと変数を持たせることにしました。
どうしても共通処理や、変数・コンストラクタを使用したい場合は、すでにあるパッケージ内のクラスを継承して抽象クラスを作成し、そこに共通変数など書く。
さらにその抽象クラスを継承してクラスを作成する・・・という手順が必要ですね。(本来こうすべきですね)
めっちゃ勉強になりました。ありがとうございます!