こんにちは!
PHPのLaravelやJavaScriptでWeb開発をしているフリラーンスエンジニアのファドと申します!
こちらの記事はLaravel教材の第5回目の記事になります。
その他のLaravel教材を学習したい方は下記リンクから直接教材へ飛ぶことができます。
-
Laravel教材
【Laravel教材①】ブログシステムを実装しながらLaravelの基礎を徹底的に学ぶ!〜環境構築編〜
-
Laravel教材
【Laravel教材②】Laravelでブログシステム構築!〜DB接続からテストデータの作成編〜
-
Laravel教材
【Laravel教材③】Laravelでブログシステム構築!〜MVCモデル解説から画面の共通化編〜
-
Laravel教材
【Laravel教材④】Laravelでブログシステム構築!〜全記事一覧画面実装から記事詳細画面実装編〜
-
Laravel教材
【Laravel教材⑥】Laravelでブログシステム構築!〜記事編集機能実装編〜
-
Laravel教材
【Laravel教材⑦】Laravelでブログシステム構築!〜記事削除機能実装編〜
記事投稿
ここまで記事一覧画面と記事詳細画面を作ってきましたが、ここからは記事を投稿する機能の作成をしていきます。
記事投稿の流れは下記のようになっています。
- ビューのフォームからデータを飛ばす
- ルートで受け取りコントローラーへ渡す
- フォームリクエストを使用してバリデーションをする
- モデルでデータを登録する
- エラー処理を記述
ビューのフォームからデータを飛ばす
まずは記事投稿フォームに記事のタイトルと内容が入力され、登録ボタンがクリックされた時にデータを飛ばしていきます。
ルートで受け取りコントローラーへ渡す
ルートでリクエストを受け取り、データを登録するためのメソッドにデータを渡します。
フォームリクエストを使用してバリデーションをする
Laravelにはバリデーションを簡単に実装するためのフォームリクエストという機能が最初から組み込まれています。
フォームリクエストの作成方法を説明する前に、そもそもバリデーションとは何なのかということについても学習していきましょう。
バリデーションとは
入力されたデータが正しい値かどうかをチェックする機能をバリデーションと言います。
例えば、マイグレーションでarticlesテーブル
を作成した時に、テーブル定義でタイトルと内容は必須で登録しなければいけないことにしています。
しかし、記事をフォームで登録する時にタイトルと内容の2つが入力されていなかった場合は、ない値をデータベースに登録することはできないので、エラーになってしまいます。
バリデーションではこのような事態を避けるために、必須チェックというものを実装することができます。
簡単に説明すると、フォームに値が入っているか入っていないかのチェックができるということです。
また、バリデーションは必須チェックだけではなく、データの型チェックや文字数チェックなど様々なチェックをすることができます。
つまり、開発者の意図しないデータがフォームに入力されることを防ぐことができるので、セキュリティ強化の面からバリデーションはほぼ必須となります。
フォームリクエストの作成方法
それではバリデーションがどのようなものか説明したので、フォームリクエストの作成方法について説明します。
フォームリクエストも下記コマンド1つで作成することができます。
$ ./vendor/bin/sail php artisan make:request クラス名
このコマンドを使用すると、1度目の実行時にfirst-app/app/Http/
ディレクトリにRequests
というディレクトリが生成され、その中にクラス名で指定した名前のPHPファイルが作成されます。
2回目以降はfirst-app/app/Http/Requests
ディレクトリがすでに存在しているので、その中に新しいフォームリクエストファイルが作成されます。
使用方法などは実装する時に説明していきます。
モデルでデータを登録
すでにモデルの機能であるallメソッド
とfindメソッド
について学習しましたが、モデルでデータを登録する時にはcreateメソッド
というものを使用します。
使い方は下記の通りです。
Article::create([
'title' => 'タイトル',
'content' => 'コンテンツ',
]);
このようにArticle::create()
と記述し、第一引数に配列で登録するデータを入れるだけです。
また、Laravelにはもう1つデータを登録するためのfillメソッド + saveメソッド
が提供されています。
使い方は下記の通りです。
$article = new Article;
$article->fill([
'title' => 'タイトル',
'content' => 'コンテンツ',
]);
$article->save();
他にも登録方法はありますが、セキュリティの観点からそれらは非推奨となっているので、上記2つの方法のどちらかでデータ登録機能を実装しましょう。
おすすめは記述するコードが少ないcreateメソッド
です。
また、これら2つの登録方法を使用した場合、変更できるカラムはfirst-app/app/Models/Article.php
のprotected $fillable
かprotected $guarded
で指定したものだけです。
そのため、first-app/app/Models/Article.php
でprotected $fillable
でtitle
とcontent
を指定しています。
エラー処理を記述
最後にデータベースへ登録できなかった場合のエラー処理をしておきます。
これをすることによって万が一エラーが発生した時に、ユーザーが視覚的に記事を登録できないことを把握することができます。
実装
それでは記事登録の流れを説明したので、さっそく実装していきましょう。
記事投稿用のルート設定
まずは記事を登録するフォームを表示するためのルートを設定していきましょう。first-app/routes/web.php
を下記の通り編集してください。
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ArticleController;
Route::get('/', function () {
return view('welcome');
});
// 投稿一覧を表示
Route::get('/articles', [ArticleController::class, 'showArticles'])->name('showArticles');
// 記事投稿フォームを表示
Route::get('/article/create', [ArticleController::class, 'createArticle'])->name('createArticle'); // ここを追加
// 投稿詳細を表示
Route::get('/article/{id}', [ArticleController::class, 'showArticle'])->name('showArticle');
いつも通りRoute::get
でパスとコントローラー、メソッド名を指定しています。
今回は記事を作成するという意味のcreateArticleメソッド
としました。
createArticleメソッドの作成
次に、Articleコントローラー
にルートで指定したcreateArticleメソッド
を記述していきましょう。
first-app/app/Http/Controllers/ArticleController.php
を下記の通り編集してください。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Article;
class ArticleController extends Controller
{
/**
* 記事一覧を表示する
*/
public function showArticles()
{
// 全ての記事データを取得
$articles = Article::all();
// compact関数で渡す場合
return view('articles.index', compact('articles'));
}
/**
* 記事投稿フォームを表示
*/
public function createArticle()
{
return view('articles.create');
}
/**
* 記事詳細を表示する
*/
public function showArticle($id)
{
// 渡されてきた記事IDのデータを取得
$article = Article::find($id);
return view('articles.detail', compact('article'));
}
}
追加したコードは下記の通りです。
/**
* 記事投稿フォームを表示
*/
public function createArticle()
{
return view('articles.create');
}
これで記事投稿フォームを表示するためのcreateArticleメソッド
を定義することができました。
投稿フォームに表示させるデータは何もないので、create.blade.php
へ渡す処理をviewメソッド
を使用して記述するだけでOKです。
記事投稿用フォームの作成
次にfirst-app/resources/views/articles
配下にcreate.blade.php
をコマンドかVSCodeで作成してください。
コマンドで作成する場合は、ターミナルにて下記コマンドをfirst-app
ディレクトリ上で実行してください。
$ touch resources/views/articles/create.blade.php
作成したfirst-app/resources/views/articles/create.blade.php
を下記の通り編集してください。
@extends('layout')
@section('title')
記事作成
@endsection
@section('content')
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h2>記事投稿フォーム</h2>
<form method="POST" action="">
<div class="form-group">
<label for="title">
タイトル
</label>
<input id="title" name="title" class="form-control" value="{{ old('title') }}" type="text">
@if ($errors->has('title'))
<div class="text-danger">
{{ $errors->first('title') }}
</div>
@endif
</div>
<div class="form-group mt-3">
<label for="content">
内容
</label>
<textarea id="content" name="content" class="form-control" rows="4">{{ old('content') }}</textarea>
@if ($errors->has('content'))
<div class="text-danger">
{{ $errors->first('content') }}
</div>
@endif
</div>
<div class="mt-3">
<a class="btn btn-secondary mr-2" href="{{ route('showArticles') }}">
キャンセル
</a>
<button type="submit" class="btn btn-primary">
投稿
</button>
</div>
</form>
</div>
</div>
@endsection
それではhttp://localhost/article/createへアクセスしてください。
下記画像のような画面が出てくればOKです。
フォーム部分のコードを説明していきます。
まず、form要素のmethod属性はPOST
にしています。
action属性は本来登録処理のルートを設定しなければなりませんが、今は何も指定していません。
というのも、存在しないルートをビューで指定してしまうと下記のようなエラーが出てしまうためです。
上記のエラーは、まだ存在していないルート名storeArticle
をrouteメソッド
で指定した時のエラーです。
次にタイトル入力欄について確認してみましょう。
<input id="title" name="title" class="form-control" value="{{ old('title') }}" type="text">
まずタイトル入力欄にはinputタグ
を使用しています。name属性
にはtitle
を設定しており、登録処理の際にname属性で入力された値を指定することでデータを受け取ることができます。
また、value属性
にold('title')
と記述していますが、こちらはLaravelのヘルパーメソッドであるoldメソッド
を使用しています。()
内にname属性と同じ値を指定することで、バリデーションのエラー時にフォームの値を保持することができます。
ここはバリデーション実装時に改めて確認しましょう。
また、下記のコードも追加しています。
@if ($errors->has('title'))
<div class="text-danger">
{{ $errors->first('title') }}
</div>
@endif
Bladeテンプレートでは条件分岐を@if(条件式)~@endif
と記述することができます。
もちろん複数の条件分岐をしたい場合は@if(条件式A)~@elseif(条件式B)~@else~@endif
のように記述することもできます。
今回のコードでは条件式に$error->has('title')
と記述されています。
このコードの意味は、タイトルをバリデーションで検証した結果、エラーがあればtrue
、なければfalse
を出力するといったものです。
そして、エラーがあった場合はtrue
となり条件分岐内の処理が実装され、$errors->first('title')
と記述されているので、1つ目のエラーメッセージが出力されます。
こちらもバリデーションを実装した時に動きを確認してみましょう。
次に内容入力欄について確認してみましょう。
ほとんどタイトル入力欄と同じですが、inputタグ
ではなくtextareaタグ
を使用しています。textarea要素
にはvalue属性
がないので、old('content')
はtextareaの開始タグ
とtextareaの終了タグ
で囲みましょう。
バリデーションエラーを出すためのコードはタイトル部分と全く同じです。
最後にボタン部分について確認してみましょう。
まず、キャンセルボタンに関してはaタグ
を使用して、クリックされた時にroute('showArticles')
に遷移する処理を実装しています。
こうすることで、キャンセルボタンをクリックされた後に処理一覧画面に戻すことができます。
次に投稿ボタンに関してはbuttonタグ
を使用して、type属性
にsubmit
を指定することで、ボタンがクリックされた時にフォームを送信するようにしています。
以上でフォーム部分の説明は終わりです。
記事投稿処理用のルート設定
次に投稿処理用のルートを設定していきましょう。first-app/routes/web.php
を下記の通り編集してください。
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ArticleController;
Route::get('/', function () {
return view('welcome');
});
// 投稿一覧を表示
Route::get('/articles', [ArticleController::class, 'showArticles'])->name('showArticles');
// 記事投稿フォームを表示
Route::get('/article/create', [ArticleController::class, 'createArticle'])->name('createArticle');
// 記事登録処理
Route::post('/article/store', [ArticleController::class,'storeArticle'])->name('storeArticle'); // ここを追加
// 投稿詳細を表示
Route::get('/article/{id}', [ArticleController::class, 'showArticle'])->name('showArticle');
今回はフォームからPOSTでデータが送られてくるので、Route::get
ではなくRoute::post
としています。
あとはいつも通りパスとコントローラー、メソッド名を指定しています。
メソッド名は保存するという意味のstoreArticleメソッド
としました。
フォームにaction属性を追加
ルートを設定したので、create.blade.php
にaction属性
を追加します。
first-app/resources/views/articles/create.blade.php
のform開始タグ
を下記のように変更してください。
<form method="POST" action="{{ route('storeArticle') }}">
action属性
に登録処理用のルート名であるstoreArticle
を指定すると、投稿ボタンがクリックされるとArticleコントローラー
のstoreArticleメソッド
が実行されます。
storeArticleメソッドの作成
では、コントローラーにstoreArticleメソッド
を記述していきましょう。first-app/app/Http/Controllers/ArticleController.php
を下記の通り編集してください。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Article;
class ArticleController extends Controller
{
/**
* 記事一覧を表示する
*/
public function showArticles()
{
// 全ての記事データを取得
$articles = Article::all();
// compact関数で渡す場合
return view('articles.index', compact('articles'));
}
/**
* 記事投稿フォームを表示
*/
public function createArticle()
{
return view('articles.create');
}
/**
* 記事投稿処理
*/
public function storeArticle(Request $request)
{
dd($request);
}
/**
* 記事詳細を表示する
*/
public function showArticle($id)
{
// 渡されてきた記事IDのデータを取得
$article = Article::find($id);
return view('articles.detail', compact('article'));
}
}
追加したコードは下記の通りです。
/**
* 記事投稿処理
*/
public function storeArticle(Request $request)
{
dd($request);
}
フォームから送信された値はstoreArticle(Request $request)
と記述することで、変数request
として受け取ることができます。
これはArticleコントローラー
の5行目あたりを見てもらうとuse Illuminate\Http\Request;
と記述されています。
この機能を使用することで、Laravelではフォームから送信された値を変数request
として受け取ることができるということです。
まずはフォームから何か値が送られてくるときはRequest $request
となることを覚えておきましょう。
フォームから送られてきた値が本当に取得できているのかを確認するためにdd($request)
と記述しておきます。
それでは、変更が完了したらhttp://localhost/article/createへアクセスし、タイトルと内容に適当なテキストを入力して投稿ボタン
をクリックしてください。
すると419 PAGE EXPIRED
エラー画面が表示されます。
CSRF保護
実は、Laravelでフォームを使用する場合はCSRF保護
というものをしなければなりません。
そもそもCSRF(シーサーフ)とは、クロスサイトリクエストフォージェリの略で、Webアプリケーションに存在する脆弱性、もしくはその脆弱性を利用した攻撃方法のことです。
具体的には、アプリケーションに対して攻撃しようとしている攻撃者
が作成した「不正なリンク」を攻撃対象者
にクリックさせることで、あたかも攻撃対象者
が操作したかのようにみせかけて攻撃者
がアプリケーションに対して不正なリクエストを送信することです。
このような攻撃からアプリケーションを守るために、LaravelではCSRF保護というものが提供されています。
やり方は至って簡単で、layout.blade.php
に<meta name="csrf-token" content="{{ csrf_token() }}">
を追加することとform開始タグ
直後に@csrf
というコードを追加するだけです。
こうすることでフォーム送信時にトークンが発行され、そのトークンを使用してリクエストがなりすましかどうかを判断することができます。
CSRFの概念は難しいので理解をする必要はありません。
どのようにCSRFの対策ができるのかを理解しておきましょう。
それではさっそくfirst-app/resources/views/layout.blade.php
を下記の通り編集してください。
<!DOCTYPE HTML>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="csrf-token" content="{{ csrf_token() }}">
<!-- titleは可変 -->
<title>@yield('title', 'ブログ')</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/css/bootstrap.min.css" integrity="sha384-r4NyP46KrjDleawBgD5tp8Y7UzmLA05oM1iAEQ17CSuDqnUK2+k9luXQOfXJCJ4I" crossorigin="anonymous">
</head>
<body>
<!-- ヘッダー -->
@include('header')
<!-- コンテンツ -->
<div class="container mt-4">
@yield('content')
</div>
<!-- フッター -->
@include('footer')
<!-- JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous"></script>
</body>
</html>
head要素
の中に<meta name="csrf-token" content="{{ csrf_token() }}">
という1コードを追加しています。
次にfirst-app/resources/views/articles/create.blade.php
のform開始タグ
の直後に下記コードを追加してください。
<form method="POST" action="{{ route('storeArticle') }}">
@csrf
再度http://localhost/article/createへアクセスして、タイトルと内容に適当なテキストを入れて投稿ボタンをクリックしてください。
下記画像のようにddメソッド
を使用した変数request
の結果が表示されていればOKです。
その中のrequest
を展開してみましょう。request
の中にはparameters
があり、それを展開すると_token
、title
、content
が入っています。_token
はCSRF保護で作成されたトークンのことであり、title
とcontent
は入力した値が入っていればフォームで入力された値が取得できていればOKです。
これで変数request
を使用すると、簡単にフォームから入力された値が取得できることが確認できました。ddメソッド
はとても便利ですね!
登録処理を実装
それでは、Articleコントローラー
にフォームから受け取った値を登録する処理を記述していきましょう。first-app/app/Http/Controllers/ArticleController.php
を下記の通り編集してください。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Article;
class ArticleController extends Controller
{
/**
* 記事一覧を表示する
*/
public function showArticles()
{
// 全ての記事データを取得
$articles = Article::all();
// compact関数で渡す場合
return view('articles.index', compact('articles'));
}
/**
* 記事投稿フォームを表示
*/
public function createArticle()
{
return view('articles.create');
}
/**
* 記事投稿処理
*/
public function storeArticle(Request $request)
{
// 記事登録処理
Article::create([
'title' => $request->title,
'content' => $request->content,
]);
return redirect()->route('showArticles');
}
/**
* 記事詳細を表示する
*/
public function showArticle($id)
{
// 渡されてきた記事IDのデータを取得
$article = Article::find($id);
return view('articles.detail', compact('article'));
}
}
変更したコードは下記の通りです。
/**
* 記事投稿処理
*/
public function storeArticle(Request $request)
{
// 記事登録処理
Article::create([
'title' => $request->title,
'content' => $request->content,
]);
return redirect()->route('showArticles');
}
先ほど説明した通りcreateメソッド
を使用しています。
また、return redirect()->route('showArticles');
と記述しています。
以前作成した他のメソッドとは少し違いviewメソッド
ではなくredirectメソッド
を使用して画面の遷移を実装しています。
viewメソッド
もredirectメソッド
も画面を遷移させる処理という点は同じですが、ルートを記述した時にRoute::get
とした場合はviewメソッド
を使用して、反対にRoute::post
とした場合はredirectメソッド
を使用して画面遷移を実装してください。
わかりやすいように表にまとめておきます。
ルート | メソッド |
---|---|
Route::get | view() |
Route::post | redirect() |
それではhttp://localhost/article/createへアクセスして、タイトルと内容に適当なテキストを入れて投稿ボタンをクリックしてください。
登録ボタンをクリックすると記事一覧画面へリダイレクトされ、入力した内容が4つ目の記事として表示されていれば記事投稿処理は無事に実装できています。
フォームリクエストを使用してバリデーションをする
投稿処理を実装することはできましたが、記事投稿画面でタイトルと内容が空の状態で投稿ボタンをクリックされると下記画像のようなエラーが発生してしまいます。
これはテーブルの設定でtitle
やcontent
がNULL(値がないこと)を許可していないからです。
なので、title
とcontent
には何かしら文字列型の値が入っている必要があります。
その制御であるバリデーションをLaravelではフォームリクエスト
という機能を使用して実装します。
それでは、先ほど説明した下記コマンドをターミナルにてfirst-app
配下で実行してください。
$ ./vendor/bin/sail php artisan make:request ArticleStoreRequest
今回は記事登録のバリデーションを作成したいので、クラス名はArticleStoreRequest
とします。
指定したクラス名+phpの拡張子のファイルであるArticleStoreRequest.php
がfirst-app/app/Http/Requests
に作成されていればOKです。first-app/app/Http/Requests/ArticleStoreRequest.php
を下記の通り編集してください。
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ArticleStoreRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true; // falseからtrueへ変更
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, mixed>
*/
public function rules()
{
return [
'title' => 'required|string|max:100',
'content' => 'required|string',
];
}
}
まず、ArticleStoreRequestクラス
ではFormRequestクラス
を継承していることから、フォームリクエスト
というLaravelの機能が使用できます。
次にauthorizeメソッド
のreturn false
をreturn true
に変更してください。
こちらのauthorizeメソッドは、フォームリクエストの利用が許可されているかどうかを示すメソッドで、false
の場合はArticleStoreRequestクラス
が使用できなくなるので、必ずtrue
にしてください。
次にrulesメソッド
のreturn [];
にバリデーションをしたいカラム名
を入力して=> ''
と記述し、''
内に、どのようなバリデーションをするかを記述します。
''
内に記述できるバリデーションは多岐に渡り、ドキュメントの使用可能なバリデーションルール
に全て記載されています。
これらのバリデーションルールを暗記する必要は全くありません!
使用したいバリデーションルールを実装する時にドキュメントを見ながら設定できればOKです!
今回、タイトルと内容に適応させたバリデーションルールは下記の通りです。
- title(タイトル):
required
(必須チェック)、string
(文字列型チェック)、max:100
(文字数制限100文字まで) - content(内容):
required
(必須チェック)、string
(文字列型チェック)
タイトルのmax:100
についてですが、テーブル設定の際に最大文字数を100文字に指定しているので、PHP側でも100文字までしかタイトルを入れられないようにしています。
これでArticleStoreRequest.php
の編集は終わりなので、Articleコントローラー
へ反映させましょう。
独自に作成したフォームリクエストをコントローラーに反映させるためにはuse文
を使用して、作成したフォームリクエストクラスを読み込まなければなりません。
なので、今回作成したArticleStoreRequest
を読み込むためには、下記のコードがArticleコントローラー
に必要となります。use App\Http\Requests\ArticleStoreRequest;
次に記事投稿処理であるstoreArticleメソッド
の引数にRequest $request
と記述していますが、Request
部分をArticleStoreRequest
とします。
こうすることでフォームから送信された値がArticleStoreRequest.php
で指定したバリデーションを通過した後に、Articleコントローラー
の変数request
で受け取れるようになります。
それではfirst-app/app/Http/Controllers/ArticleController.php
を下記の通り編集してください。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Article;
use App\Http\Requests\ArticleStoreRequest;
class ArticleController extends Controller
{
/**
* 記事一覧を表示する
*/
public function showArticles()
{
// 全ての記事データを取得
$articles = Article::all();
// compact関数で渡す場合
return view('articles.index', compact('articles'));
}
/**
* 記事投稿フォームを表示
*/
public function createArticle()
{
return view('articles.create');
}
/**
* 記事投稿処理
*/
public function storeArticle(ArticleStoreRequest $request)
{
// 記事登録処理
Article::create([
'title' => $request->title,
'content' => $request->content,
]);
return redirect()->route('showArticles');
}
/**
* 記事詳細を表示する
*/
public function showArticle($id)
{
// 渡されてきた記事IDのデータを取得
$article = Article::find($id);
return view('articles.detail', compact('article'));
}
}
これでバリデーションの実装は完了したので、さっそく動作確認をしてみましょう。
http://localhost/article/createへアクセスして、タイトルと内容に何も入力しないで投稿ボタンをクリックしてください。
下記画像の通り、エラーメッセージが表示されればOKです。
エラーメッセージが英語でわかりにくいですが、title
とcontent
は入力必須です。というエラーが起こっています。
日本語化
初期設定で日本語の設定はしたはずですが、なぜかバリデーションエラーが英語のままです。
それは、初期設定の設定だけだと設定を変更しただけで日本語のファイルがLaravelにないために起こります。
Laravelでは日本語のファイルをダウンロードする方法がドキュメントに記載されているので、そちらのコマンドを実行していきましょう。
ドキュメントには./vendor/bin/sail
が付いていませんが、今回はDocker環境なので付けて実行しましょう。
それでは、ターミナルで下記3つのコマンドをfirst-app
ディレクトリ上で実行してください。
$ ./vendor/bin/sail php -r "copy('https://readouble.com/laravel/8.x/ja/install-ja-lang-files.php', 'install-ja-lang.php');"
$ ./vendor/bin/sail php -f install-ja-lang.php
$ ./vendor/bin/sail php -r "unlink('install-ja-lang.php');"
この3つのコマンドを実行しても、特にコマンド実行成功などのテキストはターミナルには表示されませんがfirst-app/resources/lang/ja
フォルダが追加されており、さらにその中には4つのファイルが追加されます。
これら4つのファイルが日本語用のファイルになります。
改めてhttp://localhost/article/createへアクセスして、タイトルと内容に何も入力しないで投稿ボタンをクリックしてください。
下記画像の通り、エラーメッセージが表示されればOKです。
まだtitle
とcontent
が英語なので、これも修正していきましょう。
これを修正するためにはArticleStoreRequest.php
を編集する必要があります。first-app/app/Http/Requests/ArticleStoreRequest.php
を下記の通り編集してください。
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ArticleStoreRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, mixed>
*/
public function rules()
{
return [
'title' => 'required|string|max:100',
'content' => 'required|string',
];
}
public function attributes()
{
return [
'title' => 'タイトル',
'content' => '内容',
];
}
}
このようにattributesメソッド
を追加して、name属性値 => '翻訳内容'
と記述して翻訳していきましょう。
それではhttp://localhost/article/createへアクセスして、タイトルと内容に何も入力しないで投稿ボタンをクリックしてください。
下記画像の通り、エラーメッセージが表示されればOKです。
これでバリデーションのエラーメッセージの日本語化は完了です。
ちなみに、タイトルに100文字以上入力し、内容にこれは内容です。
というテキストを入力してから投稿ボタンをクリックすると、バリデーションエラーが以下画像のようなメッセージに変わります。
まず、タイトルに入力した内容がバリデーションエラーが出ても消えていないことに注目してください。
例えば、バリデーションエラーが起こった際に入力した内容が全て消えてしまっていたら、ユーザーはどのように入力してエラーになったのかがわかりません。
それだけではなく、入力した内容の一部分だけ修正したいと思っても入力内容が全てクリアされていたら、また1から書き直しということになってしまいます。
このような状態を改善するために、create.blade.php
ではoldメソッド
を使用しています。oldメソッド
はold()
の()
内にname属性値を指定することで、バリデーションエラー時にフォームの値を保持することができます。
今回タイトルに100文字以上の値を入力しているので、文字数オーバーのバリデーションエラーが出ていますが、oldメソッド
をvalue属性で使用しているため、フォームの内容が保持されており、入力内容がクリアされることなくバリデーションエラー後も出力されています。
このようにLaravelでフォームを作成する際は基本oldメソッド
を使用しましょう。
次に、内容部分に注目してください。
こちらはバリデーションエラーがないため、エラーメッセージは表示されません。
それは以前create.blade.php
で実装した下記の条件分岐の記述があったためです。
@if ($errors->has('content'))
<div class="text-danger">
{{ $errors->first('content') }}
</div>
@endif
$errors->has('content')
がfalse
となるので、エラーが表示されないということです。
一方でタイトル部分は100文字以上入力しているので、$errors->has('title')
がtrue
となり、エラーメッセージが表示されています。
エラー処理を記述
無事に記事登録処理を実装することができましたが、データベースにデータを登録する際や編集・削除する際にはエラー処理を記述することが一般的です。
というのも、もし処理がうまく行かずデータが登録できなかった場合やデータベースに繋げなかった場合などにエラーが発生してしまいます。
そのような予期せぬエラーが発生してしまった場合に備えて、エラー時にデータを登録できないようにする処理とログにエラー内容を出力させる処理、エラー画面へ遷移させるための処理の3つを実装しておきます。
まずはfirst-app/app/Http/Controllers/ArticleController.php
を下記の通り編集してください。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Article;
use App\Http\Requests\ArticleStoreRequest;
use Illuminate\Support\Facades\DB; // ここを追加
use Illuminate\Support\Facades\Log; // ここを追加
class ArticleController extends Controller
{
/**
* 記事一覧を表示する
*/
public function showArticles()
{
// 全ての記事データを取得
$articles = Article::all();
// compact関数で渡す場合
return view('articles.index', compact('articles'));
}
/**
* 記事投稿フォームを表示
*/
public function createArticle()
{
return view('articles.create');
}
/**
* 記事投稿処理
*/
public function storeArticle(ArticleStoreRequest $request)
{
// トランザクション開始
DB::beginTransaction();
try {
// 記事登録処理
Article::create([
'title' => $request->title,
'content' => $request->content,
]);
// トランザクションコミット
DB::commit();
} catch(\Exception $e) {
// トランザクションロールバック
DB::rollBack();
// ログ出力
Log::critical($e);
// エラー画面遷移
abort(500);
}
return redirect()->route('showArticles');
}
/**
* 記事詳細を表示する
*/
public function showArticle($id)
{
// 渡されてきた記事IDのデータを取得
$article = Article::find($id);
return view('articles.detail', compact('article'));
}
}
それでは追加したコードについて説明します。
まず、トランザクションという処理を実装するためにuse Illuminate\Support\Facades\DB;
を追加し、ログを出力させるためにuse Illuminate\Support\Facades\Log;
を追加しています。
これらのuse文
を暗記する必要はありません。
例えば、Laravel トランザクション
と検索するだけでトランザクションを実装するための記事がたくさん出てくるからです。
もちろん、その記事の中でuse文
を追加することが書いてあります。
ログ出力の場合もそうです。Laravel ログ出力
などで検索すると、トランザクションの時と同じように実装方法を説明してくれている記事がたくさん出てきます。
それらの記事を見ながら実装できれば完璧です。
次に、新たに記事投稿処理のstoreArticleメソッド
にtry-catch文
とトランザクション処理
、ログ出力処理
、エラー画面への遷移処理
の4つを追加しています。
追加した4つについて詳しく見ていきましょう。
try-catch文
try-catch文を使用するとtry{}
で囲まれた処理の中でエラーが発生した場合に処理を止めて、catch(){}
内の処理を実行することができます。
例えば、今回でいうと登録処理に失敗した場合は、catch文の中にデータベースにデータを登録させないようにトランザクション処理を記述していたり、エラー内容を受け取ってログを出力させる処理、エラー画面へ遷移させる処理の3つを記述しています。
このようにエラー処理をすることで、エラーが発生しているにも関わらず不正にデータが登録されることを防ぎ、開発者にどこでどのようなエラーが起こっているのかがわかるようにログを出力し、ユーザーには何かしらのエラーが起こったことを画面を通して伝えることができます。
これらの処理を実装するための記述がtry-catch文
です。
基本の使い方は下記の通りです。
try {
// 一連の処理
} catch(\Exception $e) {
// エラー時に行う処理
}
上記のように記述するだけでtry-catch文
を使用することができます。
また、catch(\Exception $e){}
と記述していますが、引数e
にはエラー内容が渡されているので、エラー内容をログに残す際などに使用することができます。
トランザクション処理
トランザクションという言葉か何回か出てきましたが、そもそもトランザクションとは一連の作業を1つの処理として管理するために用いられるもの
です。
このトランザクションを使用することで、一連の処理が全て成功した
かどこかで失敗した
の2パターンで処理を分けることができます。
今回でいうと、登録処理が無事に成功すればデータベースに値を登録し、登録処理が失敗したらデータを登録しないという処理を実装することができます。
Laravelでのトランザクションの使い方は下記の通りです。
use Illuminate\Support\Facades\DB; // use文
DB::beginTransaction();
try {
// データベースへの保存や編集、削除処理など
DB::commit();
} catch() {
DB::rollback();
// エラー時の処理
}
トランザクションはtry-catch文とセットで使うことを覚えておきましょう。
成功したらcommit
として、失敗した場合にはrollback
とすることで、処理が成功した時のみデータベースへデータが登録されることになります。
ログ出力
JavaScriptではconsole.log()
を使用して、コンソールにログを出力していましたが、Laravelでも簡単にログを出力することができます。
Laravelではddメソッド
が使用できるので、基本的なデバッグ時にログを使用することは少ないですが、ログを出力することはエラー時にエラー内容を確認したり、後々ddメソッド
が使えない場合などのデバッグをする際にもとても役立につので、ぜひ覚えておきましょう。
Laravelでのログ出力方法は下記の通りです。
use Illuminate\Support\Facades\Log;
Log::emergency('エラーメッセージ');
Log::alert('エラーメッセージ');
Log::critical('エラーメッセージ');
Log::error('エラーメッセージ');
Log::warning('エラーメッセージ');
Log::notice('エラーメッセージ');
Log::info('エラーメッセージ');
Log::debug('エラーメッセージ');
()
内にはもちろん変数も指定できるので、今回のコードのようにLog::critical($e)
とすることで、エラー内容をログに出力することができます。
※変数e
はtry-catct文を使用した際にエラーが入っている変数のこと
また、ログにはログレベルというものがあり、どのログを使用してもOKですが、エラー内容や緊急度によってログレベルを設定することができます。emergency
が緊急度MAXで、下に行くほど緊急度が下がりdebug
までになると、開発者がデバッグするために出力するためのログになります。
基本使用するのは今回使用したエラー用のcritical
とデバッグしたい時に使用するdebug
、何かの情報をログでお知らせをしたい場合(〇〇メソッドが実行された)などに使用するinfo
などが有名です。
基本はその3つをおさえておけばOKです!
またログはデフォルトでfirst-app/storage/logs/laravel.log
に出力されるので、後ほど確認してみましょう。
エラー画面への遷移
エラー画面はLaravelにもともと備わっており、エラーが発生すると表示されます。
PHPの処理が失敗した時に出力されるエラーはステータスコードが500
の500エラー
なので、Laravelのヘルパーメソッドであるabortメソッド
を使用して、abort(500)と記述してあげることで
500エラーページへ遷移させるようにしています。
今回説明したtry-catch文やトランザクション、ログ出力、エラー画面への遷移の実装は必須ではありませんが、プログラマーとして仕事をするのであれば実装しておきましょう。
最後に登録時にわざとエラーを起こして、ログが出力されているかを確認してみましょう。first-app/app/Http/Controllers/ArticleController.php
のstoreArticleメソッド
を下記の通り変更してください。
/**
* 記事投稿処理
*/
public function storeArticle(ArticleStoreRequest $request)
{
// トランザクション開始
DB::beginTransaction();
try {
// 記事登録処理
Article::create([
'title' => $request->title,
// 'content' => $request->content,
]);
// トランザクションコミット
DB::commit();
} catch(\Exception $e) {
// トランザクションロールバック
DB::rollBack();
// ログ出力
Log::critical($e);
// エラー画面遷移
abort(500);
}
return redirect()->route('showArticles');
}
登録処理のcontent
部分をコメントアウトしたことにより、必須項目がデータベースへ登録されないのでエラーが発生します。
実際にhttp://localhost/article/createへアクセスして、タイトルと内容に適当なテキストを入れて投稿ボタンをクリックしてください。
下記画像のように500 | SERVER ERROR
の画面が表示されればOKです。
もちろんこれがabortメソッド
で500
を指定していたので、サーバーエラーの画面に遷移しました。
また、first-app/storage/logs/laravel.log
を開いてください。
Laravelはデフォルトでここにログが残るようになっています。
今回Log::critical($e)
としているので、下記のようなログが出力されます。
[2022-06-17 20:26:28] local.CRITICAL: PDOException: SQLSTATE[HY000]: General error: 1364 Field 'content' doesn't have a default value in /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Connection.php:527
エラーが起こった日時
とlocal.CRITICAL
となっています。
また、エラー内容も出力されており、Field 'content' doesn't have a default value
となっています。
つまり、「contentカラム
はデフォルトの値がテーブル設定でされていないので何かしらの値が必要です」というエラーです。
ちなみに、Log::debug($e)
と記述していた場合はlocal.DEBUG
となります。
このようにログを出力することで、ブラウザでは500 | SERVER ERROR
となっていてエラー内容が確認できませんが、ログを確認することでどのようなエラーが起こっているのかを確認できます。
ブラウザでエラーが確認できない場合は積極的にログを確認しましょう。
それでは、first-app/app/Http/Controllers/ArticleController.php
のstoreArticleメソッド
を下記の通り元に戻しておきましょう。
/**
* 記事投稿処理
*/
public function storeArticle(ArticleStoreRequest $request)
{
// トランザクション開始
DB::beginTransaction();
try {
// 記事登録処理
Article::create([
'title' => $request->title,
'content' => $request->content,
]);
// トランザクションコミット
DB::commit();
} catch(\Exception $e) {
// トランザクションロールバック
DB::rollBack();
// ログ出力
Log::debug($e);
// エラー画面遷移
abort(500);
}
return redirect()->route('showArticles');
}
これで登録処理が全て完了しました。
ヘッダーの遷移先を編集
最後にヘッダーの記事投稿ボタン
から記事投稿画面へ遷移できるようにheader.blade.php
を変更しましょう。first-app/resources/views/header.blade.php
を下記の通り編集してください。
<header>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container-fluid">
<a class="navbar-brand" href="{{ route('showArticles') }}">ブログ</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse justify-content-end" id="navbarNavAltMarkup">
<div class="navbar-nav">
<a class="nav-item nav-link active" href="{{ route('showArticles') }}">記事一覧 <span class="sr-only"></span></a>
<a class="nav-item nav-link" href="{{ route('createArticle') }}">記事投稿</a>
</div>
</div>
</div>
</nav>
</header>
変更した内容は下記の通りです。
まず、記事投稿ボタン
のhref属性
に記事投稿画面を表示させるルート名
をrouteメソッド
を使用して指定しています。
これで記事投稿ボタンがクリックされると、投稿フォームを表示することができるようになりました。
また、ブログ
と記事一覧ボタン
にも記事一覧画面を表示させるルート名
をrouteメソッド
を使用して指定しています。
これでブログと記事一覧ボタンがクリックされると、記事一覧画面を表示することができるようになりました。
いい感じにブログシステムが構築できてきましたね!
これで記事投稿機能の実装は完了です。
★検索ワード
・Laravel 登録機能実装
・Laravel create fill save
・Laravel バリデーション実装
・Laravel CSRF 実装
・Laravel リダイレクト
・Laravel フォームリクエスト
・Laravel 日本語化
・PHP try-catch 使い方
・Laravel トランザクション
・Laravel ログ出力
・Laravel abortメソッド
次の教材
次の教材は下記から簡単に飛ぶことができます!
引き続きプログラミングを楽しんでいきましょう!
プログラミング学習サポートについて
「独学で挫折した。。。」
「一人でのプログラミング学習がしんどい。。。」
「未経験からエンジニア転職をしたいけど何をしたら良いかわからない。。。」
このような悩みをお持ちの方向けに、本教材作成者のファドがMENTAという学習サイトにてあなたのプログラミング学習とエンジニア転職を徹底サポートいたします!
サポート価格はなんと1日あたりたったの約300円!
教材で分からない箇所のサポートはもちろんのこと、本サイトで公開しているすべての課題の解答も公開しております。
また、MENTAで学習を終わらせていただいた方限定で懇意にしていただいている企業さんを紹介することもあります!
なお、サポート内容の詳細は下記の通りです。
- 目標設定
- マインドセット
- オリジナル教材見放題
- オリジナル課題見放題
- オリジナル課題の解答見放題
- 課題コードレビュー
- 教材への無制限質問
- 課題への無制限質問
- ポートフォリオ作成アドバイス
- 褒めのコーチング
いくつかのプランを用意させていただいておりますので、下記より一度ご覧ください!
コメント