# 機能設計書 13-メールアドレス変更

## 概要

本ドキュメントは、Shopwareストアフロントにおける「メールアドレス変更」機能の設計内容を記載する。ログイン中の顧客が自身のメールアドレスを変更する機能であり、セキュリティのためパスワード再入力と確認用メールアドレスの入力を必須とする。

### 本機能の処理概要

**業務上の目的・背景**：メールアドレスはECサイトにおける顧客識別の主要な手段であり、また注文確認や発送通知などの重要な連絡先でもある。メールアドレスの変更は、なりすましや不正アクセスを防ぐために厳格なセキュリティチェックが必要である。本機能は、現在のパスワードによる本人確認と確認用メールアドレスの一致検証を行った上で、安全にメールアドレスを変更する。

**機能の利用シーン**：
- 顧客がメールアドレスを変更したい場合
- 顧客が古いメールアドレスにアクセスできなくなった場合
- メールアドレスの入力ミスを修正する場合

**主要な処理内容**：
1. ログイン認証の確認
2. パスワードによる本人確認
3. 新しいメールアドレスの形式検証
4. 確認用メールアドレスとの一致検証
5. メールアドレスの一意性検証
6. 顧客情報の更新
7. パスワードリカバリー情報の削除（セキュリティ対策）

**関連システム・外部連携**：Store API（ChangeEmailRoute）を経由してデータを更新。国際化ドメイン名（IDN）のエンコード処理を実施。

**権限による制御**：ログイン必須（`LOGIN_REQUIRED`属性）。ゲストユーザーは利用不可。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 17 | プロフィールページ | 主画面 | メールアドレス変更フォーム |

## 機能種別

CRUD操作（Update） / バリデーション / セキュリティ処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| email.email | string | Yes | 新しいメールアドレス | Email形式、一意性検証 |
| email.emailConfirmation | string | Yes | 確認用メールアドレス | emailと一致必須 |
| email.password | string | Yes | 現在のパスワード | CustomerPasswordMatches制約 |

### 入力データソース

- フォーム入力（POSTリクエスト）
- セッションから取得される顧客情報

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| flashMessage | string | 成功/エラーメッセージ |
| redirectResponse | Response | プロフィールページへのリダイレクト |

### 出力先

- セッション（フラッシュメッセージ）
- 画面リダイレクト（frontend.account.profile.page）

## 処理フロー

### 処理シーケンス

```
1. ルーティング処理
   └─ /account/profile/email パスへのPOSTリクエストを受信

2. コントローラー処理 (AccountProfileController::saveEmail)
   ├─ emailパラメータの検証
   └─ ChangeEmailRouteを呼び出し

3. メールアドレス変更ルート処理 (ChangeEmailRoute::change)
   ├─ IDNエンコード処理
   ├─ バリデーション実行（Email形式、一致検証、パスワード検証、一意性検証）
   ├─ customerリポジトリの更新
   └─ パスワードリカバリー情報の削除

4. レスポンス生成
   ├─ 成功時：成功メッセージ設定、プロフィールページへリダイレクト
   └─ エラー時：エラーメッセージ設定、フォーム再表示
```

### フローチャート

