はじめに
私は現在(2022/11時点)社内システム開発をメインに担当しています。
執筆時点で社内システムは10個以上存在し、各システムのフレームワークもバラバラです。
その中でも比較的採用数が多いのが Slim4+Eloquent と CodeIgniter(3 or 4) になります。
Eloquent はもともとLaravelフレームワークのORMとして提供されているサービスであり、多機能であることが特徴です。
一方、CodeIgniter はLaravelに比べて軽量でパフォーマンスが良いのが特徴です。
バージョン4になってからは様々な機能が追加され、3以前でも存在していた Model に加え、取得レコードをエンティティとして扱うことに特化した Entity が新たに導入されました。1
2022年現在も機能が追加されており、バージョンが上がるたびに使いやすくなってきています。
そこで、Eloquent と CodeIgniter(4に限定します)の Model/Entity とを機能ごとに比較し、それぞれの使い勝手を見てみることにしました!
今はLaravel使ってるけど、機能多すぎだしCodeIgniterも少し気になるから使ってみたい!という方の参考になれば幸いです。
モデルの作成
どちらもコマンドで作成可能です。
CodeIgniter4のほうがデフォルトで生成されるコード量は多いです。
Eloquent
空のモデルが作成されます。
ただしそのままだとApp
直下に作成されるため、名前空間を付けたい場合はコマンド引数に Entities/User
のような形で指定する必要があります。
$ php artisan make:model User
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
//
}
Model/Entity(CodeIgniter4)
各プロパティにデフォルト値がセットされたモデルが作成されます。
名前空間は App\Models
となります。
$ php spark make:model UserModel
$ php spark make:entity User
<?php
namespace App\Models;
use CodeIgniter\Model;
class UserModel extends Model
{
protected $DBGroup = 'default';
protected $table = 'users';
protected $primaryKey = 'id';
protected $useAutoIncrement = true;
protected $insertID = 0;
protected $returnType = 'array';
protected $useSoftDeletes = false;
protected $protectFields = true;
protected $allowedFields = [];
// Dates
protected $useTimestamps = false;
protected $dateFormat = 'datetime';
protected $createdField = 'created_at';
protected $updatedField = 'updated_at';
protected $deletedField = 'deleted_at';
// Validation
protected $validationRules = [];
protected $validationMessages = [];
protected $skipValidation = false;
protected $cleanValidationRules = true;
// Callbacks
protected $allowCallbacks = true;
protected $beforeInsert = [];
protected $afterInsert = [];
protected $beforeUpdate = [];
protected $afterUpdate = [];
protected $beforeFind = [];
protected $afterFind = [];
protected $beforeDelete = [];
protected $afterDelete = [];
}
<?php
namespace App\Entities;
use CodeIgniter\Entity\Entity;
class User extends Entity
{
protected $datamap = [];
protected $dates = ['created_at', 'updated_at', 'deleted_at'];
protected $casts = [];
}
モデルにセットできる主なプロパティ
CodeIgniter4でプロパティにセットできるものが全てEloquentでもセットできるわけではなく、トレイトや定数、カスタム関数などでセットするものもあるようです。
イベントはEloquentのほうが豊富です。
Eloquent | Model(CodeIgniter4) | |
---|---|---|
DB接続先 | ○($connection) | ○($DBGroup) |
テーブル名 | ○($table) デフォルトはModel名スネークケースの複数形 |
○($table) Eloquentと異なり必須 |
主キー | ○($primaryKey) | ○($primaryKey) 複合キーは指定できない |
主キーのオートインクリメント | ○($incrementing) | ○($useAutoIncrement) |
主キーのデータ型 | ○($keyType) | ×(intのみ) |
結果オブジェクトの型 | ×(Modelに固定) | ○($returnType) Entityを指定可 |
論理削除フラグ | △(SoftDeletesトレイトを使用) | ○($useSoftDeletes) |
更新許可フィールド | ○($fillable) | ○($allowedFields) |
更新禁止フィールド | ○($guarded) | × |
表示フィールド | ○($visible) | × |
非表示フィールド | ○($hidden) | × |
現在日時の自動設定 | ○($timestamps) | ○($useTimestamps) |
DBに入れる日付値の型 | ○($dateFormat) | ○($dateFormat) |
作成日時フィールド | △(CREATED_AT定数) | ○($createdField) |
更新日時フィールド | △(UPDATED_AT定数) | ○($updatedField) |
削除日時フィールド | △(DELETED_AT定数) | ○($deletedField) |
属性値のキャスト | ○($casts) | ○(Entityの$casts) |
デフォルト属性値 | ○($attributes) | × |
バリデーション | × | ○$validateRules にルールをセットする $validationMessages にエラーメッセージをセットする$skipValidation = true でバリデーションをスキップ可 |
イベント | ○($dispatchesEvents) 以下のイベントを連想配列にして登録可 retrieved creating created updating updated saving saved deleting deleted trashed forceDeleted restoring restored replicating |
○以下のイベントをプロパティ名にして登録可 beforeInsert beforeInsert beforeUpdate afterUpdate beforeFind afterFind beforeDelete afterDelete |
インスタンス生成
データを取得せず、空のエンティティを生成したい場合の書き方です。
Eloquentだとレコード登録も同時に行なえます。
Eloquent
new
演算子を使用します。
$entity = new User();
インスタンス生成と同時にDB保存も行いたい場合は create()
を使用します。
$entity = User::create(['name' => 'セレス 太郎']);
Entity(CodeIgniter4)
new
演算子を使用します。2
$entity = new User();
Eloquentのcreate()
にあたる機能はないようです。
findによる取得
引数にIDを指定し、エンティティを1件返します。
Eloquentのほうがバリエーションが多いです。
Eloquent
引数にIDを指定し、該当レコードが見つかればModel
で返します。
配列でIDを複数指定するとCollection
で返却されます(内部的にはfindMany()
を呼び出している)。
$entity = User::find($id);
$entities = User::find([$id1, $id2, $id3]);
findOr〜
の形のメソッドもあり、find()
に失敗した場合に所定の動作をします。
// 失敗したら例外
$entity = User::findOrFail($id);
// 失敗したらインスタンスを生成
$entity = User::findOrNew($id);
Model/Entity(CodeIgniter4)
Model
でfind()
します。IDの配列指定で複数レコード取得可能です(配列で返却されます)。
$returnType
にEntity
をセットした場合はEntity
で受け取れます。
$entity = model('App\Models\UserModel')->find($id);
$entities = model('App\Models\UserModel')->find([$id1, $id2, $id3]);
findOr
系はないようです。
firstによる取得
クエリ結果の最初の1レコード目を返します。
バリエーションとしてはほぼfind()
と同様です。
Eloquent
find()
と同じくModel
で返します。
$entity = User::where(['code' => '1'])->first();
// firstWhereだとさらに簡略化できる
$entity = User::firstWhere(['code' => '1'];
firstOr
系のメソッドも定義されています。
// 存在しなければ例外
$entity = User::where(['code' => '1'])->firstOrFail();
// 存在しなければインスタンスを生成
$entity = User::firstOrNew(['code' => '1']);
// 存在しなければレコード作成
$entity = User::firstOrCreate(['id' => $id], ['code' => '1', 'name' => 'セレス 太郎']);
Model/Entity(CodeIgniter4)
find()
と同様、$returnType
に指定した型で返します。
$entity = model('App\Models\UserModel')->where(['code' => '1'])->first();
firstOr
系はないようです。
保存・変更検知
どちらにも保存機能はありますが、Eloquentのほうが変更検知機能は豊富です。
Eloquent
save()
で保存します。
こちらもsaveOr
系のメソッドが存在します。
// 保存
$entity->save();
// 保存に失敗したら例外
$entity->saveOrFail();
save()
はエンティティの内容に変更がある場合のみDBにアクセスします。
変更があるかどうかはisDirty()
を呼び出します(⇔isClean()
)。
// 変更前なのでfalse
var_dump($entity->isDirty());
// 値の変更
$entity->name = 'セレス 次郎';
// 変更したのでtrue
var_dump($entity->isDirty('name'));
save()
実行で実際に保存されたかを確認するには、以下のようにします。
// 新規作成
$entity1 = User::create(['name' => 'セレス 次郎'])
// trueを返す
var_dump($this->wasRecentlyCreated);
// 変更
$entity2 = User::find(1)->fill(['name' => 'セレス 三郎'])->save();
// trueを返す
var_dump($this->wasChanged('name'));
// 保存したのでdirtyではなくなっている(false)
var_dump($this->isDirty('name'));
Entity(CodeIgniter4)
Eloquentと同様、save()
で保存します。
saveOr
系のメソッドはないようです。
$entity->save();
エンティティが変更されたかを確認するにはhasChanged()
を使います。
$entity = new User();
// falseを返す
var_dump($entity->hasChanged('name'));
$entity->name = 'セレス 次郎';
// trueを返す
var_dump($entity->hasChanged('name'));
実際に変更が反映されたかどうかを確認するメソッドはないようです。
削除
Eloquent、Model(CodeIgniter4)、いずれもソフトデリート(SoftDeletes
)に対応しています。
Eloquent
Model
に対応するレコードを削除するにはdelete()
、レコードを直接削除するにはdestroy()
を使用します。
// モデルから削除
$user = User::find(3);
$user->delete();
// ID指定で直接削除
User::destroy(3);
また、SoftDeletes
トレイトを追加することでソフトデリート(論理削除)も扱えます。
// ソフトデリート
$user->delete();
// 復元
$user->restore();
// 完全削除
$user->forceDelete();
また、ソフトデリートに関するクエリビルダのメソッドとして以下のようなものがあります。
-
withTrashed()
: ソフトデリートしたレコードも含む -
onlyTrashed()
: ソフトデリートしたレコードのみ
Model(CodeIgniter4)
レコードを削除するにはModel
のdelete()
を使用します。
Entity
側にはdelete()
は存在しないようです。
model('App\Models\UserModel')->delete(3);
また、Model
の$useSoftDeletes
プロパティにtrue
をセットすることでソフトデリートも扱えます。
// ソフトデリート
model('App\Models\UserModel')->where('id', 3)->delete();
// 完全削除
model('App\Models\UserModel')->where('id', 3)->purgeDeleted();
また、ソフトデリートに関するクエリビルダのメソッドとして以下のようなものがあります。
-
withDeleted()
: ソフトデリートしたレコードも含む -
onlyDeleted()
: ソフトデリートしたレコードのみ
リレーション
リレーション定義はEloquentのみ可能です。
Eloquent
リレーションを定義するための専用のメソッドがあります。
また、これらの仕組みを使って Eager Loading を実現できます。
子テーブル(1対1)
hasOne()
を使用します。
public function address()
{
return $this->hasOne('App\Models\Address');
}
子テーブル(1対多)
hasMany()
を使用します。
public function articles()
{
return $this->hasMany('App\Models\Article');
}
親テーブル
belongsTo()
を使用します。
public function department()
{
return $this->belongsTo('App\Models\Department');
}
Model/Entity(CodeIgniter4)
リレーションを定義する仕組みはありません。
アクセサ(getter)、ミューテタ(setter)
アクセサ、ミューテタを使用することで、取得・保存時に追加処理を加えたり、データベースにないフィールド(カスタムフィールド)を定義することができます。
Eloquent、Entity(CodeIgniter4)どちらでも定義ができ、書き方もほとんど変わりません。
Eloquent
アクセサは get
+ フィールド(パスカルケース) + Attribute
、ミューテタは set
+ フィールド(パスカルケース) + Attribute
というメソッド名で定義します。
class User extends Model
{
// 生年月日をもとに年齢を計算する
public function getAgeAttribute()
{
if ($this->birthday === null) {
return null;
}
$diff = date_diff(date_create($this->birthday), date_create());
return $diff->y;
}
// パスワードを暗号化する
public function setPasswordAttribute($plain)
{
$this->attributes['password'] = password_hash($plain, PASSWORD_BCRYPT);
}
}
なお、日付項目については自動的にCarbon
(Laravelにも標準搭載されている日時ライブラリ)インスタンスに変換されるアクセサやミューテタがデフォルトで使われているそうです。
Entity(CodeIgniter4)
アクセサは get
+ フィールド(パスカルケース)、ミューテタは set
+ フィールド(パスカルケース) というメソッド名で定義します。
class User extends Entity
{
// 生年月日をもとに年齢を計算する
public function getAge()
{
if ($this->birthday === null) {
return null;
}
$diff = date_diff(date_create($this->birthday), date_create());
return $diff->y;
}
// パスワードを暗号化する
public function setPassword($plain)
{
$this->attributes['password'] = password_hash($plain, PASSWORD_BCRYPT);
return $this;
}
}
なお、日付項目についてはEntity
の$date
プロパティに配列形式で対象フィールドをセットしておくとTime
型に自動変換してくれるそうです。
クエリビルダ
Eloquent、Model(CodeIgniter4)にはクエリビルダ機能も備わっています。
メソッドの数が多すぎるので、主に使われるメソッドに絞って簡単に比較してみます。
Eloquent | Model(CodeIgniter4) | |
---|---|---|
全件取得 | 条件を指定せずget()
|
条件を指定せずfindAll()
|
単一行取得 |
first() またはfind()
|
first() またはfind()
|
単一値取得 | value() |
なし |
カラム値リスト取得 | pluck() |
findColumn() |
集計 |
count() ,max() ,min() ,avg() ,sum()
|
countAllResults() :集計値を整数で返すselectCount() ,selectMax() ,selectMin() ,selectAvg() ,selectSum() :SELECT項目の追加 |
レコード存在判定 |
exists() (否定形はdoesntExist() ) |
なし |
SELECT |
select() ,selectRaw() (素のSELECT) |
select() (素のSELECTも可) |
DISTINCT | distinct() |
distinct() |
WHERE |
where() ,whereRaw() (素のWHERE)など |
where() など(素のWHEREも可) |
HAVING |
having() ,havingRaw() (素のHAVING)など |
having() など(素のHAVINGも可) |
ORDER BY |
orderBy() ,orderByRaw() (素のORDER BY)など |
orderBy() など(素のORDER BYも可) |
GROUP BY |
groupBy() ,groupByRaw() (素のGROUP BY)など |
groupBy() など(素のGROUP BYも可) |
INNER JOIN | join() |
join() の第三引数にinner を指定 |
LEFT JOIN | leftJoin() |
join() の第三引数にleft を指定 |
RIGHT JOIN | rightJoin() |
join() の第三引数にright を指定 |
CROSS JOIN | crossJoin() |
join() の第二引数以降指定なし |
複雑なJOIN | クロージャーで設定可 | 全てstring で書く必要あり |
サブクエリJOIN |
joinSub() ,leftJoinSub() ,rightJoinSub()
|
サブクエリをstring 化し、エスケープなしでjoin() の第一引数にセットする |
UNION |
union() ,unionAll()
|
union() ,unionAll()
|
サブクエリWHERE |
where() やwhereExists() などにクロージャーで指定 |
サブクエリをstring 化し、エスケープなしでwhere() の第一引数にセットする |
INSERT |
insert() ,insertIgnore() (エラーの無視) |
insert() |
バルクインサート |
insert() に二次元配列をセットする |
insertBatch() |
INSERT IGNORE | insertOrIgnore() |
ignore(true)->insert() |
INSERT SELECT | insertUsing() |
なし |
UPSERT | upsert() |
なし |
更新または挿入 | updateOrInsert() |
なし |
DELETE | delete() |
delete() |
Raw | DB::raw() |
new RawSql() (v4.2以降) |
総評
双方のORMの主な機能をだいたい比較してみました。
やはりEloquentのほうがCodeIgniterに比べて断然使える機能は多いです。
ですが、CodeIgniterをバージョン3から使っている筆者としては、だいぶ機能が増えたなぁという感想です。
コレクションと Eager Loading の仕組みが無いのが一番大きいですが、そこに目を瞑ればCodeIgniterでも小〜中規模くらいのサービスなら開発できると思います。
また実際に開発してみて気づきましたが、Laravelのコードは複雑怪奇で読みにくい一方で、CodeIgniterのコードはすごく読みやすいです。
未だにLaravelは人気No.1のフレームワークですが、CodeIgniterももう少し人気が出てもいい気はします。
参考
- Laravel 9.x Eloquentの準備
- Laravel 9.x データベース:クエリビルダ
- Using CodeIgniter’s Model - CodeIgniter documentation
- Using Entity Classes - CodeIgniter documentation
- Query Builder Class - CodeIgniter documentation
- CodeIgniterとLaravelをざっとベンチマークしてみる