# 機能設計書 59-AssetMapper

## 概要

本ドキュメントは、Symfony AssetMapperコンポーネントの機能設計を記述する。AssetMapperはディレクトリ内アセットのマッピングとバージョン付きファイル名による公開機能を提供し、Webpack等のビルドツールを使わないシンプルなアセット管理を実現する。

### 本機能の処理概要

AssetMapperコンポーネントは、アプリケーション内の指定ディレクトリのアセットファイルをスキャンし、論理パスからコンテンツダイジェスト付きの公開パスへのマッピングを管理する。JavaScript ImportMap仕様のサポート、アセットコンパイル(CSSインポート解決等)、アセット圧縮機能を含む。

**業務上の目的・背景**：従来のWebpack Encore等のJavaScriptビルドツールは、設定が複雑でNode.js環境が必要であった。AssetMapperはPHPのみでアセット管理を完結させ、ES ModuleのImportMapを活用したネイティブなJavaScript解決を提供する。これによりビルドステップなしでモダンなフロントエンド開発が可能になる。

**機能の利用シーン**：JavaScript/CSSファイルのバージョニングとキャッシュバスティング、ImportMapによるJavaScriptモジュール解決、サードパーティJSパッケージの管理、開発環境でのアセット変更の即座反映、本番環境でのマニフェストベースのアセット配信。

**主要な処理内容**：
1. アセットリポジトリからのファイル検出とマッピング(AssetMapperRepository)
2. 論理パスから物理パスへの解決(find, findLogicalPath)
3. コンテンツダイジェスト(ハッシュ)の計算と公開パス生成
4. アセットコンパイル(CSS url()解決、JavaScriptインポート解決等)
5. ImportMapの管理(JavaScriptモジュールのマッピング)
6. マニフェストファイル(manifest.json)の生成と読み込み
7. アセット圧縮(Compressor)
8. 開発環境用のDevServerSubscriber

**関連システム・外部連携**：NPMレジストリ(パッケージインポート)、ブラウザのES Module/ImportMap仕様。

**権限による制御**：権限制御は実装されていない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 全画面のアセット配信基盤として機能(importmap()、asset()関数経由) |

## 機能種別

アセット管理 / ビルドツール代替

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| logicalPath | string | Yes | アセットの論理パス(例: 'styles/app.css') | リポジトリ内に存在すること |
| sourcePath | string | Yes(getAssetFromSourcePath) | アセットのソースファイル絶対パス | 有効なファイルパス |

### 入力データソース

アプリケーション設定で指定されたアセットディレクトリ(通常はassets/)、マニフェストファイル(manifest.json)。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| asset | MappedAsset | マッピングされたアセット情報(論理パス、ソースパス、公開パス、ダイジェスト、コンテンツ) |
| publicPath | string | ダイジェスト付き公開パス(例: '/assets/styles/app-abc123.css') |
| manifestData | array | マニフェストJSONのデータ(logicalPath => publicPath) |

### 出力先

HTMLレスポンス内のアセットURL、マニフェストファイル。

## 処理フロー

### 処理シーケンス

```
1. AssetMapper::getAsset($logicalPath)呼び出し
   └─ AssetMapperRepository::find()で物理パスを解決
2. MappedAssetFactoryInterface::createMappedAsset()
   └─ ファイル読み込み、コンパイラ適用、ダイジェスト計算
3. コンパイラの実行(AssetMapperCompiler)
   └─ CSS url()解決、JavaScriptインポート解決等
4. ダイジェスト計算
   └─ ファイルコンテンツ + 依存アセットのダイジェストからハッシュ生成
5. 公開パス生成
   └─ 論理パスのファイル名にダイジェストを埋め込み
6. getPublicPath()
   └─ マニフェスト存在時はマニフェストから取得、なければ動的生成
```

### フローチャート

