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});
4

モデル

モデルの生成後にすることは、テーブル名の定義くらいですね。

1class Book extends Model
2{
3    protected $table = 'users';
4}
5

主キーが文字列のケース

主キーを[code varchar(32)]に変更します。

スキーマビルダー

1Schema::table('books', function ($table) {
2    $table->string('code');
3    $table->primary('code');
4});
5

普通にフィールドを定義し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}
10

主キーのフィールド名がidと異なる場合、$primaryKeyプロパティをオーバーライドしてそれをEloquentに教えてあげます。

また、オートインクリメントを利用しない場合は$incrementingプロパティをfalseにします。
このプロパティが public なことはちょっとした落とし穴です。

さらに、文字列が主キーの場合は$castsを定義します。

これは親クラス内のgetCasts()で明示的にキャストを行っているためです。

1return array_merge([
2  $this->getKeyName() => $this->keyType,
3], $this->casts);
4

このケースで$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});
6

復号キーのため、配列をprimary()に与えます。

モデル

1class UserCard extends Model
2{
3    protected $table      = 'user_cards';
4    protected $primaryKey = ['user_id', 'card_no'];
5    public $incrementing  = false;
6}
7

今までと同様、$primaryKeyに配列を与え、$incrementingをfalseに設定します。

そのままではNG

save()できない

こうして複合主キーを持つテーブルを作ることは、一応可能です。
しかしながら、save()の実行時に次のようなエラーが発生してしまうため実用には堪えません。

1ErrorException in Model.php line 1702: Illegal offset type in isset or empty
2

INSERTは問題ありませんが、UPDATEを実行する場合エラーが発生します。

どうやら、UPDATEのWHEREではリテラルを期待しているにもかかわらず、配列が渡されるためエラーになってしまうようです。

回避方法は一応あるようですが、根っこに近いっ所にパッチを当てるのは避けたいところです。

サロゲートキーを使うのが一番

複合主キーを利用する方法は一応あるようですが、ここはおとなしくサロゲートキー(代理キー)を利用するのが一番です。

規約に乗っかるためにサロゲートキーは用意し、復号主キーに充てるつもりだったナチュラルキーはユニークに設定する感じです。
テーブル上ではキーが一つ増えますが、勝手に生成されて勝手にUPDATEのキーに利用されるため、コード上でこのサロゲートキーを意識することはそれほどないと思います。

おわりに

Eloquent ORMは十分使えるORMですが、アクセス数が膨大になることが予測されるプロジェクトは場合は別途手段を考えたほうが良いかなという印象を受けました。
それを考えてもLaravelを使うメリットは十分にありますよ。

参考

Laravel 5.3 Eloquent
https://readouble.com/laravel/5.3/ja/eloquent.html

Laravel 5.3 マイグレーション
https://readouble.com/laravel/5.3/ja/migrations.html


PHPフレームワーク Laravel Webアプリケーション開発 バージョン5.5 LTS対応

竹澤 有貴,栗生 和明,新原 雅司,大村 創太郎
出版社:ソシム  発売日:2018-09-26

Amazonで詳細を見る

PHPフレームワーク Laravel入門

掌田津耶乃
出版社:秀和システム  発売日:2017-09-16

Amazonで詳細を見る

初めてのPHP

David Sklar
出版社:オライリージャパン  発売日:2017-03-18

Amazonで詳細を見る