Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

Laravel5 チュートリアル ブログもどきを作る(4) ブログ記事を編集する

More than 1 year has passed since last update.

前回、ブログ記事を新規に作成するところまでできたので、今回は既存の記事を編集できるようにしていきます。

ルーティングの変更(ルートパラメータ)

まず、URL(ルーティング)をどうするかですが、下記のように設定して、ブログ記事の新規作成と編集を区別します。

新規/編集 URL 備考
新規作成 http://{ホスト名}/admin/form 新規作成のときは記事IDを付けない
編集 http://{ホスト名}/admin/form/{記事ID} 指定した記事IDの記事を編集できるようにする

それでは、routes/web.php を開いて、下記のように編集します。

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つのメソッドを追加します。

app/Models/Article.php
<?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メソッドを下記のように編集します。

app/Http/Controllers/AdminBlogController.php
    /**
     * ブログ記事入力フォーム
     *
     * @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 のフォーム部分を下記のように編集して、取得した記事データを表示します。

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()メソッドを下記のように編集します。

app/Http/Requests/AdminBlogRequest.php
    /**
     * 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 メソッドを下記のように編集します。

app/Http/Controllers/AdminBlogController.php
    /**
     * ブログ記事保存処理
     *
     * @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()を使いました。詳しくは日本語ドキュメントを参照してください。

ここまでできたら、実際に動作確認をして、既存記事が編集できること、新規記事が作成できることを確認してください。

次回は、ブログ記事を削除する処理を実装していきます。

参考資料

Laravel 日本語ドキュメント

プログラムソース(github)

yumgoo17
PHP歴10年超、Laravel歴2〜3年(うち実務経験は1年)の Laravel に魅せられたフリーランスエンジニア(30代後半)です。大学と大学院時代は人工知能の研究をしていました。インフラ構築から、PG、設計、PLまで大体の経験があります。活動地域は関東で、絶賛お仕事募集中。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away