# web：インフルエンサープラットフォームアプリケーション

本書は[README.md](README.md)の日本語訳です。英語版との間に相違がある場合は、英語版を正とします。

## 1. 概要

本アプリケーションは、アフィリエイトプラットフォームの`web`アプリケーションです。インフルエンサー、加盟店（広告主）、オペレーターの3つのポータルを持つLaravelアプリケーションです。各ポータルは、独自のログイン、独自のページ、そして`/api/influencer/`、`/api/merchant/`、`/api/operator/`配下の独自のJSON APIを持ちます。ページはBladeテンプレートで、対話的な部分はVue 2.7のコンポーネントです。これらはViteによってポータルごとに1つのバンドルへビルドされます。コードは外部委託先が作成したもので、Laravel 13.39、PHP 8.4、PHPUnit 11.5、Vite 7、Vue 2.7.16にアップグレード済みであり、アセットはNode 24でビルドします。Docker環境（MySQL、php-fpm、nginx、外部APIの代替サービス、アセットビルド用コンテナ、ブラウザスモークテスト用コンテナ）は、プロジェクトルート（このフォルダを含むフォルダ）にある別リポジトリです。姉妹アプリケーションの`tracking`も同じ場所にありますが、本書では扱いません。

## 2. 要件

| 項目 | 要件 |
| --- | --- |
| PHP | 8.4.1以降です（`composer.json`は`^8.4`を要求し、ロックされたパッケージは8.4.1を必要とします）。 |
| PHP拡張 | `pdo_mysql`、`mbstring`、`fileinfo`、`ctype`、`filter`、`hash`、`session`、`tokenizer`、`openssl`、`zip`、`curl`、`xml`、`sodium`。`memcached`はセッションまたはキャッシュで使う場合のみ必要です。 |
| Composer | 2.xです（Dockerイメージは2.11を使用します）。 |
| Node.jsとnpm | Node 24とnpm 11です（DockerイメージはNode 24.22.2と、それに同梱のnpm 11を使用します）。アセットのビルドにのみ必要で、実行時には不要です。 |
| データベース | `utf8mb4`と`utf8mb4_unicode_ci`を使うMySQL 8.0です。 |
| Webサーバー | ドキュメントルートは`public/`です。ファイルでないリクエストはすべて`public/index.php`に渡します。名前がドットで始まるファイルは拒否します（`.well-known`を除く）。環境リポジトリの`docker/nginx/web.conf`が動作する例です（アップロード上限20 MB、背後にphp-fpm）。 |
| 書き込み可能なパス | `storage/`、`bootstrap/cache/`、`/var/log/laravel/`です（ログのパスは`config/logging.php`に書かれています）。 |

- Dockerイメージは公式の`php:8.4-fpm`イメージを土台にして`pdo_mysql`と`zip`を追加します。表のその他の拡張は公式イメージに含まれているものであり、他のホストでも必ず有効にしてください。
- `lcobucci/jwt`は、Rakutenログインのトークンのために`sodium`を必要とします。
- セッションとキャッシュ：`config/session.php`と`config/cache.php`の既定値は`file`で、`.env.example`も同じです（`SESSION_DRIVER`、`CACHE_DRIVER`。セッションの有効時間は`SESSION_LIFETIME`で、120分です）。Memcachedストアは`config/cache.php`に設定されていますが、Docker環境では一度も動かしていません。ログインのロックアウトカウンターはキャッシュストアに保存されるため、サーバーごとに別々のストアを使うと、上限がサーバー台数倍になります。
- アプリケーションは`Asia/Tokyo`のタイムゾーンと`ja`のロケールで動作します（どちらも`config/app.php`で固定されています）。

## 3. 設定

例のファイルをコピーしてから、アプリケーションキーを生成します。

```shell
cp .env.example .env
php artisan key:generate
```

既存の環境では、現在の`APP_KEY`をそのまま使ってください。`.env`はgitの管理対象外です。コミットせず、キー、パスワード、証明書をリポジトリに入れないでください。`.env.example`の値はローカルのDocker環境専用です。本番環境の値はチームから受け取ります。

### 3.1変数

