# 機能設計書 68-Clock

## 概要

本ドキュメントは、Symfony Clockコンポーネントの機能設計書である。Clockは時間抽象化レイヤーを提供し、テスト可能な時間依存コードの実装を支援するコンポーネントである。

### 本機能の処理概要

Clockコンポーネントは、PSR-20（Clock Interface）に準拠した時間抽象化を提供する。NativeClock（システム時刻）、MockClock（テスト用固定時刻）、Clock（グローバルクロック）の3つの実装と、不変な日時表現であるDatePointクラスを提供する。

**業務上の目的・背景**：時間に依存するロジック（有効期限チェック、スケジューリング、トークン有効期限等）のテストは困難である。Clockコンポーネントは時間をインジェクション可能にし、テスト時にMockClockで時刻を制御できるようにする。

**機能の利用シーン**：
- サービスクラスでClockAwareTraitを使用して現在時刻を取得
- テストコードでMockClockを注入して時刻を固定・操作
- DatePointクラスによる型安全なDateTimeImmutable拡張
- グローバルクロックの設定による一括制御

**主要な処理内容**：
1. ClockInterface - PSR-20拡張インターフェース（now(), sleep(), withTimeZone()）
2. NativeClock - システム時刻に基づくクロック実装
3. MockClock - テスト用の固定時刻クロック（modify(), sleep()で時刻操作）
4. Clock - グローバルクロック（静的get()/set()でグローバルクロックを管理）
5. DatePoint - 厳密な型を返すDateTimeImmutable拡張
6. ClockAwareTrait - サービスに時間依存を注入するtrait

**関連システム・外部連携**：PSR-20 Clock Interface、Symfony DI（Requiredアトリビュート経由の自動注入）

**権限による制御**：特になし。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 画面関連なし（バックエンドコンポーネント） |

## 機能種別

ユーティリティ / 時間抽象化

## 入力仕様

### 入力パラメータ（Clock）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| clock | PsrClockInterface/null | No | 内部クロック実装 | null=グローバルクロック使用 |
| timezone | DateTimeZone/null | No | タイムゾーン | null=クロックのデフォルト |

### 入力パラメータ（MockClock）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| now | DateTimeImmutable/string | No | 固定する時刻 | デフォルト: 'now' |
| timezone | DateTimeZone/string/null | No | タイムゾーン | null=UTC |

### 入力パラメータ（NativeClock）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| timezone | DateTimeZone/string/null | No | タイムゾーン | null=date_default_timezone_get() |

### 入力データソース

システム時刻（NativeClock）またはプログラムで設定された時刻（MockClock）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| now | DatePoint | 現在時刻のDatePointインスタンス |

### 出力先

呼び出し元のPHPコード（メモリ上のオブジェクト）

## 処理フロー

### 処理シーケンス

```
1. サービスでClockAwareTraitを使用
   └─ setClock() で ClockInterface が #[Required] で自動注入
2. now() メソッド呼び出し
   └─ clock->now() → DatePoint インスタンスを返却
3. テスト時
   └─ MockClock を注入 → 固定時刻を返却
   └─ modify('+1 hour') で時刻操作
   └─ sleep(60) で仮想的な待機
```

### フローチャート

