Laravel の主キーで UUID を利用する時にハマった事、調べた事
起きた問題
テーブルの id カラムを UUID で登録できるようにしていました。
例えば、以下のような users テーブルがあるとします。
テーブル名:users
id | name |
---|---|
62ce6b67-f8e3-4279-a2a1-0a23f3ca5730 | はてな 太郎 |
830fd685-58dc-46b5-b7df-16e06f3c1b34 | はてな 花子 |
単純にユーザーの一覧を取得したく、User::all()
を実行してみたところ、id が上記のような 36 桁の文字列ではなく、以下のように id が欠落された状態で取得されてしまいました。
{ "data": [ { "id": "62", "name": "はてな 太郎" }, { "id": "830", "name": "はてな 花子" } ] }
これを解決すべく調べた事や、試した事などをまとめます。
原因
まさにコレだ!というものが Laravel のドキュメントに書かれていました。
Eloquentは主キーを自動増分される整数値であるとも想定しています。つまり、デフォルト状態で主キーは自動的にintへキャストされます。
※ Eloquent:利用の開始 5.8 Laravel の「主キー」
この仕様のため、各 id の最初の数字部分(830
と 62
)だけが取得された事になります。
私はコレで解決しました
上記の Laravel のドキュメントと同じ箇所にこのように書かれていました。
自動増分ではない、もしくは整数値ではない主キーを使う場合、モデルにpublicの$incrementingプロパティを用意し、falseをセットしてください。主キーが整数でない場合は、モデルのprotectedの$keyTypeプロパティへstring値を設定してください。
これを読んだ時に正直思った事は、 $incrementing
と $keyType
の両方を設定しないといけないの?それとも片方だけでいいの?でした。
まずは、他の方のサイトも参考にしつつ、両方のプロパティを設定する事にしました。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model { public $incrementing = false; // ← 追加 protected $keyType = 'string'; // ← 追加 protected $fillable = ['id', 'name']; }
これらのプロパティを追加後にデータ取得し直してみると・・・
{ "data": [ { "id": "62ce6b67-f8e3-4279-a2a1-0a23f3ca5730", "name": "はてな 太郎" }, { "id": "830fd685-58dc-46b5-b7df-16e06f3c1b34", "name": "はてな 花子" } ] }
といった具合に、今度は id の一部が欠落されることなく取得することが確認できました。
以上です。。。
だけだとつまらないので、最初に思った疑問「プロパティ両方必要?それとも片方だけ?」を少し試しました。
プロパティ両方必要?それとも片方だけ?
結果を先に言いますと、どちらか片方だけでも想定通りの動き(id がちゃんと文字列 36 桁で取得)をしました。
// public $incrementing = false; protected $keyType = 'string';
でも
public $incrementing = false; // protected $keyType = 'string';
データの登録、取得で問題なく動きました。
しかし、なんだか腑に落ちないのでもう少し調べてみました。
Model に定義されているデフォルト値
Laravel の Illuminate\Database\Eloquent\Model
の中身を見てみたところ、以下のようにデフォルト値が設定されていました。
なので、どちらかのプロパティを設定して想定通りに動いたとしても、そのモデルのプロパティ値が気持ち悪い状態だな、と思いました。
$incrementing は false で、$keyType は int であれば id に自前で用意した数値を入れるんだな、という感じですが、 $incrementing は true だけど $keyType は string の時に、矛盾しているように感じました。
/** * The "type" of the auto-incrementing ID. * * @var string */ protected $keyType = 'int'; /** * Indicates if the IDs are auto-incrementing. * * @var bool */ public $incrementing = true;
そこで、これらのプロパティが使われている(何かしらの条件で使われている)箇所を調べてみたところ、以下のような処理が実行されていることが分かりました。
Model クラスの 782 行目(私の環境では)
// If the model has an incrementing key, we can use the "insertGetId" method on // the query builder, which will give us back the final inserted ID for this // table from the database. Not all tables have to be incrementing though. $attributes = $this->getAttributes(); if ($this->getIncrementing()) { $this->insertAndSetId($query, $attributes); } // If the table isn't incrementing we'll simply insert these attributes as they // are. These attribute arrays must contain an "id" column previously placed // there by the developer as the manually determined key for these models. else { if (empty($attributes)) { return true; } $query->insert($attributes); }
$this->getIncrementing()
は、モデルの $incrementing の値を返しています。
そのため、例え $keyType が string に設定していて動いていても、この insertAndSetId が実行されてしまいそうです。 これ以上先は追いきれませんでした・・・。
insertAndSetId が実行されても問題なく動くのですが、ムダな処理が実行されてしまいそうです。
なのでやはり id に UUID を登録したい時には、両方のプロパティを設定した方が良さそうに思いました。
補足
Laravel のマイグレーションファイルで、id を UUID が登録できるようにするために、以下のように定義してテーブルを作成しました。
Schema::create('users', function (Blueprint $table) { $table->string('id', 36)->primary(); $table->string('name'); $table->timestamps(); });
まとめ
今回はちょっとした事をきっかけに、フレームワークの中身を少しだけですが追ってみました。
ソースコードを目で見て追うだけでなく、実際にどう動いているのかを確認できたら面白いんだろうな、と感じました。
この辺は次なる課題として、、、もっと手を動かして実験して、まとめていけるようになりたいなと思います。