28
15

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-01-23

はじめに

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

左上に「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

悲しいことに、エラーが出てしまいました。
エラーの内容は「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

左上に「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

これでようやく、「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

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

(追記)今回学んだこと

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

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

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

28
15
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
15