【11日目】Laravelで掲示板的なものを作る【作曲の補助ツールを作るまでの日記】

プログラミング

2023年7月25日~2023年7月26日

昨日は1日中無理な日だったので、今日からMVCと3層のアーキテクチャに則すことを考えたWebアプリを作る……といっても1日で作れるくらいの規模がいい。

無難に掲示板とかがいいのかなぁ。
掲示板式でChatGPTとの会話を記録するWebアプリにでもしようか。

流れ

マジで実務なんてしたことないので、よくありそうな

  1. 要件定義
  2. 設計
  3. 実装

という流れでやってみよう。

おままごとみたいな感じになっちゃうかもだけど、とりあえずやってみよう。

要件定義

私のおおまかな要望

  • 今回は通常の掲示板を作ろうと思ったけど、掲示板だと話相手がいないのでChatGPTと会話した記録を残す掲示板にする。
  • その為、私→ChatGPT→私→…→私→ChatGPTという感じで完全1ターン制で入力を入れ替えていく方式にしたい。
  • Googleの検索結果みたいに、タイトルと概要を付けられるようにする。
  • 掲示板でのタイトル、概要の検索機能が欲しい。

画面構成

画面構成的には、「メイン画面」「閲覧画面」「新規追加画面」「編集画面」に分ける。

メイン画面は掲示板のスレッド一覧を見る所。検索窓と新規追加ボタンがあり、スレッドの名前を押すと画面遷移。

閲覧画面はChatGPTと会話した記録を閲覧することができる所。メイン画面に戻るボタンと編集ボタンがあり、各ボタンを押すことで画面遷移する。

新規追加画面はメイン画面からのみ飛べる、スレッドを建てる所。ここにChatGPTの会話内容をコピペすることで、自動でユーザーとChatGPTに分けてくれる。
例えば、以下のような会話をコピペすると

「User

おはよう!

ChatGPT

おはようございます!初音ミクとしてあなたと会話できること、とても嬉しいです♪ どんなお話でも聞かせてくださいね。私はいつでも話し相手になっていますよ!

User

2 / 2

“初音ミクとして”とはどういうことでしょうか? あなたは初音ミクではないのですか?

ChatGPT

申し訳ありません、初音ミクではありません。私は初音ミクというキャラクターのように会話をすることができるAIです。初音ミクは日本のバーチャルシンガーで、私は彼女のような会話スタイルでお話しできるプログラムとして設計されています。ですから、初音ミクのような言葉遣いであなたと会話することができます!どんなお話でも聞かせてくださいね。

こんな感じでコピペできる。UserとChatGPTという行が追加されるので、一応それを使って区別できるはず。ただ、会話を分岐させたときの2/2とかもコピペされちゃうのでそこら辺は問題。

編集画面は一度作成したスレッドの削除や文字の編集ができる所。こちらは閲覧画面からのみ行けるようにする。

とすると、DBはどうなるんだろう。

データベースの設計

マジでここら辺はわかんないなぁ。

ユーザー機能は無いので、記事を保存するDBだけあれば問題なさそうだけど、どうなんだろう。

もし、記事を保存するDBだけであれば、

  1. 記事ID→一意のID(変更不可)
  2. 記事タイトル→ユーザーの付けた好きなID(変更可)
  3. 記事概要→ユーザーの付けた好きな概要(変更可)
  4. 記事本文→二次配列で[[user,本文],[ChatGPT,本文]]みたいな感じで保存?(変更可)

になる?

恐らく、拡張性を考えると記事本文はこのテーブルから分離させて別で管理した方が使い勝手が良さそう。でも、今回はお手軽回だし経験も兼ねて一回1個のテーブルで完結させようとしてみる。

とりあえず考えるのはこんなもんか?

設計に行ってみよう。

設計

設計って何するの? 
ここでもうコントローラとかルータとかビューの仕組みを決めとく感じ?