```mermaid
flowchart TD
    A[getAsset呼び出し] --> B[Repository::find]
    B --> C{ファイル存在?}
    C -->|No| D[null返却]
    C -->|Yes| E[MappedAssetFactory::createMappedAsset]
    E --> F[コンパイラ適用]
    F --> G[ダイジェスト計算]
    G --> H[公開パス生成]
    H --> I[MappedAssetオブジェクト返却]

    J[getPublicPath呼び出し] --> K{マニフェスト存在?}
    K -->|Yes| L[マニフェストから取得]
    K -->|No| M[getAssetで動的生成]
    L --> N[publicPath返却]
    M --> N
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-59-01 | マニフェスト優先 | manifest.jsonが存在する場合はマニフェストからpublicPathを解決する | 本番環境(manifest.json生成済み) |
| BR-59-02 | ダイジェスト付きファイル名 | 公開パスはファイル名にコンテンツハッシュを埋め込む形式(例: app-abc123.css) | 全アセット |
| BR-59-03 | 依存関係追跡 | アセットのダイジェストは依存するアセットのダイジェストも含めて計算される | コンパイラが依存を検出した場合 |

### 計算ロジック

ダイジェストはアセットのコンテンツと全依存アセットのダイジェストから算出される。

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

本コンポーネントはデータベース操作を行わない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | \LogicException | allAssets()で論理パスからアセットを解決できない場合 | アセットディレクトリの設定を確認 |
| - | null返却 | getAsset()で論理パスに対応するファイルが存在しない場合 | アセットファイルの存在を確認 |

### リトライ仕様

リトライ機構は不要。

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

トランザクション管理は対象外。

## パフォーマンス要件

- マニフェストファイルは一度読み込みメモリにキャッシュ(manifestData配列)
- 本番環境では事前ビルドされたマニフェストを使用し、ファイルシステムスキャンを回避
- 開発環境ではDevServerSubscriberにより動的にアセットを解決

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

- アセットファイルはpublicディレクトリから配信されるため、ソースマップ等の機密情報の公開に注意
- ダイジェスト付きURLはキャッシュバスティングに有効だが、ファイル内容の推測防止が目的ではない

## 備考

AssetMapperはSymfony 6.3で導入された比較的新しいコンポーネントであり、Webpack Encoreの軽量代替として位置づけられている。ImportMap仕様をネイティブにサポートし、ビルドステップなしのフロントエンド開発を可能にする。

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | MappedAsset.php | `src/Symfony/Component/AssetMapper/MappedAsset.php` | アセット情報のデータ構造(logicalPath, sourcePath, publicPath, digest, content, dependencies) |
| 1-2 | AssetMapperInterface.php | `src/Symfony/Component/AssetMapper/AssetMapperInterface.php` | AssetMapperのインターフェース定義 |

**読解のコツ**: MappedAssetはreadonlyプロパティを多用し、コンストラクタでの条件付き代入を行っている(56-73行目)。dependencies、fileDependencies、javaScriptImportsは変更可能な配列として保持。

#### Step 2: メインクラスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | AssetMapper.php | `src/Symfony/Component/AssetMapper/AssetMapper.php` | メインファサード、MANIFEST_FILE_NAME定数、マニフェスト読み込み |
| 2-2 | AssetMapperRepository.php | `src/Symfony/Component/AssetMapper/AssetMapperRepository.php` | アセットディレクトリの管理、find/findLogicalPath |
| 2-3 | CompiledAssetMapperConfigReader.php | `src/Symfony/Component/AssetMapper/CompiledAssetMapperConfigReader.php` | コンパイル済み設定の読み込み |

**主要処理フロー**:
- **AssetMapper.php 34-42行目**: getAsset() - リポジトリからfind、ファクトリでcreateMappedAsset
- **AssetMapper.php 44-53行目**: allAssets() - 全アセットのイテレーション
- **AssetMapper.php 65-75行目**: getPublicPath() - マニフェスト優先の公開パス取得
- **AssetMapper.php 77-88行目**: loadManifest() - マニフェストの遅延読み込み

#### Step 3: コンパイラとImportMapを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | AssetMapperCompiler.php | `src/Symfony/Component/AssetMapper/AssetMapperCompiler.php` | コンパイラチェーンの管理 |
| 3-2 | Compiler/ | `src/Symfony/Component/AssetMapper/Compiler/` | 各種コンパイラ実装(CSS, JS) |
| 3-3 | ImportMap/ | `src/Symfony/Component/AssetMapper/ImportMap/` | ImportMap管理 |

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

```
AssetMapper::getAsset($logicalPath)
    |
    +-- AssetMapperRepository::find($logicalPath)
    |       +-- ファイルシステムスキャン
    |
    +-- MappedAssetFactoryInterface::createMappedAsset()
            |
            +-- AssetMapperCompiler::compile()
            |       +-- [各種Compiler実装]
            |
            +-- ダイジェスト計算
            +-- 公開パス生成

AssetMapper::getPublicPath($logicalPath)
    |
    +-- loadManifest()
    |       +-- CompiledAssetMapperConfigReader::loadConfig()
    |
    +-- [マニフェストなし] getAsset($logicalPath)
```

### データフロー図

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

アセットディレクトリ ──────> AssetMapperRepository      MappedAsset
(assets/*.css, *.js)         |                           {logicalPath,
                             +-- find()                   sourcePath,
                             |                            publicPath,
                             v                            digest,
                    MappedAssetFactory                    content}
                             |
                             +-- Compiler適用
                             +-- ダイジェスト計算
                             |
                             v
                    manifest.json (本番用)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| AssetMapper.php | `src/Symfony/Component/AssetMapper/AssetMapper.php` | ソース | メインファサード |
| AssetMapperInterface.php | `src/Symfony/Component/AssetMapper/AssetMapperInterface.php` | ソース | インターフェース定義 |
| AssetMapperRepository.php | `src/Symfony/Component/AssetMapper/AssetMapperRepository.php` | ソース | アセットリポジトリ |
| MappedAsset.php | `src/Symfony/Component/AssetMapper/MappedAsset.php` | ソース | アセットデータ構造 |
| AssetMapperCompiler.php | `src/Symfony/Component/AssetMapper/AssetMapperCompiler.php` | ソース | コンパイラチェーン |
| CompiledAssetMapperConfigReader.php | `src/Symfony/Component/AssetMapper/CompiledAssetMapperConfigReader.php` | ソース | コンパイル済み設定読み込み |
| MapperAwareAssetPackage.php | `src/Symfony/Component/AssetMapper/MapperAwareAssetPackage.php` | ソース | Assetコンポーネント連携 |
| AssetMapperDevServerSubscriber.php | `src/Symfony/Component/AssetMapper/AssetMapperDevServerSubscriber.php` | ソース | 開発環境用サーバー |
| ImportMap/ | `src/Symfony/Component/AssetMapper/ImportMap/` | ソース | ImportMap関連クラス群 |
| Compiler/ | `src/Symfony/Component/AssetMapper/Compiler/` | ソース | アセットコンパイラ群 |
| Compressor/ | `src/Symfony/Component/AssetMapper/Compressor/` | ソース | アセット圧縮機能 |
