Traitのすすめ
目次
Trait(トレイト)って何?
phpで作成するClassから共通化出来る部分を分離したい場合どうするでしょうか?
まず一つ考えられるのは、共通処理を持った親Classを作成し、それを各子Classが継承することです。
ただ共通化したいパーツが複数あり、子Class側で好きなパーツを好きなように組み合わせたい場合はどうでしょうか?
phpが継承出来るClassは1つに制限されており親Classでは実現が出来ません。
そこで使用することになるのがTraitです。
Class定義の直後にuse hogehoge などと記載されているのがTraitです。
Laravel自体でも利用しているTrait
Modelの中で次のような記述を見たことはないでしょうか?
class AdminUser extends Authenticatable
{
use HasFactory;
use SoftDeletes;
この場合、テスト用データ投入用のFactoryと論理削除に関する機能を追加しています。
このように継承とは別に追加したい共通した機能などを好きにトッピングできるのがTraitです。
どんな処理をTraitにするといいの?
複数のテーブルに対して同様の処理を行う場合に、更新処理を担うServiceにそれぞれ記述するのではなくTraitとして共通化することが考えられます。
具体的には、次のような処理が考えられます。
- メニューやカテゴリに紐付く画像をアップロードして登録する処理
- メニューやカテゴリのソート順を変更する処理
- ログイン認証を行う処理(顧客のログインでも管理画面のログインでも内部的な処理は共通)
例えばソート順を変更する場合、各テーブルに共通したカラム sort_noを追加しておき、初期値としてのsort_noを自身のidとしておきます。
ソート順の変更は[ ↑ ][ ↓ ](上下矢印のボタン)およびページ内の行をドラッグして自由に変更するパターンの2種類ある場合、以下のようなTraitを作成することで共通化できます。
trait Sortable
{
/**
* 画面一括ソート処理
* @param array $ids
* @return mixed
* @throw Throwable
*/
public function setSortNum(array $ids)
{
try {
if(!is_array($ids)){
return false;
}
return DB::transaction(function () use ($ids) {
$sort_list = [];
$conditions = [
'ids' => $ids,
'orders' => ['sort_no' => 'ASC'],
];
// 元々のソート順のソートNOを取得
$models = $this->repository->list($conditions);
if($models){
foreach($models as $model){
$sort_list[] = $model->sort_no;
}
}
// 並べ替えられたidに元々のソート順のソートNOを適用
foreach($ids as $index => $id){
$model = $this->repository->find((int) $id);
$model->sort_no = $sort_list[$index];
$model->save();
}
// 操作ログ取得用に変更したModelを返す
return $models;
});
} catch (Throwable $exception) {
throw $exception;
}
}
/**
* 前後のレコードとのソート処理
* @param int $id
* @param string $type
* @param array $conditions
* @return mixed
* @throw Throwable
*/
public function exchangeSortNum($id, $type, $conditions)
{
try {
if(!$id || !$type){
return false;
}
return DB::transaction(function () use ($id, $type, $conditions) {
// 対象のモデル取得
$model = $this->repository->find((int) $id);
if(!$model){
return false;
}
$sort_no = $model->sort_no;
// 1つ上にするのか下にするのか判定
if($type == 'up'){
$order = 'DESC';
}else{
$order = 'ASC';
}
$conditions['sort_no_'.$type] = $sort_no;
$conditions['orders'] = ['sort_no' => $order];
// 指定された検索条件を保持して前(または後)のモデルを取得
$neighbor_model = $this->repository->list($conditions)->first();
if(!$neighbor_model){
return false;
}
// 対象モデルと隣接したモデルのソート順入れ替え
$model->sort_no = $neighbor_model->sort_no;
$neighbor_model->sort_no = $sort_no;
$model->save();
$neighbor_model->save();
// 操作ログ取得用に変更したModelを返す
return [$model, $neighbor_model];
});
} catch (Throwable $exception) {
throw $exception;
}
}
}
あとはメニューやカテゴリを更新するServiceに以下のように指定することで共通化が実現します。
class UpdateMenuService
{
use Sortable;
class UpdateMenuCategoryService
{
use Sortable;
ちなみに必須ではありませんが、Traitの名前としてXXXable(XXXが可能)という名前をつけることが多いです。
どこからも呼べるからと乱用しない
Traitはどこからでも呼び出すことができる強力な仕組みですが、階層の異なる複数の場所から呼び出すと、この謎のメソッドはどこで定義されているんだ?といった混乱を招きます。
共通化するというのは素敵なことですが、ServiceならServiceの階層専用のTraitとして共通化するのがおすすめです。
仮にシンプルな共通した関数を実現したい場合はTraitとして作成せずHelper関数として登録した方が良いでしょう。



















