はじめに
前回、「【30分クッキング】Laravelで投稿アプリの機能を作成 ~CRUD編~」というタイトルで記事を書かせていただきましたが、その続きの記事になります。
今回は、Auth認証できるusersテーブルと前回作ったpostsテーブルをリレーション(関連付け)させてみたいと思います。
前提
前回のデータをベースに修正していきます
なので、前回の記事から実装をお勧めします
完全コピペすれば30分ほどで実装できるはずです
https://qiita.com/ProgramingDai/items/cf6944f9cd0ac08f4e3e
テーブルイメージ
外部キーカラムの命名規則
(主テーブル名の単数形) _ id
※今回のケースではusersテーブルが主テーブルなので「user_id」が外部キーとなります
[用語説明]リレーション【relation】とは
かかわりがあること・つながりがあること・関係・関連
リレーションの一例
・誰が投稿したのかを区別して表示できる
・記事を投稿した人だけがその記事を編集したり削除したり、またそのユーザーだけ表示することもできる
など
手順レシピ
1.ログイン機能実装
Laravelでは以下のコマンドだけで簡単にAuth機能を実装可能
$ php artisan make:auth
そうするとwelcome.blade.phpの右上に「LOGIN」と「REGISTER」リンクが表示されるので
「REGISTER」からアカウント登録をおこなってください
そのアカウントで「LOGIN」ページからログインできます
(注)前回の記事で実装した方ならすでにマイグレートでusersテーブルができているかと思いますが、
まだマイグレートしてない方は以下コマンドでマイグレートお願いします
$ php artisan migrate
2.ルーティング設定
// RESTfulサービスのルーティング
Route::resource('/post', 'PostController')->middleware('auth');
// ->middleware('auth')を追加
こうすることで、このページにアクセスする際は登録したメールアドレス・パスワードが必要になります
3.モデル内修正
// belongsTo結合(主テーブル <- 従テーブル)
public function user() {
return $this->belongsTo('App\User');
}
postsテーブルは従テーブルなので「belongsTo」を設定します
// hasMany結合(主テーブル -> 従テーブル)
public function posts() {
return $this->hasMany('App\Post');
}
// 今回はこちらは使いませんが一応設定しておきます
usersテーブルは主テーブルなので「hasMany」(1対多結合) or 「hasOne」(1対1結合)を設定します
共に引数には互いのモデル名を入れます
4.コントローラー設定
// 追加 これがないとエラーになります
use Illuminate\Support\Facades\Auth;
// 中略
public function index()
{
$authUser = Auth::user(); // 認証ユーザー取得
$items = Post::with('user')->get();
// 「Post::all();」ではN+1問題になり、SQLの発行数が増える
$params = [
'authUser' => $authUser,
'items' => $items,
];
return view('post.index', $params); // ビューの描画
// return $items->toArray(); // JSONデータで描画
}
// 中略
public function show($id)
{
$authUser = Auth::user(); // 認証ユーザー取得
$item = Post::find($id);
$params = [
'authUser' => $authUser,
'item' => $item,
];
return view('post.show', $params);
}
// 中略
use追加、indexとshowメソッドのみ修正
cf. N+1問題について
https://qiita.com/B106827/items/2a33fcf288eda7769e41
5.ビューでの切り分け
<!-- 中略 -->
<!-- 前回仮入れした箇所: <input type="hidden" name="user_id" value="1"> -->
<!-- これで認証ユーザーでの記事投稿が可能になる -->
<input type="hidden" name="user_id" value="{{ $authUser->id }}">
<!-- 中略 -->
<!-- 記事描画部分 -->
@if(count($items) > 0)
@foreach($items as $item)
<div class="alert alert-primary" role="alert">
@if($authUser->id === $item->user_id)
<!-- 主キーと外部キーが同じ場合 -> リンク付きテキストと削除ボタン表示 -->
<a href="/post/{{ $item->id }}" class="alert-link">{{ $item->title }}</a>
<div>{{ $item->message }}</div>
<form action="/post/{{ $item->id }}" method="POST">
{{ csrf_field() }}
<input type="hidden" name="_method" value="DELETE">
<input type="submit" class="delete" value="削除">
</form>
@else
<!-- 主キーと外部キーが違う場合 -> 通常のテキストのみ表示 -->
<span class="alert-link">{{ $item->title }}</span>
<div>{{ $item->message }}</div>
@endif
<!-- この部分はいろいろ出し方を変えられますのでおのおのでカスタマイズしてみてください -->
</div>
@endforeach
@else
<div>投稿記事がありません</div>
@endif
<!-- 中略 -->
<!-- index.blade.php同様、value差し替え -->
<input type="hidden" name="user_id" value="{{ $authUser->id }}">
おまけ: ページネーション
public function index()
{
$authUser = Auth::user();
- $items = Post::with('user')->get();
+ $items = Post::with('user')->orderBy('id','desc')->paginate(5); // 1ページに5件表示
$params = [
'authUser' => $authUser,
'items' => $items,
];
return view('post.index', $params);
}
...
<div class="paginate">
{{ $items->links() }}
</div>
...
おまけ: ビュー少し修正(全文)
@extends('layouts.app')
@section('title', '投稿アプリ')
@section('content')
@section('maincopy', '投稿してください')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">コメント投稿してください</div>
<div class="card-body">
<!-- 投稿フォーム -->
<div class="createForm">
<form action="/post" method="post">
{{ csrf_field() }}
<input type="hidden" name="user_id" value="{{ $authUser->id }}">
<div class="">
<div class="labelTitle">タイトル</div>
<input id="title" type="text" class="userForm" name="title" placeholder="タイトル" value="{{ old('title') }}">
@if($errors->has('title'))
<div class="error_msg">{{ $errors->first('title') }}</div>
@endif
</div>
<div class="">
<div class="labelTitle">メッセージ</div>
<textarea id="message" class="userForm" name="message" placeholder="メッセージ">{{ old('message') }}</textarea>
@if($errors->has('message'))
<div class="error_msg">{{ $errors->first('message') }}</div>
@endif
</div>
<div class="buttonSet">
<input type="submit" class="btn btn-primary btn-sm postBtn" value="投稿する">
</div>
</form>
</div>
<!-- 記事描画部分 -->
<div class="createItem">
@if(count($items) > 0)
@foreach($items as $item)
<div class="alert alert-primary itemAlert" role="alert">
@if($authUser->id === $item->user_id)
<div class="comment">
<a href="/post/{{ $item->id }}" class="alert-link">{{ $item->title }}</a>
<br />
{{ $item->message }}
</div>
<div class="delete">
<form action="/post/{{ $item->id }}" method="POST">
{{ csrf_field() }}
<input type="hidden" name="_method" value="DELETE">
<button type="submit" class="deleteIcon">
<i class="fas fa-trash-alt"></i>
</button>
</form>
</div>
@else
<div class="comment">
<span class="alert-link">{{ $item->title }}</span>
<br />
{{ $item->message }}
</div>
@endif
</div>
@endforeach
@else
<div>投稿記事がありません</div>
@endif
</div>
<div class="paginate">
{{ $items->links() }}
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
おまけ: CSS少し修正(全文)
.topWrapper {
width: 150px;
margin: 0 auto;
}
.thumbnail {
width: 30px;
height: 30px;
object-fit: cover;
border-radius: 50%;
}
.editThumbnail {
width: 150px;
height: 150px;
object-fit: cover;
border-radius: 50%;
}
.buttonSet {
margin: 30px 0 0 0;
text-align: center;
}
.labelTitle {
color: #006DD9;
margin: 20px 0 5px 0;
}
input[class="userForm"],
.userForm {
width: 100%;
padding: 5px 0 5px 10px;
box-sizing: border-box;
border: 1px solid #ccc;
transition: 1s;
}
input[class="userForm"]:focus,
.userForm:focus {
background: #87ceeb;
}
.postBtn {
width: 30%;
height: 40px;
margin: 0 0 40px 0;
}
.btn-done {
background: #dc143c;
border: 1px solid #dc143c;
}
.btn-done:hover {
background: #cc1237;
border: 1px solid #cc1237;
}
.deleteIcon {
color: #006DD9;
font-size: 20px;
border: none;
background: none;
}
.error_msg {
font-size: 15px;
font-weight: 100;
color: #cc1237;
}
.itemAlert {
display: flex;
}
.comment {
width: 95%;
}
あとがき
これで2つのテーブルのリレーションはできたかと思います。
まだログイン画面やログアウト先のルーティングなどはデフォルトのままなので、
次はオリジナルのログイン画面の実装などをしていきたいと思います。
※そちらは別ページでのアップ予定です。
Laravelで投稿アプリの機能を作成
~CRUD編~
https://qiita.com/ProgramingDai/items/cf6944f9cd0ac08f4e3e
~リレーション編~
https://qiita.com/ProgramingDai/items/249acc8894079ee58268
~ログインカスタマイズ編Ⅰ~
https://qiita.com/ProgramingDai/items/fee669e5a8cf67f0e38e
~ログインカスタマイズ編Ⅱ~
https://qiita.com/ProgramingDai/items/4fe2e3cc90987356c9c4
参考書籍: PHPフレームワーク Laravel入門
https://blog.hiroyuki90.com/articles/laravel-books/
N+1問題について: https://qiita.com/B106827/items/2a33fcf288eda7769e41
cf. RESTfulのルーティング確認方法
php artisan route:list