開発1 入会申請ステップ定義を言語化する

2026年02月07日

ここからがこのプロジェクトの本体です。
コードより先に、**“絶対にブレない設計物”**を作ります。


入会申請チャット:ステップ定義(確定版)

これは

  • React(既存チャット)
  • Laravel(StepEngine)
  • OpenAI(文章整形)

すべての共通言語になります。


0️⃣ 基本思想(最重要)

  • 1ステップ = 1判断
  • ステップは 順序 を持つが、番号では管理しない
  • 判定は Laravelのみ
  • Reactは 描画係
  • AIは 言い換え係

1️⃣ ステップ一覧(全体像)

intro

email

name

kana

school

grade

phone

participation_type

preferred_date_1

preferred_date_2

motivation

confirmation

completed

※ reject は どのステップからも起こり得る


2️⃣ ステップ定義テーブル(設計の核)

🟢 Step: intro

項目内容
keyintro
質問「これから入会申請の確認を行います。よろしいですか?」
入力yes / no
必須yes
判定no → 終了
email

🟢 Step: email

項目内容
keyemail
質問「メールアドレスを教えてください」
入力text
必須yes
バリデーションemail形式
NG時再質問
name

🟢 Step: name

項目内容
keyname
質問「生徒さんのお名前を教えてください」
入力text
必須yes
kana

🟢 Step: kana

項目内容
keykana
質問「お名前をカタカナで入力してください」
入力text
バリデーションカタカナ
school

🟢 Step: school

項目内容
keyschool
質問「通っている学校名を教えてください」
入力text
grade

🟢 Step: grade

項目内容
keygrade
質問「現在の学年を選んでください」
入力select
選択肢小1〜高3
phone

🟢 Step: phone

項目内容
keyphone
質問「連絡の取れる電話番号を教えてください」
入力text
バリデーション数字 / ハイフン可
participation_type

🟢 Step: participation_type

項目内容
keyparticipation_type
質問「参加形態を教えてください」
入力select
選択肢親子 / 生徒のみ
preferred_date_1

🟢 Step: preferred_date_1

項目内容
keypreferred_date_1
質問「第一希望の日程を教えてください」
入力date
preferred_date_2

🟢 Step: preferred_date_2

項目内容
keypreferred_date_2
質問「第二希望の日程を教えてください(なければ空欄でOK)」
入力date / empty
motivation

🟢 Step: motivation

項目内容
keymotivation
質問「参加のきっかけを教えてください」
入力textarea
必須no
confirmation

🟢 Step: confirmation

項目内容
keyconfirmation
質問「内容を確認し、送信してよろしいですか?」
入力yes / no
判定no → 差し戻し
completed

🟢 Step: completed

項目内容
keycompleted
表示「申請を受け付けました」
処理DB確定

3️⃣ 機械可読な定義(Laravel / React 共通)

{
  "key": "email",
  "type": "text",
  "required": true,
  "validation": ["required", "email"],
  "next": "name"
}

👉 これが StepEngine の燃料


4️⃣ この設計の強さ

  • フォームにも戻せる
  • LINE Bot にも流用できる
  • AIを切っても成立する
  • 法務・運用に説明できる
  • テストが書ける

次にやること(選択)

ここからは 実装に落とします
どこから行くかだけ決めましょう。

次の選択肢

A:このステップ定義を
👉 Laravel 用 StepDefinition.php に落とす

B:既存 React チャットに
👉 このステップ定義を差し込む方法

C:Laravel 側
👉 StepEngine(状態遷移ロジック)を書く

返事は A / B / C のどれかだけでOKです。
ここからは一気に“動くもの”にします 🚀

了解。
では A:Laravel 用 StepDefinition に落とします
ここは そのままコピペして使える設計コードを出します。


Laravel10:入会申請 StepDefinition 設計

目的はこれです👇

  • ステップ定義を1か所に集約
  • Controller / Service / Validator が共通で使える
  • React / OpenAI に引きずられない

① ディレクトリ構成(まず決め打ち)

app/
└─ Domain/
└─ Application/
├─ Step/
│ ├─ StepDefinition.php ← 今回作る
│ └─ StepKeys.php
└─ ApplicationFlow.php

👉 Domain 配下に置くのがポイント
👉 HTTP / UI から切り離す


② StepKeys(キーのenum化)

<?php
// app/Domain/Application/Step/StepKeys.php

namespace App\Domain\Application\Step;

final class StepKeys
{
    public const INTRO               = 'intro';
    public const EMAIL               = 'email';
    public const NAME                = 'name';
    public const KANA                = 'kana';
    public const SCHOOL              = 'school';
    public const GRADE               = 'grade';
    public const PHONE               = 'phone';
    public const PARTICIPATION_TYPE  = 'participation_type';
    public const PREFERRED_DATE_1    = 'preferred_date_1';
    public const PREFERRED_DATE_2    = 'preferred_date_2';
    public const MOTIVATION          = 'motivation';
    public const CONFIRMATION        = 'confirmation';
    public const COMPLETED           = 'completed';
}

