[CodeIgniter4 Tips] Model同士をJOINするModelを作った(useSoftDeleteを考慮したテーブルの結合方法)


※先に、以下の記事を読んでいただくと理解が深まると思います

Model で useSoftDelete を使いながらJOINをしようと思うと面倒くさい

getCompiledSelect で useSoftDelete が効くようにする の解決部で触れているが、メンテナンス性を上げるためにModelの機能を使いながらJOIN句を使おうとするとどうしてもサブクエリが発生してしまう

以下、getCompiledSelect が useSoftDelete を認識してくれる場合 より

$hogeModel = model('HogeModel');
$piyoModel = model('PiyoModel');
$hogePiyoList = $hogeModel
    ->join('(' . $piyoModel->getCompiledSelect() . ') piyo', 'hoge.id = piyo.hoge_id', 'inner')
    ->find();
// SELECT * 
// FROM hoge 
// INNER JOIN (SELECT * FROM piyo WHERE deleted_at IS NULL) piyo ON hoge.id = piyo.hoge_id  ← このサブクエリが憎い
// WHERE hoge.deleted_at IS NULL;

小規模な開発では問題にならないが、JOINが増えたり、レコード数が巨大になっていくほどサブクエリがボトルネックになっていくことは想像に難くない。

そこで、getCompiledSelectを使わない(使えない)場合1 のように手動でJOINしたものが簡単に作成できるようにカスタムのModelを作成した。

カスタムモデルの作成

前回作成した MyModel.php に追記する、

app/Models/MyModel.php
/**
 * JOIN Model without delted
 *
 * モデル同士をJOINする(useSoftDeleteを利用する)
 *
 * @param object  $model
 * @param string  $cond   The join condition
 * @param string  $type   The type of join
 * @param boolean $escape Whether not to try to escape identifiers
 *
 * @return $this
 */
public function joinModel(object $model, string $cond, string $type = '', bool $escape = null) {
    if ($model->useSoftDeletes) {
        $cond .= ' AND ' . $model->table . '.' . $model->deletedField . ' IS NULL';
    }
    return parent::join($model->table, $cond, $type, $escape);
}

/**
 * JOIN Model include Deleted
 *
 * モデル同士をJOINする(deletedを含む)
 */
public function joinModelWithDeleted(object $model, string $cond, string $type = '', bool $escape = null) {
    return parent::join($model->table, $cond, $type, $escape);
}

/**
 * JOIN Model Only Deleted
 *
 * モデル同士をJOINする(deletedのみ)
 */
public function joinModelOnlyDeleted(object $model, string $cond, string $type = '', bool $escape = null) {
    if ($model->useSoftDeletes) {
        $cond .= ' AND ' . $model->table . '.' . $model->deletedField . ' IS NOT NULL';
    }
    return parent::join($model->table, $cond, $type, $escape);
}

見ての通り、useSoftDeletes が設定されていたら $cond に追記してビルダに渡しているだけである。

使い方

join() のテーブルの代わりにモデルを渡せば良い。
MyModelの継承の仕方は前回の記事を参照

$hogeModel = model('HogeModel'); // MyModelを継承していること
$piyoModel = model('PiyoModel'); // MyModelは必要ないが継承しておいて損はないと思う
$hoteModel
    ->joinModel($piyoModel, $hogeModel->table . '.id = ' . $piyoModel->table . '.hoge_id', 'inner')
    ->find();
// SELECT * 
// FROM hoge 
// INNER JOIN piyo ON hoge.id = piyo.hoge_id AND piyo.deleted_at IS NULL
// WHERE hoge.deleted_at IS NULL;

$hoteModel
    ->joinModelWithDeleted($piyoModel, $hogeModel->table . '.id = ' . $piyoModel->table . '.hoge_id', 'inner')
    ->find();
// SELECT * 
// FROM hoge 
// INNER JOIN piyo ON hoge.id = piyo.hoge_id
// WHERE hoge.deleted_at IS NULL;

$hoteModel
    ->joinModelOnlyDeleted($piyoModel, $hogeModel->table . '.id = ' . $piyoModel->table . '.hoge_id', 'inner')
    ->find();
// SELECT * 
// FROM hoge 
// INNER JOIN piyo ON hoge.id = piyo.hoge_id AND piyo.deleted_at IS NOT NULL
// WHERE hoge.deleted_at IS NULL;

複雑なJoin句には対応できないかもしれないが、結構使えるはず。