Laravel CRUD処理
CRUD処理とは
Create / Read / Upload / Delete の処理のことである。
Web開発には必要な要素たちである。
今回はこれらをLaravelというフレームワークを利用してphpで記述していく。
Create投稿ボタンを設置
投稿ボタンを入れたいファイルに<a>タグを挿入する。
そしてclassを設定してCSSで整える。
<a class='btn-style2 btn-style2--blue box-center' href="{{ route('posts.create') }}">スペースを登録</a>
このようにRouteも指定し
Route::get('/posts/create', [PostController::class,'create'])
->name('posts.create');
ROUTEを作り
public function create()
{
return view('posts.create');
}
メソッドも作り、create.blade.phpファイルも作り
実際に、投稿ボタンを押すと、、
エラーが起きてしまいます。。
何が原因かを考えていきます。
これは、Routeミスです。
Route::get('/', [PostController::class,'index'])
->name('posts.index');
Route::get('/posts/{post}', [PostController::class,'show'])
->name('posts.show');
Route::get('/posts/create', [PostController::class,'create'])
->name('posts.create');
ルーティングは上から順に、チェックしている構造なのです。
つまり、
①リンクが何も指定されてない
②posts/{post}→postsの下階層の文字が引数であれば
③posts/createが指定されたら
このように順を追っていきます。
なので、{post}に文字列createが入ってしまいます。
すると、Controller.phpで(Post $post)の$postにcreateが入ってしまい
エラーに陥ってしまうわけです。。
解決方法は2つ
①ルーティングの順番を変更する
②->whereでルーティングに条件をつけて、createを弾く
①は汎用性がないので、②でいきます。
Route::get('/posts/{post}', [PostController::class,'show'])
->name('posts.show')
->where('post','[0-9]+');
これで、数字以外を受け付けない設定にできました。
Viewに投稿フォームを追加したのち、CSRF対策を施していきます。
<form action="" method="post">
//Laravelでは@CSRFと入力するだけで、トークンの生成・確認ができる
@csrf
//labelにすると、タグ内のどこをタッチしても入力モードにできる
<label>
スペース名
<input type="text" name="space" id="">
</label>
<label>
おすすめポイント
<textarea name="body" id="" cols="30" rows="10"></textarea>
</label>
<button>登録</button>
</form>
CSSはご自由に。
投稿を保存するためのルーティングとメソッドを実装
<form action="{{ route('posts.store') }}" method="post">
投稿を保存するルーティングを設定。
ここからファイル生成やルーティング設定、メソッド設定を行なっていく。
Route::get('/posts/create', [PostController::class,'create'])
->name('posts.create');
Route::post('/posts/create/store', [PostController::class,'store'])
->name('posts.store');
post型でRouteを指定する。
Createの下にstoreを作る。(ルーティングの構造上)
//route:postで渡された値を、Requestで受け取ることができる。
public function store(Request $request)
{
//データを入れるためのインスタンスを作る。
$post = new Post();
$post->title = $request->title;
$post->body = $request->body;
$post->save();
return redirect()
->route('posts.index');
}
入力された値を検証し、必要に応じてエラーメッセージを表示
登録ボタンで登録する際に、「空白」入力すると
Nullなので、エラーが起きてしまう。
詳しくは、データベースの設定を見ていただきたい。
なので、入力された値に対して、メッセージを表示する。
public function store(Request $request)
{
$request->validate([
'title' => 'required | min:3',
'body' => 'required'
]);
こんな感じで、指定する。
これらに引っかかった時に、HTMLで表示したい内容を
今から書きに行く。
//こんな感じで、@errorで指定する。
<label>
スペース名
<input type="text" name="title">
</label>
@error('title')
<div class="error">{{ $message }}</div>
@enderror
エラーメッセージは$messageで表示されるので、いい感じにエラーを検出して、メッセを出してくれる。
エラー発生時に表示するメッセージをカスタマイズする方法
今のままでは、入力した文字がエラー後、消えてしまうので
エラー前のメッセージを残したままにしておく設定をしていきます。
<input type="text" name="title" value="{{ old('title') }}">
</label>
@error('title')
<div class="error">{{ $message }}</div>
@enderror
<label>
おすすめポイント
<textarea name="body">{{ old('body') }}</textarea>
old('name')で、値を設定すると
更新前の値を残したままにしてくれます。
続いて、
エラーメッセージを指定します。
[ 'title' => 'required | min:3',
'body' => 'required'
],[
'title.required' => '入力必須です。',
'title.min' => ':min 文字以上入力してください。',
'body.required' => '入力必須です。'
]
このように、title.requiredと付けることで、入力必須状態にする。
:minは記入済みの設定文字数を指定できる。
記事詳細ページで改行が反映されるようにnl2br()を使っていきます
このように、改行した場合どうなるでしょうか。
まあ、こんな感じに改行されてないってことですね。
なので、改行していきたいと思います。
//これで改行がつくはず。。
<p>{{ nl2br($post->body) }}</p>
悪意のある文が挿入されないように、HTMLタグは文字実体参照として認識されてしまう。
なので、これをbladeで値を埋め込みつつ、文字実体参照での変換を無効にする方法を実行してみる。
<p>{!! nl2br($post->body) !!}</p>
このように{{!! !!}}ビックリマークを付ければ、
文字実体参照を回避することができる。
悪意あるコードが実行されないように、e()を使っていきます
今のままでは、悪意のあるコードが有効化されてしまっています。
投稿フォームに<script>alert();</script>と入力すると
このようにJavasciptが適用されてしまいます。
なので、nl2brを有効にしつつ、悪意のあるコードを無効化していきます。
<p>{!! nl2br(e($post->body)) !!}</p>
Laravelではe()で囲むと、htmlspecial Charactersの効果が発揮されます。
ちなみに、二重括弧{{}}は、<?php echo?>の意味があります。
また、悪意のあるコードを弾く効果もあります。(XSS:クロスサイトスクリプティングという攻撃)
今回は、そちらを無効化したためe()を適用しました。
投稿を編集するためのルーティングとメソッドを実装していきます。
投稿画面のタイトル横に「編集」ボタンを設置したいので
<h1>– {{ $post->title }} –</h1>
これを
<h1>
<span>{{ $post->title }} </span>
<a href="">[Edit]</a>
</h1>
こうすると、こうなる。
ここからは、ルーティングとメソッドを作っていく。
<a href="{{ route('posts.edit', $post) }}">[Edit]</a>
Route::get('/posts/{post}/edit', [PostController::class,'edit'])
->name('posts.edit')
->where('post','[0-9]+');
public function edit(Post $post){
return view('posts.edit')
->with(['post' => $post]);
}
んで、viewsフォルダの中に、edit.blade.phpを作って
create.phpをコピペしておく。
編集フォームを作ったのち、フォームに値がセットされるようにしていきます。
edit.phpを編集していきます。
タイトルなどは各々変えて見てください。
//バックリンクは投稿画面に戻りたいので
« <a href="{{ route('posts.show',$post)}}">Back</a>
こんな感じで、以前入力されていた値がなくなっているので、
表示していきます。
//oldには第二引数を渡せる。もし、第一引数がなければ実行される。
{{ old('title',$post->title) }}
うん。いいっすね。
編集した内容でデータを更新するためのルーティングを設定し、PATCH形式でフォームを送信します。
//ルーティングを定義します。
<form action="{{ route('posts.upload',$post) }}" method="post">
Route::get('/posts/{post}/upload', [PostController::class,'upload'])
->name('posts.upload')
->where('post','[0-9]+');
これではいけません。
なぜなら、データをgetするのではないから
Route::post('/posts/{post}/upload', [PostController::class,'upload'])
これもあかんとです。
なぜなら、postは新しいデータを作成するとき。
一部の情報を変更する際はpatchを使うルールがあるんですねえ。
Route::patch('/posts/{post}/upload', [PostController::class,'upload'])
続いて、
<form action="{{ route('posts.upload',$post) }}" method="post">
methodをpostからpatchに変えた方がいいと思いますよね。
しかし、まだLaravelでは対応していないぽく
<form action="{{ route('posts.upload',$post) }}" method="post">
@method('PATCH')
こう書きます。
データを更新するためのメソッドを実装し、編集機能を完成させます。
コントローラーに移動します。
やり口がstore関数と似ているので、コピペします。
//implicit Bindingを利用するためPostを指定
public function update(Request $request,Post $post)
{
$request->validate([
'title' => 'required | min:3',
'body' => 'required'
],[
'title.required' => '入力必須です。',
'title.min' => ':min 文字以上入力してください。',
'body.required' => '入力必須です。'
]);
//implicitで受け取ったpostを利用すればいいので、インスタンス生成は消します。
// $post = new Post();
$post->title = $request->title;
$post->body = $request->body;
$post->save();
//showを示します。
return redirect()
->route('posts.show',$post);
こんな感じでうまくいきます。
ここで、注意したいのは
update.blade.phpの作成は必要ないということです。
なぜなら、遷移するのは既存のページだけだからです。
これもPatch形式をとっているからなのでしょう。
独自のRequestクラスを作成し、バリデーション処理をまとめていきます。
$request->validate([
'title' => 'required | min:3',
'body' => 'required'
],[
'title.required' => '入力必須です。',
'title.min' => ':min 文字以上入力してください。',
'body.required' => '入力必須です。'
]);
validateメソッドが二つあるのが、後々増えた時に
分かりづらくなってしまうので、独自のrequestクラスを作って
そこに、validate関数を置いておきます。
./vendor/bin/sail artisan make:request PostRequest
作成したファイルを開くと、、
public function authorize()
{
//ユーザー管理をして、制限などを加える機能
// return false;
return true;
}
public function rules()
{
return [
'title' => 'required | min:3',
'body' => 'required'
];
}
public function messages()
{
return[
'title.required' => '入力必須です。',
'title.min' => ':min 文字以上入力してください。',
'body.required' => '入力必須です。',
];
}
エラーメッセージは、少し変わっていて
messages()を自ら設定しなくてはいけません。
では、実際にPotsContorollerとPostRequestを繋いでいきます。
//PostRequest
namespace App\Http\Requests;
名前空間がついているので、コピペしておきます。
//PostController
use App\Http\Requests\PostRequest;
と指定
public function store(PostRequest $request)
//こうすることで、store関数にわたる前に、PostRequestのvalidate関数を実行してくれます。
実際に、挙動するか試してみてください。
OKぽいです。
削除機能のためのフォームを作ります。
<span>{{ $post->title }} </span>
<a href="{{ route('posts.edit', $post) }}">[Edit]</a>
<form action="" method="post">
<button class="btn">[x]</button>
</form>
spanタグとかではなく
削除はformタグなんですね。
おそらく、データを更新するから?
<form action="{{ route('posts.destroy',$post) }}" method="post">
@method('DELETE')
@csrf
削除処理にはdestroyがよく使われます。
また、メソッドはDELETEで指定しましょう。
Route::delete('/posts/{post}/destroy', [PostController::class,'destroy'])
->name('posts.destroy')
->where('post','[0-9]+');
ルーティングはこんな感じ。
public function destroy(Post $post)
{
$post->delete();
return redirect()
->route('posts.index');
}
Impicit Bindingでpostを受け取り、
リダイレクトはindexを指定します。
これで削除機能は完成ですが、
削除する前に、確認が欲しいところです。
JavaScriptで操作していきます。