③ StepDefinition(中核)

<?php
// app/Domain/Application/Step/StepDefinition.php

namespace App\Domain\Application\Step;

final class StepDefinition
{
    public static function all(): array
    {
        return [

            StepKeys::INTRO => [
                'type'      => 'confirm',
                'question'  => 'これから入会申請の確認を行います。よろしいですか?',
                'required'  => true,
                'next'      => StepKeys::EMAIL,
            ],

            StepKeys::EMAIL => [
                'type'       => 'text',
                'question'   => 'メールアドレスを教えてください',
                'required'   => true,
                'validation' => ['required', 'email'],
                'next'       => StepKeys::NAME,
            ],

            StepKeys::NAME => [
                'type'      => 'text',
                'question'  => '生徒さんのお名前を教えてください',
                'required'  => true,
                'next'      => StepKeys::KANA,
            ],

            StepKeys::KANA => [
                'type'       => 'text',
                'question'   => 'お名前をカタカナで入力してください',
                'required'   => true,
                'validation' => ['required', 'regex:/^[ァ-ヶー ]+$/u'],
                'next'       => StepKeys::SCHOOL,
            ],

            StepKeys::SCHOOL => [
                'type'      => 'text',
                'question'  => '通っている学校名を教えてください',
                'required'  => true,
                'next'      => StepKeys::GRADE,
            ],

            StepKeys::GRADE => [
                'type'     => 'select',
                'question' => '現在の学年を選んでください',
                'required' => true,
                'options'  => [
                    '小1','小2','小3','小4','小5','小6',
                    '中1','中2','中3',
                    '高1','高2','高3',
                ],
                'next' => StepKeys::PHONE,
            ],

            StepKeys::PHONE => [
                'type'       => 'text',
                'question'   => '連絡の取れる電話番号を教えてください',
                'required'   => true,
                'validation' => ['required', 'regex:/^[0-9\-]+$/'],
                'next'       => StepKeys::PARTICIPATION_TYPE,
            ],

            StepKeys::PARTICIPATION_TYPE => [
                'type'     => 'select',
                'question' => '参加形態を教えてください',
                'required' => true,
                'options'  => ['親子', '生徒のみ'],
                'next'     => StepKeys::PREFERRED_DATE_1,
            ],

            StepKeys::PREFERRED_DATE_1 => [
                'type'     => 'date',
                'question' => '第一希望の日程を教えてください',
                'required' => true,
                'next'     => StepKeys::PREFERRED_DATE_2,
            ],

            StepKeys::PREFERRED_DATE_2 => [
                'type'     => 'date',
                'question' => '第二希望の日程を教えてください(なければ空欄でOK)',
                'required' => false,
                'next'     => StepKeys::MOTIVATION,
            ],

            StepKeys::MOTIVATION => [
                'type'     => 'textarea',
                'question' => '参加のきっかけを教えてください',
                'required' => false,
                'next'     => StepKeys::CONFIRMATION,
            ],

            StepKeys::CONFIRMATION => [
                'type'     => 'confirm',
                'question' => '内容を確認し、送信してよろしいですか?',
                'required' => true,
                'next'     => StepKeys::COMPLETED,
            ],

            StepKeys::COMPLETED => [
                'type' => 'complete',
            ],

        ];
    }

    public static function get(string $stepKey): array
    {
        return self::all()[$stepKey] ?? [];
    }
}

④ ここまでで「できていること」

  • ステップ順序が 完全にコードで固定
  • バリデーションルールが 明示的
  • React / OpenAI 非依存
  • テストしやすい
  • 途中差し込み・分岐も可能

⑤ まだ「やっていないこと」(正解)

  • DB保存
  • セッション管理
  • 状態遷移ロジック
  • OpenAI文章生成

👉 全部、次のフェーズ


次の一手(選択)

ここからは 動かす層に入ります。

  • B:既存 React チャットに
     👉 この StepDefinition をどう流すか
  • C:Laravel 側
     👉 StepEngine(状態遷移+validation)実装

返事は B / C のどちらかでOKです。
次は **「動き出す瞬間」**です 🚀

では B:既存 React チャットに、この StepDefinition を“そのまま刺す”方法をやります。

ポイントは一つだけ👇

Reactは StepDefinition を「理解」しない
Reactは StepDefinition を「表示」するだけ


React 側の役割(再確認)

React は チャットUIエンジンであって、

  • 次は何を聞くか?
  • 入力は正しいか?
  • 次のステップは?

👉 一切考えません

それはすべて Laravel が決めて返す


全体の通信イメージ(確定)

React → Laravel

