CodeIgniterについてアウトプットしてみた～Model編～

## はじめに

今回はCodeIgniterで用意されているModelクラスについて説明します。
Modelは、単一のテーブルを便利に扱うための機能を提供するクラスです。
クエリビルダだと煩雑になりやすい各CRUD操作が楽になったりします！

## Modelクラスの説明-設定
まず、設定系のプロパティについて、説明していきます。

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

namespace App\Models;

use CodeIgniter\Model;

class UserModel extends Model
{
    protected $table = 'users';
    protected $primaryKey = 'user_id';
    protected $useAutoIncrement = false;
    protected $returnType= 'array';
    //protected $returnType = User::class;
    protected $useSoftDeletes = true;
    protected $allowedFields = [
        'username',
        'email',
        'password',
    ];
    protected bool $allowEmptyInserts = false;

    // date property
    protected $useTimestamps = true;
    protected $dateFormat = 'datetime';
    protected $createdField = 'created_at';
    protected $updatedField = 'updated_at';
    protected $deletedField = 'deleted_at';
}
```

### $table
利用するテーブル物理名を設定するプロパティです。
必ず設定する必要があります。

### $primaryKey
テーブルのプライマリーキーを設定するプロパティです。
デフォルトは`id`なので、ほとんどのテーブルでは設定しなくても問題ありませんが、
id以外のカラムをPKにする場合は、必ず設定する必要があります。

### $useAutoIncrement
オートインクリメント機能を利用するか設定するプロパティです。
デフォルトは`true`なので、ほとんどのテーブルでは設定しなくても問題ありませんが、
オートインクリメントではないカラムをPKにする場合は、`false`に設定する必要があります。

### $returnType
CRUD実行時に返却される結果の形式を設定するプロパティです。
デフォルトは`array`なので配列形式で返されますが、Entity形式も指定できます。

:::note info
Modelクラスではなく、クエリビルダでデータを取得すると、`codeIgniter\Database\MySQLi\Result`のようにResultオブジェクトが返されます。
:::

### $useSoftDeletes
delete()でのレコード削除を論理削除にするか設定するプロパティです。
デフォルトは`false`なので、物理削除テーブルとして扱われます。
`true`に設定すると論理削除になるだけでなく、find()による取得時も論理削除されたデータは取得されないような条件でSQLが構築されます。

:::note warn
$deletedFieldで指定したカラムが論理削除の判断に利用されるので、必ずNULL許可のカラムでなければなりません。
:::

### $allowedFields
登録/更新を許可するカラムを設定するプロパティです。
指定されていないカラムは値をセットしていても無視されるので、意図しないカラムの更新を防ぐことが出来ます。
なお、取得には影響しません。

:::note info
`$createdField、$updatedField、deletedField`に設定したカラムは、$allowedFieldsに設定しなくても更新されるため、設定不要です。
:::
:::note alert
プライマリーキーは更新されてはいけないので、絶対に指定しないでください。
:::

### $allowEmptyInserts
空データのinsertを許容するか設定するプロパティです。
デフォルトは`false`なので、許容されません。
何かしらの理由で。空データをまず登録した後で必要な値に更新したいケースがあれば、利用することがありそうですが、基本的には利用しないと思います。

### $useTimestamps
登録や更新に、現在日付を自動的に登録するか設定するプロパティです。
デフォルトは`false`です。
`true`にした場合は、`$dateFormat`で指定した形式で自動的に登録します。

### $dateFormat
自動登録する日付の型を設定するプロパティです。
デフォルトは`datetime`ですが、他にも`date`か`int`が設定できます。

### $createdField
登録日時に現在日時を登録するカラムを設定するプロパティです。
デフォルトは、`created_at`です。

### $updatedField
更新日時に現在日時を登録するカラムを設定するプロパティです。
デフォルトは、`updated_at`です。

### $deletedField
削除日時に現在日時を登録するカラムを設定するプロパティです。
デフォルトは、`deleted_at`です。

## Modelクラスの説明-バリデーション
次に、モデルを利用した登録/更新時にバリデーションを行う際の各種設定です。

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

namespace App\Models;

use CodeIgniter\Model;

class UserModel extends Model
{
    protected $validationRules = [
        'username'     => 'required|max_length[30]|alpha_numeric_space|min_length[3]',
        'email'        => 'required|max_length[254]|valid_email|is_unique[users.email]',
        'password'     => 'required|max_length[255]|min_length[8]',
        'pass_confirm' => 'required_with[password]|max_length[255]|matches[password]',
    ];
    protected $validationMessages = [
        'email' => [
            'is_unique' => '既に登録されているメールアドレスです。',
        ],
    ];
    protected $skipValidation = false;
    protected $cleanValidationRules = true;
}
```