とりあえず、3層のアーキテクチャに則ると、ビジネスロジックから決めるのがいいのかなと思う。今回のビジネスロジックなところは、コピペした記事本文を二次配列に直す所かなぁ……ということはこの処理をモデルに書けばいいのか。

二次配列にさえできればあとはViewでどう映すか、的な話になるのかな。

こういう時、流石にコピペした本文を処理する中核的なモデルと、DBからデータ引っ張ってくるモデルは分けた方がいいよね?
あとは適当にルータとコントローラとビューを弄ればいけそう。

ちょっと実践経験が無さ過ぎてなんもわからんここらへん。

実装してみよう!

めっちゃ適当だけど、一回やってみよう。
手順はPaiza講座公式ドキュメント、ChatGPTの助けを元に進めている。

まず、SSH接続しようとしたんだけど、VM側のIPが変わってて少しだけ手こづっちゃった。

アプリケーションフォルダの作成

まず、アプリケーションフォルダを作る。

“laravel new example-app”

で現在位置のディレクトリにアプリフォルダを作れる。今回はChatGPT_Boardという名前にする。

DBの作成

マイグレーションでDBの作成は出来ないので、MySQLでDBを事前に作成しておく。
また、日本語を入れる場合、文字化け防止でエンコードの設定をutf8mb4にしておいた方がいいっぽいのでそれもしておく。

まず、MySQLが稼働してるかの確認。
“sudo systemctl status mysqld”


確認出来たら‘mysql -u root -p’でrootにログイン。
「-u ユーザー名」でユーザ―の指定で、-pはパスワードを設定している時だけつける。

ログインして“show variables like “chara%”;”でエンコード設定を確認。

chara%は正規表現で、charaがエンコード的な意味。

見てみた感じ、databaseがutf8mb4になっているので問題なさそう。
初期から設定されてたのかこれ。

なら、DBを作成する。

“create database DB名”でDBの作成。
DB名はアプリ名と全く同じのでいいかな?

おk

.envの設定

ここら辺もやっておかないといけない。

DB関連はこんな感じで設定すればいいと思う。

パスワードは自身が設定したものを。

gitに上げてホストPCと共有

前と同じようにgitでホストPCと共有しておこう。

gitignoreとかreadme.mdとかは初期からあるので、そのままディレクトリに移って
“git init”

git対象をこのディレクトリ以下全てにしたいので、
“git add -A”

とりあえずcommit
‘git commit -m “Hello ChatGPTBoard”’

ここで、githubに移って、gitignoreとかreadmeのない空のリポジトリを作成しておく。

“git remote add origin リポジトリのURL”
で、originというリモートリポジトリをgithubにホストしてもらう。

“git push -u origin main”
でpushと、-uを指定することで毎回origin mainを指定しなくてもcommitとかpullができるように。

おk

次にホストPCでクローンする。
前と同じように自分で作りたいディレクトリに移ってcmdから
“git clone 自分のリポジトリURL”
でおk。

お~! 2回目だから滅茶苦茶スムーズにいけた

作っていく

といっても、マジで何したらいいのかわかんないので、とりあえずDBとやり取りするためのEloquentモデルをmake modelで作成してみる。

make modelについてはこちらがわかりやすい。
Qiita記事を見た感じ、factory以外はあると良さそう。

また、色々考えたんだけど、ChatGPTの内容をコピペするのは技術的に1日以上かかっちゃいそうだから、普通に掲示板っぽく1メッセージずつ入れていく方式にする。
あと、DBに二次配列は良くないみたい。基本1次元で、工夫して順序を決めるっぽいね。

DBのテーブルもこの場合複数あった方が良さそうなので、複数作成する。

ChatGPTに相談したところ、ThreadsテーブルとPostテーブルで分けた方がいいと言われた。

つまり、スレッドテーブルは

  • スレッドID
  • タイトル
  • 概要
  • 作成日

を保存し、

