九保すこひ@フリーランスエンジニア|累計300万PVのブログ運営中
さてさて、前回記事「新コンポーネント機能!実例」では、Laravel 7.x
の新機能の中からコンポーネントについてをご紹介しました。
そして、今回はもうひとつ「ちょっと地味だけど前から欲しかった」機能について実例をまとめてみました。
その内容とは・・・
ルートの新しいバインディング方法
です。
例えば、Laravel
ではルートを以下のようにしてhttp://your-site.test/user/1
にアクセスすると、自動的にID
が1
のデータをUserモデルから取得してくれるようになります。
use \App\User; Route::get('user/{user}', function(User $user) { // DBからデータを自動取得 // });
これは「モデル・バインディング」と呼ばれるものでコード量が少なくて済むLaravel
の強力な機能になっています。
ただ、この機能に以前から「あればいいのにな・・・」というものがありました。
それは、
ID以外のデータを使ってバインディングをしたい
というものです。
実は今回のアップデートでこの要望が実装されましたので使い方をご紹介したいと思います。(また、これに関連した便利機能が追加になっているので後で合わせてご紹介します)
ぜひ皆さんのお役に立てると嬉しいです😊✨
開発環境: Laravel 7.x
DBテーブルの準備
ここからご紹介する内容で必要なDBテーブルは以下の2つです。
- users ・・・ サイトのユーザー
- posts ・・・ ユーザーが投稿した内容
そして、テーブルの内容はこのようになっています。
なお、posts
テーブルのslug
は他のデータと重複「しない」IDのようなものとして利用します。またuser_id
はusers
テーブルに外部キーを通して連携をしています。
そのため、マイグレーションは以下のようになります。
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreatePostsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('posts', function (Blueprint $table) { $table->id(); $table->unsignedBigInteger('user_id'); $table->string('title'); $table->text('content'); $table->string('slug'); // 重複しない文字列 $table->timestamps(); $table->foreign('user_id')->references('id')->on('users'); $table->unique('slug'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('posts'); } }
では、DBテーブルの準備ができたら実際にID
以外でモデル・バインディングする方法をみていきましょう!
ID以外でモデル・バインディングする方法
一番シンプルな方法
では、一番シンプルにposts
のslug
を使ってバインディングする方法です。
実際の例はこうなります。
Route::get('post/{post:slug}', function(\App\Post $post) { // });
そして、このルートにアクセスするためには、例えば、「http://your-site.test/post/post-5」などのようなURLになり、その場合slug
がpost-5
のものが自動的にバインディングされることになります。
モデルの中で指定する
ルートで直接指定する方法が一番お手軽ですが、もし指定するルートが多い場合:
以降のフィールド指定を忘れてしまったり、何度も書くのがめんどうになることもあると思います。
その場合は、モデル内でバインディングのターゲットの初期値を指定することもできます。
例えば、先ほどのPost
テーブルではこうなります。
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Post extends Model { public function getRouteKeyName() { return 'slug'; // モデル・バインディングには slug を使う } }
ただし、この状態であってもし以下のようにルートでid
を使うよう指定をするとそちらが優先されます。
Route::get('post/{post:id}', function(\App\Post $post) { // });
データの整合性を考慮したバインディング
例えば、以下のようなバインディングが2つ存在するルートがあるとします。
use \App\User; use \App\Post; Route::get('user/{user}/post/{post}', function(User $user, Post $post) { // });
この場合、実際にアクセスされるURLは「http://your-site.test/user/1/post/5」のようになり、その場合取得されるのは以下のとおりです。
- $user ・・・ 「id」が「1」のもの
- $post ・・・ 「id」が「5」のもの
ただし、この2つのデータに整合性はありません。
つまり、$user->id
と、$post->user_id
が一致していない場合でもデータがバインディングされて処理が実行されてしまうわけです。
しかし、今回のアップデートで:
を使ってバインディングするターゲットを指定すると整合性をとる処理をしてくれるようになりました。
では、実際の例を見てみましょう!
なお、この機能を使うにはリレーションシップが必要なので先にUser
モデルにhasMany
をセットしておきます。
<?php // 省略 class User extends Authenticatable { // 省略 public function posts() { return $this->hasMany('App\Post'); } }
そして、ルートはこんな形です。
Route::get('user/{user}/post/{post:slug}', function(User $user, Post $post) { // });
この場合、実際にアクセスされるURLは「http://your-site.test/user/1/post/post-5」のようになりますが、もし$user->id
と$post->user_id
が一致しない場合は以下のように404
を返すようになります。
つまり、今回私の環境で用意した以下のテストデータの中でいうと、ユーザーが1
の場合はpost-9
、URL
でいうと「http://your-site.test/user/1/post/post-9」であれば通常のアクセスができるということになります。
なお、slug
を使わないけど整合性もキープしたいという場合は以下のように:id
を指定するとOKです。
Route::get('user/{user}/post/{post:id}', function(User $user, Post $post) { // });
おわりに
ということで、今回は新しいモデル・バインディングの方法についてご紹介しました。
この機能が役に立つのは、商品コードを使ったシステムや(今も効果があるのかは疑問ですが)SEO
対策としてURL
に文字列を入れておきたい場合などが考えられると思います。
また、コード量をこれまでより減らすことができると思うので積極的に活用していきたいですね。
ぜひ皆さんも使ってみてくださいね。
ではでは〜!