| 変数 | 意味 |
| --- | --- |
| `APP_NAME`、`APP_URL`、`ASSET_URL` | アプリケーション名、アプリケーションの公開ベースURL、アセット用の別ベースURL（任意）です。 |
| `APP_ENV` | `production`、`staging`、`local`のいずれかです。他の値ではブート時にアプリケーションが停止します（3.2を参照）。 |
| `APP_KEY` | 暗号化キーです。`php artisan key:generate`で設定します。 |
| `APP_DEBUG` | `true`にするとデバッグページが表示されます。本番環境では必ず`false`にします。 |
| `TRACKING_APP_URL` | `tracking`アプリケーションのベースURLです。 |
| `LOG_CHANNEL`、`LOG_DEPRECATIONS_CHANNEL` | ログチャンネルと、PHPの非推奨（deprecation）を受け取るチャンネルです（未設定の場合、非推奨はどこにも書かれません）。 |
| `LOG_SLACK_WEBHOOK_URL`、`LOG_STDERR_FORMATTER` | Slackログチャンネルとstderrログチャンネルの設定です。 |
| `DB_CONNECTION` | データベースドライバーで、`mysql`です。 |
| `DB_MASTER_HOST`、`DB_MASTER_PORT`、`DB_DATABASE`、`DB_USERNAME`、`DB_PASSWORD` | 書き込み用の接続です。 |
| `DB_SLAVE_HOST`、`DB_SLAVE_PORT`、`DB_SLAVE_DATABASE`、`DB_SLAVE_USERNAME`、`DB_SLAVE_PASSWORD` | 読み取り用の接続（`mysql_read`）です。書き込み用と同じサーバー・データベースを指しても構いません。 |
| `DB_FOREIGN_KEYS`、`MYSQL_ATTR_SSL_CA` | 外部キーのチェック（このアプリケーションが使わない`sqlite`接続だけが読みます）と、MySQLへのTLS接続に使うCAファイルのパスです。 |
| `CACHE_DRIVER`、`SESSION_DRIVER`、`SESSION_LIFETIME`、`BROADCAST_DRIVER` | キャッシュストア、セッションストア、セッションの有効時間（分）、ブロードキャストドライバーです。 |
| `QUEUE_CONNECTION` | キュー接続です。`sync`ではジョブとキュー対象のメールがリクエストの中で実行されます（セクション5のステップ14を参照）。 |
| `MAIL_MAILER`、`MAIL_HOST`、`MAIL_PORT`、`MAIL_USERNAME`、`MAIL_PASSWORD`、`MAIL_ENCRYPTION` | メールのトランスポートです。`MAIL_DRIVER`は`MAIL_MAILER`の代替としてまだ読み込まれますが、`MAIL_MAILER`を使ってください。 |
| `MAIL_FROM_ADDRESS`、`MAIL_FROM_NAME`、`MAIL_ERROR_NOTICE_ADDRESS` | 送信者と、エラー通知の宛先です。 |
| `MAIL_DEBUG` | `true`にすると、オペレーターの一括メッセージが10通で止まります（ステージング環境とローカル実行用）。 |
| `FILESYSTEM_DRIVER`、`FILESTORE_DRIVER`、`FILESTORE_ROOT`、`FILESTORE_URL` | 既定のディスクと、アップロードに使う`filestore`ディスクです。 |
| `PASSWORD_VALID_DAYS`、`BCRYPT_ROUNDS` | パスワードの有効日数（`config/password_policy.php`）と、bcryptのコストです。`BCRYPT_ROUNDS`は明示的に設定してください。 |
| `OBIC_API_URL`、`OBIC_API_KEY` | OBIC支払APIのアドレスとキーです。キーは環境からのみ読み込まれます。 |
| `RAKUTEN_CLIENT_ID`、`RAKUTEN_CLIENT_SECRET`、`RAKUTEN_AUTHORIZATION_ENDPOINT`、`RAKUTEN_TOKEN_ENDPOINT` | Rakutenログインの設定です。同じ4つの名前に接尾辞`_OMNI`を付けたものが、2つ目のRakutenクライアント用にあります。 |
| `GOOGLE_CLIENT_ID`、`GOOGLE_CLIENT_SECRET` | Googleログインの設定です。 |
| `MIX_APP_URL` | アセットのビルドが読み込む値です（実行時には読み込まれません）。JavaScriptバンドルに書き込まれる公開ベースURLです。詳細はセクション4にあります。 |

### 3.2本番環境で必ず設定する値