```mermaid
flowchart TD
    A[POST /account/profile/email] --> B{ログイン済み?}
    B -->|No| C[ログイン画面へリダイレクト]
    B -->|Yes| D{emailパラメータ存在?}
    D -->|No| E[RoutingException発生]
    D -->|Yes| F[IDNエンコード処理]
    F --> G[ChangeEmailRoute::change]
    G --> H[バリデーション実行]
    H --> I{Email形式有効?}
    I -->|No| J[ConstraintViolationException]
    I -->|Yes| K{email = emailConfirmation?}
    K -->|No| J
    K -->|Yes| L{パスワード正しい?}
    L -->|No| J
    L -->|Yes| M{メールアドレス一意?}
    M -->|No| J
    M -->|Yes| N[customerリポジトリ更新]
    N --> O[パスワードリカバリー情報削除]
    O --> P[成功メッセージ設定]
    J --> Q[エラーメッセージ設定]
    Q --> R[emailFormViolation=trueでフォーム再表示]
    P --> S[プロフィールページへリダイレクト]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-13-01 | ログイン必須 | 未ログインユーザーはアクセス不可 | 全リクエスト |
| BR-13-02 | パスワード必須 | 現在のパスワードで本人確認 | 全リクエスト |
| BR-13-03 | Email形式検証 | 有効なメールアドレス形式であること | 全リクエスト |
| BR-13-04 | 確認用一致 | emailとemailConfirmationが一致すること | 全リクエスト |
| BR-13-05 | 一意性検証 | 他の顧客と重複しないこと | CustomerEmailUnique制約 |
| BR-13-06 | IDNエンコード | 国際化ドメイン名をASCII形式に変換 | 全リクエスト |
| BR-13-07 | リカバリー削除 | メールアドレス変更時に既存のパスワードリカバリー情報を削除 | 全リクエスト |

### 計算ロジック

特になし

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| メールアドレス更新 | customer | UPDATE | 顧客のメールアドレス更新 |
| リカバリー情報検索 | customer_recovery | SELECT | 顧客IDでリカバリー情報を検索 |
| リカバリー情報削除 | customer_recovery | DELETE | 該当顧客のリカバリー情報を削除 |

### テーブル別操作詳細

#### customer

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | email | 新しいメールアドレス | IDNエンコード済み |

#### customer_recovery

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id | customerId = 対象顧客ID | 削除対象ID取得 |
| DELETE | id | 取得したID | パスワードリカバリー情報削除 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | RoutingException | emailパラメータが存在しない | パラメータ不足エラー表示 |
| - | ConstraintViolationException | バリデーション失敗 | フォームにエラー内容を表示、再入力を促す |
| - | Throwable | その他の例外 | エラーログ出力、汎用エラーメッセージ表示 |

### リトライ仕様

特になし（ユーザーによる再入力）

## トランザクション仕様

- customerの更新とcustomer_recoveryの削除は別々のトランザクションで実行される
- customer_recoveryが存在しない場合も正常終了

## パフォーマンス要件

- 単一レコードの更新のため、特別なパフォーマンス要件なし

## セキュリティ考慮事項

- LOGIN_REQUIRED属性によりログイン必須を強制
- パスワードによる本人確認（CustomerPasswordMatches制約）
- メールアドレスの一意性検証（CustomerEmailUnique制約）
- パスワードリカバリー情報の削除によるセキュリティ強化
- IDNエンコードによる国際化ドメイン名の正規化

## 備考

- メールアドレス変更後は、ログインセッションは維持される
- 変更後のメールアドレスで確認メールは送信されない（ダブルオプトインではない）
- EmailIdnConverterにより、国際化ドメイン名（例：日本語.jp）がPunycode形式に変換される

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

メールアドレス変更に関連するデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | CustomerEntity.php | `src/Core/Checkout/Customer/CustomerEntity.php` | emailフィールドの定義を確認 |
| 1-2 | CustomerRecoveryEntity.php | `src/Core/Checkout/Customer/Aggregate/CustomerRecovery/CustomerRecoveryEntity.php` | パスワードリカバリーエンティティの構造 |

**読解のコツ**: customerとcustomer_recoveryはcustomerIdで関連付けられている。

#### Step 2: エントリーポイントを理解する

コントローラーでのリクエスト処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | AccountProfileController.php | `src/Storefront/Controller/AccountProfileController.php` | saveEmailメソッド（111-137行目）がエントリーポイント |

**主要処理フロー**:
1. **120-123行目**: emailパラメータの存在確認とRequestDataBagへの変換
2. **124行目**: `$this->changeEmailRoute->change($emailParam->toRequestDataBag(), $context, $customer)` でメール変更ルート呼び出し
3. **126行目**: 成功時、成功メッセージをフラッシュに設定
4. **127-130行目**: ConstraintViolationException時、emailFormViolation=trueでフォームに戻る
5. **131-133行目**: その他例外時、エラーログ出力とエラーメッセージ設定

#### Step 3: Store APIルートを理解する

実際のメールアドレス変更ロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ChangeEmailRoute.php | `src/Core/Checkout/Customer/SalesChannel/ChangeEmailRoute.php` | changeメソッド（69-90行目）の処理フローを確認 |

**主要処理フロー**:
- **71-72行目**: IDNエンコード処理（emailとemailConfirmation両方）
- **74行目**: バリデーション実行
- **76-81行目**: customerリポジトリの更新
- **83-87行目**: パスワードリカバリー情報の削除

#### Step 4: バリデーションを理解する

バリデーションの定義と制約を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | ChangeEmailRoute.php | `src/Core/Checkout/Customer/SalesChannel/ChangeEmailRoute.php` | validateEmailメソッド（92-110行目） |
| 4-2 | CustomerPasswordMatches.php | `src/Core/Checkout/Customer/Validation/Constraint/CustomerPasswordMatches.php` | パスワード検証制約 |
| 4-3 | CustomerEmailUnique.php | `src/Core/Checkout/Customer/Validation/Constraint/CustomerEmailUnique.php` | メールアドレス一意性検証制約 |

**バリデーション定義**:
- **96-102行目**: Email形式、emailConfirmationとの一致、一意性検証、パスワード検証の設定
- **109行目**: EqualTo制約による一致検証の追加実行

#### Step 5: IDNエンコードを理解する

国際化ドメイン名のエンコード処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | EmailIdnConverter.php | `src/Core/Checkout/Customer/Service/EmailIdnConverter.php` | encodeDataBagメソッドの処理 |

### プログラム呼び出し階層図

```
AccountProfileController::saveEmail
    │
    ├─ emailパラメータの検証
    │
    └─ ChangeEmailRoute::change
           │
           ├─ EmailIdnConverter::encodeDataBag (email)
           │
           ├─ EmailIdnConverter::encodeDataBag (emailConfirmation)
           │
           ├─ validateEmail (private)
           │      │
           │      ├─ Email制約
           │      ├─ EqualTo制約 (emailConfirmation)
           │      ├─ CustomerEmailUnique制約
           │      ├─ CustomerPasswordMatches制約
           │      │
           │      └─ dispatchValidationEvent (BuildValidationEvent)
           │
           ├─ customerRepository.update
           │
           └─ customerRecoveryRepository.delete
