# TriggerHandler 基底クラスとフィールド変更検知

このドキュメントは、ApexTools が提供する `TriggerHandler` 基底クラスの解説です。他のツールも含めた一覧は [ApexTools ガイド](/ja/apex-stem/docs/apex-tools-guide) から確認できます。

## 何ができるか

`TriggerHandler` 基底クラスを継承するだけで、Trigger からのエントリーポイント処理が「7 イベントを宣言するだけのトリガーファイル」と「override したフックだけを書くハンドラクラス」というシンプルな形にまとまります。「特定フィールドが変更されたレコードだけを絞り込む」ヘルパーも組み込まれていて、`afterUpdate` の絞り込みがコード 1 行で書けます。

## 継承と 7 つのフック + andFinally

各 Handler は `TriggerHandler` 基底クラスを継承し、必要なフックだけを override します。すべて `protected virtual` なので、override しないフックは何もしません。トリガーの 7 イベントに対応する 7 つと、コンテキストによらず最後に走る `andFinally` の計 8 つです。

| フック | シグネチャ |
|---|---|
| `beforeInsert` | `(List<SObject> newRecords)` |
| `beforeUpdate` | `(Map<Id, SObject> newMap, Map<Id, SObject> oldMap)` |
| `beforeDelete` | `(Map<Id, SObject> deletedMap)` |
| `afterInsert` | `(Map<Id, SObject> newMap)` |
| `afterUpdate` | `(Map<Id, SObject> newMap, Map<Id, SObject> oldMap)` |
| `afterDelete` | `(Map<Id, SObject> deletedMap)` |
| `afterUndelete` | `(Map<Id, SObject> undeletedMap)` |
| `andFinally` | `()` 常に最後に呼ばれる (どのコンテキストでも) |

## Trigger.cls の固定パターンと連動

トリガーファイルは Salesforce の慣例に従い、書き方を固定します。7 つのイベントをすべて宣言し、Handler を 1 行で呼ぶだけです。

```apex
trigger Opportunity on Opportunity(
  before insert,
  before update,
  before delete,
  after insert,
  after update,
  after delete,
  after undelete
) {
  (new TriggerOppHandler()).execute();
}
```

> 🚨 **カスタムオブジェクトでは、トリガー名に `__c` をそのまま付けられません。** Apex の識別子は `__` の連続を含められない (Salesforce の予約) ため、`trigger SalesActivity__c on SalesActivity__c(...)` は `Invalid character in identifier` でデプロイに失敗します。**トリガー名だけ別名にしてください** (`SalesActivityTrigger` など。ファイル名も合わせます)。標準オブジェクトは `trigger Opportunity on Opportunity(...)` のままで問題ありません。

Handler 側は必要なフックだけ override します。

```apex
public with sharing class TriggerOppHandler extends TriggerHandler {
  protected override void afterInsert(Map<Id, SObject> newRecordsMap) {
    Set<Id> opportunityIds = newRecordsMap.keySet();
    (new CopyAccountIndustryToOpportunityUsecase(opportunityIds)).invoke();
  }
}
```

## フィールド変更検知ヘルパー

`afterUpdate` で「特定フィールドが変更されたレコードだけ」に絞りたいケースは頻出です。`TriggerHandler` 基底クラスがそのためのヘルパーを提供します。

| メソッド | 戻り値 | 用途 |
|---|---|---|
| `getUpdatedRecordsWithChangedField(SObjectField field)` | `List<SObject>` | 単一フィールド変更レコード |
| `getUpdatedRecordsWithChangedFields(List<SObjectField> fields)` | `List<SObject>` | 複数フィールドのいずれかが変更されたレコード |
| `getUpdateRecordIdsWithChangedField(SObjectField field)` | `Set<Id>` | 上記の Id 版 |
| `getUpdateRecordIdsWithChangedFields(List<SObjectField> fields)` | `Set<Id>` | 上記の Id 版 |

使い方の例:

```apex
public with sharing class TriggerOppHandler extends TriggerHandler {
  protected override void afterUpdate(Map<Id, SObject> newMap, Map<Id, SObject> oldMap) {
    Set<Id> needIds = this.getUpdateRecordIdsWithChangedFields(new List<SObjectField>{
      Opportunity.AccountId,
      Opportunity.StageName
    });
    (new RegenerateCollectionUsecase(needIds)).invoke();
  }
}
```

「特定のフィールドが変わった時だけ何かする」というロジックがコード 1 行に集約され、Handler は依然として「条件判定と Usecase 呼び出しだけ」の薄さを保てます。

## その他の注意点

### override しないフックはそのまま無処理

`TriggerHandler` 基底クラスのフックはすべて `protected virtual` で、デフォルトでは何もしません。`afterInsert` だけ処理したい Handler は `afterInsert` だけ override すれば OK で、他のフックを空メソッドで埋める必要はありません。

### andFinally の使いどころ

`andFinally()` は **どのコンテキストでも最後に呼ばれる** フックです。「before / after の種類によらず、最後に必ず実行したい処理」(監査ログの確定、Trace のラッピング処理など) を置く先に使います。多くの Handler では不要です。

## 次に読む

- [ApexTools ガイド](/ja/apex-stem/docs/apex-tools-guide): ApexTools の他のツールを含む入り口
- [Handler-Usecase Architecture](/ja/apex-stem/docs/handler-usecase-architecture): `TriggerHandler` が活きる Apex Stem の中核アーキテクチャ
- [Apex Stem 導入ガイド](/ja/apex-stem/docs/apex-stem-full-guide): 動くコード付きの 4 ステップ