- `APP_ENV=production`。アプリケーションが受け付けるのは`production`、`staging`、`local`のみです（`app/Providers/AppServiceProvider.php`）。それ以外の値ではブート時に`unknown app.env`の例外を投げます。`production`または`staging`では、生成されるすべてのURLが`https`になります。`APP_ENV`が未設定の場合、値は`production`です。
- `APP_DEBUG=false`。
- `APP_URL`、`DB_*`（masterとslaveの両方の名前）、`MAIL_*`（`MAIL_DRIVER`ではなく`MAIL_MAILER`を使います。`MAIL_FROM_ADDRESS`と`MAIL_ERROR_NOTICE_ADDRESS`も設定します）、`LOG_CHANNEL`。
- `OBIC_API_KEY`：本番サーバーの`.env`に必ず設定します。かつてコードにコミットされていたキーはコードから削除済みです。ローテーションしてください。`OBIC_API_URL`は未設定の場合、本番のOBICホストが既定値になります。そのため、他のすべての環境（ステージング環境、ローカル）では、代替サービスを明示的に設定してください。
- `LOG_DEPRECATIONS_CHANNEL`：デプロイ後の最初の数日間は、実在するチャンネルを設定してください。Laravel 8以降、PHPの非推奨は既定ではどこにも書かれません。どのテストも通らない経路は、ここでしか確認できません。
- `MIX_APP_URL`：アセットビルドの環境に設定します（セクション4）。稼働中のアプリケーションには設定しません。
- `QUEUE_CONNECTION`、`SESSION_DRIVER`、`CACHE_DRIVER`、`FILESYSTEM_DRIVER`、および`RAKUTEN_*`と`GOOGLE_*`の値を、本番システムと照らして確認してください。引用符で囲んだ値では、シングルクォートの中の`${NAME}`に頼らないでください。もう展開されません。

### 3.3テスト環境

PHPのテストは`.env`を読み込みません。必要な値はすべて`phpunit.xml`が設定します（データベース`web_test`、`APP_ENV=local`、配列キャッシュとセッション、外部APIの代替サービス、ダミーのキー）。`bootstrap/cache/config.php`がある間はテストを実行しないでください。`phpunit.xml`ではなく、キャッシュされた設定が使われてしまいます。

## 4. インストールとビルド（開発）

```shell
composer install
npm ci
MIX_APP_URL=http://localhost:8081 npm run development
```

`package.json`のスクリプト：

| スクリプト | 動作 |
| --- | --- |
| `npm run development`（`npm run dev`でも可） | `vite build --mode development`：1回のビルドで、圧縮せず、Vueの警告を残します。 |
| `npm run production`（`npm run prod`でも可） | `vite build`：1回のビルドで、圧縮し、Vueの警告を除きます。リリースにはこれを使います。 |
| `npm run watch` | `vite build --watch --mode development`：ソースファイルが変わるたびに再ビルドします。 |
| `npm run watch-poll` | 同じ動作を1000ミリ秒間隔のポーリングで行います。変更イベントを送らないファイルシステム（バインドマウント）向けです。 |
| `npm run hot` | `vite`：Viteの開発サーバーです。アプリケーションのコンテンツセキュリティポリシーがこれを拒否するため、緩和したポリシーが必要ですが、それは今回の納品に含まれていません。`npm run watch`を使ってください。 |

- ビルドは`public/build/`と、マニフェスト`public/build/manifest.json`を書き出します（ハッシュ付きのファイル名は`public/build/assets/`配下、すべてのスクリプトにソースマップが付きます）。`public/build`は生成物です。gitの管理対象外で、コミットしません。`public/js`と`public/css`の手書きファイルは管理対象です。
- ビルドがないと、Bladeテンプレートがマニフェストを読み込むため、ポータルのページはサーバーエラーになります。開発サーバーを動かしているとき以外は`public/hot`が存在してはいけません。残っていると、ページが開発サーバーにスクリプトを要求します。
- `MIX_APP_URL`は必須です。この名前は以前のビルドツールに由来し、そのまま残されています。未設定の場合、ビルドは`MIX_APP_URL is not set`というメッセージで停止します。ビルドの環境変数として（上の例のように）、または`.env`の`MIX_APP_URL=`の行として設定します。値は末尾のスラッシュを付けないアプリケーションの公開ベースURLで、たとえば`https://your-host.example`です。この値はソースの5か所で読み取られ、バンドルでは値そのものに置き換えられるため、環境ごとにビルドが必要です。
- `npm ci`は`package-lock.json`に書かれているとおりにインストールします。環境リポジトリは`npm ci --ignore-scripts`を実行します。
- 環境リポジトリは、固定されたNode 24のコンテナ内で`bash docker/web-assets.sh production`（または`development`）によりビルドします。このコンテナは`MIX_APP_URL=http://localhost:8081`を設定します。