```mermaid
flowchart TD
    A{クロック種別}
    A -->|NativeClock| B[new DateTimeImmutable now]
    A -->|MockClock| C[clone $this->now]
    A -->|Clock| D{内部clockが設定?}
    D -->|No| E[Clock::get で globalClock取得]
    D -->|Yes| F[内部clock->now]
    E --> G{globalClock設定済み?}
    G -->|No| H[new NativeClock]
    G -->|Yes| I[globalClock->now]
    B --> J[DatePoint::createFromInterface]
    C --> J
    F --> J
    H --> J
    I --> J
    J --> K{timezone設定?}
    K -->|Yes| L[DatePoint::setTimezone]
    K -->|No| M[DatePoint返却]
    L --> M
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | グローバルクロックの遅延初期化 | Clock::get()呼び出し時にglobalClockが未設定ならNativeClockを自動生成 | 初回get()呼び出し時 |
| BR-02 | MockClock sleep | sleep()は実際にスリープせず、内部時刻を指定秒数分進める | MockClock使用時 |
| BR-03 | NativeClock sleep | sleep()はPHPのsleep()/usleep()を使用して実際にスリープ | NativeClock使用時 |
| BR-04 | DatePoint不変性 | DatePointはDateTimeImmutableを継承し、全操作がstaticを返す | 常時 |
| BR-05 | Clock::set | PSR-20 ClockInterfaceをグローバルクロックとして設定。ClockInterface以外はClockでラップ | Clock::set()呼び出し時 |
| BR-06 | ClockAwareTrait自動注入 | #[Required]アトリビュートによりDIコンテナが自動的にsetClock()を呼ぶ | DIコンテナ使用時 |
| BR-07 | MockClockデフォルトTZ | timezone未指定でstring指定時はUTCを使用 | MockClockコンストラクタ |

### 計算ロジック

MockClock::sleep()の時刻計算:
- `(float) $this->now->format('Uu') + $seconds * 1e6` でマイクロ秒精度の加算
- `@` プレフィックスのUnixタイムスタンプ形式で新しいDatePointを生成

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

データベース操作は行わない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | DateMalformedStringException | 無効な日時文字列 | 正しい日時文字列を使用 |
| - | DateInvalidTimeZoneException | 無効なタイムゾーン名 | 正しいタイムゾーン名を使用 |
| - | DateRangeError | setMicrosecond()に0-999999範囲外の値 | 範囲内の値を使用 |

### リトライ仕様

該当なし。

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

該当なし。

## パフォーマンス要件

- NativeClock::sleep()はsleep()とusleep()を分離して呼び出し、秒の整数部とマイクロ秒部を別々に処理（NativeClock.php 38-44行目）

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

- 特になし。

## 備考

- PSR-20（psr/clock）準拠
- DatePointはDateTimeImmutableを継承しており、PHPのネイティブ日時関数と互換性がある
- 'now'以外の日時文字列指定時、00:00:00.000000の場合は時刻を0にリセット（DatePoint.php 38-39行目）

---

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

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

### 推奨読解順序

#### Step 1: インターフェースを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | ClockInterface.php | `src/Symfony/Component/Clock/ClockInterface.php` | PSR-20拡張。sleep()とwithTimeZone()を追加 |

**読解のコツ**: ClockInterfaceはPsrClockInterface（now(): DateTimeImmutable）を拡張し、sleep(float|int)とwithTimeZone(DateTimeZone|string)を追加する。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | DatePoint.php | `src/Symfony/Component/Clock/DatePoint.php` | DateTimeImmutable拡張。strict型返却の保証 |

**主要処理フロー**:
- **24-46行目**: コンストラクタ。Clock::get()->now()で参照時刻を取得し、modifyで相対時刻設定
- **28-30行目**: 'now'でない場合、参照時刻をcreateFromInterfaceで変換
- **38-39行目**: 00:00:00.000000パターンの場合、時刻を0にリセット（日付のみ指定時の考慮）
- **51-53行目**: createFromFormat()。失敗時はDateMalformedStringExceptionをthrow
- **71-92行目**: add/sub/modify/setTimestamp等がstaticを返すオーバーライド
- **119-126行目**: setMicrosecond()。0-999999の範囲チェック

#### Step 3: クロック実装を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | NativeClock.php | `src/Symfony/Component/Clock/NativeClock.php` | システム時刻クロック |
| 3-2 | MockClock.php | `src/Symfony/Component/Clock/MockClock.php` | テスト用固定時刻クロック |
| 3-3 | Clock.php | `src/Symfony/Component/Clock/Clock.php` | グローバルクロック |

**主要処理フロー（NativeClock）**:
- **26-29行目**: コンストラクタ。timezone未指定時はdate_default_timezone_get()
- **31-34行目**: now()。new DateTimeImmutable('now', $this->timezone) → DatePoint変換
- **36-44行目**: sleep()。整数部はsleep()、小数部はusleep()で処理

**主要処理フロー（MockClock）**:
- **29-42行目**: コンストラクタ。string/DateTimeImmutable → DatePoint変換、timezone適用
- **44-47行目**: now()。clone $this->nowで常に新しいコピーを返す
- **49-60行目**: sleep()。マイクロ秒精度で内部時刻を加算（実際のスリープなし）
- **65-68行目**: modify()。DateTimeImmutable::modify()で相対的な時刻変更

**主要処理フロー（Clock）**:
- **37-40行目**: get()。globalClock未設定時はNativeClockをフォールバック生成
- **42-45行目**: set()。PsrClockInterface→ClockInterfaceの場合はそのまま、それ以外はClockでラップ
- **47-56行目**: now()。内部clock→globalClock→DatePoint変換→timezone適用

#### Step 4: Trait利用法を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | ClockAwareTrait.php | `src/Symfony/Component/Clock/ClockAwareTrait.php` | #[Required]によるDI自動注入 |

**主要処理フロー**:
- **26-30行目**: setClock()。#[Required]アトリビュートでDIコンテナが自動呼び出し
- **32-37行目**: now()。clock未設定時はnew Clock()をフォールバック生成、DatePoint保証

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

```
アプリケーションコード
    │
    ├─ ClockAwareTrait::now()
    │      └─ ClockInterface::now()
    │             ├─ [NativeClock] new DateTimeImmutable('now') → DatePoint
    │             ├─ [MockClock]   clone $this->now → DatePoint
    │             └─ [Clock]
    │                    ├─ $this->clock->now() [内部clock設定時]
    │                    └─ Clock::get()->now() [未設定時]
    │                           └─ self::$globalClock->now()
    │                                  └─ [NativeClock] (未設定時にフォールバック生成)
    │
    ├─ ClockInterface::sleep($seconds)
    │      ├─ [NativeClock] sleep() + usleep()
    │      ├─ [MockClock]   内部時刻 += $seconds (仮想スリープ)
    │      └─ [Clock]       内部clock or globalClock のsleep()
    │
    └─ ClockInterface::withTimeZone($timezone)
           └─ clone + timezone設定
