Salesforce Platform Developer I：Apexトリガー

#Apexトリガー
https://trailhead.salesforce.com/ja/content/learn/modules/apex_triggers

##Apexトリガの使用開始
###トリガ構文
```
trigger HelloWorldTrigger on Account (before insert, after insert, before update, 
                                      after update, before delete, after delete, after undelete) {
    if (Trigger.isInsert) {
        if (Trigger.isBefore) {
            // Process before insert
        } else if (Trigger.isAfter) {
            // Process after insert
        }        
    }
    else if (Trigger.isDelete) {
        // Process after delete
    }
}
```
###トリガ種類
時間別：beforeトリガとafterトリガ
処理別：insert、update、delete、undelete

####まとめ
- before insert
- before update
- before delete
- after insert
- after update
- after delete
- after undelete

<font color="red">※undelete処理はbeforeトリガがない。（復元処理ですので、前に何もできない）</font>

###トリガコンテキスト変数
判断用：isExecuting、isInsert、isUpdate、isDelete、isBefore、isAfter、isUndelete
リスト格納：
　・new：insert、update、undeleteのみ使用可能、beforeのみ変更可能
　・old：update、deleteのみ使用可能
　・newMap：Map<SFid,SObject>。before update、after insert、after update、after undeleteのみ使用可能（他の場合、IDがないので）
　・oldMap：update、deleteのみ使用可能
操作種別：operationType
レコード合計数：size

###トリガの例外の使用
addError()メソッド利用、下記の例でレコードにエラーメッセージ出力、項目レベルに出力したい場合（指定入力項目のフォーマット判断など）はもっと指定して可能です

```
trigger AccountDeletion on Account (before delete) {
   
    // Prevent the deletion of accounts if they have related opportunities.
    for (Account a : [SELECT Id FROM Account
                     WHERE Id IN (SELECT AccountId FROM Opportunity) AND
                     Id IN :Trigger.old]) {
        Trigger.oldMap.get(a.Id).addError(
            'Cannot delete account with related opportunities.');
    }
    
}
```

###トリガ作成のルール
・トリガの実際処理はクラスメソッドに格納優先。<font color="red">理由はトリガテスト複雑、単独テスト不可、クラスになるとテスト簡単。</font>
・beforeトリガ：システムの入力規則 ＋ カスタム入力規則 ＋ 重複ルール処理、また、対象レコードの項目値変更の場合
・afterトリガ：以外のオブジェクト変更処理
<font color="red">※理由はCPUとメモリ出来るだけ無駄な利用を避けたい、beforeトリガ処理後対象レコード変更不可になる</font>

###トリガと実行の順序
- 01.元のレコードがデータベースから読み込まれるか、upsert ステートメント用にレコードが初期設定されます。
- 02.要求から新しいレコード項目の値が読み込まれ、古い値を上書きします。
　　要求が標準 UI 編集ページから行われた場合は、Salesforce がシステム検証を実行して、レコードについて次の点を確認します。
　　・レイアウト固有のルールへの準拠
　　・レイアウトレベルおよび項目定義レベルで必要な値
　　・有効な項目形式
　　・最大項目サイズ
　　要求が Apex アプリケーションや SOAP API コールなど他の提供元から送信されている場合、Salesforce では外部キーのみを検証します。トリガを実行する前に、Salesforce がカスタム外部キーがオブジェクト自体を参照しないことを確認します。
　　見積品目や商談品目など、複数行の品目が作成された場合、Salesforce はユーザ定義の入力規則を実行します。

- 03.レコードの保存前に実行されるように設定されたレコードトリガフローを実行します。
- 04.すべての before トリガが実行されます。
- 05.すべての必須項目に null 以外の値が入力されていることの確認や、ユーザ定義の入力規則の実行など、システム検証のほとんどの手順がもう一度実行されます。Salesforce が標準 UI + 編集ページから要求が行われた場合に再度実行しない唯一のシステム検証は、レイアウト固有のルールの適用です。
- 06.重複ルールが実行されます。重複ルールが重複するレコードを特定してブロックアクションを実行した場合は、レコードが保存されず、after トリガやワークフロールールなどの後続のステップが実行されません。
- 07.レコードはデータベースに保存されますが、まだ確定されません。
- 08.レコード再ロード
- 09.すべての after トリガが実行されます。
- 10.割り当てルールが実行されます。
- 11.自動応答ルールが実行されます。
- 12.ワークフロールールが実行されます。
- 13.ワークフロー項目自動更新が存在する場合、レコードが再度更新されます。
- 14.ワークフロー項目自動更新でレコードが更新された場合、標準の入力規則に加えて、before update トリガおよび after update トリガがもう一度 (さらに 1 回のみ) 実行されます。カスタム入力規則、フロー、重複ルール、プロセスおよびエスカレーションルールは再実行されません。
- 15.次の Salesforce フロー自動化を実行しますが、順序は保証されません。
　　・プロセス
　　・プロセスによって起動されたフロー
　　プロセスまたはフローで DML 操作が実行されるときに、影響を受けるレコードに対して保存手順が実行されます。