## 5. デプロイ／リリースのチェックリスト（本番環境）

1. ビルド用ホストとサーバーで、納品されたタグまたはブランチをチェックアウトします。サーバーにはPHP 8.4.1以降が必要です（セクション2）。
2. 開発用パッケージなしでPHPパッケージをインストールします：`composer install --no-dev --optimize-autoloader`。本番環境には開発用パッケージを決してインストールしないでください。`laravel/dusk`は`production`以外で認証なしの`_dusk`ログインルートを登録し、Ignitionは`_ignition`ルートを登録します。
3. アセットをビルドします（Node 24のあるホストで、CIでも実行できます）：`npm ci`、続いて`MIX_APP_URL=https://your-host.example npm run production`。対象環境の実際の公開ベースURLを使ってください。
4. `public/build/`をサーバーにアップロードします。そこに`public/hot`が存在しないことを確認します。古いビルドのファイル（`mix-manifest.json`、`public/js/influencerApp.js`など）は不要です。
5. サーバーの`.env`をセクション3.2に照らして確認します。
6. 実行前にマイグレーションを確認します：`php artisan migrate:status`。納品物には、同じクラス名`AddReplyIdToMessageContentsTable`を宣言するマイグレーションファイルが2つあります（`2026_01_23_110844_...`と`2026_03_18_165600_add_reply_id_to_message_contents_table.php`）。PHPは同じクラスを2回宣言できないため、両方のファイルが未実行の状態（たとえば空のデータベース）では、素の`php artisan migrate`は致命的エラーで停止します。Docker環境の固定スキーマには1月の定義が含まれています。本番データベースがどちらを持っているかは分かっていません。マイグレーションの前に`migrations`テーブルで確認し、どちらのコピーを残すかを決めてください。
7. マイグレーションを実行します：`php artisan migrate --force`。データベーススキーマはマイグレーションから作られます。テストはその固定コピー（環境リポジトリの`docker/schema.sql`）を使うため、新しいマイグレーションを追加したときは、そこの固定スキーマも更新する必要があります（セクション6.3）。
8. 古いパッケージ検出ファイルを削除します：`bootstrap/cache/`の`*.php`ファイルを削除します（`.gitignore`は除く）。たとえば`rm -f bootstrap/cache/*.php`です。
9. コンパイル済みビューを消去します：`php artisan view:clear`。
10. キャッシュ：`php artisan config:cache`は使用できます（ゲートは実行のたびにこれを実行し、`config/`の外のコードは`env()`を呼びません）。`php artisan route:cache`は決して実行しないでください。7つのルート名が複数のルートで共有されており（たとえば`influencer.login.rakuten.callback`と`operator.operator.create`）、Laravelはルートをキャッシュするとき重複した名前を拒否するため、失敗します。誤って実行した場合は`php artisan route:clear`を実行してください。
11. 新しいコードが読み込まれるように、php-fpmを再起動するか、OPcacheをリセットします。
12. `storage/`と`bootstrap/cache/`が、php-fpmのユーザーとcronジョブを実行するユーザーの両方から書き込み可能であること、また`/var/log/laravel/`が存在し、両方から書き込み可能であることを確認します。
13. スケジューラー：`app/Console/Kernel.php`がバッチコマンドをスケジュールしています（キャンペーンのメール、報酬とクリック報酬の計算、キャンペーンの終了、請求データと支払データ、キャンペーンの延長、加盟店とインフルエンサー向けのメール、15分ごとの成果の一括登録、メッセージ添付ファイルの期限切れ削除）。アプリケーションを所有するユーザーに、このフォルダのパスを入れた標準のcron行を追加します：

```shell
* * * * * cd /path/to/web && php artisan schedule:run >> /dev/null 2>&1
```