ポストテーブルは

  • ポストID
  • ロール(ChatGPTかユーザーか)
  • 本文
  • スレッドID
  • 投稿日

を保存する。

スレッドIDでポストテーブルと紐づけて、投稿日で順序を決めるという感じ。

なるほどねぇ。

ちょっとポストテーブルだとわかりにくいので、メッセージテーブルという名前にしようか。

make model

make modelでモデルファイルを作るけど、コントローラはどっちも必要だよね? どっちもCRUDするもんね? ファクトリーは簡単にDBテストが出来るらしいんだけど、ちょっとわからないのでスルー。

ってことでモデルファイル+マイグレーションファイル+コントローラファイルを一気に作る。

“php artisan make:model Thread -m -c -r”

“php artisan make:model Message -m -c -r”

-mはマイグレーションファイルのことで、-cがコントローラファイル、-rがコントローラファイルの内容にCRUD系のテンプレメソッドを作ってくれる。

マイグレーションファイルを弄る

スレッドテーブルのマイグレーションファイルは以下のようにした。

public function up()
    {
        Schema::create('threads', function (Blueprint $table) {
            $table->id();
            $table->string("title");
            $table->string("overview");
            $table->timestamps();
        });
    }

既にあるものにtitleとoverviewを追加した形。
id()は一意のidになるらしい。

あまり分からないのはup()とdown()の違いと、Schema::createとその中身。

upメソッドはmigration時に呼び出されるもので、downメソッドはロールバックする処理を”自分”で記述するところ。要するに、ロールバックさせたいときは自分で動作をdownメソッドに記述しておかなきゃいけない。

Schema::createはファザードで、Schemaクラスのcreateメソッドを呼び出している。createとある通り、テーブルの作成メソッド。

第一引数の名前のテーブルを作成(今回はthreadsを作成)し、第二引数でカラムを設定できる。その際に、functionでBlueprintというオブジェクトを呼び出し、ここにカラムを指定し、第二引数に渡しているイメージ。

次に、メッセージテーブルのマイグレーションファイルの設定。

public function up()
    {
        Schema::create('messages', function (Blueprint $table) {
            $table->id();
            $table->foreignId('thread_id')->constrained()->onDelete('cascade');
            $table->text("message");
            $table->string("role");
            $table->timestamps();
        });
    }

ここでの謎は「$table->foreignId(‘thread_id’)->constrained()->onDelete(‘cascade’);」の部分。

  • foreignId()
    これは外部キー制約というものらしく、threadのidを参照し一致しているかを確認してくれる的な感じ。
  • constrained()
    はforeignIdに渡された値から対象のテーブルとカラムを予測してくれるらしい。そんなことある? 恐らくこれはLaravelの規則に従ったテーブル名じゃないとダメらしく、もし独自の名前を付けたならconstrained()にその名前を渡さなきゃいけない。
    恐らく、今回はこのままで大丈夫。
  • onDelete()
    cascadeが滝→連鎖的に的な意味で、今回の場合はthread_idが無くなったら一緒にこのメッセージも消えるという設定。

ここらへんの詳細は公式ドキュメントを。
バージョンによっても変わるっぽい。

設定し終わったら”php artisan migrate”でマイグレート。

おk!

一応出来てるか確認する。
“mysql -u root -p”
でmysql入って
“use DB名”
でDB選択して

“show tables;”でテーブル確認。

おk。

モデルのリレーション設定

さっきのはマイグレーション、つまりDB側のリレーションの設定でモデルの設定はEloquent ORMのリレーションの設定。

これをすることで、簡単に関連データを取得できるようになるらしい。

こちらも詳しくは公式ドキュメント(日本語訳)を見てもらうといいと思う。

スレッドモデル

use App\Models\Message;
class Thread extends Model
{
    public function messages()
    {
        return $this->hasMany(Message::class);
    }
}

のようになった。

