BeginnerEngineerBlog
中の人
中の人

勝手にlaravelチュートリアル #6

公開: 2021-09-02 22:05
更新: 2023-04-06 15:04
993
laravel6.x php #6 編集 削除
今回は登録したブログの一覧ページの作成と編集機能を実装していきます。ここではif文の使い方や、テンプレートの使い回しなどを覚えましょう!
変更履歴
2021/09/12 validationのリダイレクト処理を編集しました。

こんにちは!

前回は多対多リレーションを利用してタグの登録を実装しましたね!


今回は今まで投稿したブログの一覧、編集機能を実装していこうと思います!

  1. チュートリアル説明
  2. プログラミングの準備(エディタ、dockerの用意、環境構築)
  3. 認証機能を追加してログイン機能を実装する
  4. ブログのタイトルと記事を登録する
  5. ブログの記事へタグを登録する
  6. ブログの一覧表示、編集機能の実装(今ここ)
  7. ブログの検索機能の実装
  8. ユーザーへの通知機能の実装


さて、今までブログの投稿をやってきましたが、投稿しっぱなしになってますよね。

ブログ一覧画面もなければ、ブログ編集機能もありません。

これでは誤って投稿してしまったブログを編集することができませんし、そもそもurlを直接入力しないと投稿したブログが見れないですね。


ということで、まずはブログ一覧画面を作成していこうと思います!


ブログ一覧画面を作成する


routes/web.phpを開いて以下のrouteを追加してください。

Route::get('/article/list', 'ArticleController@list')->name('article.list');

そしたら、ArticleControllerへ以下のアクションを追加してください。

//省略

public function list(Request $request)
{
    $articles = Article::all();
    return view(
        'article.list',
        [
            'articles' => $articles
        ]
    );
}

$articles = Article::all();

これはarticlesテーブルに保存されているレコード(モデル)の一覧を取得する記述になります。

それではresources/views/article/list.blade.phpを作成しましょう。

で、以下をコピペしてください。

@extends('layouts.app')
@section('content')
    @foreach ($articles as $article)
        <div style="border-bottom: solid 1px gray;">
            <div>
                user: {{$article->user->name}}
            </div>
            <div>
                title: {{$article->title}}
            </div>
        </div>
    @endforeach
@endsection

はい、controllerから渡した$articlesを@foreachで回して必要なデータを表示します。

それでは、resourcds/views/welcome.blade.phpへブログ一覧ページへのリンクを設定しましょう。

//省略
<a href="{{route('article.new')}}">記事投稿</a>
<a href="{{route('article.list')}}">ブログ一覧</a>//<-追加
//省略

はい、こんな感じですね!

それではwelcomeページを見てみましょう。


ブログ一覧へのリンクが出来上がってますね!ではクリックしてアクセスしてみましょう。


はい、こんな感じで今まで投稿した内容の一覧が表示されましたね!

それではこのブログリストのタイトルをクリックしたら、ブログの閲覧ページにアクセスできるようにリンクを設定しましょう!

@extends('layouts.app')
@section('content')
    @foreach ($articles as $article)
        <div style="border-bottom: solid 1px gray;">
            <div>
                user: {{$article->user->name}}
            </div>
            <div>
                title: <a href="{{route('article.show', ['id' => $article->id])}}">{{$article->title}}</a>//<-編集
            </div>
        </div>
    @endforeach
@endsection

