[CodeIgniter4 Tips] getCompiledSelect で useSoftDelete が効くようにする


※以前の記事「getCompiledSelect の罠、 useSoftDelete が抜け落ちる」を解決するための記事です。

getCompiledSelect で useSoftDelete が効かない原因

  • system/BaseModel.php
  • system/Model.php

getCompiledSelect に関する記述がなく、直接Bulderが発火しているため。

つまり、Modelに getCompiledSelect メソッドを追加してやればよい

とは言え system クラスを直接上書きはできない(?)ので、1つModelを噛むことにする

app/Models/MyModel.php
<?php
namespace App\Models;
use CodeIgniter\Model;

class MyModel extends Model {
    public function __construct() {
        parent::__construct();
    }

    public function getCompiledSelect() {
        if ($this->tempUseSoftDeletes) {
            $this->where($this->table . '.' . $this->deletedField, null);
        }
        return parent::getCompiledSelect();
    }
}

tempUseSoftDeletes プロパティを使っているのは withDeleted や onlyDeleted で一時的に useSoftDelete の挙動が変わるため。

実際に利用する場合はこうなる

app/Models/HogeModel.php
namespace App\Models;
use CodeIgniter\Model;
class HogeModel extends MyModel {  // Modelではなく、MyModelを継承する
    protected $table = 'hoge';
    protected $primaryKey = 'id';
    protected $returnType = 'array';
    protected $useSoftDeletes = true;
    protected $allowedFields = [];
    protected $useTimestamps = false;
    protected $createdField = 'created_at';
    protected $updatedField = 'updated_at';
    protected $deletedField = 'deleted_at';
    protected $validationRules = [];
    protected $validationMessages = [];
    protected $skipValidation = false;
    protected $afterFind = [];

    public function fuga() {
        echo $this->getCompiledSelect();
        // SELECT * FROM `hoge` WHERE `hoge`.`deleted_at` IS NULL
        echo $this->onlyDeleted()->getCompiledSelect();
        // SELECT * FROM `hoge` WHERE `hoge`.`deleted_at` IS NOT NULL
        echo $this->withDeleted()->getCompiledSelect();
        // SELECT * FROM `hoge`
    }
}

これができるとどう便利なの?

Model同士をJOIN句でテーブル結合をしたい時に有用。
特に集計を取るときや子テーブル側に検索条件が存在するに活躍する。

例えば上記サンプルと同様に PiyoModel があり、hoge.id = piyo.hoge_id のリレーションが存在する場合

getCompiledSelectを使わない(使えない)場合1

  • PiyoModel 側の $deletedField を手動で解決しなければならない(AND piyo.deleted_at IS NOT NULL の部分)
  • 見ての通りメンテナンス性が悪いが、クエリの効率は一番良い
$hogeModel = model('HogeModel');
$piyoModel = model('PiyoModel');
$hogePiyoList = $hogeModel
    ->join('piyo', 'hoge.id = piyo.hoge_id AND piyo.deleted_at IS NULL', '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

getCompiledSelectを使わない(使えない)場合2

$hogeModel = model('HogeModel');
$piyoModel = model('PiyoModel');
$piyoModel->find(); // 空打ちする
$hogePiyoList = $hogeModel
    ->join('(' . $piyoModel->getLastQuery() . ') 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

getCompiledSelectuseSoftDelete を認識してくれる場合

  • getCompiledSelectを使わない(使えない)場合2getCompiledSelect が解決できる
$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;

// より安全に書くならテーブル名もModelに解決させる(どうせリレーションは書かないといけないが)
$hogePiyoList = $hogeModel
    ->join(
        '(' . $piyoModel->getCompiledSelect() . ') ' . $piyoModel->table, 
        $hogeModel->table . '.id = ' . $piyoModel->table . '.hoge_id', 
        'inner'
    )
    ->find();

感想: 「何かあったときのために」って言うけど、どうせまず見ないんだから物理削除させて!