以前に開発したコメント掲示板の機能をどんどん拡張してきましょう.
これまでのテストデータが3件しかなかったので,シードを増やして100件にしてみよう.シードを増やすためには database/seeds/CommentsTableSeeder.php に insert
を追加すればよい.しかしながら,でたらめなデータをたくさん作るのも骨が折れる.Laravel には Faker というありそうででたらめなテストデータを生成するための便利な機能があるのでこれを利用してみよう.database/seeds/CommentsTableSeeder.php を次のように編集する.21行目で日本語モードで Faker を利用するための準備を行い,24行目では title にでたらめな日本人の名前をセットする.本文には住所と電話番号と電子メールアドレスをセットする.なお,25行目の .
は文字列を連結するための演算子です.例えば,"abc" . "-" . "ABC"
で "abc-ABC"
となる.
database/seeds/CommentTableSeeder.php (抜粋)
public function run()
{
// 一旦中身を削除する
DB::table('comments')->delete();
DB::table('comments')->insert([
'title' => '最初のコメント',
'body' => '最初のコメントです!'
]);
DB::table('comments')->insert([
'title' => '2つ目',
'body' => '2つ目のコメントです!'
]);
DB::table('comments')->insert([
'title' => '<三個目>のコメント',
'body' => 'シーダによってテストデータを設定します.'
]);
$faker = Faker\Factory::create('ja_JP');
DB::table('comments')->insert([
'title' => $faker->name,
'body' => $faker->address . ' : ' . $faker->phoneNumber . ' : ' . $faker->email
]);
}
シーダの変更をしたので,データベースをロールバックしてから,再度マイグレーションし,テストデータを登録してみよう.
[GakuinHana@rin06 myapp]$ php artisan migrate:rollback; php artisan migrate; php artisan db:seed Rolling back: 2019_09_26_100151_create_comments_table Rolled back: 2019_09_26_100151_create_comments_table (0 seconds) Migrating: 2019_09_26_100151_create_comments_table Migrated: 2019_09_26_100151_create_comments_table (0 seconds) Seeding: CommentsTableSeeder Database seeding completed successfully. [GakuinHana@rin06 myapp]$
Webサーバを起動して,実際にアクセスしてみよう.ありそうででたらめな「江古田 直子」というデータが投入できた.もちろん毎回内容は異なる.
[GakuinHana@rin06 myapp]$ php artisan serve --host=rin06.ba.kobegakuin.ac.jp --port 8385 ⏎ Laravel development server started: <http://rin06.ba.kobegakuin.ac.jp:8385>
テストデータをトータルで100件準備したいので,シーダを次のように変更しよう.while()
文を使って,insert を97回繰返している.特に29行目の $i++;
の記述を忘れないようにしよう.これがなければ無限ループに陥って無限にデータが登録されてしまう.
database/seeds/CommentTableSeeder.php (抜粋)
public function run()
{
// 一旦中身を削除する
DB::table('comments')->delete();
DB::table('comments')->insert([
'title' => '最初のコメント',
'body' => '最初のコメントです!'
]);
DB::table('comments')->insert([
'title' => '2つ目',
'body' => '2つ目のコメントです!'
]);
DB::table('comments')->insert([
'title' => '<三個目>のコメント',
'body' => 'シーダによってテストデータを設定します.'
]);
$faker = Faker\Factory::create('ja_JP');
$i = 1;
while ($i < 98) {
DB::table('comments')->insert([
'title' => $faker->name,
'body' => $faker->address . ' : ' . $faker->phoneNumber . ' : ' . $faker->email
]);
$i++;
}
}
シーダの変更をしたので,データベースをロールバックしてから,再度マイグレーションし,テストデータを登録してみよう.
[GakuinHana@rin06 myapp]$ php artisan migrate:rollback; php artisan migrate; php artisan db:seed ⏎ Rolling back: 2019_09_26_100151_create_comments_table Rolled back: 2019_09_26_100151_create_comments_table (0 seconds) Migrating: 2019_09_26_100151_create_comments_table Migrated: 2019_09_26_100151_create_comments_table (0 seconds) Seeding: CommentsTableSeeder Database seeding completed successfully. [GakuinHana@rin06 myapp]$
Webサーバを起動して,実際にアクセスしてみよう.合計で100件のデータが登録できた.
[GakuinHana@rin06 myapp]$ php artisan serve --host=rin06.ba.kobegakuin.ac.jp --port 8385 ⏎ Laravel development server started: <http://rin06.ba.kobegakuin.ac.jp:8385>
データの件数が多くなると,5件や10件ずつ表示してページを切り替えるようなことがしたくなる.Laravel には Pagination という便利な機能があるので,これを利用してページの切り替えを実装しよう.
まずは CommentsController の index 関数を修正する.これまでは get()
で全件取得していたところを,paginate(5)
で5件に限定できる.(5件に限定するだけでなく,ページ切り替えなどにも対応できる.)
app/Http/Controllers/CommentsController.php (抜粋)
public function index()
{
$comments = Comment::paginate(5);
return view('comments.index')
->with('comments', $comments);
}
これだけの修正で取得件数を5件に限定できた.
[GakuinHana@rin06 myapp]$ php artisan serve --host=rin06.ba.kobegakuin.ac.jp --port 8385 ⏎ Laravel development server started: <http://rin06.ba.kobegakuin.ac.jp:8385>
次に,ページの切り替えのためのリンクを設置しよう.リンクの設置も非常に簡単で,{{ $comments->links() }}
を追加するだけである.
resources/views/comments/index.blade.php (抜粋)
<h1>コメント一覧</h1>
<div>
{{ $comments->links() }}
</div>
<ul>
@foreach ($comments as $comment)
<li>
<a href="{{ action('CommentsController@show', $comment->id) }}">
{{ $comment->title }}
</a>
</li>
@endforeach
</ul>
デザインはまだ不細工だが,簡単にページ切り替えのリンクが設置できた.ページを切り替えて正しく動作できているか確認しよう.また,ページを切り替えたときのアドレスには http://rin06.ba.kobegakuin.ac.jp:8385/comments?page=7 のようにパラメータが付けられていることも確認しておくこと.
[GakuinHana@rin06 myapp]$ php artisan serve --host=rin06.ba.kobegakuin.ac.jp --port 8385 ⏎ Laravel development server started: <http://rin06.ba.kobegakuin.ac.jp:8385>
なお,データ件数が多くなった場合にはペジネーション以外に,Twitter や Facebook などでもおなじみの無限スクロールを使う方法も考えられる.これは Ajax (Asynchronous JavaScript + XML) と呼ばれる JavaScript による非同期通信を利用して動的にページのドキュメント要素を書き換える方法で実現可能である.
次にスタイルシートを設置して,Webサイト全体のデザインを変更しよう.スタイルシートなどの静的な(変更されない)ファイルは public ディレクトリに設置する.今回は public/mycss/ ディレクトリを作成し,その中に styles.css ファイルを作成する.なお mkdir
はディレクトリ(フォルダ)を作成するコマンド,touch
は空のファイルを作成するコマンドである.
[GakuinHana@rin06 myapp]$ pwd ⏎ /home/students/GakuinHana/Documents/laravel/myapp [GakuinHana@rin06 myapp]$ ls ⏎ app composer.json database public routes tests artisan composer.lock package.json readme.md server.php vendor bootstrap config phpunit.xml resources storage webpack.mix.js [GakuinHana@rin06 myapp]$ ls public ⏎ css favicon.ico index.php js robots.txt web.config [GakuinHana@rin06 myapp]$ mkdir public/mycss ⏎ [GakuinHana@rin06 myapp]$ touch public/mycss/styles.css ⏎ [GakuinHana@rin06 myapp]$ ls public/mycss/ ⏎ styles.css [GakuinHana@rin06 myapp]$
作成した styles.css ファイルを編集しよう.まずは<h1> 見出しのデザインを変更する.
public/mycss/styles.css
h1 {
color: #02397B;
border-left: 20px solid #3F8AE3;
padding: 3px 0 3px 10px;
}
次に,全てのビューで styles.css を読み込むように変更しよう.すでにビューのレイアウト化ができていれば,default.blade.php を修正するだけで,サイト全体のデザインを一気に変更できる(レイアウト化の有り難みが理解できたであろう).
resources/views/layouts/default.blade.php (抜粋)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>@yield('title')</title>
<link href="{{ asset('/mycss/styles.css') }}" rel="stylesheet">
</head>
これで全てのページの見出し要素のデザインを変更できた.他のページ(ビュー)のデザインも変更されていることを確認しよう.
この要領で,ページ切り替えリンクのデザインも変更しよう.これは箇条書きを縦に並べずに,横に並べ,箇条書きの行頭文字を削除することで実現します.ポイントは15行目の display: inline-block;
です.
public/mycss/styles.css
h1 {
color: #02397B;
border-left: 20px solid #3F8AE3;
padding: 3px 0 3px 10px;
}
ul.pagination {
padding:0;
font-size:0px;
}
ul.pagination li {
width:35px;
border:1px solid #3F8AE3;
display: inline-block;
text-align: center;
font-size:16px;
}
ページ切り替えリンクのデザインも美しくなりました.
やはりその都度テストを実行して,思わぬところでエラーがでないか確認する癖をつけよう.
[GakuinHana@rin06 myapp]$ history | grep phpunit ⏎ 963 ./vendor/bin/phpunit tests/Feature/TopPageTest.php 964 ./vendor/bin/phpunit 965 ./vendor/bin/phpunit tests/Feature/TopPageTest.php 966 ./vendor/bin/phpunit 979 ./vendor/bin/phpunit 986 ./vendor/bin/phpunit --coverage-html ~/public_html/cv_report/ 990 ./vendor/bin/phpunit --coverage-html ~/public_html/cv_report/ 991 php artisan migrate:rollback; php artisan migrate; php artisan db:seed; ./vendor/bin/phpunit --coverage-html ~/public_html/cv_report/ 1005 history | grep phpunit 1006 php artisan migrate:rollback; php artisan migrate; php artisan db:seed; ./vendor/bin/phpunit --coverage-html ~/public_html/cv_report/ 1038 history | grep phpunit [GakuinHana@rin06 myapp]$ !1006 ⏎ php artisan migrate:rollback; php artisan migrate; php artisan db:seed; ./vendor/bin/phpunit --coverage-html ~/public_html/cv_report/ Rolling back: 2019_09_26_100151_create_comments_table Rolled back: 2019_09_26_100151_create_comments_table (0 seconds) Migrating: 2019_09_26_100151_create_comments_table Migrated: 2019_09_26_100151_create_comments_table (0 seconds) Seeding: CommentsTableSeeder Database seeding completed successfully. PHPUnit 7.5.16 by Sebastian Bergmann and contributors. ........ 8 / 8 (100%) Time: 1.35 seconds, Memory: 22.00 MB OK (8 tests, 8 assertions) Generating code coverage report in HTML format ... done [GakuinHana@rin06 myapp]$
コメントを新規投稿すると,現状ではリストに最後に追加されてしまい,新しい投稿があったかどうかがわかりにくい.ここでは,リストの取得順を変更して新規投稿が先頭に表示されるようにしよう.一般的に SQL では ORDER BY
を使えばよいが,Laravel では orderBy
が利用できる.
app/Http/Controllers/CommentsController.php (抜粋)
public function index()
{
$comments = Comment::orderBy('id', 'DESC')
->paginate(5);
return view('comments.index')
->with('comments', $comments);
}
Web サーバを起動して確認しよう.
[GakuinHana@rin06 myapp]$ php artisan serve --host=rin06.ba.kobegakuin.ac.jp --port 8385 ⏎ Laravel development server started: <http://rin06.ba.kobegakuin.ac.jp:8385>
新しい投稿が先頭に表示されるようになりました.
投稿完了時に「投稿されました」とか,投稿の更新をしたときに「更新が完了しました」といったメッセージを一瞬だけ表示したい.このような機能を一般的にフラッシュメッセージと呼ぶ.このフラッシュメッセージの機能を実現しよう.
フラッシュメッセージを実現するためには HTTP のセッションを利用すれば良い.まずは,ビューを編集して,flash_message
というキーでセッションが保存されていたらその内容を表示する部分を追加しよう.
resources/views/layouts/default.blade.php (抜粋)
<body>
<div class="container">
@if (session('flash_message'))
<div>
{{ session('flash_message') }}
</div>
@endif
@yield('content')
</div>
</body>
次に,新規投稿が完了した旨の情報をセッションに格納してビューに渡す処理を実装しよう.これはコントローラの store メソッド内で, redirect()
に with()
を使ってパラメータを渡すだけで良い.
app/Http/Controllers/CommentsController.php (抜粋)
public function store(Request $request)
{
...(中略)....
$comment->save(); // データベースに登録
return redirect('/comments')
->with('flash_message', '投稿しました');
}
実際に動作を確認してみよう.
[GakuinHana@rin06 myapp]$ php artisan serve --host=rin06.ba.kobegakuin.ac.jp --port 8385 ⏎ Laravel development server started: <http://rin06.ba.kobegakuin.ac.jp:8385>
タイトルと本文を適当に入力して,投稿ボタンを押してみる.
「投稿しました」というフラッシュメッセージがページの先頭に表示された.
このページを再読込すると,フラッシュメッセージが消えた.
基本的な機能の実装ができたので,デザインを整えるとともに,ページを再読込しなくともフラッシュメッセージをクリックするだけで消去できるようにしてみよう.まずは,ビューの div 要素に flash_message
クラスを追加する.また,その要素をクリックすると hidden
クラスが追加されるように設定する.
resources/views/layouts/default.blade.php (抜粋)
<body>
<div class="container">
@if (session('flash_message'))
<div class="flash_message" onclick="this.classList.add('hidden')">
{{ session('flash_message') }}
</div>
@endif
@yield('content')
</div>
</body>
次に,スタイルシートに flash_message
クラスと hidden
クラスのデザインを記述(追加)しよう.
public/mycss/styles.css (抜粋)
.flash_message {
text-align: center;
color: blue;
background: yellow;
}
.hidden {
display: none;
}
実際に動作を確認してみよう.
[GakuinHana@rin06 myapp]$ php artisan serve --host=rin06.ba.kobegakuin.ac.jp --port 8385 ⏎ Laravel development server started: <http://rin06.ba.kobegakuin.ac.jp:8385>
タイトルと本文を適当に入力して,投稿ボタンを押してみると,フラッシュメッセージが上部中央に表示された.また,フラシュメッセージはクリックで消去できることも確認しよう.
最後に,コメントの詳細表示,更新や削除でもフラッシュメッセージが表示されるようにする.新規投稿と同様に,コントローラの show, edit update および destroy メソッドで with()
を使ってセッションに値を渡すだけでできる.これによって,処理が成功したり失敗した場合にユーザがその結果を認識できるようになる.
app/Http/Controllers/CommentsController.php (抜粋)
public function show($id)
{
// SELECT * FROM comments WHERE id = 2; のイメージ
$comment = Comment::where('id', '=', $id)
->first();
if (!$comment) { // コメントが取得できない(IDが不正の)場合はリダイレクト
return redirect('/comments')->with('flash_message', 'コメントが見つかりません');
}
return view('comments.show')
->with('comment', $comment);
}
...(中略)...
public function edit($id)
{
$comment = Comment::where('id', '=', $id)
->first();
if (!$comment) {
return redirect('/comments')->with('flash_message', 'コメントが見つかりません');
}
return view('comments.edit') // show 関数との違いはここだけ
->with('comment', $comment);
}
public function update(Request $request)
{
$this->validate($request, [
'title' => 'required|max:10', // 入力が必須で,最大10文字
'body' => 'required' // 入力が必須
]);
$comment = Comment::where('id', '=', $request->id)
->first();
if (!$comment) {
return redirect('/comments')->with('flash_message', 'コメントが見つかりません');
}
$comment->title = $request->title;
$comment->body = $request->body;
$comment->save();
return redirect()->action('CommentsController@show', $request->id)
->with('flash_message', '更新しました');
}
public function destroy($id)
{
$comment = Comment::where('id', '=', $id)
->first();
if (!$comment) {
return redirect('/comments')->with('flash_message', 'コメントが見つかりません');
}
$comment->delete();
return redirect('/comments')
->with('flash_message', '削除しました');
}
わずか数行のコードを記述するだけで,更新,削除などの処理後にフラッシュメッセージが表示できるようになりました.
[GakuinHana@rin06 myapp]$ php artisan serve --host=rin06.ba.kobegakuin.ac.jp --port 8385 ⏎ Laravel development server started: <http://rin06.ba.kobegakuin.ac.jp:8385>
これまでは ID の順に表示をしていたが,更新日時が新しいものが先頭に来るように変更したい.
comments テーブルのマイグレーションファイルには次のような記述があった.
database/migrations/yyyy_mm_dd_hhmmss_create_comments_table.php (抜粋)
public function up()
{
Schema::create('comments', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('title', 255);
$table->text('body');
$table->timestamps();
});
}
上記コードの7行目によって,comments テーブルには created_at
と updated_at
のフィールドが作成されるとともに,コメントを新規に書き込んだり,編集をすれば,これらのフィールドに作成日時と更新日時が自動的に保存されるようになっている.実際に,ブラウザで編集と新規投稿を行った後,sqlite3
コマンドを使って,データベースの内容を覗いて確認すると良い.
[GakuinHana@rin06 myapp]$ php artisan migrate:rollback; php artisan migrate; php artisan db:seed; ⏎ Rolling back: 2019_09_26_100151_create_comments_table Rolled back: 2019_09_26_100151_create_comments_table (0 seconds) Migrating: 2019_09_26_100151_create_comments_table Migrated: 2019_09_26_100151_create_comments_table (0 seconds) Seeding: CommentsTableSeeder Database seeding completed successfully. [GakuinHana@rin06 myapp]$ php artisan serve --host=rin06.ba.kobegakuin.ac.jp --port 8385 ⏎ Laravel development server started: <http://rin06.ba.kobegakuin.ac.jp:8385> #### ブラウザから最新の投稿を編集し,その後,新規に投稿する ^C [GakuinHana@rin06 myapp]$ sqlite3 database/database.sqlite ⏎ SQLite version 3.7.17 2013-05-20 00:56:22 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> SELECT * FROM comments LIMIT 5 OFFSET 96; ⏎ 97|大垣 修平|3823092 滋賀県山岸市北区浜田町青山9-7-8 コーポ木村101号 : 03-6393-1825 : kenichi03@gmail.com|| 98|近藤 知実|5613967 千葉県田中市西区若松町浜田8-1-10 ハイツ渚104号 : 04-4685-7735 : naoki04@hotmail.co.jp|| 99|山本 直子|1258887 茨城県近藤市西区田中町井上4-4-4 コーポ高橋105号 : 09884-7-9480 : taro.uno@nakajima.jp|| 100|宇野 さゆり(編集)|7981460 島根県喜嶋市南区吉田町工藤8-2-8 : 06857-3-7576 : kato.yasuhiro@kanou.org||2019-09-26 15:56:39 101|新規投稿|新規投稿|2019-09-26 15:56:57|2019-09-26 15:56:57 sqlite> .exit ⏎ [GakuinHana@rin06 myapp]$
上記のid=97...99のレコードには作成日時や更新日時が設定されていないが,id=100は編集をしたので更新日時だけが設定されている.またid=101のレコードは新規投稿であるので,作成日時と更新日時の両方が設定されている.シーダーで設定したレコードには作成日時と更新日時が設定されていないので,シーダを編集して,作成日時はID順になるように,更新日時はランダムな日時を設定するように修正しよう.
database/seeds/CommentTableSeeder.php (抜粋)
public function run()
{
// 一旦中身を削除する
DB::table('comments')->delete();
DB::table('comments')->insert([
'title' => '最初のコメント',
'body' => '最初のコメントです!',
'created_at' => '2019-07-01 10:10:10',
'updated_at' => '2019-07-01 10:10:10'
]);
DB::table('comments')->insert([
'title' => '2つ目',
'body' => '2つ目のコメントです!',
'created_at' => '2019-07-01 10:20:10',
'updated_at' => '2019-07-01 10:20:10'
]);
DB::table('comments')->insert([
'title' => '<三個目>のコメント',
'body' => 'シーダによってテストデータを設定します.',
'created_at' => '2019-07-01 10:30:10',
'updated_at' => '2019-07-01 10:30:10'
]);
$faker = Faker\Factory::create('ja_JP');
$i = 1;
while ($i < 98) {
// created_at がID順になるような値を作る
$s = floor($i / 2); // 秒
$m = $i % 10; // 分 10で割った余り
$h = floor($i / 10); // 時
$format = '2019-07-10 %02d:%02d:%02d';
$created_at = sprintf($format, $h, $m, $s);
// updated_at はランダムに
$s = rand(0,59);
$m = rand(0,59);
$h = rand(0,23);
$d = rand(1,30);
$format = '2019-08-%02d %02d:%02d:%02d';
$updated_at = sprintf($format, $d, $h, $m, $s);
DB::table('comments')->insert([
'title' => $faker->name,
'body' => $faker->address . ' : ' . $faker->phoneNumber . ' : ' . $faker->email,
'created_at' => $created_at,
'updated_at' => $updated_at
]);
$i++;
}
}
では,実際にテーブルを再生成してシーダを実行してみよう.テストデータの全てのレコードに投稿日時と更新日時が設定された.
[GakuinHana@rin06 myapp]$ php artisan migrate:rollback; php artisan migrate; php artisan db:seed; ⏎ Rolling back: 2019_09_26_100151_create_comments_table Rolled back: 2019_09_26_100151_create_comments_table (0 seconds) Migrating: 2019_09_26_100151_create_comments_table Migrated: 2019_09_26_100151_create_comments_table (0 seconds) Seeding: CommentsTableSeeder Database seeding completed successfully. [GakuinHana@rin06 myapp]$ sqlite3 database/database.sqlite ⏎ SQLite version 3.7.17 2013-05-20 00:56:22 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> SELECT id, title, created_at, updated_at FROM comments LIMIT 5 OFFSET 96; ⏎ 97|藤本 修平|2019-07-10 09:04:47|2019-08-28 13:50:22 98|松本 治|2019-07-10 09:05:47|2019-08-19 23:34:07 99|山口 舞|2019-07-10 09:06:48|2019-08-13 13:54:06 100|佐藤 洋介|2019-07-10 09:07:48|2019-08-16 01:57:38 sqlite> .exit ⏎ [GakuinHana@rin06 myapp]$
次は,コメントの詳細ページに投稿日時と更新日時を表示しよう.このためには次のとおりビューに項目を追加するだけで良い.
resources/views/comments/show.blade.php (抜粋)
<h1>コメント</h1>
<dl>
<dt>ID:</dt>
<dd>{{ $comment->id }}</dd>
<dt>Title:</dt>
<dd>{{ $comment->title }}</dd>
<dt>Body:</dt>
<dd>{{ $comment->body }}</dd>
<dt>投稿日時:</dt>
<dd>{{ $comment->created_at }}</dd>
<dt>更新日時:</dt>
<dd>{{ $comment->updated_at }}</dd> ⏎
</dl>
最後にコントローラでデータベースからの取得順を指定し直そう.
app/Http/Controllers/CommentsController.php (抜粋)
public function index()
{
$comments = Comment::orderBy('updated_at', 'DESC')
->paginate(5);
return view('comments.index')
->with('comments', $comments);
}
実際にデータベースを初期状態に戻してから,新規投稿と更新をブラウザから実行してみます.
[GakuinHana@rin06 myapp]$ php artisan migrate:rollback; php artisan migrate; php artisan db:seed; ⏎ Rolling back: 2019_09_26_100151_create_comments_table Rolled back: 2019_09_26_100151_create_comments_table (0 seconds) Migrating: 2019_09_26_100151_create_comments_table Migrated: 2019_09_26_100151_create_comments_table (0 seconds) Seeding: CommentsTableSeeder Database seeding completed successfully. [GakuinHana@rin06 myapp]$ php artisan serve --host=rin06.ba.kobegakuin.ac.jp --port 8385 ⏎ Laravel development server started: <http://rin06.ba.kobegakuin.ac.jp:8385>
まず,ID順ではなく,更新順になっていることを確認する.
実際に新規に投稿してみる.
表示順で5番目のデータ「小林 治」を更新してみる.
トップページに戻ると思った通り(更新順)の表示になっている.