🔥

CodeIgniter4 クエリビルダの「失敗時は FALSE」はいつ発生するのか

2022/10/20に公開約6,200字2件のコメント

はじめに

クエリビルダクラス内 insert update 等のデータ挿入・更新を扱うメソッドの戻り値について、公式ドキュメントでは以下の様に記載されている。

戻り値: 成功時は TRUE、失敗時は FALSE

以下の様な失敗時のエラーハンドリングの要否が自分の中で曖昧であった為、動作確認を行った。

app\Controllers\TestController.php
<?php

namespace App\Controllers;

use App\Controllers\BaseController;
use App\Models\TestTableModel;

class TestController extends BaseController
{
    //...
    
    public function store()
    {
        $model = new TestTableModel();
        if ($model->insert($this->request->getPost())) {
	    // 成功時
        } else {
            // 失敗時
        }
    }
}
app\Models\TestTableModel.php
<?php

namespace App\Models;

class TestTableModel extends Model
{
    /**
     * @var string
     */
    protected string $table = 'test_table';
}

抽象モデルは kenjis さまの CodeIgniter\Modelを使わない場合 を参考にさせていただきました。
ありがとうございます。

app\Models\Model.php
<?php

namespace App\Models;

use CodeIgniter\Database\BaseBuilder;
use CodeIgniter\Database\ConnectionInterface;

abstract class Model
{
    /**
     * @var string
     */
    protected string $table;

    /**
     * @var ConnectionInterface
     */
    protected $db;

    /**
     * @var BaseBuilder
     */
    protected BaseBuilder $builder;

    public function __construct(?ConnectionInterface $db = null)
    {
        if ($db === null) {
            $this->db = db_connect();
        } else {
            $this->db = $db;
        }

        $this->builder = $this->db->table($this->table);
    }

    /**
     * @param array $data
     * @return bool
     */
    public function insert(array $data): bool
    {
        return $this->builder->insert($data);
    }

    /**
     * @param array $where
     * @param array $data
     * @return bool
     */
    public function update(array $where, array $data): bool
    {
        $this->builder->where($where);
        return $this->builder->update($data);
    }

    /**
     * @param array $where
     * @return bool|string
     */
    public function delete(array $where): bool|string
    {
        $this->builder->where($where);
        return $this->builder->delete();
    }
}

実行環境

  • CodeIgniter 4.2.7
  • PostgreSQL 9.2

動作確認

確認を行ったパターン

データベースの接続に失敗した場合

誤ったホスト名を指定

CodeIgniter\Database\Exceptions\DatabaseException #8
Unable to connect to the database.
Main connection [Postgre]: pg_connect(): Unable to connect to PostgreSQL server: could not translate host name "XXX.XXX.XXX.XXX" to address: Unknown host

テーブル名の指定に誤りがある場合

ErrorException
pg_query(): Query failed: ERROR: リレーション"Test_table"は存在しません

存在しないカラムを指定した場合

ErrorException
pg_query(): Query failed: ERROR: リレーション"test_table"の列"hoge"は存在しません

データ挿入時にプライマリキーに値が指定されていない場合

NOT NULL制約違反

ErrorException
pg_query(): Query failed: ERROR: 列"id"内のNULL値はNOT NULL制約違反です

プライマリキーの値が重複した場合

ErrorException
pg_query(): Query failed: ERROR: 重複キーが一意性制約"test_table_pk"に違反しています

確認結果

常に HTTP ステータスコード 500 で例外が投げられていたので
もしや FALSE が返却されるケースは存在しないのでは?とも一瞬思ったが、よく確認すると development 環境では Config\Database.phpDBDebugTRUE で判定されていた為であった。

app\Config\Database.php
<?php
    //...
    
    public $default = [
        //...
        'DBDebug'  => (ENVIRONMENT !== 'production'),
        //...
    ];

production 環境かつ DBDebugFALSE で確認したところ
ErrorException のケースでは HTTP ステータスコード 200 で FALSE が返されるようになった。

結論

production 環境かつ Config\Database.phpDBDebugFALSE の場合には
失敗時のエラーハンドリングを行ったほうが良い。
今回は抽象モデル側でハンドリングを行うこととした。

app\Models\Model.php
<?php

namespace App\Models;

use CodeIgniter\Database\BaseBuilder;
use CodeIgniter\Database\ConnectionInterface;
use CodeIgniter\Database\Exceptions\DatabaseException;

abstract class Model
{
    /**
     * @var string
     */
    protected string $table;

    /**
     * @var ConnectionInterface
     */
    protected $db;

    /**
     * @var BaseBuilder
     */
    protected BaseBuilder $builder;

    public function __construct(?ConnectionInterface $db = null)
    {
        if ($db === null) {
            $this->db = db_connect();
        } else {
            $this->db = $db;
        }

        $this->builder = $this->db->table($this->table);
    }

    /**
     * @param array $data
     * @return bool
     */
    public function insert(array $data): bool
    {
        $result = $this->builder->insert($data);
        if ($result === false) {
            throw new DatabaseException();
        } else {
            return $result;
        }
    }

    /**
     * @param array $where
     * @param array $data
     * @return bool
     */
    public function update(array $where, array $data): bool
    {
        $this->builder->where($where);
        $result = $this->builder->update($data);
        if ($result === false) {
            throw new DatabaseException();
        } else {
            return true;
        }
    }

    /**
     * @param array $where
     * @return bool|string
     */
    public function delete(array $where): bool|string
    {
        $this->builder->where($where);
        $result = $this->builder->delete();
        if ($result === false) {
            throw new DatabaseException();
        } else {
            return $result;
        }
    }
}

参考

Discussion

これですが、本番環境だけ振る舞いが変わるのがよくないんですよ。
ほとんどのユーザーは本番環境の振る舞いをテストするテストコードを書いていないでしょうし、返り値のチェックをしていないコードは、本番環境でエラーが発生すれば異常な状態のまま処理が続くことになります。

4.3からは DBDebug のデフォルト値が本番環境でも true に変更されます。
https://github.com/codeigniter4/CodeIgniter4/blob/4.3/user_guide_src/source/changelogs/v4.3.0.rst#changes
また、例外クラスも統一されます。
https://github.com/codeigniter4/CodeIgniter4/blob/4.3/user_guide_src/source/changelogs/v4.3.0.rst#exceptions-when-database-errors-occur

コメントありがとうございます。
わたしの認識が大きく間違っていなかったようで安心しました。

本記事冒頭に v4.3 での更新内容について追記させていただきました。

ログインするとコメントできます