[PHP]Traitにコンストラクタを作成するのは、呼ばれない可能性があるのでよろしくない

# はじめに
PHPには「Trait」という機能があります。
通常のクラスの継承は、1つのクラスしかできませんが、traitを使用すれば、複数のクラスの変数や関数などを使用することができるので、とっても便利です。（説明ざっくり）

が、実際にコーディングしていて気付いたのですが、**traitのコンストラクタにはちょっと、気を付けた方が良さそうです。**
というのも、**traitのコンストラクタは、実装方法によっては呼ばれない可能性が非常に高いです。**

# 検証
実際に検証してみました。今回はLaravelを採用します。

## やりたいこと
クラス、抽象クラス、traitそれぞれにコンストラクタを設け、それぞれのコンストラクタをすべて実行する

## パターン1：抽象クラスとtraitの同時実装

まずは、抽象クラスとtraitを作成してみます。

~~~ ModelBase.php
<?php
namespace App;

abstract class ModelBase{
    public function __construct(){
        echo 'call ModelBase ';
    }
}
~~~


~~~ ModelTrait.php
<?php
namespace App;

trait ModelTrait{
    public function __construct(){
        echo 'call ModelTrait ';
    }
}
~~~

それぞれ、echoでメッセージを表示するだけのものです。
これらを、Modelクラスで呼び出します。親クラスとtraitで実装したコンストラクタを呼び出したく、parent::__construct();を実行しました。

~~~ Model.php
<?php
namespace App;

class Model extends ModelBase{
    use ModelTrait;

    public function __construct(){
        echo 'call Model ';

        parent::__construct();
    }
}
~~~

そしてページを表示します。

~~~ web.php
<?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');
});
~~~

これを実行すると、以下のように表示されます。

![capture_20190123131011.png](https://qiita-image-store.s3.amazonaws.com/0/204834/471d11e2-f26d-90ea-2a15-1c4a3518497b.png)

左上に「cal Model」と「call ModelBase」と表示されています。（薄くてごめんね）
しかし、「call ModelTrait」は表示されません。
「parent::__construct();」は親となるModelBaseが実行されますが、ModelTraitは実行されないようですね。
言われてみれば当たり前かもしれないですが、試してみてしっくりしました。


## パターン2：traitのみの実装
今度は、ModelBaseの継承をやめて、ModelTraitのみの実装としてみます。

~~~ Model.php
<?php
namespace App;

//class Model extends ModelBase{
class Model{
    use ModelTrait;

    public function __construct(){
        echo 'call Model ';

        parent::__construct();
    }
}
~~~

これだとどうなるか？結果は以下でした。
![capture_20190123132048.png](https://qiita-image-store.s3.amazonaws.com/0/204834/578795d9-8991-a68e-c54f-22fb9e4eb550.png)


悲しいことに、エラーが出てしまいました。
エラーの内容は「Cannot access parent:: when current class scope has no parent」ということで、「親がない」というエラーです。
parentはextendsの場合だけ利用可能で、traitの場合はダメみたいですね。残念。


## パターン3：parent::__construct();削除
parent::__construct();を消してみます。

~~~ Model.php
<?php
namespace App;

//class Model extends ModelBase{
class Model{
    use ModelTrait;

    public function __construct(){
        echo 'call Model ';

        //parent::__construct();
    }
}
~~~

![capture_20190123132231.png](https://qiita-image-store.s3.amazonaws.com/0/204834/87b653dd-5060-79a1-5f46-55f38d61beb2.png)

左上に「call Model」だけ出て、「call ModelTrait」は出ませんでした。
完全に__construct関数をオーバーライドしているようです。なのでtrait側のコンストラクタは呼び出せません。


## パターン4：Modelのコンストラクタそのものを削除
Modelのコンストラクタそのものを削除してみます。

~~~ Model.php
<?php
namespace App;

//class Model extends ModelBase{
class Model{
    use ModelTrait;

    //public function __construct(){
    //    echo 'call Model ';
    //
    //    parent::__construct();
    //}
}

~~~
![capture_20190123132451.png](https://qiita-image-store.s3.amazonaws.com/0/204834/589dcffc-a004-2886-7edf-a5a1df26b1de.png)

これでようやく、「call ModelTrait」が表示されました。
しかし、そもそものModel側のコンストラクタ側を消しちゃってるので、本末転倒ですね。


## (追記)パターン5：エイリアス
@mpyw 様にコメントにて記入いただきました！！本当にありがとうございます。
エイリアスを使用することで解決するそうです。

~~~ Model.php
<?php
namespace App;

class Model extends ModelBase
{
    use ModelTrait {
        ModelTrait::__construct as traitConstructor;
    }

    public function __construct()
    {
        echo 'call Model ';
        
        $this->traitConstructor();     
    }
}

~~~
![capture_20190124113252.png](https://qiita-image-store.s3.amazonaws.com/0/204834/272d24e0-696a-5fc2-08f1-85f747620fdf.png)


これでようやく、一番やりたかった「call Model call ModelTrait」が表示されました。
本当にありがとうございます！


# (追記)今回学んだこと
元々は、「すでにあるパッケージ内のクラスを継承して、クラスを実装する」という処理を行う際、「手軽に共通処理や共通変数を実装できないかなあ」というところから、traitを使ってみました。
ですが、Twitterなどでいただいたコメントを見てみると、traitはコンストラクタを持たせるのは良くないらしく、ただ単に処理・関数のみを共通化するのに使うのが良いみたいですね。
「Laravelのパッケージでも、traitにコンストラクタを持たせることは絶対にやってない」というコメントもいただきました。

なので今回の用途では、素直にtraitを止めて、元々のクラスでだけ、コンストラクタと変数を持たせることにしました。
どうしても共通処理や、変数・コンストラクタを使用したい場合は、すでにあるパッケージ内のクラスを継承して抽象クラスを作成し、そこに共通変数など書く。
さらにその抽象クラスを継承してクラスを作成する・・・という手順が必要ですね。（本来こうすべきですね）

めっちゃ勉強になりました。ありがとうございます！