未経験からエンジニア転職をするための最強ロードマップロードマップ

【Laravel教材⑥】Laravelでブログシステム構築!〜記事編集機能実装編〜

ファド

こんにちは!
PHPのLaravelやJavaScriptでWeb開発をしているフリラーンスエンジニアのファドと申します!

こちらの記事はLaravel教材の第6回目の記事になります。

その他のLaravel教材を学習したい方は下記リンクから直接教材へ飛ぶことができます。

目次

記事編集

記事詳細画面表示と記事投稿ができるようになったので、次は投稿した記事の編集をする機能の作成をしていきます。
記事編集機能は記事詳細画面表示と記事投稿を合わせたような機能になっているので、それらの実装方法を思い出しながら実装してみましょう!

記事編集の流れは下記のようになっています。

  1. 記事詳細からIDに紐付けたリンクを作成して編集フォームへ遷移
  2. 編集フォームからPOSTで編集内容を送る
  3. コントローラーとモデルを使用してデータを編集する
  4. フォームリクエストを使用してバリデーション
  5. エラー処理

データの更新メソッド

リンクの作成方法やバリデーション、エラー処理などについてはこれまでの機能実装時に説明したので、まだ説明していないデータ更新のメソッドについて説明します。

実は記事投稿用メソッドの際に2つの方法を説明したと思いますが、データの更新にも2つの方法があります。
それがfillメソッド + saveメソッドupdateメソッドです。

まずはfillメソッド + saveメソッドの使い方から説明します。
実は記事投稿用メソッドを紹介した時にfillメソッド + saveメソッドの使い方も紹介しています。
その時の内容とほとんど一緒ですが、今回は記事編集なので編集するための記事の取得が必要になります。

使い方は下記の通りです。

$article = Article::find(1);

$article->fill([
    'title' => 'タイトル',
    'content' => 'コンテンツ',
]);

$article->save();

まず、編集したい記事の情報をfindメソッドを使用して取得します。
今回は例として記事IDが1の記事を取得しています。

次にfillメソッドを使用して、編集したカラム名と編集内容を=>で結びつけます。
今回は例としてタイトルコンテンツとテキストをそのまま入力していますが、実際はフォームで入力された値をこちらに設定します。

そして最後にsaveメソッドを使用して保存します。

updateメソッドの使い方はfillメソッド + saveメソッドとほとんど同じです。
使い方は下記の通りです。

$article = Article::find(1);

$article->update([
    'title' => 'タイトル',
    'content' => 'コンテンツ'
]);

イメージとしては、fillメソッド + saveメソッドを一気に行えるのがupdateメソッドです。

fillメソッド + saveメソッドupdateメソッドの違いは、fillメソッド + saveメソッドが差分更新なのに対して、updateメソッドはメソッドが実行されると差分があるかないか関係なく毎回更新します。

データベースへ負荷をなるべくかけないためにも、データベースに登録されている値と登録しようとしている値に差分があったときのみ更新するfillメソッド + saveメソッドを使用するのが一般的です。

実装

fillメソッド + saveメソッド以外の内容は記事詳細画面表示機能と記事投稿機能とほぼ同じなので、さっそく実装してみましょう。

記事詳細からIDに紐付けたリンクを作成して編集フォームへ遷移

まずは編集画面表示と編集処理用のルートを作成していきます。

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/edit/{id}', [ArticleController::class, 'editArticle'])->name('editArticle'); // ここを追加

// 記事編集処理
Route::post('/article/update/{id}', [ArticleController::class,'updateArticle'])->name('updateArticle'); // ここを追加

// 投稿詳細を表示
Route::get('/article/{id}', [ArticleController::class, 'showArticle'])->name('showArticle');

追加したのは下記の2行です。

// 記事編集フォームを表示
Route::get('/article/edit/{id}', [ArticleController::class, 'editArticle'])->name('editArticle'); // ここを追加

// 記事編集処理
Route::post('/article/update/{id}', [ArticleController::class,'updateArticle'])->name('updateArticle'); // ここを追加

記事編集フォームを表示させるためには記事IDが必要なので、/article/edit/{id}としています。
また、記事編集処理ではフォームを使用してPOSTで内容を送信しているのでRoute::postとしています。

