手軽に学ぶ:Laravel 10.xによるウィッシュリストへのメール登録とキュー送信

こんにちは。フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、この数ヶ月はChatGPTに代表する生成AIが一般的に広がったので、私も例にもれずドップリと体験することになりました。

そして、そんな中で「あ、これは個人サービスにもあると便利だな」と思う機能に遭遇しました。

それは・・・・・・

ウィッシュリスト

です。

ウィッシュリストとは、例えば「まだこのサイトはリリースまで時間があるけど、リリースしたらお知らせするよ👍」という機能になります。

※ これは、ChatGPTBardでも採用されてましたよね。

そして、このウィッシュリストがなぜプラスなのかというと・・・・・・

リリース先に需要を知ることができるから

です。

つまり、ウィッシュリストが多く集まればみんなが欲しがっているということになりますし、その逆の場合は「あれ!?自分の中では盛り上がってたけど、みんなは好きじゃないのね…」という判断ができるからです。(👈これクラウドファンディングや受注生産と同じ作戦ですね)

そこで❗

今回は「Laravel 10.x + React」を使ってこの「ウィッシュリスト」機能をつくってみたいと思います。

ぜひ何かの参考になりましたら嬉しいです。😄✨

「左右5本ずつじゃなく、
10本の指を1つの意識で
ピアノ演奏できるよう研究中です」

開発環境: Laravel 10.x、React、Inertia.js、TailwindCSS、Vite

モデル&マイグレーションを用意する

では、まずはDB周りを用意します。
以下のコマンドを実行してください。

php artisan make:model Wishlist -m

すると、モデルとマイグレーションのファイルが作成されるので中身をそれぞれ次のように変更します。

app/Models/Wishlist.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Wishlist extends Model
{
    use HasFactory;

    protected $guarded = ['id']; // 👈ここを追加しました
}

そして、マイグレーションです。