はい、閲覧ページへのrouteを設定しましょう。showアクションへのリンク設定は、ブログ作成後のリダイレクトで設定(勝手にlaravelチュートリアル#4の2)しましたが、bladeでも書き方は同じですね!

では一覧ページをリロードしてみましょう。


はい、色が変わってリンクが設定されましたね。

ではクリックして閲覧ページに飛ぶか確認してみましょう。


オッケーそうですね!

これで投稿されたブログを全て閲覧できるようになりました。よかったよかった。

では、次にブログの編集機能を実装しましょう。


ブログ編集機能を実装する


それでは、先ほど作成した一覧画面で、自分の投稿したブログのみ編集できるように実装していきます。

実装していきたいんですが、まず自分の投稿したブログかどうかを判断するにはどうすればいいか考えましょう。

articleはuser_idフィールドがあるので、user_idを見て、どのuserが投稿したブログなのか判断すればよさそうですね。

では、userのidを取得するにはどうればいいかというと、ログインしていれば、ログインユーザーのidが取得できますね。

なら、ブログ投稿ページみたいに、ログインしないと一覧ページにアクセスできないようにしましょうか?

それだと、ログアウトユーザーはブログの一覧が見れないですね。

ということで、ブログ一覧ページは

  • ログアウトユーザーは、ブログの一覧が見れるけど、どのブログも編集できない(編集画面へのリンクを出さない)
  • ログインユーザーは、ブログの一覧が見れて、自分が投稿したブログのみ編集できる(編集画面へのリンクを表示する)

という条件を満たす必要がありますね!

ではその条件をプログラムミングして実現しましょう。

list.blade.phpを以下のように編集してください。

@extends('layouts.app')
@section('content')
    @foreach ($articles as $article)
        <div style="border-bottom: solid 1px gray;">
            <div>
                user: {{$article->user->name}}
            </div>
            <div>
                title: <a href="{{route('article.show', ['id' => $article->id])}}">{{$article->title}}</a>
            </div>
            ↓追加
            @if (Auth::check() && Auth::user()->id === $article->user_id)
                <a href="#">編集</a>
            @endif
        </div>
    @endforeach
@endsection

Auth::check()

これはログインしているかどうかを判断する書き方になります。

&&

これは"AND"という意味で、日本語では"かつ"に該当します。

Auth::user()->id

これは、ログインしているuserをゲットして、そのuserのidを取得する記述になりますね。(参考: 勝手にlaravelチュートリアル#4の2)

===

これは、"イコール"です。ほぼ同じ意味で"=="と書くこともできます。この"==="と"=="の違いは「型」も厳密に比較するかしないかということになります。挙動の違いはご自身で調べてみてください。今回は予期しないバグを避けるため、"==="で厳密に比較をします。

$article->user_id

これは$articleからuser_idを取得する書き方ですね。

ということで、このif文でやっていることは

  1. Auth::check()でログインしているか判定する
  2. ログインしていなければ何も表示しない
  3. ログインしていれば、ログインユーザーのidと$articleのuser_idを比較する
  4. idが同じであれば、編集リンクを表示する。
  5. idが違えば、何も表示しない

という処理をしています。

上記のリストの1,2のとおり、if文で"&&"で複数の条件式を書いた場合、最初に書いた条件式(Auth::check())でfalse(ログインしていない)と判定した場合、次の条件式の判定は行いません。
つまり、ログインしていない場合、Auth::user()->idはuserが取得できないため、エラーとなるところですが、最初の条件式でログインしているか判定しているので、エラーにはならないということです。

へーとだけ思っていれば大丈夫です!

さて、それでは、複数のuserでブログを投稿してみて、ログイン中のuserが投稿したブログだけ編集リンクが表示されるか確認してみましょう。


今私はtest3でログインしているので、投稿userがtest3のブログリストにだけ編集のリンクが表示されてますね!

これでログインユーザーが投稿したブログだけ編集画面へアクセスできるようになりますね!

そしたら、今度は編集画面を表示するアクションを定義しましょう!

まずweb.phpに以下を追加してください。

Route::get('/article/edit/{id}', 'ArticleController@edit')->name('article.edit')->middleware('auth');

はい、今回もmiddlewareでauthを指定します。編集はログインしていることが前提ですからね。

そして{id}でどのarticleを編集するかを判定します。

ではArticleControllerにeditアクションを追加しましょう!以下をコピペして追加してください。

public function edit(Request $request)
{
    $id = $request->route('id');
    $article = Article::find($id);
    if (!isset($article) || $article->user_id !== Auth::id()) {
        abort(404);
    }
    $title = $article->title;
    $content = $article->content;
    $tags = $article->tags->pluck('name')->toArray();
    return view(
        'article.new',
        [
            'id' => $id,
            'title' => $title,
            'content' => $content,
            'tag' => !empty($tags) ? implode(',', $tags) : null,
        ]
    );
}

showアクションでのバリデーションの処理と似ていますが、取得した$articleがログインユーザーが投稿したものか判定する処理を追加しています。

if (!isset($article) || $article->user_id !== Auth::id()) {

"||"これは"OR"で、日本語では"または"に該当します。
また、"!=="これはさきほどの"==="の否定の書き方で、「イコールではない」ということです。

つまり、

  • $articleが取得できない
  • または
  • $articleが取得できた場合、$articleのuser_idとログインユーザーのidを比較して、idが一致しない

場合、abort(404)でエラーページに飛ばす処理をしています。

ちなみにですが、"||"の場合、最初の条件式がfalseの場合、次の条件式が実行されます。
つまり、!isset($article)がfalse($articleがある)の場合、次の$article->user_id !== Auth::id()が実行されるので、$articleがあることが最初の条件式で証明されているため、これまたエラーにならないということですね。

そして、

$title = $article->title;
$content = $article->content;
$tags = $article->tags->pluck('name')->toArray();

これは、各パラメータを変数に格納するしています。

で、

$tags = $article->tags->pluck('name')->toArray();

この書き方ですが、

$article->tags

で、ブログに紐づくタグを取得していますが、この時、tagsにはCollectionというクラスにタグのデータが入ってきています。

で、

->pluck('name')

これは、Collectionの関数で、取得したデータのフィールド名を引数に渡すことで、そのフィールド名のみのデータを取得することができます。

で、

->toArray();

これは、取得したデータを配列に変換する関数になります。

つまり、取得したタグの名前を配列に変換して、$tagsに代入している処理になります。
へーと思っていれば大丈夫です。

で、
return view(
    'article.new',
    [
        'id' => $id,
        'title' => $title,
        'content' => $content,
        'tag' => !empty($tags) ? implode(',', $tags) : null,
    ]
);

'tag' => !empty($tags) ? implode(',', $tags) : null,

これは、比較演算子(三項演算子)といって、もし$tagsに値が入っていたら、配列を文字列に変換する。なければnullを返す書き方になります。

さて、

'article.new',

これはブログの新規作成ページのbladeですね。

あれ、新規作成?編集用のbladeを新しく作るんじゃないの?と感じる方もいるかもしれませんが、これで大丈夫です。
というのも、まず、編集画面を想像してみてください。ほぼほぼ新規作成画面と同じ感じの画面を想像しませんでした?そういうことです。ほぼほぼ同じbladeなら、使いまわしてしまいましょう!

new.blade.phpを以下のように編集してください。

@extends('layouts.app')
@section('content')
    <form action="{{route('article.create')}}" method="post">
        @csrf
        <input type="hidden" name="id" value="{{$id ?? null}}">//<-追加
        <input name="title" type="text" value="{{old('title', $title ?? null)}}">//<-編集
        <input type="text" name="tag" value="{{old('tag', $tag ?? null)}}">//<-編集
        <textarea name="content" cols="30" rows="10">{{old('content', $content ?? null)}}</textarea>//<-編集
        <input type="submit">
    </form>
@endsection

<input type="hidden" name="id" value="{{$id ?? null}}">

これは、編集したブログがなんのブログか判断するために、hiddenでarticleのidを保持するために追加しています。

{{old('title', $title ?? null)}}

old関数については#4の2で少し説明しましたが、直前(バリデーション前)に入力した値を表示してくれるだけではなく、第2引数に値を設定すると、直前に入力された値がない場合の値を設定することができます。

$title ?? null

この書き方は、変数の$titleが未定義、または、nullの場合、nullを返す書き方になります。
ちなみにこの書き方は、先ほど紹介した比較演算子と一緒で、

isset($title) ? $title : null

と書き換えることもできます。

色々と説明が長くなってよくわからなくなってしまったかと思いますが、つまり、new.blade.phpへアクセスしたとき、$titleや$tagの変数がなければ、何もフォームに表示しないし(新規作成)、あればその値をフォームに表示(編集)するということになります。
こうすれば、new.blade.phpを新規作成にも、編集にも対応できますので、使いまわすことができますね!

そしたら最後に編集画面へのリンクを設定しましょう。

list.blade.phpを以下のように編集してください。

//省略
@if (Auth::check() && Auth::user()->id === $article->user_id)
    <a href="{{route('article.edit', ['id' => $article->id])}}">編集</a>//<-編集
@endif
//省略

こんな感じですね!

ようやく編集画面へアクセスする準備が整いました。

では適当に自分が投稿したブログの編集リンクをクリックして、編集画面へアクセスしてみましょう!


はい、こんな感じで、投稿した内容がフォームに入った状態で表示できましたね!

よっしゃーあとは内容を編集して既存のブログをアップデートすることができれば、編集機能の実装が完了しますね!

ただ、このままだと、formのactionがブログ新規作成のアクションが設定されているので、この状態で送信すると新規にブログが投稿されてしまいます。

いくつか方法があるのですが、今回はArticleControllerのcreateアクションを編集して、更新処理を実装しようと思います。

createアクションを以下のように編集してください。

public function create(Request $request)
{
    $validator = Validator::make($request->all(), [
        'title' => [
            'required',
            'string',
            'max:25',
        ],
        'content' => [
            'required',
            'string',
            'max:4000',
        ],
        'tag' => [
            'nullable',
            'string',
        ],
    ]);
    $validator->validate();
    //↓ここから
    $id = $request->get('id');
    $article = Article::find($id);
    if (isset($article) && $article->user_id !== Auth::user()->id) {
        abort(404);
    }
    //↑ここまで追加
    $title = $request->get('title');
    $content = $request->get('content');

    ↓ここから
    if (isset($article)) {
        $article->title = $title;
        $article->content = $content;
        $article->save();
    } else {
        $user_id = Auth::id();//<-こっちに移動
        $article = Article::create(
            [
                'title' => $title,
                'content' => $content,
                'user_id' => $user_id,
            ]
        );
    }
        ↑ここまで編集


    $input_tag = $request->get('tag');
    if (isset($input_tag)) {
        $tag_ids = [];
        $tags = explode(',', $input_tag);
        foreach ($tags as $tag) {
            $tag = Tag::updateOrCreate(
                [
                    'name' => $tag,
                ]
            );
            $tag_ids[] = $tag->id;
        }
        $article->tags()->sync($tag_ids);
    }


    return redirect()->route('article.show', ['id' => $article->id]);
}

$id = $request->get('id');
$article = Article::find($id);
if (isset($article) && $article->user_id !== Auth::user()->id) {
    abort(404);
}

さて、最初のこちらですが、まずhiddenで送信されたidを$requestから取得して、$articleモデルを取得します。

で、もし、
$articleがあって
かつ
その$articleモデルのuser_idと、ログインuserのidが一致しなかった場合
abort(404)でエラーページに飛ばします。

if (isset($article)) {
    $article->title = $title;
    $article->content = $content;
    $article->save();
} else {
    $user_id = Auth::id();
    $article = Article::create(
        [
            'title' => $title,
            'content' => $content,
            'user_id' => $user_id,
        ]
    );
}

これは、
もし$articleモデルがあったら、

$article->title = $title;
$article->content = $content;

$articleモデルのフィールドを上書きします。

$article->title

この書き方は、$articleモデルのtitleを取得する書き方ですが、

$article->title = $title;

このように値を代入することで、そのフィールドを上書きすることができます。(ここでは保存はされません。あくまでデータだけ上書きされます。)

$article->save();

これは、文字通りセーブする書き方になります。

上書きしてセーブする。つまり、更新ということですね!


で、
もし$articleがなかったら(else)
新規作成の処理をするということです。

$user_id = Auth::id();

このuser_idを取得する処理をelse側にだけ書いたのは、更新処理にはuser_idは必要ない(取得した$articleは既にuser_idがあるため)ので、新規作成の時だけ取得するようにしました。要するに、無駄な処理を省いたということです。

そしたら、適当に投稿したブログを編集してみましょう。

はい、こんな感じで適当に編集しました。

そしたら送信しましょう。


ちゃんと送信されて保存されましたね!

では、ちゃんと編集されたか一覧ページにアクセスして、編集したブログのタイトルを確認してみましょう。



オッケーそうですね!

タイトルが編集前と後で変わっていれば編集されているということです!必要であればphpmyadminでデータを確認してみましょう!

では、新規作成もちゃんと動くか確認してみましょう。

適当にブログを新規に作成してみてください。



私はこんな感じで入力しました。

で、送信してみます。


新規に作成されましたね!

成功です!


そしたら、ついでに削除機能も追加してしまいましょう。


ブログ削除機能を実装する


ArticleControllerへ以下のアクションを追加してください。

public function delete(Request $request)
{
    $id = $request->get('id');
    $article = Article::find($id);
    if (!isset($article) || $article->user_id !== Auth::id()) {
        abort(404);
    }
    $article->delete();
    $articles = Article::all();
    return view(
        'article.list',
        [
            'articles' => $articles,
        ]
    );
}

前半はeditアクションのエラーハンドリングと一緒です。間違っても他のユーザーに削除させてはいけませんからね。

$article->delete();

データの削除の処理はこれだけです!
簡単ですね!

で、削除が終わったらブログ一覧画面を表示するようにします。

それではrouteを設定しましょう。

Route::post('/article/delete', 'ArticleController@delete')->name('article.delete')->middleware('auth');

では、ブログ一覧画面へ削除ボタンを設置しましょう。

//省略
@if (Auth::check() && Auth::user()->id === $article->user_id)
    <a href="{{route('article.edit', ['id' => $article->id])}}">編集</a>
    //↓ここから
    <form action="{{route('article.delete')}}" method="post">
        @csrf
        <button type="submit" name="id" value="{{$article->id}}">
            削除
        </button>
    </form>
    //↑ここまで追加
@endif
//省略


そしたらブログ一覧画面にアクセスしてください。


削除ボタンが表示されてますね!

では適当な記事の削除ボタンを押してみましょう!

削除できましたか?できていれば成功です!

これで、ブログの作成、編集、削除が実装できましたね!お疲れ様でした!


ということで、以上で今回の記事を終了したいと思います。

だんだんブログアプリとしての形が出来上がってきましたね!

チュートリアルもいよいよ終盤戦です!

頑張っていきましょー!🔥🕺🔥

ではまた!


0
0
0
0
通信エラーが発生しました。
似たような記事