### $validationRules
バリデーションルールを配列形式で設定するプロパティです。
デフォルトは、`[]`です。

### $validationMessages
バリデーションエラー時のメッセージを配列形式で設定するプロパティです。
デフォルトは、`[]`です。

### $skipValidation
登録/更新時にバリデーションを行うか設定するプロパティです。
デフォルトは、`false`なのでバリデーションは行います。
ただ、前述の`$validationRules`を指定しないと行われません。

### $cleanValidationRules
渡されたデータに存在しないバリデーションルールは、バリデーション対象から除外するか設定するプロパティです。
デフォルトは`true`なので除外されます。
更新時に一部カラムだけ更新したい場合に、trueにしておけばバリデーションエラーを回避できます。

## Modelクラスの説明-コールバック
次に、モデルを利用した各CRUD操作時に行えるコールバックです。
こちらは簡単な紹介のみになります。

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

namespace App\Models;

use CodeIgniter\Model;

class UserModel extends Model
{
    protected $allowCallbacks = true;
    protected $beforeInsert = [];
    protected $afterInsert = [];
    protected $beforeUpdate = [];
    protected $afterUpdate = [];
    protected $beforeFind = [];
    protected $afterFind = [];
    protected $beforeDelete = [];
    protected $afterDelete = [];
}
```

### $allowCallbacks
コールバック処理を許可するか設定するプロパティです。
デフォルトは`true`なので、コールバック処理が実装されていれば実行されます。

### $beforeInsert、$beforeUpdate、$beforeFind、$beforeDelete
それぞれ、`登録/更新/取得/削除前`に行う処理を記載するメソッドです。
メソッドに渡される引数はそれぞれ異なります。詳しくは[こちら](https://codeigniter.com/user_guide/models/model.html#event-parameters)

### $afterInsert、$afterUpdate、$afterFind、$afterDelete
それぞれ、`登録/更新/取得/削除後`に行う処理を記載するメソッドです。
beforeと同様、メソッドに渡される引数はそれぞれ異なります。

## Modelクラスの説明-CRUDメソッド
最後に、モデルを利用した各CRUD操作時を行える各メソッドです。

### find($pk)
PKを条件にデータを取得するメソッドです。
論理削除されたデータは取得されません。
```php
$user = $userModel->find(1); // PKで単一取得
$user = $userModel->find([1, 2]); // PKで複数取得
// SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL AND `users`.`id` IN (1,2)
```

### findColumn($columeName)
単一カラムの取得を行うメソッドです。
論理削除されたデータは取得されません。
```php
$user = $userModel->findColumn('login_id');
// SELECT `login_id` FROM `users` WHERE `users`.`deleted_at` IS NULL
```

### findAll($limit, $offset)
全てのデータを取得するメソッドです。
条件を絞りたい場合はクエリビルダーのwhereなどを利用します。
論理削除されているデータは取得されません。
```php
$user = $userModel->where('user_code', 'test')->findAll();
// SELECT * FROM `users` WHERE `user_code` = 'test' AND `users`.`deleted_at` IS NULL
```

### first()
1件データを取得するメソッドです。
条件を絞りたい場合はクエリビルダーのwhereなどを利用します。
論理削除されているデータは取得されません。
```php
$user = $userModel->where('user_code', 'test')->first();
// SELECT * FROM `users` WHERE `user_code` = 'test' AND `users`.`deleted_at` IS NULL  LIMIT 1
```

### withDeleted()
論理削除されたデータも取得したい時に利用するメソッドです。
```php
$user = $userModel->where('user_code', 'test')->withDeleted()->findAll();
// SELECT * FROM `users` WHERE `user_code` = 'test'
```

### onlyDeleted()
論理削除されたデータのみ取得したい時に利用するメソッドです。
```php
$user = $userModel->onlyDeleted()->where('user_code', 'test')->findAll();
// SELECT * FROM `users` WHERE `user_code` = 'test' AND `users`.`deleted_at` IS NOT NULL
```

### insert($data)
新規登録したい場合に利用するメソッドです。
```php
$data = [
  'login_id' => 'user1',
  'password' => 'password',
];
$userModel->insert($data); // return PK
$userModel->insert($data, false); // returnクエリ成否。成功(true)、失敗(false)
$userModel->getInsertID(); // return 登録したPKの値
// INSERT INTO `users` (`login_id`, `password`, `created_at`, `updated_at`) VALUES ('user1', 'password', '2024-04-23 09:20:33', '2024-04-23 09:20:33')
```

### allowEmptyInserts()
空のデータを新規登録したい場合に利用するメソッドです。
```php
$userModel->allowEmptyInserts()
// created_at, updated_atの自動登録をモデルで設定している場合
// INSERT INTO `users` (`created_at`, `updated_at`) VALUES ('2024-04-23 09:33:57', '2024-04-23 09:33:57')
// created_at, updated_atの自動登録をモデルで設定していない場合
// INSERT INTO `users` VALUES ()
```

### update($pk, $data)
データを更新したい場合に利用するメソッドです。
```php
$data = [
   'login_id' => 'user1',
   'password' => 'password',
];
$userModel->update(1, $data); // return クエリ成否。返り値は成功(true)、失敗(false)。
// UPDATE `users` SET `login_id` = 'user1', `password` = 'password' WHERE `users`.`id` IN (1)