14. キュー：既定の`QUEUE_CONNECTION`は`sync`で、ワーカーは不要です。アプリケーションにはキュー対象のジョブが1つ（`CreateMerchantReport`）と、`ShouldQueue`を実装した多数のメールクラスと通知クラスがあります。本番の`.env`が`QUEUE_CONNECTION`を`database`または`redis`に設定している場合は、プロセス管理ツールの下でワーカー（`php artisan queue:work`）を動かす必要があります。また、ジョブやメールのクラスを変更するデプロイの前に、キューを空にしてください。判断する前に、本番の設定値を確認してください。スケジューラーはワーカーを起動しません（Kernelにあるそのための行はコメントアウトされています）。
15. 最初の数日間：`LOG_DEPRECATIONS_CHANNEL`が指すチャンネル、`/var/log/laravel/`のログ、`MAIL_ERROR_NOTICE_ADDRESS`に送られるメールを監視します。エラーメールとログ行には、データベースのホスト、ポート、スキーマ名、バインドされた値が含まれます。誰が受け取るかを確認してください。デプロイの前に、ステージング環境のホストから本番のメールリレー経由で実際のメールを1通送信してください。
16. デプロイ後のスモークテスト：
    - `/influencer/login`、`/merchant/login`、`/operator/login`がステータス200で応答し、ログインフォームが表示されます。
    - インフルエンサーとオペレーターのログインページのソースで、バンドルが`/build/assets/...`から要求され、そのリクエストが200で応答されます。`/build/manifest.json`が配信されます。
    - ログイン後、各ポータルのトップページが開き、Vueのルートがマウントされています（`{{ }}`のテキストや、`influencer-`、`merchant-`、`operator-`で始まるタグがページに残っていません）。
    - 古いパス`/mix-manifest.json`と`/js/influencerApp.js`は404で応答し、`/.env`のようなドットファイルは拒否されます。
    - `/test/*`ルートにはリクエストしないでください（セクション9を参照）。

## 6. テストの実行

テストはDocker環境（このフォルダの1つ上のフォルダにあるルートリポジトリ）で実行します。そこでWeb系のコンテナだけを起動します。手順は[WEB-UPGRADE-REPORT.md](../WEB-UPGRADE-REPORT.md)のセクション3にあります。たとえば`docker compose --profile web up -d --build --no-deps web web-nginx`です（制限なしにcomposeプロジェクト全体を起動しないでください。trackingのコンテナが同じプロジェクトを共有しています。`--no-deps`を付ける場合は`mysql`と`obic-stub`がすでに起動している必要があります。初回の起動には、プロジェクトルートのREADMEにある`docker compose --profile web up -d --build`を使ってください）。以下のコマンドは、このフォルダから`web`コンテナの中で実行します。たとえば`docker compose --profile web exec -T web vendor/bin/phpunit`です。

### 6.1 PHPテストスイート

- `web_test`という名前の実在のMySQLデータベースが必要です。`phpunit.xml`が接続を固定しています（ホスト`mysql`、データベース`web_test`、DockerのMySQLサービスのユーザーとパスワード、masterとslaveの両方の名前）。データベースは、環境リポジトリの固定スキーマ`docker/schema.sql`から`bash docker/web-db-reset.sh`で作成されます。このスクリプトは手動利用向けの`web_app`も再作成します。Dockerとは異なるデータベースサーバーを使う場合は、専用の環境変数（`DB_HOST`、`DB_MASTER_HOST`、`DB_DATABASE`など）が必要です。実際の環境変数は`phpunit.xml`より優先されます。
- 名前が`web_`で始まり、実データを含まないデータベースだけを使ってください。各テストは終了時にロールバックされるトランザクションの中で実行されますが、シーダーと一部のヘルパーは固定の行を書き込みます。重要なデータベースにスイートを向けてはいけません。`ExternalHttpGuardTest`は、それ以外のデータベース名を拒否します。
- 可能な限り、PHPUnitのプロセスは1つずつ実行してください。

```shell
vendor/bin/phpunit
vendor/bin/phpunit tests/Feature/HarnessTest.php
vendor/bin/phpunit --filter testTheApplicationRunsAsLocal
vendor/bin/phpunit --group laravel-13-only
vendor/bin/phpunit --exclude-group laravel-13-only
vendor/bin/phpunit --order-by=random
vendor/bin/phpunit --order-by=random --random-order-seed=12345
```

