こんにちは、管理人の@Salesforce.Zです。
今日は、最新版のSalesforce SFDC トリガーテンプレートを共有する
Salesforce DeveloperのGitによる持ってきたものである。
読んだら得ること
★ 最新版SFDC トリガーテンプレート
目次
最新版SFDC トリガーテンプレート
TriggerHandler
/** * トリガハンドラフレームワーク */ public virtual class TriggerHandler { private static Map<String, LoopCount> loopCountMap; private static Set<String> bypassedHandlers; @TestVisible private TriggerContext context; @TestVisible private Boolean isTriggerExecuting; static { loopCountMap = new Map<String, LoopCount>(); bypassedHandlers = new Set<String>(); } /** * TriggerHandlerカスタム例外クラス */ public class TriggerHandlerException extends Exception { } /** * コンストラクタ */ public TriggerHandler() { this.setTriggerContext(); } /** * This is main brokering method that is called by the trigger. * It's responsible for determining the proper context, and calling the * correct method * @example * AccountTriggerHandler.run(); */ public void run() { if (!validateRun()) { return; } addToLoopCount(); switch on context { when BEFORE_INSERT { this.beforeInsert(); } when BEFORE_UPDATE { this.beforeUpdate(); } when AFTER_INSERT { this.afterInsert(); } when AFTER_UPDATE { this.afterUpdate(); } when BEFORE_DELETE { this.beforeDelete(); } when AFTER_DELETE { this.afterDelete(); } when AFTER_UNDELETE { this.afterUndelete(); } } } /** * Allows developers to prevent trigger loops, or allow * a limited number of them by setting the maximum number of times * this trigger is called. * @param max A valid number (generally 1) of times you'd like * to allow the trigger to run. * @example * In the context of a TriggerHandler class, * this.setMaxLoopCount(5); */ public void setMaxLoopCount(Integer max) { String handlerName = getHandlerName(); if (!TriggerHandler.loopCountMap.containsKey(handlerName)) { TriggerHandler.loopCountMap.put(handlerName, new LoopCount(max)); } else { TriggerHandler.loopCountMap.get(handlerName).setMax(max); } } /** * ループ回数の最大値をクリアする */ public void clearMaxLoopCount() { this.setMaxLoopCount(-1); } /** * この triggerHandler を実装している他のトリガーを * 条件付きでバイパス(無効化)する */ public static void bypass(String handlerName) { TriggerHandler.bypassedHandlers.add(handlerName); } /** * バイパスされたトリガーハンドラのリストから * 指定されたトリガーハンドラのクラス名を削除します。 */ public static void clearBypass(String handlerName) { TriggerHandler.bypassedHandlers.remove(handlerName); } /** * 与えられたトリガーハンドラクラスが * 現在バイパスされているかどうかをチェックする */ public static Boolean isBypassed(String handlerName) { return TriggerHandler.bypassedHandlers.contains(handlerName); } /** * バイパスリストからすべてのクラスを削除 */ public static void clearAllBypasses() { TriggerHandler.bypassedHandlers.clear(); } /*************************************** * private instancemethods ***************************************/ /** * トリガーコンテキストを強制的に設定する */ @TestVisible private void setTriggerContext() { this.setTriggerContext(null, false); } /** * 手動でトリガーコンテキストを設定する */ @TestVisible private void setTriggerContext(String ctx, Boolean testMode) { if (!Trigger.isExecuting && !testMode) { this.isTriggerExecuting = false; return; } else { this.isTriggerExecuting = true; } if (Trigger.isExecuting && !testMode) { switch on Trigger.operationType { when BEFORE_INSERT { context = TriggerContext.BEFORE_INSERT; } when BEFORE_UPDATE { context = TriggerContext.BEFORE_UPDATE; } when BEFORE_DELETE { context = TriggerContext.BEFORE_DELETE; } when AFTER_INSERT { context = TriggerContext.AFTER_INSERT; } when AFTER_UPDATE { context = TriggerContext.AFTER_UPDATE; } when AFTER_DELETE { context = TriggerContext.AFTER_DELETE; } when AFTER_UNDELETE { context = TriggerContext.AFTER_UNDELETE; } } } else if (ctx != null && testMode) { switch on ctx { when 'before insert' { context = TriggerContext.BEFORE_INSERT; } when 'before update' { context = TriggerContext.BEFORE_UPDATE; } when 'before delete' { context = TriggerContext.BEFORE_DELETE; } when 'after insert' { context = TriggerContext.AFTER_INSERT; } when 'after update' { context = TriggerContext.AFTER_UPDATE; } when 'after delete' { context = TriggerContext.AFTER_DELETE; } when 'after undelete' { context = TriggerContext.AFTER_UNDELETE; } when else { throw new TriggerHandler.TriggerHandlerException( 'Unexpected trigger context set' ); } } } } /** * ループ数をインクリメントする */ @TestVisible private void addToLoopCount() { String handlerName = getHandlerName(); if (TriggerHandler.loopCountMap.containsKey(handlerName)) { Boolean exceeded = TriggerHandler.loopCountMap.get(handlerName) .increment(); if (exceeded) { Integer max = TriggerHandler.loopCountMap.get(handlerName).max; throw new TriggerHandlerException( 'Maximum loop count of ' + String.valueOf(max) + ' reached in ' + handlerName ); } } } /** * このトリガーが継続して実行されることを確認する */ @TestVisible private Boolean validateRun() { if (!this.isTriggerExecuting || this.context == null) { throw new TriggerHandlerException( 'Trigger handler called outside of Trigger execution' ); } if (TriggerHandler.bypassedHandlers.contains(getHandlerName())) { return false; } return true; } /** * ハンドラクラス名を取得 */ @TestVisible private String getHandlerName() { return String.valueOf(this) .substring(0, String.valueOf(this).indexOf(':')); } /*************************************** * context methods ***************************************/ @TestVisible protected virtual void beforeInsert() { } @TestVisible protected virtual void beforeUpdate() { } @TestVisible protected virtual void beforeDelete() { } @TestVisible protected virtual void afterInsert() { } @TestVisible protected virtual void afterUpdate() { } @TestVisible protected virtual void afterDelete() { } @TestVisible protected virtual void afterUndelete() { } /*************************************** * inner classes ***************************************/ /** * ハンドラごとのループ数を管理するための内部クラス */ @TestVisible private class LoopCount { private Integer max; private Integer count; /** * 5をデフォルトとするループカウンタ */ public LoopCount() { this.max = 5; this.count = 0; } /** * パラメーターに基づいてループ数を設定 */ public LoopCount(Integer max) { this.max = max; this.count = 0; } /** * カウンタをインクリメントして this.exceeded() の結果を返す */ public Boolean increment() { this.count++; return this.exceeded(); } /** * ループ数を超えようとしているかどうかを判断 */ public Boolean exceeded() { if (this.max < 0) { return false; } if (this.count > this.max) { return true; } return false; } /** * 現在のループ数を返す */ public Integer getCount() { return this.count; } /** * ループの最大値を返す */ public Integer getMax() { return this.max; } /** * ループの最大値を設定 */ public void setMax(Integer max) { this.max = max; } } /** * トリガーコンテキスト */ @TestVisible private enum TriggerContext { BEFORE_INSERT, BEFORE_UPDATE, BEFORE_DELETE, AFTER_INSERT, AFTER_UPDATE, AFTER_DELETE, AFTER_UNDELETE } }
AccountTriggerHandler
/** * 取引先トリガハンドラ */ public inherited sharing class AccountTriggerHandler extends TriggerHandler { private List<Account> triggerNew; private List<Account> triggerOld; private Map<Id, Account> triggerMapNew; private Map<Id, Account> triggerMapOld; /** * カスタム例外クラス */ public class AccountTriggerHandlerException extends Exception { } /** * コンストラクタ */ public AccountTriggerHandler() { this.triggerOld = (List<Account>) Trigger.old; this.triggerNew = (List<Account>) Trigger.new; this.triggerMapNew = (Map<Id, Account>) Trigger.newMap; this.triggerMapOld = (Map<Id, Account>) Trigger.oldMap; } /** * Before Insert context method **/ public override void beforeInsert() { // for (Account newRec : this.triggerNew) { // } } /** * After Insert context method. **/ public override void afterInsert() { } /** * Before Update context method. **/ public override void beforeUpdate() { // for (Account newRec : this.triggerNew) { // } } /** * After Update context method. **/ public override void afterUpdate() { // for (Account newRec : this.triggerNew) { // } } }
AccountTrigger
trigger AccountTrigger on Account(
before insert,
after insert,
before update,
after update,
before delete,
after delete,
after undelete
) {
new AccountTriggerHandler().run();
}
終わりに
その他のサイトでもトリガーテンプレートがあるが、一応Salesforce Developer提供したものであり
積極的に使おうと思う。
実際は、基本的な考え方は変わらない
その他トリガーテンプレートのサイト紹介
2011年 [salesforce]トリガーテンプレート
2018年 初心者 SFDCトリガ肥大化(汗) 柔軟性のトリガ 実装論
2021年 【Salesforce】テンプレート集!