$userModel->update([10, 11, 12], $data); // 更新処理を実施
// UPDATE `users` SET `login_id` = 'user1', `password` = 'password' WHERE `users`.`id` IN (10,11,12)

$userModel->where('name', '山田')->set($data)->update(); // 主キー以外のupdateをしたい場合
// UPDATE `users` SET `login_id` = 'user1', `password` = 'password' WHERE `name` = '山田'

$userModel->set($data)->update(); // 更新処理を実施
// 4.3からwhereを指定しないとエラーになります
// Updates are not allowed unless they contain a "where" or "like" clause
```

### save()
insert()/update()のラッパーで、PKが指定されていれば更新、されていなければ新規登録するメソッドです。
```php
$data = [
   'login_id' => 'user1',
   'password' => 'password',
];
$userModel->save($data); // 新規登録。return クエリ成否。返り値は成功(true)、失敗(false)
// INSERT INTO `users` (`login_id`, `password`) VALUES ('user1', 'password')

$data = [
   'id' => 10,
   'login_id' => 'user1',
   'password' => 'password',
];
$userModel->save($data); // 更新
// UPDATE `users` SET `login_id` = 'user1', `password` = 'password' WHERE `users`.`id` IN (10)
```

### delete()
データを削除したい場合に利用するメソッドです。
$useSoftDeletesが設定されていれば論理削除、されていなければ物理削除になります。
```php
$userModel->delete(1); // PKで単一削除。return クエリ成否。返り値は成功(true)、失敗(false)。
// UPDATE `users` SET `deleted_at` = '2024-04-23 10:11:36' WHERE `id` IN (1) AND `deleted_at` IS NULL

$userModel->delete([1, 2]); // PKで複数削除
// UPDATE `users` SET `deleted_at` = '2024-04-23 10:12:14' WHERE `id` IN (1,2) AND `deleted_at` IS NULL

$userModel->where('login_id', 'user1')->delete(); // PK以外の条件場合はクエリビルダーのwhere等を使う
// UPDATE `users` SET `deleted_at` = '2024-04-23 10:13:00' WHERE `login_id` = 'user1' AND `deleted_at` IS NULL
```

### purgeDeleted()
論理削除されたデータを全て物理削除するメソッドです。
```php
$userModel->purgeDeleted();
// DELETE FROM `users` WHERE `users`.`deleted_at` IS NOT NULL
```

## モデルクラスの説明-おまけ
### chunk()
バッチなどで大量のデータを処理する場合にメモリが枯渇しないように、分割取得して処理したい場合に利用するメソッドです。

```php
$userModel->where('company_code', 1)->chunk(2, static function ($data) {
    // 何かしらの処理
});
// 取得するデータが5件ある場合
// SELECT COUNT(*) AS `numrows` FROM `users` WHERE `company_code` = 1
// SELECT * FROM `users` WHERE `company_code` = 1 LIMIT 2
// SELECT * FROM `users` WHERE `company_code` = 1 LIMIT 2, 2
// SELECT *　FROM `users`　WHERE `company_code` = 1　LIMIT 4, 2
```

### protect()
一時的に`$allowedFields`を無視して、登録/更新したい際に利用するメソッドです。
seedでのテストデータ登録などで利用しますが、通常は使わないようにしましょう。
```php
$userModel->protect(false)->insert($data)->protect(true); // シード実施時など、一時的に
```

### asArray()、asObject()
$returnTypeに指定した形式以外で取得したい場合に利用するメソッドです。
```php
$userModel->asArray()->where('status', 'active')->findAll();
$userModel->asObject()->where('status', 'active')->findAll();
```

### クエリビルダーの呼び出し
モデルからクエリビルダーのメソッドは呼び出すことが出来ます。
（ビルダーからモデルのメソッドは呼び出すことは出来ません）

```php
$userModel->builder();
$userModel->builder('books'); // 別テーブルのビルダーを取得
```

## おわりに

CodeIgniterのModelについて、説明しました。
Modelと[前の記事](https://qiita.com/namasa/items/a987cb2b09fda2cce167)で紹介したEntityを使えば、DB操作周りの開発が加速しそうですね😃