# 機能設計書 39-Workflow

## 概要

本ドキュメントは、Symfony Workflowコンポーネントの機能設計を記述する。Workflowコンポーネントは、ワークフローまたは有限状態マシンの管理ツールを提供し、ビジネスプロセスの状態遷移を宣言的に定義・管理する。

### 本機能の処理概要

Workflowコンポーネントは、Place（場所/状態）、Transition（遷移）、Marking（マーキング/現在状態）を核とするペトリネットベースのワークフローエンジンを提供する。オブジェクトの状態遷移を定義し、ガード条件やイベントを通じた遷移制御を行い、状態遷移の履歴追跡とビジュアライゼーションを可能にする。

**業務上の目的・背景**：ビジネスプロセスにおいて、注文処理（新規→確認→出荷→完了）、承認ワークフロー（申請→審査→承認/却下）、コンテンツ管理（下書き→レビュー→公開）等の状態遷移を管理する必要がある。Workflowコンポーネントは、これらのビジネスプロセスをコード内で宣言的に定義し、不正な状態遷移を防止しながら処理フローを制御する。

**機能の利用シーン**：ECサイトの注文ステータス管理、コンテンツ管理システムの公開フロー、チケットシステムの状態管理、承認ワークフロー、ドキュメントレビュープロセスなど、状態遷移を伴うあらゆるビジネスプロセスで使用される。

**主要な処理内容**：
1. ワークフロー定義（Definition: Places + Transitions + MetadataStore）
2. マーキング（Marking）による現在状態の管理
3. 遷移の可否判定（can / buildTransitionBlockerList）
4. 遷移の適用（apply: leave → transition → enter → entered → completed → announce）
5. ガードイベント（GuardEvent）による遷移条件の制御
6. MarkingStoreによるオブジェクトの状態永続化
7. StateMachine拡張（単一状態の有限状態マシン）
8. ワークフロー定義のバリデーション
9. Dumperによるワークフローのビジュアライゼーション（GraphvizDumper, PlantUmlDumper, MermaidDumper）
10. DataCollectorによるWebProfilerでの状態遷移可視化

**関連システム・外部連携**：EventDispatcherコンポーネント（ガードイベント、遷移イベント）、DependencyInjectionコンポーネント（ワークフロー定義のDI統合）、Twig Bridge（WorkflowExtension: テンプレートでの状態確認）、SecurityBundle（ガード条件でのロールベースアクセス制御）と連携する。

**権限による制御**：GuardEventリスナーを使用して、ユーザーのロールや権限に基づいた遷移制御が可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 34 | ワークフローパネル | 主機能 | Workflowコンポーネントによる状態遷移の可視化・履歴の表示 |

## 機能種別

状態遷移管理処理（ワークフロー / ステートマシン）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| subject | object | Yes | 状態遷移の対象オブジェクト | オブジェクト型であること |
| transitionName | string | Yes | 適用する遷移の名前 | 定義済みの遷移名であること |
| context | array | No | 遷移に付加するコンテキスト情報 | - |

### 入力データソース

- ワークフロー定義（YAML/XML/PHP設定）
- 対象オブジェクトの現在状態（MarkingStore経由）
- アプリケーションコードからの遷移要求

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| marking | Marking | 遷移後の新しいマーキング（状態） |
| can | bool | 遷移可否の判定結果 |
| enabledTransitions | Transition[] | 実行可能な遷移のリスト |

### 出力先

- 対象オブジェクト（MarkingStore経由で状態を更新）
- イベントリスナー（遷移イベントの通知）

## 処理フロー

### 処理シーケンス

```
1. ワークフローの構築
   ├─ Definition(places, transitions, initialPlaces, metadataStore)
   └─ Workflow(definition, markingStore, dispatcher, name, eventsToDispatch)
2. 遷移可否の判定
   ├─ getMarking(): 現在のマーキング取得（初期状態の自動設定含む）
   ├─ 各遷移のFromプレースがマーキング内にあるか確認
   └─ GuardEvent のディスパッチ（ブロッカーの評価）
3. 遷移の適用（apply）
   ├─ leave: Fromプレースからの離脱（LeaveEvent）
   ├─ transition: 遷移実行（TransitionEvent - contextの更新可能）
   ├─ enter: Toプレースへの進入（EnterEvent）
   ├─ markingStore.setMarking(): 状態の永続化
   ├─ entered: 進入完了（EnteredEvent）
   ├─ completed: 遷移完了（CompletedEvent）
   └─ announce: 次に可能な遷移の通知（AnnounceEvent）
4. 状態の永続化
   └─ MarkingStore::setMarking() でオブジェクトの状態を更新
```

### フローチャート