- 16.エスカレーションルールが実行されます。
- 17.エンタイトルメントルールが実行されます。
- 18.レコードの保存後に実行されるように設定されたレコードトリガフローを実行します。
- 19.レコードに積み上げ集計項目が含まれる場合、またはレコードがクロスオブジェクトワークフローの一部である場合、計算が実行され、親レコードの積み上げ集計項目が更新されます。親レコードに対して保存手順が実行されます。
- 20.親レコードが更新され、さらにその親レコードに積み上げ集計項目が含まれるか、その親レコードがクロスオブジェクトワークフローの一部である場合、計算が実行され、親の親レコードの積み上げ集計項目が更新されます。親の親レコードに対して保存手順が実行されます。
- 21.条件に基づく共有の評価が実行されます。
- 22.すべての DML 操作がデータベースで確定されます。
- 23.メール送信など、確定後のロジックが実行されます。

###CHALLENGE
```AccountAddressTrigger.apxt
trigger AccountAddressTrigger on Account (before insert,before update) {
    for(Account a : Trigger.New) {
        if (a.Match_Billing_Address__c == true){
            a.ShippingPostalCode = a.BillingPostalCode;
        }
    }
}
```

##一括Apexトリガ
要点：
・複数処理考え必要
・SOQLはループ内に使用を避ける
・DML処理は事前に変数定義、最後一括実行

###複数処理考え必要
```java
trigger MyTriggerBulk on Account(before insert) {
    for(Account a : Trigger.New) {
        a.Description = 'New description';
    }
}
```

###SOQLはループ内に使用を避ける
####事前定義変数、SOQL実行

```java
trigger SoqlTriggerBulk on Account(after update) {  
    // Perform SOQL query once.    
    // Get the related opportunities for the accounts in this trigger.
    List<Opportunity> relatedOpps = [SELECT Id,Name,CloseDate FROM Opportunity
        WHERE AccountId IN :Trigger.New];
  
    // Iterate over the related opportunities    
    for(Opportunity opp : relatedOpps) { 
        // Do some other processing
    }
}
```
<font color="red">問題点：件数が多くになると、ガバナ制限発生</font>

####改善方法
・for声明の中にSOQL利用、Javaの指針みたいなる

```java
trigger SoqlTriggerBulk on Account(after update) {  
    // Perform SOQL query once.    
    // Get the related opportunities for the accounts in this trigger,
    // and iterate over those records.
    for(Opportunity opp : [SELECT Id,Name,CloseDate FROM Opportunity
        WHERE AccountId IN :Trigger.New]) {
  
        // Do some other processing
    }
}
```

###DML処理は事前に変数定義、最後一括実行
・oppList事前声明、処理の中にリスト追加、最後一括投入

```java
trigger AddRelatedRecord on Account(after insert, after update) {
    List<Opportunity> oppList = new List<Opportunity>();
    
    // Add an opportunity for each account if it doesn't already have one.
    // Iterate over accounts that are in this trigger but that don't have opportunities.
    for (Account a : [SELECT Id,Name FROM Account
                     WHERE Id IN :Trigger.New AND
                     Id NOT IN (SELECT AccountId FROM Opportunity)]) {
        // Add a default opportunity for this account
        oppList.add(new Opportunity(Name=a.Name + ' Opportunity',
                                   StageName='Prospecting',
                                   CloseDate=System.today().addMonths(1),
                                   AccountId=a.Id)); 
    }
    
    if (oppList.size() > 0) {
        insert oppList;
    }
}
```

###CHALLENGE
```ClosedOpportunityTrigger.apxt
trigger ClosedOpportunityTrigger on Opportunity (after insert,after update) {
    List<Task> taskList = new List<Task>();
    for (Opportunity opp :[SELECT Id,Name FROM Opportunity WHERE Id IN :trigger.New]){
        taskList.add(new Task(Subject='Follow Up Test Task',WhatId = opp.Id));
    }
    if (taskList.size()>0){
        insert taskList;
    }
}
```