はじめに
Laravel でアプリケーションを作っていく際に、何らかの理由で URL に主キーをそのまま表示させたくない場合どうしたらいいか、そこらへんを書いていきます。
例:
https://example.com/posts/1
ではなく
https://example.com/posts/Lk6-W7gbKmv
にしたい場合。
環境
Laravel 6.x
インストールと設定
Laravel のインストール:
composer create-project --prefer-dist laravel/laravel foo
プロジェクトルートに移動:
cd $_
主キーをハッシュ化するのに便利なライブラリをインストール:
composer require vinkla/hashids
Laravel Hashids の設定ファイルを作成:
php artisan vendor:publish
リストが表示されるので Vinkla\Hashids\HashidsServiceProvider
の番号を入力。これで config 以下に hashids.php が作成される。
.env ファイルに hashids で使う salt を追加:
HASHIDS_SALT=GkQDUgz4c8UmSY2P6nozchOz0fOxg5sk
php artisan tinker
を使って Str::random(32);
とかで値を生成する。APP_KEY と同様に絶対に公開してはいけない。
hashids の設定:
'connections' => [
'main' => [
// ハッシュ化するときの魔法の粉
'salt' => env('HASHIDS_SALT'),
// ハッシュ値で使いたい文字列
'alphabet'=> 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_-',
// ハッシュ値の長さ
'length' => 11,
],
// ...
],
tinker で確認してみる:
php artisan tinker
>>> Hashids::encode(1);
=> "Lk6-W7gbKmv"
>>> Hashids::decode('Lk6-W7gbKmv')[0];
=> 1
実際に使ってみる
MySQL を起動:
mysql.server start
データベースの作成:
mysql --login-path=local
mysql> create database laravel;
posts テーブル用のマイグレーションファイルを生成:
php artisan make:migration create_posts_table
生成されたマイグレーションファイルの編集:
class CreatePostsTable extends Migration
{
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('content');
$table->timestamps();
});
}
}
テーブルの作成:
php artisan migrate
モデルとコントローラの作成:
php artisan make:controller PostController --resource --model=Post
ルートの登録:
Route::resource('posts', 'PostController');
データを 1 件挿入:
php artisan tinker
>>> App\Post::forceCreate(['content' => 'foo']);
Route Model Binding に Hashids を適用:
use Illuminate\Database\Eloquent\Model;
use Vinkla\Hashids\Facades\Hashids;
class Post extends Model
{
// ...
public function getRouteKey(): string
{
return Hashids::encode($this->getKey());
}
public function resolveRouteBinding($value): ?Model
{
$value = Hashids::decode($value)[0] ?? null;
return $this->where($this->getRouteKeyName(), $value)->first();
}
}
tinker で確認してみる:
php artisan tinker
>>> route('posts.show', App\Post::find(1));
=> "http://localhost/posts/Lk6-W7gbKmv"
これでコントローラーやビューで主キーのエンコードやデコード処理をやらずとも、Route Model Binding を通して良い感じにやってくれる。
まとめ
Route Model Binding 自体便利だけど、コードの見通しが悪くなるのであまり好きではないが、ここまで簡単にできてしまう感じが「おーなんか Laravel らしいね」と思ってついつい記事にしてみました。
備考
上記の実装の場合、各テーブルの主キーのハッシュ値が同じになるので、テーブル毎にハッシュ値を設定したい場合はコネクションを複数用意して使うこともできる:
'default' => 'foo',
'connections' => [
'foo' => [
'salt' => env('HASHIDS_SALT_FOO'),
'alphabet' => 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_-',
'length' => 11,
],
'bar' => [
'salt' => env('HASHIDS_SALT_BAR'),
'alphabet' => 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_-',
'length' => 11,
],
],
php artisan tinker
>>> Hashids::connection('foo')->encode(1);
=> "Lk6-W7gbKmv"
>>> Hashids::connection('bar')->encode(1);
=> "BnP-1Ll3wLA"