Laravel
はWeb職人好みの使いやすいフレームワークです。
Laravelが初期装備している Eloquent ORM
はコレクション機能と親和性が高く、組み合わせて利用するメリットは大きいのですが、苦手なものも当然もあります。
その一つが「 主キーの変更が苦手 」という点です。
Eloquentで開発した結果、 主キーは変更せず規約に従うのがベター という結論に至りましたので、主キー周りについて私が理解したことをまとめます。
環境は以下の通りです。
- PHP 7
- Laravel 5.3
Eloquent ORMの主キー規約
Eloquent ORMでは次のように主キーに対する規約を設けております。
- 符号なしINT (unsigned int)
- フィールド名は
id
- オートインクリメント
この規約に従う場合に限り、とてもシンプルにコードを書くことができます。
スキーマビルダー
規約に従った主キーを、マイグレーションのスキーマビルダーで定義する場合は次のように指定します。
1Schema::table('users', function ($table) {
2 $table->increments('id');
3});
モデル
モデルの生成後にすることは、テーブル名の定義くらいですね。
1class Book extends Model
2{
3 protected $table = 'users';
4}
主キーが文字列のケース
主キーを[code varchar(32)]に変更します。
スキーマビルダー
1Schema::table('books', function ($table) {
2 $table->string('code');
3 $table->primary('code');
4});
普通にフィールドを定義し primary()
で主キーとして定義します。
モデル
1class Book extends Model
2{
3 protected $table = 'books';
4 protected $primaryKey = 'code';
5 public $incrementing = false;
6 protected $casts = [
7 'code' => 'string',
8 ];
9}
主キーのフィールド名が id
と異なる場合、$primaryKey
プロパティをオーバーライドしてそれをEloquentに教えてあげます。
また、オートインクリメントを利用しない場合は $incrementing
プロパティをfalseにします。
このプロパティが public なことはちょっとした落とし穴です。
さらに、文字列が主キーの場合は $casts
を定義します。
これは親クラス内のgetCasts()で明示的にキャストを行っているためです。
1return array_merge([
2 $this->getKeyName() => $this->keyType,
3], $this->casts);
このケースで$castsを明示しなかった場合、 DBからSelectした値がintにキャストされ、0になってしまう という異常事態が発生してしまいますよ。
(まさにPHPという感じですね。)
複合主キーを利用する
定義はきますが、 save()できず実用性はありません 。
スキーマビルダー
1Schema::table('user_cards', function ($table) {
2 $table->integer('user_id')->unsigned();
3 $table->integer('card_no')->unsigned();
4 $table->primary(['user_id', 'card_no']);
5});
復号キーのため、配列を primary()
に与えます。
モデル
1class UserCard extends Model
2{
3 protected $table = 'user_cards';
4 protected $primaryKey = ['user_id', 'card_no'];
5 public $incrementing = false;
6}
今までと同様、$primaryKeyに配列を与え、$incrementingをfalseに設定します。
そのままではNG
save()できない
こうして複合主キーを持つテーブルを作ることは、一応可能です。
しかしながら、save()の実行時に次のようなエラーが発生してしまうため実用には堪えません。
1ErrorException in Model.php line 1702: Illegal offset type in isset or empty
INSERTは問題ありませんが、UPDATEを実行する場合エラーが発生します。
どうやら、UPDATEのWHEREではリテラルを期待しているにもかかわらず、配列が渡されるためエラーになってしまうようです。
回避方法は一応あるようですが、根っこに近いっ所にパッチを当てるのは避けたいところです。
サロゲートキーを使うのが一番
複合主キーを利用する方法は一応あるようですが、ここはおとなしく サロゲートキー(代理キー)
を利用するのが一番です。
規約に乗っかるためにサロゲートキーは用意し、復号主キーに充てるつもりだったナチュラルキーはユニークに設定する感じです。
テーブル上ではキーが一つ増えますが、勝手に生成されて勝手にUPDATEのキーに利用されるため、コード上でこのサロゲートキーを意識することはそれほどないと思います。
おわりに
Eloquent ORM
は十分使えるORMですが、アクセス数が膨大になることが予測されるプロジェクトは場合は別途手段を考えたほうが良いかなという印象を受けました。
それを考えてもLaravelを使うメリットは十分にありますよ。
参考
https://readouble.com/laravel/5.3/ja/eloquent.html
https://readouble.com/laravel/5.3/ja/migrations.html