- 「グリーンかつクリーン（pristine）」が基準です。失敗、エラー、警告、非推奨、リスキー、スキップ、未完了のテストが1つもない状態を指します。`phpunit.xml`は、通知と警告があると実行を失敗にします。ベースクラス`Tests\TestCase`は、テスト中にPHPがアプリケーションのファイルについて非推奨を報告した場合、または任意の場所から"Passing null to parameter"が報告された場合も、そのテストを失敗にします。Laravelはテスト中のPHPの非推奨を隠すためです。
- 既定の実行では、アプリケーションが離れたバージョンのグループ（たとえば`laravel-12-only`）と、グループ`built-assets`を除外します。除外されるグループは`phpunit.xml`に列挙されています。
- 実際のアセットビルドを必要とするテストには、グループ`built-assets`が付いています。`npm run production`（セクション4）の後に、`vendor/bin/phpunit --group built-assets --exclude-group mix-only`で実行します。
- テストを追加した後は、スイート全体を`--order-by=random`で1回実行してください。すべてのテストが、単独でも、1ファイルでも、どんな順序でも成功する必要があります。

### 6.2 Nodeヘルパーテスト

`resources/js`にあるフロントエンドのヘルパースクリプトのテストは、Node自身の`assert`と小さなランナーを使います。追加パッケージは不要です。

```shell
node tests/js/run.js
node tests/js/run.js insert
```

- 省略可能な引数を渡すと、ファイル名またはテスト名にそれを含むテストだけを実行します。テストが失敗した場合、またはテストが1件も見つからない場合、ランナーはステータス1で終了します。
- 特定のツールバージョンでのみ真となるテストには、グループを付けます：`test(name, fn, { group: 'node-22-only' })`。`tests/js/excluded-groups.txt`に、ランナーが除外するグループを1行に1つずつ列挙します（`#`はコメントの開始）。出力の最終行がその件数を示します。
- 一部のテストは`public/build`にある本番ビルドを読み込むため、先にビルドしてください（セクション4）。これらは`MIX_APP_URL=http://localhost:8081`でビルドしたものに対して書かれています。これはDockerのビルドコンテナが使う値です。実行時も同じ値を使ってください。
- `tests/js/node-24.test.js`に固定したファイルのハッシュは、ソースファイルの改行がLFであることを前提としています。ビルドはソースのテキストを出力にコピーするためです。リポジトリの`.gitattributes`が`eol=lf`を指定しているので、通常のチェックアウトであれば問題ありません。ファイルがCRLFに変換されている場合（エディターや、Windowsの`core.autocrlf`設定による）、ビルドに問題がなくてもこの1つのテストが失敗します。
- Docker環境では：`docker compose --profile web-assets run --rm -T web-node node tests/js/run.js`。

### 6.3ブラウザスモークテストとワンコマンドのゲート

どちらも環境リポジトリにあり、このフォルダではなくそのルートから実行します。

- ブラウザスモークテスト：`bash docker/web-browser-smoke.sh`は本番アセットをビルドし、3つのポータルのページをヘッドレスChromium（Playwright。外へ出られないDockerの内部ネットワーク上のコンテナ内）で読み込み、その後ビルドを取り除きます。`bash docker/web-browser-smoke.sh --no-build`は既存のビルドを使い、`operator`のような名前の一部を渡すと、それを含むチェックだけを実行します。先に、ブラウザイメージを1回ビルドしてください：`docker compose --profile web-browser build web-browser`。チェックは`tests/e2e`にあります。ライブラリのバージョンに依存する部分は`tests/e2e/expectations.js`にあります。
- ゲート：`bash docker/web-gate.sh`は、すべてを1つのコマンドで実行します。composerのvalidateとaudit、`php -l`、ルートとコマンドの数、設定・ルート・ビューのキャッシュ（`route:cache`は失敗するのが期待値です）、PHPスイート全体の単独実行とランダム順実行、大文字小文字のチェック、ガードテスト、本番アセットのビルド、built-assetsテスト、Nodeテスト、マイグレーションチェック、nginxに対するライブスモークテスト、ブラウザスモークテスト、そして実行後に何も残していないことの確認です。`--quick`はアセットビルド、built-assetsテストとNodeテスト、ランダム順の実行、ブラウザスモークテストを省略します。`--ledger`は台帳を表示するだけです。ゲートは、`web/`に未コミットの変更がある場合、ほかが`web_test`を使っている場合、`bootstrap/cache`にキャッシュファイルがある場合には、起動を拒否します。
- 個別のチェック：`bash docker/migrate-check.sh`（空の一時データベースでマイグレーションを実行し、結果を`docker/schema.sql`と比較します）、`bash docker/web-smoke.sh`（ポート8081のnginxに対するHTTPチェック）、`bash docker/web-case-check.sh`（大文字小文字を区別するコピー上でのファイル名の大文字小文字。Linuxサーバーは区別しますが、開発用ディスクは区別しません）。