スレッドIDは一意なのに対し、メッセージ側でスレッドIDと紐づいているのは複数あるので、一対多の関係。その場合はhasManyをつける。
hsaMany以外の具体的な指定が無いけど、こちらもLaravelの規約に従った名前を付けていれば自動で探してくれるらしい。

また、ファザードでMessageモデルを指定しているので、use App\Models\Message;が必要。

メッセージモデル

こちらも大体おなじ。

use App\Models\Thread;
class Message extends Model
{
    public function thread()
    {
        return $this->belongsTo(Thread::class);
    }
}

逆にこちらは複数側なのでbelongsToを付ける。

とりあえず、これでDB関連の設定は出来たと信じる。

メイン画面の作成

とりあえずPaiza講座と同じようにメイン画面から作っていこう。

ルータ設定

とりあえず

use App\Http\Controllers\ThreadController;
Route::get("/threads", [ThreadController::class, "index"])->name("threads.list");
Route::get('/', function () {
    return redirect("/threads");
});

に変更。

@で書くやり方もあるんだけど、現在はこの書き方が推奨されてるっぽい。

つまり、/threadsをメイン画面にし、/threadsにアクセスすると、threadcontrollerのindexメソッドの処理を行う。

スレッドコントローラ

ここらへんはPaizaを参考にやってる。

public function index()
    {
        $threads=Thread::all();
        return view('thread.index',["threads"=>$threads]);
    }

つまり、モデルThreadから全てのデータを取得するというもの。Threadモデルにall()メソッドなんてなかったと思うけど、なんかプリセットされてるみたい。詳しくは公式ドキュメントを。

それと、今回はall()で取得してるけど量が多い場合は$threads = Thread::paginate(10);などで制限して取得する。

この情報はLaravelのAPI一覧にある。

また、今回付けたthread.indexという名前はディレクトリ構造を表していて、view/theread/indexにビューがあるということになる。

ビューの制作

ここは完全にPaiza様の講座のhtmlをパクらせていただく。

<!DOCTYPE html>
<html>
    <head>
        <meta charset='utf-8'>
        <title>ChatGPT_Board</title>
        <style>body {padding: 10px;}</style>
    </head>
    <body>
        <h1>スレッド一覧</h1>
        @foreach ($threads as $thread)
            <p>
                {{ $thread->title}},
                {{ $thread->overview }},
                {{ $thread->timestamps }}
            </p>
        @endforeach
    </body>
</html>

DBにテストデータを入れる為にシーダーを使う

動作確認を行いたいが、テストデータが無いと出来ないのでDBにデータを入れてみる。
シーダー→シード→seed→種ってことで、DBに初期値を入れることができるみたい。

“php artisan make:seeder ThreadsTableSeeder”
でシーダーを作ってみる。

database/seedersにシーダーが出来ている。

中身は以下の通り

<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;


class ThreadsTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        DB::table('threads')->insert([
            [
                'title' => 'サンプルスレッド1',
                'overview' => 'これはサンプルのスレッド1です。',
                'created_at' => now(),
                'updated_at' => now(),
            ],
            [
                'title' => 'サンプルスレッド2',
                'overview' => 'これはサンプルのスレッド2です。',
                'created_at' => now(),
                'updated_at' => now(),
            ],
            [
                'title' => 'サンプルスレッド3',
                'overview' => 'これはサンプルのスレッド3です。',
                'created_at' => now(),
                'updated_at' => now(),
            ]
        ]);
    }
}

created_atなんてカラム作ってないと思うかもなんだけど、timestampsが担ってるみたい。

これを記述した後に
“php artisan db:seed –class=ThreadsTableSeeder”
でシーダーを適用。

よし、これでいいのかな。

テストをしてみる

早速接続

404。

恐らく、アプリを新しくしたのでバーチャルホストの設定を更新しなきゃいけないなこれ。
詳しくは7日目と8日目参照。

更新してきた。

前もこうなった気がする。

権限問題だと思われるので、フォルダのパーミッションを設定する。

おk!

