前回、ブログ記事を新規に作成するところまでできたので、今回は既存の記事を編集できるようにしていきます。
ルーティングの変更(ルートパラメータ)
まず、URL(ルーティング)をどうするかですが、下記のように設定して、ブログ記事の新規作成と編集を区別します。
新規/編集 | URL | 備考 |
---|---|---|
新規作成 | http://{ホスト名}/admin/form | 新規作成のときは記事IDを付けない |
編集 | http://{ホスト名}/admin/form/{記事ID} | 指定した記事IDの記事を編集できるようにする |
それでは、routes/web.php
を開いて、下記のように編集します。
// URL中の値を取り出したいときは、ルートパラメータを利用する。{}で囲んだ部分を取り出すことができる
// パラメータ名末尾の `?` は、任意パラメータを表すもので、このパラメータはあっても無くても良い、ということになる
Route::get('admin/form/{article_id?}', 'AdminBlogController@form')->name('admin_form');
Route::post('admin/post', 'AdminBlogController@post')->name('admin_post');
ルートパラメータを使いました。詳細は日本語ドキュメントを参照してください。
モデルの編集
app/Models/Article.php
を開き、下記2つのメソッドを追加します。
<?php
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
...(中略)...
/**
* post_date のアクセサ YYYY/MM/DD のフォーマットにする
*
* @return string
*/
public function getPostDateTextAttribute()
{
// アクセサを定義しておくと $article->post_date_text という
// プロパティにアクセスしたときに、このメソッドの返り値が返る
// 'post_date' は $dates プロパティに設定してあるので、自動的に Carbon インスタンスとなる
return $this->post_date->format('Y/m/d');
}
/**
* post_date のミューテタ YYYY-MM-DD のフォーマットでセットする
*
* @param $value
*/
public function setPostDateAttribute($value)
{
// ミューテタはプロパティに設定しようとする値を受け取って加工する
// そして加工したものを Eloquent モデルの $attributes プロパティに設定する
// 例えば $article->post_date = '2018/07/07' とすると、
// このメソッドが自動的に呼び出され、引数 $value には '2018/07/07' が渡されます
// 今回はDBに入れることのできる YYYY-MM-DD のフォーマットにする
$post_date = new Carbon($value);
$this->attributes['post_date'] = $post_date->format('Y-m-d');
}
}
アクセサとミューテタは、モデルの属性値を取得するときに値を加工して渡したり(アクセサ)、モデルの属性値を設定するときに、受け取った値を加工して設定したり(ミューテタ)するときに利用します。
post_date
は日付ミューテタとして設定したので、このまま利用すると自動的に Carbon インスタンスに変換され、時・分・秒まで表示されてしまうという不都合が生じてしまいます。そこで新たにpost_date_text
というプロパティを追加して、ここにアクセスすれば post_date
の値を YYYY/MM/DD
のフォーマットで返すように、アクセサを使って定義しておきます。
これで、フォームで post_date
の値が YYYY/MM/DD
の形式で表示されるようになりましたが、この形式のままでは DB に入れようとするとエラーになってしまいます。そこでミューテタを定義して、YYYY-MM-DD
というフォーマットに変換して、DB に入れられるようにしておきます。これで、日付として認識できる文字列であればDB(post_dateカラム)にデータを入れられるようになりました。
コントローラーの編集(フォームの表示)
コントローラの form メソッドを改修して、DBから記事データを読み出し、読み出したデータがフォームに表示されるようにします。app/Http/Controllers/AdminBlogController.php
を開き、formメソッドを下記のように編集します。
/**
* ブログ記事入力フォーム
*
* @param int $article_id 記事ID
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function form(int $article_id = null)
{
// メソッドの引数に指定すれば、ルートパラメータを取得できる
// Eloquent モデルはクエリビルダとしても動作するので find メソッドで記事データを取得
// 返り値は null か App\Models\Article Object
$article = $this->article->find($article_id);
// 記事データがあれば toArray メソッドで配列にしておき、フォーマットした post_date を入れる
$input = [];
if ($article) {
$input = $article->toArray();
$input['post_date'] = $article->post_date_text;
} else {
$article_id = null;
}
// old ヘルパーを使うと、直前のリクエストのフラッシュデータを取得できる
// ここではバリデートエラーとなったときに、入力していた値を old ヘルパーで取得する
// DBから取得した値よりも優先して表示するため、array_merge の第二引数に設定する
$input = array_merge($input, old());
// View テンプレートへ値を渡すときは、第二引数に連想配列を設定する
// View テンプレートでは 連想配列のキー名で値を取り出せる
// return view('admin_blog.form', ['input' => $input, 'article_id' => $article_id]);
// compact 関数を使うと便利
return view('admin_blog.form', compact('input', 'article_id'));
}
ここで少し対応が必要なのは、既存記事編集時、通常の画面遷移と、バリデートエラーになってフォーム画面に遷移してくる2パターンが存在するので、どちらにも対応できるような作りにしておく必要があります。画面が表示されたときに、予めフォームに入っていて欲しい値は、通常遷移の場合、DBから取り出した値、バリデートエラーで遷移してきた場合は、元々フォームに入力していた値です。
これをどのように対応しているかというと、DBから既存記事の内容を読み出し、バリデートエラーになる直前に入力していた内容も old ヘルパーで読み出し、それをマージすることで対応しています。
Laravel には old ヘルパー以外にも便利なヘルパーがたくさんあります。詳しくは日本語ドキュメントを参照してください。
新規記事作成時、つまり $article_id = null
のとき、エラーにならずに処理が完了することも、ソースを追って確認しておいてください。
View テンプレートの編集
resources/views/admin_blog/form.blade.php
のフォーム部分を下記のように編集して、取得した記事データを表示します。
<form method="POST" action="{{ route('admin_post') }}">
<div class="form-group">
<label>日付</label>
{{--{{$variable or 'Default'}} は {{isset($variable) ? $variable : 'Default'}} と同じ意味で、変数があるかどうかわからないときに便利です--}}
<input class="form-control" name="post_date" size="20" value="{{ $input['post_date'] or null }}" placeholder="日付を入力して下さい。">
</div>
<div class="form-group">
<label>タイトル</label>
<input class="form-control" name="title" value="{{ $input['title'] or null }}" placeholder="タイトルを入力して下さい。">
</div>
<div class="form-group">
<label>本文</label>
<textarea class="form-control" rows="15" name="body" placeholder="本文を入力してください。">{{ $input['body'] or null }}</textarea>
</div>
<input type="submit" class="btn btn-primary btn-sm" value="送信">
{{--article_id があるか無いかで新規作成か既存編集かを区別する--}}
<input type="hidden" name="article_id" value="{{ $article_id }}">
{{--CSRFトークンが生成される--}}
{{ csrf_field() }}
</form>
記事を新規作成する場合は、$input['title']
のようなデータは無く、$input
の配列には title
というキーすら存在しません。そこで、{{ $input['title'] or null }}
と書くことによって、エラーとなることを防ぎ null を表示させています(nullを表示という表現もなんか変だな)。
ここまでできたら、http://{ホスト名}/admin/form/{任意の記事ID}
にアクセスして、フォームに記事のデータが入力された状態で表示されることや、バリデートエラー時に、今まで入力していた値がフォームに残っていることを確認してください。
リクエストクラスの編集
hidden の値ですが article_id がパラメータとして渡ってくるようになったので、バリデーションを追加します。app/Http/Requests/AdminBlogRequest.php
を開いて、rules()
メソッドとmessages()
メソッドを下記のように編集します。
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
// バリデーションルールはここに追加する
// 項目名 => ルールという形式で、ルールが複数ある場合は '|' で区切る
return [
'article_id' => 'integer|nullable', // 整数・null でもOK
'post_date' => 'required|date', // 必須・日付
'title' => 'required|string|max:255', // 必須・文字列・最大値(10000文字まで)
'body' => 'required|string|max:10000', // 必須・文字列・最大値(10000文字まで)
];
}
public function messages()
{
// 表示されるバリデートエラーメッセージを編集したい場合は、ここに追加する
// 項目名.ルール => メッセージという形式で書く
// プレースホルダーを使うこともできる
// 下記の例では :max の部分にそれぞれ設定した値(255, 10000)が入る
return [
'article_id.integer' => '記事IDは整数でなければなりません',
'post_date.required' => '日付は必須です',
'post_date.date' => '日付は日付形式で入力してください',
'title.required' => 'タイトルは必須です',
'title.string' => 'タイトルは文字列を入力してください',
'title.max' => 'タイトルは:max文字以内で入力してください',
'body.required' => '本文は必須です',
'body.string' => '本文は文字列を入力してください',
'body.max' => '本文は:max文字以内で入力してください',
];
}
追加したのは、rules()
messages()
両メソッド共に、article_id の部分です。記事の新規作成のときは article_id を必要としないので、required
は設定していません。ただし、このままだと、新規作成のとき article_id の値が null で渡り、null は整数ではないので、バリデーションに引っかかってしまいます。そこで nullable
をつけて、null でも OK だよ、という設定にしています。
詳しくは日本語ドキュメントを参照してください。
コントローラーの編集(記事の保存)
再びコントローラークラスを開いて、ブログ記事保存処理の post メソッドを編集し、記事の新規作成・編集のどちらにも対応できるように改修します。app/Http/Controllers/AdminBlogController.php
を開いて、post メソッドを下記のように編集します。
/**
* ブログ記事保存処理
*
* @param AdminBlogRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function post(AdminBlogRequest $request)
{
// 入力値の取得
$input = $request->input();
// array_get ヘルパは配列から指定されたキーの値を取り出すメソッド
// 指定したキーが存在しない場合のデフォルト値を第三引数に設定できる
// 指定したキーが存在しなくても、エラーにならずデフォルト値が返るのが便利
$article_id = array_get($input, 'article_id');
// Eloquent モデルから利用できる updateOrCreate メソッドは、第一引数の値でDBを検索し
// レコードが見つかったら第二引数の値でそのレコードを更新、見つからなかったら新規作成します
// ここでは article_id でレコードを検索し、第二引数の入力値でレコードを更新、または新規作成しています
$article = $this->article->updateOrCreate(compact('article_id'), $input);
// フォーム画面にリダイレクト。その際、route メソッドの第二引数にパラメータを指定できる
return redirect()
->route('admin_form', ['article_id' => $article->article_id])
->with('status', '記事を保存しました');
}
今回はDBにデータを保存するために、updateOrCreate()
を使いました。詳しくは日本語ドキュメントを参照してください。
ここまでできたら、実際に動作確認をして、既存記事が編集できること、新規記事が作成できることを確認してください。