### 6.4台帳とtests/README.md

- 台帳とは、いくつのルートとアプリケーションクラスが、いずれかのテストのドキュメントコメントにある`@route`または`@subject`タグで名指しされているかを数えたものです。静的な指標であり、行カバレッジではありません。
- `bash docker/web-gate.sh --ledger`は、タグのないルートとクラスとともに、これを表示します。`LedgerAnnotationsTest`は、何も指していないタグがあると失敗します。
- [tests/README.md](tests/README.md)は詳細なリファレンスです。ヘルパー、ファクトリー、テストを書くときのルール、バージョンのグループ、各アップグレードで何が変わったかを扱います。

### 6.5所要時間と引き渡し時の件数

| 項目 | 値 |
| --- | --- |
| PHPスイート | 5,937件のテスト、63,402件のアサーション。所要時間は約2〜3分 |
| Nodeヘルパーテスト | 83件が成功（グループにより別に44件を除外） |
| ブラウザスモークテスト | 15項目のチェック |
| ゲート | 28項目のチェック（`--quick`では22項目または23項目）。所要時間は約450秒 |

## 7. コードを安全に書き換えるために

- 新しいテストにはすべて、その目的を示すタグを付けます。ドキュメントコメントのタグ`@route GET /exact/uri/{param}`と`@subject Fully\Qualified\Class`が台帳に反映されます。PHPUnitが読み込むものはすべて属性にします：`#[Group('...')]`、`#[DataProvider('...')]`、`#[Test]`。PHPUnit 11は、自身が読み込むドキュメントコメントのタグを非推奨として報告し、実行がクリーンでなくなります。
- 特定のバージョンでのみ真となるテストには、書くときにグループを付けます：`laravel-N-only`、`laravel-6-to-13-only`のような範囲、`php-8.4-and-later`、`phpunit-11-only`、Nodeでは`node-N-only`です。PHP側では属性で、たとえば`#[Group('laravel-13-only')]`とします。そのグループを偽にする変更が、`phpunit.xml`（または`tests/js/excluded-groups.txt`）にグループを列挙します。古いテストを、新しいバージョンに合わせて編集してはいけません。
- どのテストも外部ホストを呼び出してはいけません。OBIC APIとRakutenのトークンエンドポイントは、Docker環境の代替サービス`obic-stub`に置き換えられています。`ExternalHttpGuardTest`は、`phpunit.xml`の外向きの宛先が代替サービスやループバックアドレスでない場合、またはテストファイルに実在のホスト名がある場合に失敗します。
- 実際のキー、パスワード、証明書をリポジトリに入れないでください。OBICキーのテキストを出力したり、アサートしたりしてもいけません。
- テストには、アサートする内容にちなんだ名前を付けます。癖（quirk）を固定するテストには、その癖にちなんだ名前を付けます。テストがアサートする値（ステータス、日付、金額、所有者）は、すべて明示的に設定します。行はモデルファクトリーとシーダーで作成します。
- Duskのファイル（`tests/Browser`、`tests/DuskTestCase.php`）は手を加えておらず、どちらのスイートにも含まれません。`laravel/dusk`は開発用の依存パッケージです。本番環境には開発用パッケージを決してインストールしないでください（セクション5）。
- `route:cache`は使わないでください（セクション5）。ルートを追加するときは、そのルートに固有の名前を付けてください。

## 8. 変更の受け入れ／外部委託コードの扱い

1. この版の納品されたタグまたはブランチからブランチを作成します。たとえば`git switch -c delivery/<name> <delivered-tag>`です。
2. そのブランチに、外部委託先の変更を適用します。コミット、パッチシリーズ、マージのいずれでも構いません。
3. `composer install`を実行し、続けてPHPスイート（セクション6.1）、Nodeテスト（セクション6.2）、最後にゲート（セクション6.3）を実行します。
4. 失敗したテストはすべて、挙動の変更として読み取ります。テストは、既知の癖を含めてアプリケーションが現在行っていることを記述しています。そのため、古いテストが失敗した場合は、意図的かどうかを問わず挙動が変わったことを意味します。変更をレビューし、テストを黙って書き換えないでください。変更が望ましいものであれば、理由を書いた独立したコミットでテストを変更し（フレームワークのアップグレードによって強制された期待値には、`UPGRADE STEP`で始まるコメントを付けます）、新しい挙動のためのテストを追加します。
5. 納品物がテストフォルダで何を変更したかを確認します：`git diff --diff-filter=M --name-only <delivered-tag> HEAD -- tests phpunit.xml`は、変更された古いテストファイルを列挙します。件数は少ないはずで、それぞれに理由が必要です。
6. デプロイが依存するその他のファイルを確認します：`composer.json`と`composer.lock`（新しいパッケージ、新しい`require-dev`の項目）、`package.json`と`package-lock.json`、新しいマイグレーション（固定スキーマの更新も必要です）、`app/Console/Kernel.php`の新しいスケジュールコマンド、新しい`env()`の呼び出し（`config/`のファイルにのみ置きます）、新しいルート名（重複なし）。