POST /api/application/chat
{
  "session_id": "uuid",
  "message": "入力値"
}

Laravel → React

{
  "step": "email",
  "question": "メールアドレスを教えてください",
  "type": "text",
  "options": null,
  "status": "waiting"
}

or

{
  "step": "completed",
  "status": "completed"
}

👉 ReactはこのJSONを描画するだけ


① React 側:Stateの最小構成

既存 DiagnosisChat.jsx を前提に書きます。

const [messages, setMessages] = useState([]);
const [currentStep, setCurrentStep] = useState(null);
const [inputType, setInputType] = useState('text');
const [options, setOptions] = useState([]);

② API 通信関数(最重要)

async function sendMessage(message) {
  const res = await fetch('http://localhost:8003/api/application/chat', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      session_id: sessionId,
      message
    })
  });

  return await res.json();
}

③ Laravel レスポンスをそのまま反映

const handleSend = async (userMessage) => {

  // ユーザー発言を追加
  setMessages(prev => [...prev, {
    role: 'user',
    content: userMessage
  }]);

  const data = await sendMessage(userMessage);

  // 質問を表示
  if (data.question) {
    setMessages(prev => [...prev, {
      role: 'system',
      content: data.question
    }]);
  }

  setCurrentStep(data.step);
  setInputType(data.type || 'text');
  setOptions(data.options || []);
};

👉 判定ゼロ
👉 分岐ゼロ


④ 入力UIの切り替え(typeベース)

{inputType === 'text' && (
  <TextInput onSend={handleSend} />
)}

{inputType === 'select' && (
  <SelectInput options={options} onSelect={handleSend} />
)}

{inputType === 'confirm' && (
  <ConfirmButtons onSelect={handleSend} />
)}

{inputType === 'date' && (
  <DateInput onSend={handleSend} />
)}

👉 typeLaravel が決める


⑤ 「戻る」「修正」は考えない(今は)

  • 確認画面でまとめてやる
  • ステップ途中での戻りは 後回し

👉 まずは 一本道を完成させる


⑥ 既存 DiagnosisChat からの変更点まとめ

項目変更
questions_phaseX.js❌ 削除
Phase コンポーネント❌ 不要
質問文❌ Reactに持たない
判定ロジック❌ Reactに書かない
APIレスポンス✅ 主役

⑦ React 側の完成条件

これができていれば 成功👇

  • React は API レスポンスだけ見ている
  • StepDefinition を知らない
  • message / question / type を描画するだけ
  • Laravel を止めると何も進まない(正しい)

次の一手(確定)

次は C:Laravel StepEngine です。

ここでやること:

  • session_id 管理
  • current_step 保持
  • StepDefinition を使った遷移
  • validation
  • JSON レスポンス生成

👉 ここが心臓部

進めてよければ、次はそのまま
C:StepEngine 実装に入ります。

了解。
ここからが本丸です。
**Laravel 側 StepEngine(状態遷移+検証+レスポンス生成)**を、
そのまま使える形で出します。


Laravel10:Application StepEngine 実装

目的はこれ👇

  • セッション単位で進行管理
  • StepDefinition に完全依存
  • React は何も考えなくていい
  • AI なしでも成立

① 全体構成(最終形)

app/
└─ Domain/
   └─ Application/
      ├─ Step/
      │  ├─ StepDefinition.php   ← 既存
      │  ├─ StepKeys.php         ← 既存
      │  └─ StepEngine.php       ← ★ 今回
      └─ ApplicationSession.php  ← セッション状態

② ApplicationSession(状態保持)

DBに入れる前の 最小モデル です。

<?php
// app/Domain/Application/ApplicationSession.php

namespace App\Domain\Application;

class ApplicationSession
{
    public string $sessionId;
    public string $currentStep;
    public array $answers = [];

    public function __construct(string $sessionId)
    {
        $this->sessionId = $sessionId;
        $this->currentStep = 'intro';
    }

    public function setAnswer(string $step, $value): void
    {
        $this->answers[$step] = $value;
    }

    public function getAnswer(string $step)
    {
        return $this->answers[$step] ?? null;
    }

    public function moveTo(string $nextStep): void
    {
        $this->currentStep = $nextStep;
    }
}

※ 最初は DB保存しない
※ Redis / DB はあとで差し替え


③ StepEngine(心臓部)

<?php
// app/Domain/Application/Step/StepEngine.php

namespace App\Domain\Application\Step;

use App\Domain\Application\ApplicationSession;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;