database/migrations/****_**_**_******_create_wishlists_table.php

// 省略

public function up(): void
{
    Schema::create('wishlists', function (Blueprint $table) {
        $table->id();
        $table->string('email')->comment('メールアドレス');
        $table->text('memo')->nullable()->comment('メモ');
        $table->timestamps();
    });
}

// 省略

では、この状態でテーブルを追加します。
以下のコマンドを実行してください。

php artisan migrate

すると、実際のテーブルはこうなりました。

コントローラーをつくる

続いて、ウィッシュリストの登録フォームをつくっていきます。
以下のコマンドを実行してください。

php artisan make:controller WishlistController

すると、ファイルが作成されるので中身を次のようにします。

app/Http/Controllers/WishlistController.php

<?php

namespace App\Http\Controllers;

use App\Models\Wishlist;
use Illuminate\Http\Request;
use Inertia\Inertia;

class WishlistController extends Controller
{
    public function create()
    {
        return Inertia::render('Wishlist/Create');
    }

    public function store(Request $request)
    {
        $request->validate([
            'email' => 'required|email',
        ]);

        $wishlist = Wishlist::firstOrNew([
            'email' => $request->email,
        ]);
        $wishlist->email = $request->email;
        $wishlist->memo = $request->memo;
        $wishlist->save();

        return redirect()->route('wishlist.create')->with('message', 'ウィッシュリストが登録されました!');
    }
}

この中では、create()がウィッシュリストの登録フォームで、store()がその送信先になっています。

フラッシュメッセージが React 側で受け取れるようにする

なお、先ほどコントローラー内で以下のようなコードがあったと思います。

return redirect()->route('wishlist.create')->with('message', 'ウィッシュリストが登録されました!');

意味としては、指定したURLにリダイレクトするというものですが、それと同時に「フラッシュメッセージ」もセットしています。

フラッシュメッセージとは、今回のように「登録が完了しました」というような一時的なメッセージのことで、これは「セッション」と呼ばれる箱で保持されます。そして、リダイレクトした先でそのデータを取り出すことができるというわけですね。

しかし、LaravelでセットされたデータをInertia + Reactで受け取るためには以下のようなひと手間必要になるので、先に追加しておいてください。

app/Providers/AppServiceProvider.php

// 省略

public function boot(): void
{
    // 👇 ここを追加しました
    Inertia::share('flash', function (Request $request) {
        return [
            'message' => $request->session()->get('message'),
        ];
    });
}

// 省略

そして、次の項目でご紹介しますが、React内では以下のようにしてそのデータを取得することができるようになります。

import { usePage } from '@inertiajs/react'

// 省略
const { flash } = usePage().props;

ビューをつくる

では、WishlistControllerコントローラー内で指定した登録フォームのビュー(React)をつくっていきます。

以下のファイルを作成してください。

resources/js/Pages/Wishlist/Create.jsx

import { useForm, usePage } from '@inertiajs/react'

export default function Create() {

    // データ
    const { data, setData, post, reset, processing, errors } = useForm({
        email: '',
        memo: '',
    });
    const handleChange = (e) => {

        const key = e.target.id;
        const value = e.target.value;

        setData(values => ({
            ...values,
            [key]: value,
        }))

    }

    // 送信
    const submit = (e) => {

        e.preventDefault();

        const url = route('wishlist.store');
        post(url, {
            data,
            onSuccess: () => reset('email', 'memo') // 入力をリセット
        });

    };

    // フラッシュメッセージ
    const { flash } = usePage().props;

    return (
        <div className="container mx-auto px-4 sm:px-6 lg:px-8 py-12">

            {flash.message && (
                <div className="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative mb-6" role="alert">
                    <span className="block sm:inline">{flash.message}</span>
                </div>
            )}

            <h1 className="text-3xl font-bold text-center mb-6">ウィッシュリスト作成</h1>

            <form onSubmit={submit} className="w-full max-w-lg mx-auto">

                <div className="flex flex-wrap -mx-3 mb-6">
                    <label htmlFor="email" className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2">
                        メールアドレス:
                    </label>

                    <input
                        id="email"
                        type="email"
                        value={data.email}
                        onChange={handleChange}
                        className="appearance-none block w-full bg-gray-200 text-gray-700 border-none rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white"
                    />

                    {errors.email && <p className="text-red-500 text-xs italic">{errors.email}</p>}
                </div>

                <div className="flex flex-wrap -mx-3 mb-6">
                    <label htmlFor="memo" className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2">
                        その他欲しい機能などがありましたら入力してください<small>(任意):</small>
                    </label>

                    <textarea
                        id="memo"
                        value={data.memo}
                        onChange={handleChange}
                        className="appearance-none block w-full h-auto bg-gray-200 text-gray-700 border-none rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white"
                    />
                </div>

                <div className="flex items-center justify-between">
                    <button
                        type="submit"
                        className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
                        disabled={processing}
                    >
                        送信
                    </button>
                </div>
            </form>
        </div>

    )

}

ちなみに、いつもはReactっぽさを出したいのでuseStateを使っていますが、今回はInertiaが用意してくれているuseForm()を使ってみました。確かにこれも便利ですね👍

ということで、これでフォームからデータ送信すると「wishlists」テーブルにデータが登録されていくようになります。

メール送信をキューに登録できる Artisan コマンドをつくる

では、ここからの想定はサイトの開発が完了し、リリース段階になったものとして作業を進めます。

まずはウィッシュリストに登録されたメールアドレスをキューに登録できるようにします。

「キュー」というのは、とてもシンプルに言うと「ちょっとずつ実行する」機能です。

例えば、一気に大量のメール送信するしようとすると、途中で止まったりメモリ不足になったりして都合が悪いのでこれを分散して実行しようね、というものです。

ということで、まずは独自のArtisanコマンドをつくってウィッシュリストのメールアドレスをこの「キュー」に登録できるようにしましょう。

以下のコマンドを実行してください。

php artisan make:command QueueWelcomeEmails

すると、ファイルが作成されるので、中身を次のようにします。

app/Console/Commands/QueueWelcomeEmails.php

<?php

namespace App\Console\Commands;

use App\Models\Wishlist;
use App\Mail\WelcomeEmail;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Mail;

class QueueWelcomeEmails extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'queue:welcome-emails';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'ウィッシュリストのメールをキューに登録する';

    /**
     * Execute the console command.
     */
    public function handle()
    {
        $wishlists = Wishlist::query()
            ->whereNull('queued_at')
            ->limit(1000)
            ->get();
        $now = now();

        foreach ($wishlists as $wishlist) { // キューに登録

            Mail::to($wishlist->email)->queue(new WelcomeEmail());

            $wishlist->queued_at = $now;
            $wishlist->save();

        }

        $this->info('Done!');

        return Command::SUCCESS;
    }
}

この中では、登録されたウィッシュリストの中から最大1000件をキューに登録するようになっています。

実際の実行は、php artisan queue:welcome-emailsとコマンド実行するだけでOKです。

なお、キューはコマンドを実行すると基本的にずっと待機してくれてjobsテーブルにデータが入ってくるとすぐ実行されます。

