Laravel で電子掲示板を作ろう ①(JetStream のインストールなど)

皆さん、こんにちは。管理人です。このシリーズ「Laravel で電子掲示板を作ろう」では、その名の通り、Laravel を用いて電子掲示板を作っていこうと思っています。(電子掲示板というよりは、記事投稿サイトと言ったほうが良いのかも知れませんが……。)今回は第一回目となります。なお、開発環境の構築はすでに済んでいるものとします。それがまだの方は、
Laravel 入門 ①(Windows PC における開発環境の構築)
を見ながら開発環境を構築して下さい。

なお、今回私が作成した(作成している)電子掲示板のソースコードは
https://github.com/s31104989/BBS
で公開しています。よろしければ参考にして下さい。

Laravel プロジェクトの新規作成とデータベースの設定

では、次のコマンドを実行して BBS という名前の Laravel プロジェクトを作成しましょう。

composer create-project laravel/laravel BBS --prefer-dist

プロジェクトが無事に作成されたら、phpMyAdmin を開き、お好きな名前(ここでは「bbs」)のデータベースを作成して下さい。

そうしましたら、エディター(ここでは VS Code)で BBS を開いて下さい。そして .env ファイルの11行目あたりから16行目あたりまでを次のように編集して下さい。(なお、データベースにパスワードを設定する方法については、Laravel 入門 ③(データベースの作成や認証機能の導入など)を参考にして下さい。)

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=bbs # 先ほど決めたデータベースの名前
DB_USERNAME=root
DB_PASSWORD=データベースのパスワード

JetStream のインストール

ここでは認証機能として JetStream をインストールしたいと思います。ターミナルを立ち上げて、カレントディレクトリが BBS になっていることを確認して、

composer require laravel/jetstream

を実行します。ここで、フロントの開発は Livewire と Inertia のどちらかを選択することになりますが、ここでは Livewire を選択することにします。php artisan jetstream:install livewire を実行して下さい。

PS C:\Users\Public\Laravel\BBS> php artisan jetstream:install livewire

   INFO  Migration created successfully.

./composer.json has been updated
Running composer update livewire/livewire
Loading composer repositories with package information
Updating dependencies
Lock file operations: 1 install, 0 updates, 0 removals
  - Locking livewire/livewire (v2.10.7)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
  - Installing livewire/livewire (v2.10.7): Extracting archive
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi

   INFO  Discovering packages.

  jenssegers/agent .................................................................................................................. DONE  
  laravel/fortify ................................................................................................................... DONE  
  laravel/jetstream ................................................................................................................. DONE  
  laravel/sail ...................................................................................................................... DONE  
  laravel/sanctum ................................................................................................................... DONE  
  laravel/tinker .................................................................................................................... DONE  
  livewire/livewire ................................................................................................................. DONE  
  nesbot/carbon ..................................................................................................................... DONE  
  nunomaduro/collision .............................................................................................................. DONE  
  nunomaduro/termwind ............................................................................................................... DONE  
  spatie/laravel-ignition ........................................................................................................... DONE  

81 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
> @php artisan vendor:publish --tag=laravel-assets --ansi --force

   INFO  No publishable resources for tag [laravel-assets].


   INFO  Publishing assets.

> build
> vite build

    vite v3.0.8 building for production...
transforming...
    ✓ 58 modules transformed.
    rendering chunks...
public/build/manifest.json             0.25 KiB
public/build/assets/app.9fa9f508.css   43.26 KiB / gzip: 7.24 KiB
public/build/assets/app.bf268e08.js    129.08 KiB / gzip: 46.75 KiB

   INFO  Livewire scaffolding installed successfully.

PS C:\Users\Public\Laravel\BBS>

次に、

php artisan migrate

を実行し、そのあとに

php artisan serve

も実行して下さい。そうしましたら、XAMPP のコントロールパネルにおいて MySQL が緑色になっていることを確認し、ブラウザで http://localhost:8000/ にアクセスして下さい。次のようなページが表示されると思います。

そうしましたら、画面右上の「Register」をクリックして会員登録して下さい。
ログイン後の画面はこのようになっています。

日本語化

今の段階では、まだパスワードを求める画面などに英語が残っていますので、これを日本語化していきたいと思います。

まず、laravel-lang/publisherlaravel-lang/lang をインストールします:

composer require laravel-lang/publisher laravel-lang/lang --dev

続いて、lang フォルダ直下に ja フォルダと ja.json を作成します:

php artisan lang:add ja

続いて、config/app.php の 85 行目あたりに

'locale' => 'en',

とあるので、これを

'locale' => 'ja',