次にルートで作成した記事編集画面表示のためのeditArticleメソッドを作成します。

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::debug($e);

            // エラー画面遷移
            abort(500);
        }

        return redirect()->route('showArticles');
    }

    /**
     * 記事編集画面を表示する
     */
    public function editArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        return view('articles.edit', compact('article'));
    }

    /**
     * 記事詳細を表示する
     */
    public function showArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        return view('articles.detail', compact('article'));
    }
}

追加したコードは下記の通りです。

/**
 * 記事編集画面を表示する
 */
public function editArticle($id)
{
    // 渡されてきた記事IDのデータを取得
    $article = Article::find($id);

    return view('articles.edit', compact('article'));
}

記事詳細画面を表示させる時とほぼ同じコードですね!
違うのは遷移させるのがedit.blade.phpになっているだけです。

次に、記事詳細画面から編集画面に遷移できるようにボタンを作成していきましょう。

first-app/resources/views/articles/detail.blade.phpを下記の通り編集してください。

@extends('layout')

@section('title')
    記事詳細
@endsection

@section('content')
    <h2>{{ $article->title }}</h2>
    <div class="d-flex">
        <span class="mr-2">作成日:{{ $article->created_at }}</span>
        <span>更新日:{{ $article->updated_at }}</span>
    </div>
    <p class="mt-4">{{ $article->content }}</p>
    <a href="{{ route('showArticles') }}" class="mt-3 btn btn-secondary">戻る</a>
    <a href="{{ route('editArticle', $article->id) }}" class="mt-3 btn btn-primary">編集</a>
@endsection

追加したのは下記コードです。

<a href="{{ route('editArticle', $article->id) }}" class="mt-3 btn btn-primary">編集</a>

これで編集ボタンをクリックすると記事編集画面へ遷移し、ArticleコントローラーeditArticleメソッドへ引数として記事IDを渡すことができます。

編集フォームからPOSTで編集内容を送る

次に編集フォームからPOSTで編集内容を送信するためにedit.blade.phpを作成していきます。
first-app/resources/views/articlesディレクトリにedit.blade.phpをコマンドかVSCodeで作成してください。
コマンドで作成する場合は、ターミナルで下記コマンドをfirst-appディレクトリ上で実行してください。

$ touch resources/views/articles/edit.blade.php

作成したedit.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="{{ route('updateArticle', $article->id) }}">
                @csrf
                <div class="form-group">
                    <label for="title">
                        タイトル
                    </label>
                    <input id="title" name="title" class="form-control" value="{{ old('title', $article->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', $article->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('showArticle', $article->id) }}">
                        キャンセル
                    </a>
                    <button type="submit" class="btn btn-primary">
                        更新
                    </button>
                </div>
            </form>
        </div>
    </div>
@endsection

http://localhost/article/edit/1へアクセスして、下記画像のように表示されればOKです!

今回は例として記事IDが1の記事の編集画面を表示しています。

編集フォームの内容はほとんど登録フォームと同じですが、変更した部分について解説します。

まず、ページタイトルを記事編集、ページの見出しを記事編集フォームとしています。

次に、フォームで送信する先をaction属性の属性値として、編集処理のメソッドであるupdateArticleメソッドに指定しました。
また、記事IDを渡すためにrouteメソッドの第二引数に$article->idと指定しています。

次にinput要素valuetextarea要素内容をそれぞれ編集しています。
編集した理由は、データベースに保存されている記事タイトルと記事内容のデータをそれぞれのフォームに反映させるためです。
oldメソッドの第二引数に値を設定することで、編集フォームが表示された直後はデフォルト値としてデータベースに保存されたデータをフォームに表示してくれます。

つまり、記事IDが1の編集画面を表示すると、下記画像のようにデータベースに保存されている記事ID1の記事タイトルと記事内容がフォームに表示されるということです。

一方でバリデーションに引っかかった場合はデフォルトの値ではなく、バリデーションで引っかかった時に入力された内容がフォームに反映されて、さらにバリデーションのエラーメッセージがその下に赤文字で出力されるようになります。

次に、キャンセルボタンの遷移先を記事詳細画面にするためにhref属性の属性値を記事詳細画面を表示するルート名showArticleに変更しています。
もちろん記事詳細画面への遷移には、どの記事の詳細画面なのかを指定しなければならないので、routeメソッドの第二引数に記事IDを渡しています。

最後に投稿ボタンを編集用に更新ボタンにして記事編集フォームの実装は終わりです。

コントローラーとモデルを使用してデータを編集する