そのため、このコマンドは基本的にcrontabなどで定期実行することを想定しています。(今回はウィッシュリストの数が少ないので1回で完了します)

メール送信する部分をつくる

先ほどのqueue:welcome-emailsコマンドで指定した「WelcomeEmail」をLaravelのメール送信機能「Mailable」で作っていきます。

以下のコマンドを実行してください。

php artisan make:mail WelcomeEmail

するとMailableファイルが作成されるので中身を変更します。

app/Mail/WelcomeEmail.php

// 省略

class WelcomeEmail extends Mailable
{
    // 省略

    public function envelope(): Envelope
    {
        return new Envelope(
            subject: 'ついにリリースされました!',
        );
    }

    public function content(): Content
    {
        return new Content(
            view: 'emails.welcome',
        );
    }

// 省略

そして、メール本文は以下のファイルになります。

resources/views/emails/welcome.blade.php

<h1>ついにリリースされました!</h1>
<p>
    以下のURLからユーザー登録していただけます。<br><br>
    <a href="{{ $url }}">(サイト名)</a>
</p>

キューをデータベースで管理できるようにする

Laravelではいろいろなキューの実行方法がありますが、シンプルなので今回はデータベースを使った方法にします。

そこで、キューをDBで管理できるよう次の作業をしてください。
まずは以下2つのコマンドを実行します。

php artisan queue:table
php artisan migrate

すると、jobsというテーブルが作成されます。
実際のテーブルはこうなります。

そして、「データベースを使ったキュー」を有効にするため.envを変更します。

QUEUE_CONNECTION=database

これでDBのキューが有効になりました。

これで作業はすべて完了です。
お疲れ様でした😄✨

テストしてみる

では、実際にテストしてみましょう❗

まずはViteを起動して「https://******/wishlist/create」にアクセスしてみましょう。

※ なお、今回はメール送信テストにmailcatcherを使いますので、これも起動しておきました。

すると、以下のような表示になりました。

では、このフォームから以下3つのメールアドレスを登録してみます。

  • taro@example.com
  • jiro@example.com
  • saburo@example.com

では、登録が終わったのでDBテーブルを確認してみましょう。

はい❗
うまく登録されていますね😄

では、サイトがリリースされたものとして、メールキューを実行してみます。
まずは以下のコマンドを実行してキューの実行を待機させておきます。

php artisan queue:work

すると、以下のような表示になりました。

では、この状態で(このコマンドは止めず、別のコマンドとして)以下を実行してみましょう。

php artisan queue:welcome-emails

すると・・・・・・

はい❗
何も手動では実行していませんが、キューが実行されました。

では、メールの方も確認してみましょう。

はい❗
うまく3人に「ウェルカムメール」が送信されてました。

すべて成功です😄✨

企業様へのご提案

今回のように「ウィッシュリスト」機能を活用すると以下のようなメリットがあります。

  • サイトリリースだけでなく、機能のリリース前にウィッシュリストをつくることで本当に作成すべきかどうか(ユーザーが望んでいるのかどうか)を知ることができる
  • 今回のようにウィッシュリストのフォームに要望などの入力を用意することで、さらにユーザーが何を求めているのかを知ることができる
  • もしもウィッシュリストへの登録が少なかった場合でも、それは「ニーズがなかった」という判断ができ、お金だけでなく時間のコストを最小限にして撤退することができる

もしこういった機能をご希望でしたら、ぜひお問い合わせからご相談ください。
お待ちしております。😄✨

開発のご依頼お待ちしております
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします! by 九保すこひ

おわりに

ということで、今回は「Laravel + React」でウィッシュリスト機能をつくってみました。

正直なところ、個人サービスはお金をかけにくい側面もあると思うので、ランディングページとウィッシュリスト・フォームだけをつくってユーザーの反応を見るというのも良い作戦じゃないかと考えています。

また、たくさんアイデアがある場合も、アイデアごとにランディングページをサブドメインで大量につくり、ウィッシュリストの反応の良かったプロジェクトだけ実際にサイト化するというのも理にかなった方法かもしれませんね。(この場合ウェブ広告などを併用する必要があると思うので、多少コストはかかりますが…😅)

ぜひみなさんも色々とウィッシュリストの使い方を考えてみてくださいね。

ではでは〜❗

「姪っ子がいたずらで
驚かしてきた結果、
ちょっと腰をいためました😂」

このエントリーをはてなブックマークに追加       follow us in feedly