class StepEngine
{
    public function handle(
        ApplicationSession $session,
        $input
    ): array {
        $stepKey = $session->currentStep;
        $definition = StepDefinition::get($stepKey);

        // 完了済み
        if ($stepKey === StepKeys::COMPLETED) {
            return [
                'status' => 'completed',
                'step'   => StepKeys::COMPLETED,
            ];
        }

        // バリデーション
        if (!empty($definition['validation'])) {
            $this->validate($definition['validation'], $input);
        }

        // 値保存
        $session->setAnswer($stepKey, $input);

        // 次のステップへ
        $nextStep = $definition['next'] ?? StepKeys::COMPLETED;
        $session->moveTo($nextStep);

        $nextDef = StepDefinition::get($nextStep);

        return [
            'status'   => 'waiting',
            'step'     => $nextStep,
            'type'     => $nextDef['type'] ?? null,
            'question' => $nextDef['question'] ?? null,
            'options'  => $nextDef['options'] ?? null,
        ];
    }

    protected function validate(array $rules, $value): void
    {
        $validator = Validator::make(
            ['value' => $value],
            ['value' => $rules]
        );

        if ($validator->fails()) {
            throw new ValidationException($validator);
        }
    }
}

👉 ロジックは完全に StepDefinition 駆動


④ Controller(API入口)

<?php
// app/Http/Controllers/ApplicationChatController.php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Domain\Application\ApplicationSession;
use App\Domain\Application\Step\StepEngine;
use Illuminate\Validation\ValidationException;

class ApplicationChatController extends Controller
{
    protected static array $sessions = [];

    public function __invoke(Request $request)
    {
        $sessionId = $request->input('session_id');
        $message   = $request->input('message');

        if (!$sessionId) {
            return response()->json(['error' => 'session_id required'], 400);
        }

        if (!isset(self::$sessions[$sessionId])) {
            self::$sessions[$sessionId] = new ApplicationSession($sessionId);
        }

        $session = self::$sessions[$sessionId];
        $engine  = new StepEngine();

        try {
            return response()->json(
                $engine->handle($session, $message)
            );
        } catch (ValidationException $e) {
            return response()->json([
                'status'   => 'error',
                'step'     => $session->currentStep,
                'message'  => '入力内容を確認してください',
                'errors'   => $e->errors(),
            ], 422);
        }
    }
}

⑤ Route 定義

// routes/api.php

use App\Http\Controllers\ApplicationChatController;

Route::post('/application/chat', ApplicationChatController::class);

⑥ ここまでで「動くもの」

  • React → /api/application/chat
  • Laravel が:
    • 今の step を判断
    • validation
    • 次の質問を返す
  • React は描画だけ

👉 完全に設計どおり


⑦ 今は“あえて”やっていないこと(正解)

  • DB保存
  • セッション永続化
  • 差し戻し
  • OpenAI
  • 確認画面生成

👉 一本道を完成させるのが最優先


次の段階(選択)

ここからは 仕上げフェーズです。

次の選択肢

D:セッションを DB / Redis に永続化
E:確認ステップ(summary)生成
F:OpenAI を質問文整形として組み込み

返事は D / E / F のどれかでOKです。
ここまで来たら、ほぼ完成です 🚀

最新のお知らせ

thumb
2026年4月29日
AI-Native DDD 技術スタック統合仕様書:WSL/Docker環境における自律型開発プロトコル

1. 開発理念とAI-Native DDDの定義 現代のエンジニ...

thumb
2026年4月28日
AI連携用語事典:AI-Native DDDで切り拓く次世代開発手法

1. はじめに:混乱を解きほぐす「2つのDDD」 IT業界で「...

thumb
2026年4月27日
AIネイティブ・ドキュメント駆動開発(AI-DDD)標準規約

1. はじめに:AI-DDDの基本理念と目的 現代のソフトウェ...

No Image
2026年4月26日
AI-DDD(AIネイティブ・ドキュメント駆動開発)完全ガイド:情報の「蒸留」から「昇華」まで

1. はじめに:なぜ「コードを書く前」が一番大切なのか?...

thumb
2026年4月25日
NotebookLM:「深い洞察」を引き出す10のプロンプト

1. 教科書や文献から「深い洞察」を引き出す10のプロンプト...

No Image
2026年4月25日
🚀 GPT-5.5 の主な進化点

GPT-5.5の登場、非常にエキサイティングですね!2026年4月現在...

thumb
2026年4月24日
AI-DDD(AIネイティブ・ドキュメント駆動開発)完全ガイド:情報の「蒸留」から「昇華」まで

1. はじめに:なぜ「コードを書く前」が一番大切なのか?...

thumb
2026年4月23日
開発者を惹きつけてやまない「Vue.js」の正体:単なるフレームワークを超えた3つの驚き

1. イントロダクション:フロントエンド迷子のための処方箋...

thumb
2026年4月21日
AIによる次世代業務効率化ガイド

AIを「単なる道具」から「最強のパートナー」へ変える5つの...

thumb
2026年4月21日
NotebookLM:高度なプロンプト設計と活用戦略に関するブリーフィング・ドキュメント

エグゼクティブ・サマリー Googleが提供するNotebookLMは、...