【Laravel】【PHP】トレイト（Trait）を活用して実装を再利用する

# はじめに

今回はPHPに実装されている
　**トレイト（Trait）** 
と呼ばれる仕組みについて記載してみました。

# トレイト（Trait）とは

以下、PHP公式ドキュメントでの説明が下記となります

> PHP は、コードを再利用するための「トレイト」という仕組みを実装しています。
> 
> トレイトは、PHP のような単一継承言語でコードを再利用するための仕組みのひとつです。 トレイトは、単一継承の制約を減らすために作られたもので、 いくつかのメソッド群を異なるクラス階層にある独立したクラスで再利用できるようにします。 トレイトとクラスを組み合わせた構文は複雑さを軽減させてくれ、 多重継承や Mixin に関連するありがちな問題を回避することもできます。
> 
> トレイトはクラスと似ていますが、トレイトは単にいくつかの機能をまとめるためだけのものです。 トレイト自身のインスタンスを作成することはできません。 昔ながらの継承に機能を加えて、振る舞いを水平方向で構成できるようになります。 つまり、継承しなくてもクラスのメンバーに追加できるようになります。

https://www.php.net/manual/ja/language.oop5.traits.php

## 個人的な解釈

ここからは個人的な解釈となります。

### トレイト（Trait）
関連のない【複数クラスで使用する可能性のあるメソッドを再利用できるようにする】ための仕組みです。

### 継承
【複数クラスで使用する可能性のあるメソッドを再利用できるようにする】を実現させるにあたって、トレイト（Trait）以外を使った方法を先に紹介します。

それが **継承** と呼ばれる仕組みを使った方法です。

 **継承** は超ざっくり言うと、親クラスの構造を引き継いだ（拡張した）子クラスを定義することです。継承した関係にあるクラスであれば基本的に親と同じメソッドを持っているので、メソッドの再利用を実現可能です。

```php
class Hoge{

    protected string $name = 'hoge';

    public function toArray()
    {
        return get_object_vars($this);
    }
}

class HogeFuga extends Hoge{

    protected string $name = 'hoge-fuga';

}

$hogeFuga = new HogeFuga;
var_dump($hogeFuga->toArray());

// 出力
// array(1) {
//   ["name"]=>
//   string(8) "HogeFuga"
// }
```

上記コードのサンプルでは
`Hoge`クラスを継承した`HogeFuga`クラスを用意しました。

この`HogeFuga`クラス（子）は`Hoge`クラス（親）を継承しているので
`Hoge`クラス（親）に搭載されている`toArray`メソッドを使用可能です。

### 親子関係のない別のクラスにも`toArray`を取り入れたい...

ある時、`Hoge`クラスの`toArray()`メソッドを
用途としてはまったく関係のない別の`Color`クラスにも導入したいとなりました。

この際に`Hoge`を継承させた`Color`クラスを作るやり方だと
`Hoge`クラスに変更を加えた際に`Color`クラスまで影響を受けることになります。



今回はあくまでメソッドだけを取り入れたいので
**トレイト（Trait）** を使ってメソッドだけを取り入れることにしました。

```php
<?php

trait toArrayConvertable{
    
    public function toArray()
    {
        return get_object_vars($this);
    }
}

class Hoge{
    
    use toArrayConvertable;

    protected string $name = 'hoge';

}

class HogeFuga extends Hoge{

    protected string $name = 'hoge-huga';

}


// 関係のないColorクラス
class Color{
      
    use toArrayConvertable;
    
    protected string $name = 'color';

}


$hogeFuga = new HogeFuga;
var_dump($hogeFuga->toArray());

$color = new Color;
var_dump($color->toArray());

// 出力
// array(1) {
//   ["name"]=>
//   string(9) "hoge-huga"
// }
// array(1) {
//   ["name"]=>
//   string(5) "color"
// }

?>

```

Traitを使用することで関係のないクラスに対して
メソッドを提供することができました。

### トレイト（Trait）と継承の使い分け

個人的な使い分けの観点ですが

- 親子関係にあるクラス内でのメソッド => 継承
- 親子関係にないクラス間でのメソッド => トレイト（Trait）

というイメージで使い分けています。

# ユースケース

トレイト（Trait）クラスの活用例をいくつか載せてみました。
Laravel内で使用されている例や、個人的にLaravelでの実装時に使用した事例となります。


## Laravelで特定モデルへ論理削除を適用する

こちらはLaravelで使用されている例となります。

Laravelで特定のテーブルに論理削除を導入したい場合に、
モデルクラスへ`Illuminate\Database\Eloquent\SoftDeletes`のトレイトを適用することで
簡単に論理削除を導入することができます。
（`deleted_at`を基準として論理削除となります）

こちらはLaravel 11でインストール時に作られる`User`モデルクラスへ
論理削除のトレイトを適用する例です。

```php:app/Models/User.php
<?php

namespace App\Models;

// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\SoftDeletes; //★ Use宣言

class User extends Authenticatable
{
    use HasFactory, Notifiable, SoftDeletes; //★ SoftDeletesの適用

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * Get the attributes that should be cast.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'password' => 'hashed',
        ];
    }
}

```

## 特定機能のアクションメソッドを定義する

こちらは個人的に試した事例となります。

各リソースに対する機能のうち、
CSV形式で出力するルーティング`export/csv`をコントローラに対して設定したケースです。

こちらのCSV出力機能はリソースによっては追加しない想定のため、
アクションメソッドをトレイトで共通化しました。

```php:app/Http/Controllers/Controller.php
<?php

namespace App\Http\Controllers;

use App\Models\User;
use App\Http\Controllers\Traits\CsvExportable;

class UserController exuends Controller
{
    use CsvExportable;

    protected $model = User::class;
}
```

```php:app/Http/Controllers/Traits/CsvExportable.php
<?php

namespace App\Http\Controllers\Traits;

use Illuminate\Http\Request;

trait CsvExportable
{
    /**
     * CSVエクスポート
     */
    public function exportCsv(Request $request)
    {
        if (is_null($this->model)) {
            throw new \Exception('modelプロパティが設定されていません');
        }

        // CSV出力処理
    }
}
```




# まとめ
- Trait（トレイト）は実装したメソッドを共通化するための仕組みの１つ
- Traitを実装して、クラス内へ`use トレイト名`でメソッドが可能になる

```php
<?php

trait Greetable {

    public function hello()
    {
        echo 'hello!';
    }
}

class Hoge {

    use Greetable; // Greetableトレイトのメソッドを使用可能とする
    
    public function hoge()
    {
        echo 'hoge';
    }
}

$hoge = new Hoge;
$hoge->hello(); // hogeが挨拶してくれたようです

// 出力
// hello!
?>
```



# 参考
https://www.php.net/manual/ja/language.oop5.traits.php

https://qiita.com/azukiazusa/items/deb604a38fd6377cb68a

https://qiita.com/kouki_o9/items/94ee72921024d5feecd9

# 告知
最後にお知らせとなりますが、イーディーエーでは一緒に働くエンジニアを
募集しております。詳しくは採用情報ページをご確認ください。

https://eda-inc.jp/recruit/

みなさまからのご応募をお待ちしております。