なんかURLの末尾に意図しないものがついてるけど、とりあえず動いた。

viewのテンプレート化

ここらへんもPaizaの受け売りなんだけど、Bladeを使ってテンプレート化できるのでテンプレ化する……といっても今はメイン画面のみなのでテンプレート化する意味は薄め。

layout.blade.phpをthreadフォルダ内に作って、以下のようにする。

<!DOCTYPE html>
<html>
    <head>
        <meta charset='utf-8'>
        <title>ChatGPT_Board</title>
        <style>body {padding: 10px;}</style>
    </head>
    <body>
        @yield('content')
    </body>
</html>

そして、それに合わせてindex.blade.phpを修正

@extends('thread.layout')

@section('content')
    <h1>スレッド一覧</h1>
    @foreach ($threads as $thread)
        <p>
            {{ $thread->title}},
            {{ $thread->overview }},
            {{ $thread->timestamps }}
        </p>
    @endforeach
@endsection

たぶん、プログラミング出来る人だったら説明しなくてもこのコード見るだけで理解できると思うけど、一応説明。

layoutのcontentの部分だけオリジナル要素で、それ以外はテンプレというレイアウトを作成する→それに合わせてindex.blade.phpを、contentの部分だけ残し、残りは削除という感じ。

何も変わってはないけど、一応適応できたことを確認。

Paiza様のをほぼそのままもってきて、layoutを以下のようなコードに更新しBootstrapを適用。

<!DOCTYPE html>
<html>
    <head>
        <meta charset='utf-8'>
        <meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no'>
        <link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css' >
        <title>ChatGPT_Board</title>
        <style>body {padding-top: 80px;}</style>
    </head>
    <body>
        <nav class='navbar navbar-expand-md navbar-dark bg-dark fixed-top'>
            <a class='navbar-brand' href={{route('threads.list')}}>ChatGPT_Board</a>
        </nav>
        <div class='container'>
        @yield('content')
        </div>
    </body>
</html>

結果こんな感じ。

まあ、さっきよりは見た目が良くなったね。

Bootstrapを使うと、画面比率に合わせてレイアウトを自動調整してくれるよ。
それ以外の詳しいことはわからないけど、便利ではあると思う。

閲覧画面の作成

閲覧画面というより、詳細画面といった方がいいのか? 

ルーティング

「Route::get(‘/shop/{id}’, ‘ShopController@show’)->name(‘shop.detail’);」このルーティングはPaizaのやつなんだけど、こんな感じでURLに{id}とやることで、今回の場合は”/shop/{ここ}”の部分をidとして受け取れる。つまり、”/shop/1”というURLならidに1を代入して受け取ることができる。

これは変数名をidとしているだけで、数字だけじゃなく文字列でもなんでも受け取れてしまうので注意。

それに倣って今回、閲覧画面のルーティングをするとすれば、
“Route::get(“/thread/{id}”,[ThreadController::class, “show”])->name(“threads.detail”);”
みたいな感じでいいのかなと思う。

コントローラ

恐らくここがPaizaと違く、2つのモデルからDBのデータを引っ張ってきてViewに渡すという動作をしなきゃいけない。

具体的には、Threadモデルのタイトルと、ThreadIdと一致するMessageモデルの全メッセージを取得し、Viewに送ってやる必要がある。
とりあえず、該当のThreadモデルの全データを取得する。要するに、クリックした記事のID、タイトル、概要、timestanmpの全てを取得する。

$thread = Thread::find($id);

これで、urlから取得したidのthreadを見つける。
次に、その記事と結びつくメッセージを全て取得する。

$messages = $thread->messages;

これでいけるらしい。
これは、threadモデルでhasManyのリレーションを定義したからORMであるEloquentが認識してやってくれているみたい。

これをもうViewに渡してしまえば終わりなのでは?

        return view(‘thread.show’,[“thread”=>$thread,”messages”=>$messages]);