```mermaid
flowchart TD
    A[Workflow::apply] --> B[getMarking - 現在状態取得]
    B --> C[遷移検索ループ]
    C --> D{遷移名一致?}
    D -->|No| C
    D -->|Yes| E[TransitionBlockerList構築]
    E --> F{From条件チェック}
    F -->|NG| G[BLOCKED_BY_MARKING]
    F -->|OK| H[GuardEvent ディスパッチ]
    H --> I{ガードブロック?}
    I -->|Yes| J[TransitionBlockerList返却]
    I -->|No| K[承認済み遷移リストに追加]
    K --> L{全遷移チェック完了?}
    L -->|No| C
    L -->|Yes| M{承認済み遷移あり?}
    M -->|No| N[NotEnabledTransitionException]
    M -->|Yes| O[leave: LeaveEvent]
    O --> P[transition: TransitionEvent]
    P --> Q[enter: EnterEvent]
    Q --> R[setMarking: 状態永続化]
    R --> S[entered: EnteredEvent]
    S --> T[completed: CompletedEvent]
    T --> U[announce: AnnounceEvent]
    U --> V[Marking返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-39-01 | マーキングチェック | 遷移のFromプレースが現在のマーキングに含まれていること。含まれていない場合はBLOCKED_BY_MARKINGブロッカーが追加される | 遷移試行時 |
| BR-39-02 | ガード条件 | GuardEventリスナーがブロッカーを追加することで遷移を阻止できる | dispatcherが設定されている場合 |
| BR-39-03 | 初期状態設定 | マーキングが空の場合、定義のinitialPlacesから自動的に初期状態が設定される | getMarking()呼び出し時 |
| BR-39-04 | イベント無効化 | eventsToDispatch配列やコンテキストのDISABLE_*キーで特定イベントを無効化できる | 設定時 |
| BR-39-05 | コンテキスト伝搬 | TransitionEventでコンテキストを更新でき、後続のイベントに伝搬する | TransitionEvent使用時 |
| BR-39-06 | 重み付きアーク | 遷移のFrom/Toにweight（重み）を設定でき、ペトリネットの複数トークン消費・生成を表現できる | weight設定時 |
| BR-39-07 | 未定義遷移エラー | 存在しない遷移名でapplyするとUndefinedTransitionExceptionをスローする | 未定義遷移名の指定時 |

### 計算ロジック

特になし。

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

本機能自体はデータベース操作を行わない。MarkingStoreを通じてオブジェクトの状態を更新し、永続化はアプリケーション層（Doctrine等）で行う。

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| - | - | - | 直接のデータベース操作なし |

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

該当なし。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | UndefinedTransitionException | 定義に存在しない遷移名を指定 | 遷移名を確認する |
| - | NotEnabledTransitionException | 現在の状態で有効でない遷移を実行 | can()で事前チェックする |
| - | LogicException | マーキングが空で初期状態も未定義 | initialPlacesを設定する |
| - | LogicException | 不正なプレース名を持つマーキング | ワークフロー定義を確認する |

### リトライ仕様

リトライは不要（同期処理のため）。

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

トランザクション管理は行わない。MarkingStoreによる状態更新のアトミック性はアプリケーション層で保証する。

## パフォーマンス要件

- ワークフロー定義は通常小規模であるため、パフォーマンスへの影響は最小限
- MetadataStoreのInMemoryMetadataStoreはメモリ内でメタデータを保持

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

- GuardEventリスナーを使用して、ロールベースのアクセス制御で遷移を制限する
- ワークフロー定義の変更は管理者のみが行えるようにする

## 備考

- ワークフロー（Workflow）は複数のプレースに同時に存在でき（ペトリネット）、StateMachineは単一プレースのみ許容する
- MermaidDumperでMermaid形式のワークフロー図を生成できる
- DataCollectorが用意されており、WebProfilerのワークフローパネルで状態遷移を可視化できる

---

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

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

### 推奨読解順序

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

ワークフローの基本データ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Definition.php | `src/Symfony/Component/Workflow/Definition.php` | ワークフロー定義。places, transitions, initialPlaces, metadataStoreを保持 |
| 1-2 | Transition.php | `src/Symfony/Component/Workflow/Transition.php` | 遷移の定義。名前、From/Toのプレースリスト |
| 1-3 | Marking.php | `src/Symfony/Component/Workflow/Marking.php` | マーキング（現在状態）。プレースとトークン数の管理 |
| 1-4 | Arc.php | `src/Symfony/Component/Workflow/Arc.php` | アーク（遷移とプレースの接続）。place + weight |
| 1-5 | TransitionBlocker.php | `src/Symfony/Component/Workflow/TransitionBlocker.php` | 遷移ブロッカー |

**読解のコツ**: Workflowコンポーネントはペトリネット理論に基づいている。Place=場所、Transition=遷移、Marking=トークンの配置、Arc=プレースと遷移の接続を表す。

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

Workflowクラスの主要メソッドを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | WorkflowInterface.php | `src/Symfony/Component/Workflow/WorkflowInterface.php` | ワークフローのインターフェース |
| 2-2 | Workflow.php | `src/Symfony/Component/Workflow/Workflow.php` | ワークフロー本体 |

**主要処理フロー**:
1. **63-71行目**: コンストラクタ。definition, markingStore, dispatcher, name, eventsToDispatchの注入
2. **73-110行目**: `getMarking()` - 現在のマーキング取得。空の場合は初期状態を自動設定
3. **112-130行目**: `can()` - 遷移可否判定。TransitionBlockerListが空ならtrue
4. **165-228行目**: `apply()` - 遷移適用。leave → transition → enter → setMarking → entered → completed → announce
5. **230-243行目**: `getEnabledTransitions()` - 実行可能な遷移のリスト取得
6. **284-305行目**: `buildTransitionBlockerListForTransition()` - マーキングチェックとGuardEvent評価
7. **307-320行目**: `guardTransition()` - GuardEventのディスパッチ（3段階: グローバル、ワークフロー名、遷移名）
8. **322-340行目**: `leave()` - Fromプレースからの離脱とLeaveEventディスパッチ
9. **342-355行目**: `transition()` - TransitionEventディスパッチとコンテキスト更新
10. **357-375行目**: `enter()` - Toプレースへの進入とEnterEventディスパッチ
11. **428-447行目**: `shouldDispatchEvent()` - イベント無効化の判定

#### Step 3: ステートマシンを理解する

単一状態版のStateMachineを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | StateMachine.php | `src/Symfony/Component/Workflow/StateMachine.php` | 有限状態マシン拡張 |

#### Step 4: MarkingStoreを理解する

状態の永続化機構を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | MarkingStoreInterface.php | `src/Symfony/Component/Workflow/MarkingStore/MarkingStoreInterface.php` | マーキングストアインターフェース |
| 4-2 | MethodMarkingStore.php | `src/Symfony/Component/Workflow/MarkingStore/MethodMarkingStore.php` | メソッド呼び出しによるマーキング読み書き |

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

```
Workflow::apply()
    |
    +-- Workflow::getMarking()
    |       +-- MarkingStore::getMarking()
    |       +-- [初期状態設定]
    |       +-- MarkingStore::setMarking()
    |       +-- Workflow::entered() [初期状態時]
    |
    +-- Workflow::buildTransitionBlockerListForTransition()
    |       +-- Transition::getFroms() [マーキングチェック]
    |       +-- Workflow::guardTransition()
    |               +-- EventDispatcher::dispatch(GuardEvent)
    |
    +-- Workflow::leave()
    |       +-- EventDispatcher::dispatch(LeaveEvent)
    |       +-- Marking::unmark()
    |
    +-- Workflow::transition()
    |       +-- EventDispatcher::dispatch(TransitionEvent)
    |
    +-- Workflow::enter()
    |       +-- EventDispatcher::dispatch(EnterEvent)
    |       +-- Marking::mark()
    |
    +-- MarkingStore::setMarking()
    |
    +-- Workflow::entered()
    |       +-- EventDispatcher::dispatch(EnteredEvent)
    |
    +-- Workflow::completed()
    |       +-- EventDispatcher::dispatch(CompletedEvent)
    |
    +-- Workflow::announce()
            +-- Workflow::getEnabledTransitions()
            +-- EventDispatcher::dispatch(AnnounceEvent)