編集フォームからPOSTで送信されたデータをデータベースに保存するための処理を実装していきましょう。
すでにルートには編集処理用のルーティングをしているので、そのルーティングに従ってArticleコントローラーupdateArticleメソッドを作成していきましょう。

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::debug($e);

            // エラー画面遷移
            abort(500);
        }

        return redirect()->route('showArticles');
    }

    /**
     * 記事編集画面を表示する
     */
    public function editArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        return view('articles.edit', compact('article'));
    }

    /**
     * 記事編集処理
     */
    public function updateArticle(Request $request, $id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        // 編集する内容をfillメソッドを使用して記述
        $article->fill([
            'title' => $request->title,
            'content' => $request->content,
        ]);

        // 保存処理
        $article->save();

        return redirect()->route('showArticle', $article->id);
    }

    /**
     * 記事詳細を表示する
     */
    public function showArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        return view('articles.detail', compact('article'));
    }
}

追加したコードは下記の通りです。

/**
 * 記事編集処理
 */
public function updateArticle(Request $request, $id)
{
    // 渡されてきた記事IDのデータを取得
    $article = Article::find($id);

    // 編集する内容をfillメソッドを使用して記述
    $article->fill([
        'title' => $request->title,
        'content' => $request->content,
    ]);

    // 保存処理
    $article->save();

    return redirect()->route('showArticle', $article->id);
}

記事登録時の時と同じ様に、POSTで送信された値はupdateArticleメソッドの引数でRequest $requestと記述することで受け取る事ができます。

また、今回は編集する記事IDもフォームから送られてきているので、$idと記述して引数を受け取ります。
これでPOSTで送信されてきた値と記事IDをupdateArticleメソッド内で使用できるようになりました。

次は渡されてきた記事IDを使用して記事データを取得していきます。
取得するにはいつも通りfindメソッドを使用するだけです。

先ほど説明したfillメソッド + saveメソッドを使用して編集処理をしています。
$request->titleと記述することでフォームで入力したタイトルを取得することができます。
また、$request->contentと記述することでフォームで入力した内容を取得することができます。
これも記事登録処理と同じですね。

最後にリダイレクト先を記事詳細画面に指定すれば終わりです。
記事詳細画面を表示するためには記事IDが必要になるので、routeメソッドの第二引数に$article->idとして渡しておきましょう。

これで投稿を編集することができました。
しっかり記事が編集できるかの動作確認は各自行ってください!

フォームリクエストを使用してバリデーション

次にフォームリクエストを使用してバリデーションを実装しましょう。

今回は記事編集用のバリデーションなのでArticleUpdateRequest.phpを作成しましょう。
それでは、ターミナルで下記コマンドをfirst-appディレクトリ上で実行してください。

$ ./vendor/bin/sail php artisan make:request ArticleUpdateRequest

first-app/app/Http/Requests配下にArticleUpdateRequest.phpが作成されていればOKです。

それではfirst-app/app/Http/Requests/ArticleUpdateRequest.phpを下記の通り編集してください。

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class ArticleUpdateRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true; // 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' => '内容',
        ];
    }
}

内容はArticleStoreRequest.phpと全く同じなので、説明は省略します。

次にArticleコントローラーupdateArticleメソッドArticleUpdateRequestのバリデーションを反映させましょう。

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 App\Http\Requests\ArticleUpdateRequest; // ここを追加
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::debug($e);

            // エラー画面遷移
            abort(500);
        }

        return redirect()->route('showArticles');
    }

    /**
     * 記事編集画面を表示する
     */
    public function editArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        return view('articles.edit', compact('article'));
    }

    /**
     * 記事編集処理
     */
    public function updateArticle(ArticleUpdateRequest $request, $id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        // 編集する内容をfillメソッドを使用して記述
        $article->fill([
            'title' => $request->title,
            'content' => $request->content,
        ]);

        // 保存処理
        $article->save();

        return redirect()->route('showArticle', $article->id);
    }

    /**
     * 記事詳細を表示する
     */
    public function showArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        return view('articles.detail', compact('article'));
    }
}

まずはArticleUpdateRequestArticleコントローラー内で使用できるように、use文を使用して下記のように記述しましょう。
use App\Http\Requests\ArticleUpdateRequest;

次にupdateArticleメソッドの第一引数をRequest $requestからArticleUpdateRequest $requestに変更します。

これで記事編集時にArticleUpdateRequest.phpで設定したバリデーションが実行されるので、記事編集フォームで何も入力しないで更新ボタンをクリックしたり、タイトルを100文字以上にすると、エラーメッセージが出力されるようになりました。