```

### データフロー図

```
[入力]                         [処理]                              [出力]

POST /account/profile/email
         │
         ▼
RequestDataBag (email) ─────▶ AccountProfileController::saveEmail
         │                            │
         │                            ▼
         │                    パラメータ検証
         │                            │
         │                            ▼
         │                    ChangeEmailRoute::change
         │                            │
         │               ┌────────────┴────────────┐
         │               ▼                         ▼
         │        IDNエンコード              バリデーション
         │               │                         │
         │               └────────────┬────────────┘
         │                            │
         │                            ▼
         │                    成功/失敗判定
         │                            │
         │               ┌────────────┴────────────┐
         │               ▼                         ▼
         │           成功時                    失敗時
         │               │                         │
         │               ▼                         ▼
         │     customerRepository.update   ConstraintViolation
         │               │                     Exception
         │               ▼                         │
         │     customerRecoveryRepository          │
         │            .delete                      │
         │               │                         │
         └───────────────┴─────────────────────────┘
                                  │
                                  ▼
                     フラッシュメッセージ設定
                                  │
                                  ▼
                     プロフィールページへリダイレクト
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| AccountProfileController.php | `src/Storefront/Controller/AccountProfileController.php` | コントローラー | HTTPリクエスト処理のエントリーポイント |
| ChangeEmailRoute.php | `src/Core/Checkout/Customer/SalesChannel/ChangeEmailRoute.php` | Store API | メールアドレス変更ビジネスロジック |
| AbstractChangeEmailRoute.php | `src/Core/Checkout/Customer/SalesChannel/AbstractChangeEmailRoute.php` | 抽象クラス | Store APIの抽象定義 |
| CustomerPasswordMatches.php | `src/Core/Checkout/Customer/Validation/Constraint/CustomerPasswordMatches.php` | 制約 | パスワード検証ロジック |
| CustomerEmailUnique.php | `src/Core/Checkout/Customer/Validation/Constraint/CustomerEmailUnique.php` | 制約 | メールアドレス一意性検証 |
| EmailIdnConverter.php | `src/Core/Checkout/Customer/Service/EmailIdnConverter.php` | サービス | 国際化ドメイン名のエンコード |
| CustomerRecoveryEntity.php | `src/Core/Checkout/Customer/Aggregate/CustomerRecovery/CustomerRecoveryEntity.php` | エンティティ | パスワードリカバリーデータモデル |
| index.html.twig | `src/Storefront/Resources/views/storefront/page/account/profile/index.html.twig` | テンプレート | メールアドレス変更フォーム |