とりあえず、これでやってみよう。

ビュー

ビューを作っていこう!

ここら辺は少ないHTMLの知識を総動員して作ろうと思ったんだけど、別にテンプレートを使いまわしても問題ないのかこれ。
やってみよう。

@extends('thread.layout')
@section('content')
    <h1>{{ $thread->title }}</h1>
    @foreach ($messages as $message)
        <p>{{ $message->role }}</p>
        <p>{{ $message->message }}</p>
        <p>{{ $message->created_at }}</p>
    @endforeach
@endsection

とりあえずこんなもんでいいのでは。

テストの為のシーダー

こちらも動作確認をしたいので、初期値を設定する。

“php artisan make:seeder ThreadsDetailSeeder”

でいいかな。

内容は

<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class ThreadsDetailSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        DB::table('messages')->insert([
            [
                'thread_id' => '1',
                'message' => 'テストメッセージ1です',
                'role' => 'User',
                'created_at' => now(),
                'updated_at' => now(),
            ],
            [
                'thread_id' => '1',
                'message' => 'テストメッセージ2です',
                'role' => 'ChatGPT',
                'created_at' => now(),
                'updated_at' => now(),
            ],
            [
                'thread_id' => '1',
                'message' => 'テストメッセージ3です',
                'role' => 'User',
                'created_at' => now(),
                'updated_at' => now(),
            ]
        ]);
    }
}

でやってみる。
thread_idは1から順々に振られていくはずなので、これで問題ないはず。

“php artisan db:seed –class=ThreadsDetailSeeder”
で適用。

テスト

動くかなぁ~

何も問題なく動いた! 嬉しいね!

時間が恐らく世界標準時間になっている所を覗けば予想通り。
そうか、時間の設定も必要だよねそりゃ。

メイン画面の修正

現在の設定だとURLに直接idを打ち込む必要があるので、リンクさせる。
HTML(Hyper Text Markup Language)って言うくらいだしね。

ついでに、Paizaの方を見るとメイン画面をテーブル化していたので、それも参考にさせていただいて、以下のようなコードに修正。

@extends('thread.layout')

@section('content')
    <h1>スレッド一覧</h1>
    <a href={{ route('threads.create') }} class='btn btn-outline-primary'>新規スレッドを作成</a>
    <table class='table table-striped table-hover'>
        <tr>
            <th>タイトル</th>
            <th>概要</th>
            <th>作成日時</th>
        </tr>
        @foreach ($threads as $thread)
            <tr>
                <td>
                    <a href={{ route('threads.detail', ['id' => $thread->id]) }}>
                        {{ $thread->title }}
                    </a>
                </td>
                <td>{{ $thread->overview }}</td>
                <td>{{ $thread->created_at }}</td>
            </tr>
        @endforeach
    </table>
@endsection

すると。

こんな感じでスッキリ&リンクを押すことで該当記事に飛べるように。

新規追加画面の作成

スレッドを自分で作成出来るようにする。
つまり、DBにデータを保存する作業。

Paizaの方ではコントローラのstoreメソッドで新規作成の全てを賄ってたんだけど、じゃあcreateメソッドって何のためにあるの? ってなるよね。

ちなみに、今回はコントローラを
“php artisan make:model Thread -m -c -r”
のrオプションを付けて作ったのでテンプレのメソッドがある。

実際にメソッドを挙げると

  1. index
  2. create
  3. store
  4. show
  5. edit
  6. update
  7. destroy

の7つ。

ここで気になるのはcreate-storeの関係とedit-updateの関係。
どっちも同じような意味だけど、どう違うの? という話。

簡潔に言えば、create,editメソッドはインターフェースの部分を呼び出すメソッドで、storeとupdateはデータを保存する処理を呼び出すメソッド。

