PHP
PHPUnit

PHPのトレイトを使うならおさえておきたい5つのこと

More than 3 years have passed since last update.

PHP5.4から使えるようになったトレイト(trait)を使えば
単一継承だけでは限界のあったコードの再利用が可能になるのでとっても便利です。

「php 多重継承」こうググったあなたは是非traitの利用を検討してみましょう。

traitとはメソッドやプロパティーだけをクラスから切り出したもので
クラスはtraitを装備することで「ふるまい」を増やすことが出来ます

利用方法

下記はプロパティをJsonで欲しくなったので、クラスではなくトレイトとしてtoJsonメソッドを実装してみた例です。

  trait Jsonable
  {
    public function toJson()
    {
      return json_encode($this->getAttributes());
    }
  }

  // 親クラス
  class Store
  {
    use Jsonable;

    protected $attributes;

    public function getAttributes()
    {
      return $this->attributes;
    }
  }

  // 子クラス
  class WebStore extends Store
  {
    protected $attributes = ['name' => ''];
  }

  // 子クラス
  class RealStore extends Store
  {
    protected $attributes = ['name' => '', 'address' => ''];
  }

Jsonableトレイトを親クラスでuseしていますが、子クラスでuseしたって構いません。
Traitを利用すれば継承関係を汚すこと無く「ふるまい」を増やせるのです。

前提条件を定める

先程の例に出てきたJsonableトレイトですが、StoreクラスにはgetAttributesメソッドがあるはずと決めつけて書かれています。
もしgetAttributesメソッドが無かった場合は実行時にエラーになっちゃいますね。

下記のようにすればuseした段階でエラーとなってくれるので安心です。

  trait Jsonable
  {
    abstract public function getAttributes(); // 僕を使うならgetAttributesメソッドを実装していること

    public function toJson()
    {
      return json_encode($this->getAttributes());
    }
  }

これによってトレイトを使うための前提条件を明確に出来るんです。
インターフェースみたいですな。

基本的なアレコレ

  • トレイトはいくつでもuseできるとか
  • もしその中に同名メソッドを持つトレイトがいたらとか
  • その場合の回避方法だとか
  • トレイトが更に別のトレイトをuseできるとか

PHPの公式マニュアルで丁寧に説明されているので一読しておくと良いでしょう。

命名例

PHPとして命名規則はありません。どんな名前を付けても自由ですがオススメはあります

オススメといっても僕がわかりやすい!と感じた2つの法則です。

  • トレイトをuseすると「〜できる」ようになるので「able」使うと分かりやすい。
  • 単純にTraitを付けちゃう。クラスとかと同じディレクトリに置く場合に区別しやすい。
トレイト名 トレイト名からイメージできる機能
Jsonable JSON化出来る
JsonParserTrait JSONを解析できる
Authenticatable 認証出来る
Queueable キューに突っ込めるようになる

ユニットテスト

トレイト単体ではインスタンス化する事が出来ません。
そうなると仮のクラスにuseさせてテストしなくてはいけません。激めんどいっすね。
でもPHPUnitのgetMockForTraitメソッドを使えばスマートにテスト出来ます

getMockForTraitメソッド便利
https://phpunit.de/manual/current/ja/test-doubles.html#test-doubles.mocking-traits-and-abstract-classes

テクニックてきな

トレイトはインターフェースのような役割も果たすため、
instanceOfを使って処理を分岐するように、method_existsを使って分岐させてという感じで
フレームワークでしばしそういった処理を見ます。