エラー処理

最後にエラー処理を実装しておきましょう。

実装する内容は下記の通りです。

  1. try-catch文
  2. トランザクション
  3. ログ出力
  4. エラー画面への遷移

これも登録処理とほとんど変わりません。

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 App\Http\Requests\ArticleUpdateRequest;
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::debug($e);

            // エラー画面遷移
            abort(500);
        }

        return redirect()->route('showArticles');
    }

    /**
     * 記事編集画面を表示する
     */
    public function editArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        return view('articles.edit', compact('article'));
    }

    /**
     * 記事編集処理
     */
    public function updateArticle(ArticleUpdateRequest $request, $id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        // トランザクション開始
        DB::beginTransaction();
        try {
            // 編集する内容をfillメソッドを使用して記述
            $article->fill([
                'title' => $request->title,
                'content' => $request->content,
            ]);

            // 保存処理
            $article->save();

            // トランザクションコミット
            DB::commit();
        } catch(\Exception $e) {
            // トランザクションロールバック
            DB::rollBack();

            // ログ出力
            Log::debug($e);

            // エラー画面遷移
            abort(500);
        }

        return redirect()->route('showArticle', $article->id);
    }

    /**
     * 記事詳細を表示する
     */
    public function showArticle($id)
    {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        return view('articles.detail', compact('article'));
    }
}

変更したコードは下記の通りです。

/**
 * 記事編集処理
 */
public function updateArticle(ArticleUpdateRequest $request, $id)
{
    // トランザクション開始
    DB::beginTransaction();
    try {
        // 渡されてきた記事IDのデータを取得
        $article = Article::find($id);

        // 編集する内容をfillメソッドを使用して記述
        $article->fill([
            'title' => $request->title,
            'content' => $request->content,
        ]);

        // 保存処理
        $article->save();

        // トランザクションコミット
        DB::commit();
    } catch(\Exception $e) {
        // トランザクションロールバック
        DB::rollBack();

        // ログ出力
        Log::debug($e);

        // エラー画面遷移
        abort(500);
    }

    return redirect()->route('showArticle', $article->id);
}

実装内容自体は記事登録時と同じですね!
もしわからない箇所がある場合は、記事登録部分を読み直してください!

★検索ワード
・Laravel 編集機能実装
・Laravel fill save update

次の教材

次の教材は下記から簡単に飛ぶことができます!
引き続きプログラミングを楽しんでいきましょう!

あわせて読みたい
【Laravel教材⑦】Laravelでブログシステム構築!〜記事削除機能実装編〜 こちらの記事はLaravel教材の第7回目の記事になります。 その他のLaravel教材を学習したい方は下記リンクから直接教材へ飛ぶことができます。 【記事削除】 ここまで記...

プログラミング学習サポートについて

「独学で挫折した。。。」

「一人でのプログラミング学習がしんどい。。。」

「未経験からエンジニア転職をしたいけど何をしたら良いかわからない。。。」

このような悩みをお持ちの方向けに、本教材作成者のファドがMENTAという学習サイトにてあなたのプログラミング学習とエンジニア転職を徹底サポートいたします!

サポート価格はなんと1日あたりたったの約300円!

教材で分からない箇所のサポートはもちろんのこと、本サイトで公開しているすべての課題の解答も公開しております。
また、MENTAで学習を終わらせていただいた方限定で懇意にしていただいている企業さんを紹介することもあります!

なお、サポート内容の詳細は下記の通りです。

  • 目標設定
  • マインドセット
  • オリジナル教材見放題
  • オリジナル課題見放題
  • オリジナル課題の解答見放題
  • 課題コードレビュー
  • 教材への無制限質問
  • 課題への無制限質問
  • ポートフォリオ作成アドバイス
  • 褒めのコーチング

いくつかのプランを用意させていただいておりますので、下記より一度ご覧ください!

あわせて読みたい
【プログラミング学習】1日あたりたったの「300円」のみでプログラミングを学び、最短で転職or稼ぐ!|【ME... プログラミングを学びたいすべての方へこれからの時代は格安でプログラミングを学び、最短で稼げ自己紹介はじめまして!この度はプランをご覧いただき、誠にありがとうござ...

コメント

    この記事が気に入ったら
    フォローしてね!

    よかったらシェアしてね!
    • URLをコピーしました!

    コメント

    コメントする

    目次