だから新規追加をする場合、流れ的には

  1. ユーザーが新規追加画面へ行くボタンを押す
  2. ルーティングがコントローラのcreateメソッドを呼び出す
  3. createメソッドが新規追加画面のviewに遷移させる
  4. ユーザーが新規追加画面でデータを入れ送信
  5. ルーティングがコントローラのstoreメソッドに渡す
  6. storeメソッドが受け取り、DBに反映させるモデルメソッドを呼び出す
  7. モデルがDBにデータを保存する

という感じになる。

恐らく、Paizaではこれくらいの小規模であれば必要ないと割り切りシンプルさに振ったんだと思う。
今回は別にそんなに複雑になるわけではないと思うので、メソッドは全部使ってみる。

ルーティングの作成

とりあえず、createメソッドの方から。

Route::get(“/thread/create”, [ThreadController::class, “create”])->name(“threads.create”);

を追加。

因みに、ルーティングの順序は大切で、/thread/{id}が上にあると/thread/createにアクセスしても全て{id}として吸われてしまうので注意。

コントローラの作成

といっても、静的なところなのでviewに遷移させるだけ。

public function create()
    {
        return  view('thread.create');
    }

ビューの作成

テンプレートを利用し以下の通りに。

@extends('thread.layout')
@section('content')
    <h1>新しい記事の作成</h1>
    <form method="POST" action="">
        @csrf
        <div>
            <label for="title">タイトル:</label>
            <input type="text" id="title" name="title">
        </div>
        <div>
            <label for="overview">概要:</label>
            <input type="text" id="overview" name="overview">
        </div>
        <button type="submit">記事を作成</button>
    </form>
@endsection

@CSRFというのは、「クロスサイトリクエストフォージェリ」という攻撃から守る為の物。詳しくは公式ドキュメントを……といいたいんだけど、マジでわかりにくい。
理解するにはセッションとかクッキーの知識が必要で、私も説明できるほど理解できてないので、とりあえずユーザーが入力してPOSTする系の物には@CSRFを付けとこうという認識で。

また、Paizaでは{{ Form::label(‘name’, ‘店名:’) }}のようにFormファザードを使っているんだけど、Laravelでの標準サポートは現在されていないらしく、使うにはパッケージを入れなきゃいけないっぽいので今回は通常の方法で。

まだコントローラを作成していないので未入力だけど、formのところを

    <form method=”POST” action=”{{ route(‘threads.store’) }}”>

にすることでthreads.storeルーティングを呼び出して、storeメソッドを呼び出すという感じ。

テスト

一応この画面が動くかのテスト。

動くねぇ。

次はstoreの処理を書く。

ルーティングの作成

ルーティングを追加。
追加後の全体像はこんな感じ。

<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ThreadController;
Route::get("/threads", [ThreadController::class, "index"])->name("threads.list");
Route::get("/thread/create", [ThreadController::class, "create"])->name("threads.create");
Route::post("/thread/store", [ThreadController::class, "store"])->name("threads.store");    //追加
Route::get("/thread/{id}",[ThreadController::class, "show"])->name("threads.detail");
Route::get('/', function () {
    return redirect("/threads");
});

コントローラの作成

不安だけど、こんな感じ。

public function store(Request $request)
    {
        $thread = new Thread;
        $thread->title = $request->title;
        $thread->overview = $request->overview;
        $thread->save();
        return redirect()->route('threads.list');
    }

保存後にメイン画面にリダイレクトするようになってる。

細かい調整

storeメソッドを作成したのでさっき作ったviewのPOST先を指定。

<form method=”POST” action=”{{ route(‘threads.store’) }}”>

単純にURLに直接入力して新規画面行ったり、一覧画面に行ったりするのが面倒なので、リンクの追加。

<a href={{ route(‘threads.list’) }} class=’btn btn-outline-primary’>記事一覧へ戻る</a>

<a href={{ route(‘threads.create’) }} class=’btn btn-outline-primary’>新規スレッドを作成</a>

を追加。

テスト

動くかな?