## 9. 既知の挙動と未決定事項

- 不具合に見える挙動は、アップグレードで変わらないように、意図的に固定されています。たとえば、同じクラス名を持つ2つ目のマイグレーション、本番ホストを既定値とするOBICのアドレス、PSR-4のパスにないバリデータートレイトとプロバイダー、存在しないメソッドを指すルートです。これらは`MigrationFilesTest`、`ExternalHttpGuardTest`、`NamespaceCaseTest`と、[WEB-UPGRADE-REPORT.md](../WEB-UPGRADE-REPORT.md)のセクション9に記載されています。
- セキュリティ：出発点で44件の指摘事項が記録され、テストで固定されていますが、修正はされていません。認証なしの3つの`/test/*`ルート（`/test/achievement/calculate/{dateStr}`、`/test/mailForInfluencer`、`/test/mailForMerchant`）は、ログインなしで報酬計算と一括メールを実行します。削除されるまでは、Webサーバーで制限してください。Rakutenの`id_token`は検証されません。[WEB-SECURITY-FINDINGS.md](../WEB-SECURITY-FINDINGS.md)を参照してください。
- チームの未決定事項：本番環境のPHPバージョンとデータベース、セッションとキャッシュのストア、本番のメールリレー、ソースマップの公開、共有されているルート名の変更、サポート終了を迎えるVue 2、レポートに記載された古いライブラリです。詳細は[WEB-UPGRADE-REPORT.md](../WEB-UPGRADE-REPORT.md)のセクション5から7と、[docs/upgrade/web-upgrade-summary.md](../docs/upgrade/web-upgrade-summary.md)にあります。
- これらの文書はパッケージに同梱されています。`WEB-UPGRADE-REPORT.md`と`WEB-SECURITY-FINDINGS.md`はプロジェクトルートに、`web-upgrade-summary.md`は`docs/upgrade/`にあります。いずれにも`.ja.md`の日本語版があります。

## 10. プロジェクト構成

| フォルダ | 内容 |
| --- | --- |
| `app/` | アプリケーションコードです：`Http/Controllers`（ページと3つのJSON API）、`Http/Middleware`、`Http/Requests`、`Mail`、`Notifications`、`Jobs`、`Providers`。 |
| `app/Model/` | Eloquentモデルです（62クラス）。 |
| `app/Console/` | `Kernel.php`（スケジュール）と`Commands/`（`batch:*`コマンドと、いくつかのコード生成コマンド）です。 |
| `routes/` | `web.php`にすべてのルートがあります。`api.php`は空です。 |
| `resources/js/` | Vue 2.7のソースです：3つのエントリーファイル`influencerApp.js`、`merchantApp.js`、`operatorApp.js`、`components/`、`apiClient/`。 |
| `resources/views/` | 3つのポータルのBladeテンプレートです。 |
| `database/` | `migrations/`、`factories/`、`seeds/`です。 |
| `config/` | 設定です。`env()`を呼び出す唯一の場所です。 |
| `tests/Feature`、`tests/Unit` | PHPのテストです。ヘルパーは`tests/Concerns`に、ベースクラスは`tests/TestCase.php`にあります。 |
| `tests/js/` | Nodeのヘルパーテスト、ランナー`run.js`、`excluded-groups.txt`です。 |
| `tests/e2e/` | ブラウザスモークテストのチェックです（環境リポジトリから実行します）。 |
| `public/` | Webルートです。`public/build/`は生成されたアセットビルドで（gitの管理対象外）、`public/js`と`public/css`は手書きで管理対象です。 |
| `storage/`、`bootstrap/cache/` | 実行時のファイルです。書き込み可能である必要があります。 |