```

### データフロー図

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

(なし/時刻文字列)  ───▶  ClockInterface::now()        ───▶  DatePoint
                         │
                ┌────────┼────────┐
                ▼        ▼        ▼
          NativeClock  MockClock  Clock
          (システム)   (固定)    (グローバル)
                                    │
                         ┌──────────┤
                         ▼          ▼
                   内部clock    globalClock
                   (指定時)    (フォールバック)

テスト時:
MockClock::modify('+1 day')  ───▶  内部時刻を+1日
MockClock::sleep(3600)       ───▶  内部時刻を+3600秒（仮想）
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| ClockInterface.php | `src/Symfony/Component/Clock/ClockInterface.php` | ソース | 拡張ClockIF（PSR-20+sleep+withTimeZone） |
| Clock.php | `src/Symfony/Component/Clock/Clock.php` | ソース | グローバルクロック管理 |
| NativeClock.php | `src/Symfony/Component/Clock/NativeClock.php` | ソース | システム時刻クロック |
| MockClock.php | `src/Symfony/Component/Clock/MockClock.php` | ソース | テスト用固定時刻クロック |
| DatePoint.php | `src/Symfony/Component/Clock/DatePoint.php` | ソース | 不変日時クラス |
| ClockAwareTrait.php | `src/Symfony/Component/Clock/ClockAwareTrait.php` | ソース | DI自動注入trait |
