見出し画像

お知らせアプリを作ろう(#4) #Laravel基礎 #Laravelの教科書

こちらはLaravelを利用したお知らせアプリの記事の4回目です。表紙はこちらのマガジンからどうぞご確認ください

前回管理側の記事一覧とユーザーページを作成しました。本稿では記事の編集と記事の削除の機能を作成します。


# 管理側記事詳細ページの作成

記事編集フォームを表示するManageEntryController@showEditForm()の修正を行います。

app/Http/Controllers/user/ManageEntryController.php

class ManageEntryController extends Controller
{
	function showEditForm($id){
	
		$user = Auth::user();
		$news = $user->newsEntry()->find($id);
		
		if(!$news){
			return redirect("home")->withStatus("記事がありません");
		}
	
		return view("news.edit_form",[
			"news" => $news
		]);
	}

記事データの取り出しに$user->newsEntry()を利用している点に注意してください。

$news = $user->newsEntry()->find($id);

News::find($id)ではなく、$user->newsEntry()を使うと関係性チェックをしてくれます。

この書き方で自分以外が作った記事を編集出来なくしています。この書き方は次のように自分でuser_idをチェックしているのと同じことです。

$news = NewsEntry::find($id);
if(!$news || $news->user_id != Auth::id()){
    return redirect("home")->withStatus("記事がありません")
}

Controllerで記事編集ページで指定している「news.edit_form」のテンプレートを作成していきます。管理側の記事作成用のテンプレート「create_form.blade.php」とほとんど同じです。

resources/views/news/edit_form.blade.php

@extends('layouts.admin')
@section('content')
<div class="container">
	<div class="card">
		<div class="card-header">記事の編集</div>
		<div class="card-body">
			@if ($errors->any())
			<div style="color:red;">
			<ul>
				@foreach ($errors->all() as $error)
				<li>{{ $error }}</li>
				@endforeach
			</ul>
			</div>
			@endif

			<form method="post" action="{{ url('news/edit/' . $news->id ) }}">
			@csrf 

			<div class="form-group">
				<label>タイトル: </label><br />
				<input class="form-control" type="text" name="title" value="{{old('title', $news->title)}}" />
			</div>

			<div class="form-group">
				<label>概要: </label><br />
				<input class="form-control" type="text" name="description" value="{{old('description', $news->description)}}" />
			</div>

			<div class="form-group">
				<label>本文: </label><br />
				<textarea class="form-control" name="body">{{old('body', $news->body)}}</textarea>
			</div>
			
			<div class="mt-3">
				<input class="btn btn-primary" type="submit" value="保存" />
			</div>

			</form>
		</div>
	</div>
</div>
@endsection

create_formと違う点はまずformの遷移先です。

<form method="post" action="{{ url('news/edit/' . $news->id ) }}">

編集のactionをnews/edit/IDにしています。

次に各フォームの要素をold()で指定している箇所に初期値を入れていきます。

{{old('title', $news->title)}}
{{old('description', $news->description)}}
{{old('body', $news->body)}}

これは管理側のユーザー編集でも同じことを行いました。編集のためのフォームはこのやり方を良く使います。

<input class="btn btn-primary" type="submit" value="保存" />

ボタン名が作成ではなく保存となるように直したらブラウザで編集フォームを確認しましょう。

保存の処理は作成していませんが、リダイレクトは設定しているので、保存ボタンを押すと記事一覧に戻ります。


# 記事更新処理の作成

先程作成したフォームの遷移先を作成しきます。フォームの遷移先はroutes/web.phpで次のように設定しています。

Route::post('news/edit/{id}', 'user\ManageEntryController@update');

ManageEntryControllerのupdate()関数を作成していきます。ほとんどcreate()関数と同じですが、保存するNewsEntryの取得方法に注意してください。

app/Http/Controllers/user/ManageEntryController.php

class ManageEntryController extends Controller
{
	function update(Request $request, $id){

		$user = Auth::user();
		$news = $user->newsEntry()->find($id);

		if(!$news){
			return redirect("home")->withStatus("記事がありません");
		}
		/* 入力値の受け取り */
		
		$input = $request->only('title', 'description', 'body');
		
		$validator = Validator::make($input, [
			'title' => 'required|string|max:200',
			'description' => 'string|max:200',
			'body' => 'required|string'
		]);
		//バリデーション失敗
		if($validator->fails()){
			return redirect('news/edit/' . $news->id)
				->withErrors($validator)
				->withInput();
		}
		
		//バリデーション成功
		$news->title = $input["title"];
		$news->description = $input["description"];
		$news->body = $input["body"];
		$news->user_id = Auth::id();
		$news->thumbnail_url = "";
		$news->image_url = "";
		$news->save();

		return redirect("home")->withStatus("記事を更新しました");
	}

$news = $user->newsEntry()->find($id);

更新対象がnewsEntry()経由である点以外はほとんどcreate()メソッドと変わりません。

ブラウザで記事詳細を開いて編集を行って正しく更新がされているか確認してください。

更新されない場合はフォームの名前が間違っているなどのミスを確認してください。

# 記事の削除機能の作成

記事の編集フォームの下部に記事の削除フォームを追加します(+が追加箇所)。

resources/views/news/edit_form.blade.php

@extends('layouts.admin')
@section('content')
<div class="container">
	<div class="card">
		<div class="card-header">記事の編集</div>
		<div class="card-body">
			@if ($errors->any())
			<div style="color:red;">
			<ul>
				@foreach ($errors->all() as $error)
				<li>{{ $error }}</li>
				@endforeach
			</ul>
			</div>
			@endif
			<form method="post" action="{{ url('news/edit/' . $news->id ) }}">
			@csrf 
			<div class="form-group">
				<label>タイトル: </label><br />
				<input class="form-control" type="text" name="title" value="{{old('title', $news->title)}}" />
			</div>
			<div class="form-group">
				<label>概要: </label><br />
				<input class="form-control" type="text" name="description" value="{{old('description', $news->description)}}" />
			</div>
			<div class="form-group">
				<label>本文: </label><br />
				<textarea class="form-control" name="body">{{old('body', $news->body)}}</textarea>
			</div>
			
			<div class="mt-3">
				<input class="btn btn-primary" type="submit" value="保存" />
			</div>
			</form>

+			<hr />
+			<form method="post" action="{{ url('news/delete/' . $news->id ) }}">
+			@csrf 
+			<input class="btn btn-primary" type="submit" value="記事の削除" />
+			</form>

		</div>
	</div>
</div>
@endsection

スクリーンショット 2020-06-22 22.55.21

このように一つの画面に複数のフォームを設置することはよくあります。その場合はフォームの遷移先を変更する形で作ると良いでしょう。

記事の削除フォームの遷移先はroutes/web.phpで次のように設定しています。

//記事の削除
Route::post('/news/delete/{id}', 'user\ManageEntryController@delete');

ManageEntryControllerにdelete()関数を追加していきましょう。

app/Http/Controllers/user/ManageEntryController.php

class ManageEntryController extends Controller
{
    function delete($id){
		$user = Auth::user();
		$news = $user->newsEntry()->find($id);
		if(!$news){
			return redirect("home")->withStatus("記事がありません");
		}
		$news->delete();
		return redirect("home")->withStatus("記事を削除しました");
	}

記事の取り出しが編集と共通です。

記事の削除はdelete()関数で実行できます。

$news->delete();

delete()関数の呼び出しでデーターベースからレコードが削除されます。

このような削除方式を物理削除と言います。

この処理は戻せない処理なため、ユーザーに確認画面を出したり、アラートを出すことが一般的です。(今回は省略しました)

Webアプリケーションでは削除したものを取り出し可能とするため、フラグを利用した削除を使うことも一般的です。

例えばis_deletedというフラグを用意して削除する場合はdelete()ではなくsave()を使います。

$news->is_deleted = true;
$news->save();

この時、表示側は全てis_deletedの値をチェックする必要があります。このようなフラグを使った削除方法を論理削除といいます。

物理削除から論理削除へ切り替えることはデーターベースから値を取り出している箇所全てを書き換える必要があるため、アプリケーションの設計では最初に検討する必要があります。

今回で言うと「管理側記事一覧」「管理側詳細」「ユーザーページ」「ユーザーページ記事詳細」の4箇所を書き換える必要があります。

このようなお知らせアプリであれば本来は論理削除とするのが良いです。

一般的なCMSでは削除フラグだけではなく、「下書き」「公開」といった状態を持つことがあります。下書きでもどのように表示されるかの確認が出来るようになど、処理がどんどん複雑になっていきます。

今回は物理削除のままとし、このようなフラグを使った処理をきれいに書く方法については別の記事に起こしたいと思います。

ブラウザで記事編集画面から記事の削除が出来るかを確認しましょう。

スクリーンショット 2020-06-22 23.06.38


# まとめ

記事の作成、一覧、編集、削除の一連の開発がこれで完了しました。

データーベースの操作では作成、更新の「save()関数」、削除の「delete()関数」などを学んでいきました。

基礎的なWebアプリケーションはこれらの組み合わせで出来ています。

次回は記事作成、記事編集に画像のアップロード機能を追加していきます。

おつかれさまでした!


完成まで突っ走る意気込みです。サポートしていただけると非常に嬉しいです。応援よろしくお願いします。