```

### データフロー図

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

対象オブジェクト ──────> Workflow::apply(subject, transitionName)
遷移名                        |
                        MarkingStore::getMarking()
                              |
                        TransitionBlockerList構築
                              |
                        GuardEvent評価
                              |
                        leave → transition → enter
                              |
                        MarkingStore::setMarking() ──────> 更新された
                              |                             オブジェクト状態
                        entered → completed → announce
                              |
                        Marking ──────────────────────> 新しいマーキング
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| Workflow.php | `src/Symfony/Component/Workflow/Workflow.php` | ソース | ワークフロー本体 |
| WorkflowInterface.php | `src/Symfony/Component/Workflow/WorkflowInterface.php` | ソース | ワークフローインターフェース |
| Definition.php | `src/Symfony/Component/Workflow/Definition.php` | ソース | ワークフロー定義 |
| DefinitionBuilder.php | `src/Symfony/Component/Workflow/DefinitionBuilder.php` | ソース | 定義ビルダー |
| Transition.php | `src/Symfony/Component/Workflow/Transition.php` | ソース | 遷移定義 |
| Arc.php | `src/Symfony/Component/Workflow/Arc.php` | ソース | アーク定義 |
| Marking.php | `src/Symfony/Component/Workflow/Marking.php` | ソース | マーキング（現在状態） |
| TransitionBlocker.php | `src/Symfony/Component/Workflow/TransitionBlocker.php` | ソース | 遷移ブロッカー |
| TransitionBlockerList.php | `src/Symfony/Component/Workflow/TransitionBlockerList.php` | ソース | ブロッカーリスト |
| StateMachine.php | `src/Symfony/Component/Workflow/StateMachine.php` | ソース | 有限状態マシン |
| Registry.php | `src/Symfony/Component/Workflow/Registry.php` | ソース | ワークフローレジストリ |
| MarkingStore/ | `src/Symfony/Component/Workflow/MarkingStore/` | ソース | マーキングストア群 |
| Event/ | `src/Symfony/Component/Workflow/Event/` | ソース | イベント群 |
| Dumper/ | `src/Symfony/Component/Workflow/Dumper/` | ソース | ダンパー群（Graphviz, PlantUml, Mermaid） |
| DataCollector/ | `src/Symfony/Component/Workflow/DataCollector/` | ソース | WebProfiler用データコレクター |