おー! 何も問題なく作れた!
「新規スレッドを作成」ボタンと「記事一覧へ戻る」ボタンも問題なし!
素晴らしいねぇ

新しい記事を作成するときのフォームがめっちゃちっちゃいくらいかな、不満点は。

編集画面の作成(投稿フォームの作成)

編集画面なんだけど、掲示板形式だし閲覧画面に新規投稿フォームを一番下にくっつければいいんじゃないかなと思ったりしている。

実際に2chとかを見てみると

みたいな感じのフォームが掲示板の一番下にくっついてるんだよね。
こんな感じの方が便利そう。

投稿したメッセージの編集はできなくて、スレッドの編集、削除のみ可能みたいな感じで。

となると、閲覧画面であるThread.showのビューに投稿フォームをくっつけて、Messageコントローラのstoreメソッドを使えばいいのかな。

とりあえずやってみようか。

Thread.show.blade.phpの編集

投稿フォームと、一覧に戻るリンクを付ける。

<form method="POST" action="{{ route('messages.store') }}">
        @csrf
        <input type="hidden" name="thread_id" value="{{ $thread->id }}">
        <div>
            <label for="role">役割:</label>
            <input type="radio" id="role" name="role" value="User" checked>User
            <input type="radio" id="role" name="role" value="ChatGPT">ChatGPT
        </div>
        <div>
            <label for="message">メッセージ:</label>
            <input type="text" id="message" name="message">
        </div>
        <button type="submit">メッセージを作成</button>
    <a href={{ route('threads.list') }} class='btn btn-outline-primary'>記事一覧へ戻る</a>

ルーティングの作成

message.storeのルーティングを作成する。

Route::post(“/message/store”, [MessageController::class, “store”])->name(“messages.store”);

コントローラの作成

Messageコントローラのstoreメソッドを弄る。

public function store(Request $request)
    {
        $message = new Message;
        $message->thread_id = $request->thread_id;
        $message->message = $request->message;
        $message->role = $request->role;
        $message->save();
        return redirect()->route('threads.detail',["id"=>$request->thread_id]);
    }

こんな感じ。

一回動作確認をしてみる。

テスト

フォームは出来てるっぽい。

投稿!

あ、ルーティングに
”use App\Http\Controllers\MessageController;”
追加してなかった。

再度投稿!

できた!

全文追加してみた!

ひとまず、完成

最低限の機能しか追加できてないけど、とりあえず完成!
1日で作るとか言ってたけど、たぶん開始から26時間くらい経ってるのでギリギリ無理だった。

完成形はこんな感じ。

スレッド一覧

こんな感じ。シンプルだね。

新規投稿画面

新規スレッドを作成ボタンを押すと記事作成ページに画面遷移します。

記事閲覧

実際に記事タイトルをクリックすると記事閲覧ページに遷移します。

投稿フォーム

投稿フォームは閲覧ページの一番下についています。

改善点を挙げようとすると本当に無限にあるんだけど、とりあえず作成の流れとか実践的な経験が出来たので一回終わり。

一応改善点を無限に上げてみる

  1. フォーム投稿のセキュリティ対策をほぼ何もしていないので、このままだと恐らく危険
  2. 閲覧ページが見にくい
  3. 文字数の制限かけてないし、メッセージもスレッドも無限に投稿できちゃう
  4. つまり、バリデーションしていない
  5. UserとChatGPTで交互に投稿するので、ラジオボタンもそれに応じて自動で切り替えてほしい
  6. Userは右揃え、ChatGPTは左揃えにすると見やすそう
  7. 記事の削除、メッセージの削除機能がない
  8. 同じく編集機能がない
  9. 日付が世界標準時刻
  10. 投稿フォームの大きさが全体的に小さい。

意外とこんなもんかもしれない。

おわりに

次はこのWebサイトを改良するか、Qiitaみたいな記事投稿Webサイトを作りたい。

というか改めて見るとこのブログ長すぎ、これ誰が見るん?

おやすみなさい。