とします。これで日本語化されているはずです。ついでに、config/app.php の 72 行目あたりを次のように編集しておきましょう。(マイナスの行を削除してプラスの行を追加して下さい。コピペする場合はプラスを含めないで下さい。)

- 'timezone' => 'UTC',
+ 'timezone' => 'Asia/Tokyo',

モデルとマイグレーションファイルの作成

それでは、新しいターミナルを立ち上げて、カレントディレクトリが BBS になっていることを確認して、次のコマンドを実行して下さい。Post という名前のモデル(app/Models/Post.php)とマイグレーションファイル(database/migrations/yyyy_MM_dd_******_create_posts_table.php)が作成されます。

php artisan make:model Post --migration

そうしましたら、マイグレーションファイルを次のように編集して下さい。

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            // 追記ここから
            $table->string('title', 100);
            $table->text('content');
            $table->unsignedBigInteger('user_id');
            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); // 外部キー制約
            $table->timestamp('created_at')->default(DB::raw('CURRENT_TIMESTAMP'));
            $table->timestamp('updated_at')->default(DB::raw('CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP'));
            // 追記ここまで
            // $table->timestamps(); この行はコメントアウトまたは削除します。
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('posts');
    }
};

そうしましたら、(XAMPP のコントロールパネルにおいて MySQL が緑色になっている状態で)次のコマンドを実行します。

php artisan migrate

特にエラーがでなければ、データベースに posts テーブルが出来ているはずです。

新しい記事の作成ページ

続いて、新しい記事の作成ページを作成しようと思います。

routes/web.php を次のように編集して下さい。

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PostController; // 追記

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

Route::middleware([
    'auth:sanctum',
    config('jetstream.auth_session'),
    'verified'
])->group(function () {
    Route::get('/dashboard', function () {
        return view('dashboard');
    })->name('dashboard');
    // 追記ここから
    Route::get('/create', function () {
        return view('create');
    })->name('create');
    Route::post('/store', [PostController::class, 'store'])->name('store');
    // 追記ここまで
});

次に、resources/views/create.blade.php を作成し、内容は次のようにして下さい。

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            新しい記事の作成
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-2xl px-6 lg:px-8">
            <form class="grid grid-cols-1 gap-6" method='POST' action="{{ route('store') }}" enctype="multipart/form-data">
                @csrf
                <label class="block">
                    <span class="text-gray-700">タイトル</span>
                    <input name="title" type="text" class="mt-1 block w-full rounded-md shadow-sm">
                </label>
                <label class="block">
                    <span class="text-gray-700">内容</span>
                    <textarea id="markdown-editor" class="block w-full mt-1 rounded-md" name="content" rows="5"></textarea>
                </label>
                <button type='submit' class="w-20 bg-blue-600 hover:bg-blue-500 text-white rounded px-4 py-2">投稿</button>
            </form>
        </div>
    </div>
</x-app-layout>

続きまして、コントローラーを作成しようと思います。次のコマンドを実行して下さい。

php artisan make:controller PostController

すると、app/Http/Controllers/PostController.php が作成されると思います。これを次のように編集します。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Post; // 追記
use Auth; // 追記

class PostController extends Controller
{
    /**
     * このアクションを追加
     */
    public function store(Request $request)
    {
        $data = $request->all(); // フォームで送信されたデータをすべてとってきます

        Post::insertGetId([
            'user_id' => Auth::id(), // ログイン中のユーザーの ID を格納します
            'title' => $data['title'], // 入力された文字列を格納します
            'content' => $data['content'], // 入力された文字列を格納します
        ]);

        return redirect()->route('dashboard'); // ダッシュボードにリダイレクトします
    }
    
}

次に、resources/views/navigation-menu.blade.php を開き、次のように編集します。

               (略)

                <!-- Navigation Links -->
                <div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
                    <x-jet-nav-link href="{{ route('dashboard') }}" :active="request()->routeIs('dashboard')">
                        {{ __('Dashboard') }}
                    </x-jet-nav-link>
                    <!-- 追記ここから -->
                    <x-jet-nav-link href="{{ route('create') }}" :active="request()->routeIs('create')">
                        新しい記事の作成
                    </x-jet-nav-link>
                    <!-- 追記ここまで -->
                </div>
            </div>

           (中略)

    <!-- Responsive Navigation Menu -->
    <div :class="{'block': open, 'hidden': ! open}" class="hidden sm:hidden">
        <div class="pt-2 pb-3 space-y-1">
            <x-jet-responsive-nav-link href="{{ route('dashboard') }}" :active="request()->routeIs('dashboard')">
                {{ __('Dashboard') }}
            </x-jet-responsive-nav-link>
            <!-- 追記ここから -->
            <x-jet-responsive-nav-link href="{{ route('create') }}" :active="request()->routeIs('create')">
                新しい記事の作成
            </x-jet-responsive-nav-link>
            <!-- 追記ここまで -->
        </div>

       (以下略)

