お知らせアプリを作ろう(#3) #Laravel基礎 #Laravelの教科書
こちらはLaravelを利用したお知らせアプリの記事の3回目です。表紙はこちらのマガジンからどうぞご確認ください
前回で記事の作成が出来るようになりました。本稿では管理側の記事一覧、公開側のユーザページの作成を行います。
# 記事一覧機能の作成
ログイン後のTOPの/homeに記事一覧機能を作成していきます。
HomeControllerがこちらを使っているので、開いて修正していきます。
Webアプリケーションでは一覧→詳細の画面構成が非常に良く出てきます。難しいことはほとんどなく「データの取り出し→Viewに渡す」だけで完成することがほとんどです。すでに作成済の管理側のユーザー一覧機能などを参考にしましょう。
app/Http/Controllers/HomeController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\NewsEntry;
use Auth;
class HomeController extends Controller
{
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
}
/**
* Show the application dashboard.
*
* @return \Illuminate\Contracts\Support\Renderable
*/
public function index()
{
$user = Auth::user();
$news_list = $user->newsEntry()
->orderBy("id", "desc")
->paginate(10);
return view("home", [
"news_list" => $news_list,
"user" => $user
]);
}
}
まずは忘れないように使うクラスの宣言を追加します。
use App\NewsEntry;
use Auth;
ログイン中のユーザーに紐付いた記事を取得するためにAuthユーザーが必須となります。
$user = Auth::user();
Auth::user()でログイン中のUserオブジェクトを取得することが出来ます。IDが欲しい場合はAuth::id()と使い分けましょう。
記事一覧を取得する部分は管理側のユーザー一覧と異なります。
ここでは
NewsEntryのうち、user_idが現在ログイン中のユーザーのIDと一致するものを取得
という処理を行う必要があります。
今までの方法では次のように書くことができます。
$news_list = NewsEntry::where("user_id", Auth::id())
->orderBy("id", "desc")
->paginate(10);
UserクラスからNewsEntryを取り出すことが出来るようにNewsEntryの関係性をhasMany()で宣言しました。
class User extends Authenticatable
{
public function newsEntry() {
return $this->hasMany("App\NewsEntry", 'user_id', 'id');
}
}
hasMany()を使うことでUserのオブジェクトから直接関連しているNewsEntryを取り出すことが出来ます。
$news_list = $user->newsEntry()
->orderBy("id", "desc")
->paginate(10);
内部ではNewsEntry::where()でやっていることと同じですが、Laravelではこのように同じ処理でも簡単に書くことが出来ます。
今の所、この書き方にメリットを感じづらいかもしれませんが、活用できる箇所が今後必ず現れます。Laravelにはこのような機能があることを理解しておきましょう。
◆
ユーザートップのテンプレートを編集して記事一覧を表示しましょう。ControllerからはNewsEntryのコレクションがnews_list、Userがuserで渡ってきます。
resources/views/home.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">Dashboard</div>
<div class="card-body">
@if (session('status'))
<div class="alert alert-success" role="alert">
{{ session('status') }}
</div>
@endif
<div class="mb-3">
<a href="{{ url('/news/create') }}" class="btn btn-primary">記事作成</a>
</div>
<ul class="list-group">
@foreach ($news_list as $news)
<li class="list-group-item">
<a href="{{ url('news/edit/' . $news->id) }}">
<h5>{{ $news->title }}</h5>
</a>
<p>{{ $news->description }}</p>
<p>create: {{ $news->created_at->format("Y-m-d H:i:s") }}</p>
<p class="text-right">
<a class="btn btn-outline-secondary" href="{{ url('u/' . $user->display_name . '/' . $news->id) }}">確認</a>
</p>
</li>
@endforeach
</ul>
<div class="mt-3">
{{ $news_list->links() }}
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
記事の編集リンクを設定している部分と、公開側のリンクを設定している部分があること以外は特に凝ったことをしていません。
<a href="{{ url('news/edit/' . $news->id) }}">
<a class="btn btn-outline-secondary" href="{{ url('u/' . $user->display_name . '/' . $news->id) }}">確認</a>
公開側のリンクは「/u/【ユーザーのdisplay_name】/【記事ID】」としています。
ブラウザでログイン後トップを開いて作成した記事が並んでいるかチェックしましょう。
# 公開側のユーザーページを作る
公開側のユーザーページを作成していきます。管理側の記事一覧から編集リンクが無いくらいで、取り出しもテンプレートもほとんど同じです。
Controllerは既に作成済の「UserPageController」です。
app/Http/Controllers/UserPageController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\User;
use App\NewsEntry;
class UserPageController extends Controller
{
function show($name){
$user = User::where("display_name", $name)->first();
if(!$user){
return abort(404);
}
$news_list = $user->newsEntry()
->orderBy("id", "desc")
->paginate(10);
return view("user_page", [
"news_list" => $news_list,
"user" => $user
]);
}
}
HomeViewControllerとほぼ同じです。まずは利用するクラスの宣言を行います。
use App\NewsEntry;
use Auth;
表示対象のUserをdisplay_name経由で受け取ります。
$user = User::where("display_name", $name)->first();
記事一覧の受け取りはHomeViewControllerと処理が同じです。
$news_list = $user->newsEntry()
->orderBy("id", "desc")->paginate(10);
次にユーザーページのテンプレートを編集します。こちらも先程作成した記事一覧のテンプレートとほぼ同じで作れます。
resources/views/user_page.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<div class="card">
<div class="card-header">
{{ $user->name }}からのお知らせ
</div>
<div class="card-body">
<ul class="list-group">
@foreach ($news_list as $news)
<li class="list-group-item">
<a href="{{ url('u/' . $user->display_name . '/' . $news->id) }}">
<h5>{{ $news->title }}</h5>
</a>
<p>{{ $news->description }}</p>
<p>create: {{ $news->created_at->format("Y-m-d H:i:s") }}</p>
</li>
@endforeach
</ul>
<div class="mt-3">
{{ $news_list->links() }}
</div>
</div>
</div>
</div>
@endsection
ユーザーページではタイトルに表示しているリンクが「{{ url('u/' . $user->display_name . '/' . $news->id) }}」という公開側のリンクになっている点が注意するポイントです。
ブラウザでユーザーページを開いて表示を確認しましょう。
# ユーザーページ記事詳細を作成する
先程設定した公開側のリンク「/u/【ユーザーのdisplay_name】/【記事ID】」を作成していきます。
まずは記事詳細のためのルーティングを設定します。
routes/web.phpを開いて、UserPageController@showの下にUserPageController@showDetailのルーティングを作成します。
routes/web.php
Route::get('/u/{name}', 'UserPageController@show');
Route::get('/u/{name}/{id}', 'UserPageController@showDetail');
UserPageControllerを編集し、showDetail()関数を作成します。
app/Http/Controllers/UserPageController.php
class UserPageController extends Controller
{
....
function showDetail($name, $id){
$news = NewsEntry::find($id);
if(!$news){
return abort(404);
}
$user = $news->user()->first();
//display_nameが違う(不正なアクセス!)
if($user->display_name != $name){
return abort(404);
}
return view("user_news_detail", [
"news" => $news,
"user" => $user
]);
}
}
URLで指定されたidからNewsEntryを取得します。
$news = NewsEntry::find($id);
belongsTo()で設定した関係性の設定を使うと、NewsEntryからUserを取り出すことが出来ます。
$user = $news->user;
この時関数名の「user()」ではなく「user」で取り出している所が注意点です。
$news->user()とするとUserオブジェクトではなくデーターベースを処理するオブジェクトが返却されます。
次のように書くとuserと同じ意味になります。
$user = $news->user()->first();
Laravelにはこのように細かい記述で意味が異なる場合や、同じことをやる場合でも違う記述方法があることが非常に多いので注意しましょう。
◆
次に記事詳細ページのテンプレートを作成します。Controllerで指定しuser_news_detail.blade.phpを作成します。
resources/views/user_news_detail.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<div class="card">
<div class="card-header">
{{ $user->name }}からのお知らせ
</div>
<div class="card-body">
<h5>{{ $news->title }}</h5>
<p>create: {{ $news->created_at->format("Y-m-d H:i:s") }}</p>
<div>
{!! nl2br(e($news->body)) !!}
</div>
<div class="mt-3 text-center">
<a href="{{ url('u/' . $user->display_name )}}">一覧に戻る</a>
</div>
</div>
</div>
</div>
@endsection
{!! nl2br(e($news->body)) !!}
こちらの記述は掲示板を作ろうでも解説したものですが、エスケープしつつ本文を改行するための記法です。気になる方はこちらを確認してください。
テンプレートが作成できましたら、記事詳細が動いているかブラウザで確認してみてください。
# まとめ
今回はここでひとまず終了です。次回に記事編集、記事の削除を作成していきます。
本稿では新しい機能というのはほとんど出てきていませんが、だからこそ今まで作成したものをコピーして書き換えることでサクサクと進めることを実感して欲しいと思っています。
Webアプリケーションというのは言ってしまえば
1.データーベースからデータを取り出す
↓
2. HTMLに加工する
↓
3. Formでデータを送信する
↓
4. データをデーターベースに書き込む
ことを繰り返しているだけです。
データーベースからのデータの取り出しや書き込みはLaravelの機能(Eloquentと言う名前)がほとんどのことをやってくれます。
HTMLに加工することも条件式や繰り返し処理などの利用方法を覚えてしまえばあとは簡単です。
よくある処理はLaravelにやらせてササッと作り、空いた時間で学ぶべきことは何かというと、一つはHTMLとCSSに熟知することです。
HTMLについては本稿ではBootstrapを使ったシンプルなものしか取り扱っていませんが、HTML/CSSを使いこなすことができればもっと使いやすい、もっと魅力的なWebアプリケーションにすることが出来ます。
学ぶべきことのもう一つは設計です。
こういうものを作りたいと思った時にどのように作ると効率が良いか、どの順番で作っていくのが効率が良いのかを学ぶこと、それが設計です。
Webアプリケーションの開発を家を作ることに例えるとするならば、Laravelは土台であって骨組みです。ここから壁を作って内装を作るのが早いのか、どんな間取りにするのが良いのか、屋根をどうするのが良いのか、学ぶべきことはたくさんあります。
そこにたどり着くまでにLaravelを使いこなして土台と骨組みをササっと作れるようになること、それが目標の一つと考えています。本noteがその一助となれば、そう思っています。
おつかれさまでした!
完成まで突っ走る意気込みです。サポートしていただけると非常に嬉しいです。応援よろしくお願いします。