それでは、新しいターミナルを開いて

npm run dev

を実行して、ブラウザで http://localhost:8000/dashboard にアクセスします。(ちなみにですが、本番環境では npm run build というコマンドを使います。)

すると「ダッシュボード」の右どなりに「新しい記事の作成」というのが出ているはずです。
新しい記事の作成のページはこのようになっています。
スマホ版はこのような感じです。
「新しい記事の作成」のページで、タイトルと内容を入力して、投稿ボタンをクリックすると
このようにデータベースbbsのpostsテーブルに記事が格納されていきます。

記事をブラウザで表示する

続いて、データベースに格納された記事がブラウザで閲覧できるようにしたいと思います。routes/web.php に一行を追加して次のようにして下さい。

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PostController;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});
Route::get('/post/{post_id}', [PostController::class, 'post'])->name('post'); // 追記

Route::middleware([
    'auth:sanctum',
    config('jetstream.auth_session'),
    'verified'
])->group(function () {
    Route::get('/dashboard', function () {
        return view('dashboard');
    })->name('dashboard');
    Route::get('/create', function () {
        return view('create');
    })->name('create');
    Route::post('/store', [PostController::class, 'store'])->name('store');
});

そうしましたら、app/Http/Controllers/PostController.php に post アクションを追加して次のようにします。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Post;
use Auth;

class PostController extends Controller
{
    public function store(Request $request)
    {
        $data = $request->all(); // フォームで送信されたデータをすべてとってきます

        Post::insertGetId([
            'user_id' => Auth::id(), // ログイン中のユーザーの ID を格納します
            'title' => $data['title'], // 入力された文字列を格納します
            'content' => $data['content'], // 入力された文字列を格納します
        ]);

        return redirect()->route('dashboard'); // ダッシュボードにリダイレクトします
    }
    
    /**
     * このアクションを追加
     */
    public function post($post_id)
    {
        $post = Post::where('id', $post_id)->first();
        if (is_null($post)) {
            abort(404);
        }
        return view('post', compact('post'));
    }
}

そうしましたら、resources/views/post.blade.php というファイルを作成して次のようにします。

<x-guest-layout>
    <h1>{{ $post['title'] }}</h1>
    <p>{{ $post['content'] }}</p>
</x-guest-layout>

続いて、resources/views/layouts/guest.blade.php を次のようにします。
(38 行目において © となっているところは &copy; に置き換えて下さい。)

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="csrf-token" content="{{ csrf_token() }}">
        <title>{{ config('app.name', 'Laravel') }}</title>
        <!-- Fonts -->
        <link rel="stylesheet" href="https://fonts.bunny.net/css2?family=Nunito:wght@400;600;700&display=swap">
        <!-- Scripts -->
        @vite(['resources/css/app.css', 'resources/js/app.js'])
    </head>
    <body>
        <div class="flex flex-wrap justify-between">
            <div class="px-4 py-4">
                <a href="/" class="text-sm text-gray-700 dark:text-gray-500 underline">トップページ</a>
            </div>
            @if (Route::has('login'))
                <div class="px-4 py-4 text-right sm:block">
                    @auth
                        <a href="{{ url('/dashboard') }}" class="text-sm text-gray-700 dark:text-gray-500 underline">Dashboard</a>
                    @else
                        <a href="{{ route('login') }}" class="text-sm text-gray-700 dark:text-gray-500 underline">Log in</a>
                        @if (Route::has('register'))
                            <a href="{{ route('register') }}" class="ml-4 text-sm text-gray-700 dark:text-gray-500 underline">Register</a>
                        @endif
                    @endauth
                </div>
            @endif
        </div>
        
        <div class="font-sans text-gray-900 antialiased px-4">
            <div class="max-w-2xl mx-auto prose">
                {{ $slot }}
            </div>
        </div>
        <footer class="text-white h-10 bg-green-700 text-center mt-10">
            <small class="leading-10">© {{ config('app.name') }}</small>
        </footer>
    </body>
</html>

そうしましたら、ターミナルで(BBSフォルダをカレントディレクトリとして)npm run build を実行し、ブラウザで http://localhost:8000/post/1 や http://localhost:8000/post/2 などにアクセスしてみて下さい。(URL最後の数字はデータベースにおける記事のidに対応します。)

すると、次のように記事が表示されるかと思います。

コメント

タイトルとURLをコピーしました